OpenCV是一個強大的工具,結合RaspberryPi可以打開許多便攜式智能設備的大門,我們將學習如何利用OpenCV的強大功能并在我們的實時閉路電視畫面上構建一個RaspberryPi運動檢測系統。
我們將編寫一個 Python 腳本,它可以同時監控所有四個閉路電視攝像機的任何活動(運動)。如果在任何攝像頭上檢測到活動,我們的 Raspberry Pi 將自動切換到該特定攝像頭屏幕并突出顯示發生了哪些活動,所有這些都是實時的,只有 1.5 秒的延遲。我還添加了一個警報功能,例如蜂鳴器,如果檢測到活動,它可以通過蜂鳴聲提醒用戶。但是您可以輕松地將其放大以發送消息或電子郵件或其他什么!令人興奮的權利!讓我們開始吧
使用 Buster 和 OpenCV 設置 Raspberry Pi
我正在使用運行 Buster OS 的 Raspberry Pi 3 B+,OpenCV 的版本是 4.1。如果您是新手,請先按照以下教程開始操作。
樹莓派入門
在樹莓派上安裝 OpenCV
Raspberry Pi 上的 RTSP CCTV 錄像監控
目標是讓您的 Pi 啟動并準備好進行開發。可以在您的 Pi 上安裝任何版本的 Raspbian OS,但請確保 OpenCV 的版本為 4.1 或更高版本。您可以按照上面的教程編譯您的 OpenCV,這將花費數小時,但對于繁重的項目更可靠,或者直接使用以下命令從 pip 安裝它。
?
$ pip install opencv-contrib-python==4.1.0.25
?
如果您是第一次使用 pip 安裝 OpenCV,則還必須安裝其他依賴項。為此,請使用以下命令。
?
$ sudo apt-get install libavcodec-dev libavformat-dev libswscale-dev libv4l-dev $ sudo apt-get install libxvidcore-dev libx264-dev $sudo apt-get install libatlas-base-dev gfortran $ sudo apt-get install libhdf5-dev libhdf5-serial-dev libhdf5-103 $ sudo apt-get install libqtgui4 libqtwebkit4 libqt4-test python3-pyqt5
?
我們已經構建了許多Raspberry Pi OpenCV 項目,您也可以查看它們以獲得更多靈感。
為 Raspberry Pi 5 英寸顯示屏添加蜂鳴器
在硬件方面,除了 5 英寸顯示屏和蜂鳴器外,我們沒有太多東西。將5 英寸顯示器與樹莓派接口后,我們可以直接將蜂鳴器安裝在顯示器的背面,為我們擴展了一些 GPIO 引腳。我已經連接了我的蜂鳴器,如下所示-
如果您對使用更多 I/O 引腳感興趣,那么下面的引腳描述將很有用。正如您在擴展引腳中看到的那樣,大多數引腳都被顯示器本身用于觸摸屏界面。但是,我們仍然有沒有連接的引腳 3、5、7、8、10、11、12、13、15、16 和 24,我們可以將其用于我們自己的應用程序。在本教程中,我將蜂鳴器連接到 GPIO 3。
為閉路電視運動檢測編程樹莓派
這個項目的完整 python 腳本可以在這個頁面的底部找到,但是讓我們討論代碼的每個部分以了解它是如何工作的。
使用 RTSP 在 Raspberry Pi 上無延遲監控多個攝像頭
完成這項工作的挑戰性部分是減少 Raspberry pi 上的負載以避免流式傳輸延遲。最初,我嘗試在所有四個攝像機之間切換以尋找運動,但它非常滯后(大約 10 秒)。所以我將所有四個攝像頭組合成一個圖像,并在該圖像上進行所有運動檢測活動。我寫了兩個函數,分別是創建相機和讀取相機。
創建相機功能用于打開帶有相應通道號的凸輪。請注意,RTSP URL 以“02”結尾,這意味著我正在使用分辨率較低的子流視頻源,因此閱讀速度更快。此外,您使用的視頻編解碼器類型也有助于提高速度,我嘗試了不同的編碼,發現 FFMPEG 是所有編碼中最快速的。
?
def create_camera(頻道): rtsp = "rtsp://" + rtsp_username + ":" + rtsp_password + "@" + rtsp_IP + ":554/Streaming/channels/" + channel + "02" #更改 IP 以適合您的 cap = cv2.VideoCapture(rtsp, cv2.CAP_FFMPEG) cap.set(3, cam_width) # 寬度的 ID 號為 3 cap.set(4, cam_height) # 高度的 ID 號是 480 cap.set(10, 100) # 亮度 ID 號為 10 返回帽
?
在讀取相機功能中,我們將讀取所有四個凸輪,即 cam1、cam2、cam3 和 cam4,以將它們全部組合成一個名為Main_screen的圖像。一旦這個主屏幕準備好,我們將在這個圖像上完成我們所有的 OpenCV 工作。
?
def read_camera (): 成功,current_screen = cam1.read() Main_screen [:cam_height, :cam_width, :3] = current_screen 成功,current_screen = cam2.read() Main_screen[cam_height:cam_height*2, :cam_width, :3] = current_screen 成功,current_screen = cam3.read() Main_screen[:cam_height, cam_width:cam_width*2, :3] = current_screen 成功,current_screen = cam4.read() Main_screen[cam_height:cam_height*2, cam_width:cam_width*2, :3] = current_screen 返回(主屏幕)
?
組合了所有四個凸輪的主屏幕圖像將如下圖所示。
使用 Raspberry Pi 在 OpenCV 上進行運動檢測
現在我們已經準備好圖像,我們可以從運動檢測開始。在while 循環中,我們首先讀取兩個不同的幀,即 frame1 和 frame2,然后將它們轉換為灰度
?
frame1 = read_camera() #讀取第一幀 grayImage_F1 = cv2.cvtColor(frame1, cv2.COLOR_BGR2GRAY) # 轉換為灰色 frame2 = read_camera() #讀取第2幀 grayImage_F2 = cv2.cvtColor(frame2, cv2.COLOR_BGR2GRAY)
?
然后我們對這兩個圖像進行比較,看看發生了什么變化,并使用一個閾值,將所有發生變化的地方分組,有點像一團。模糊和擴大圖像以避免銳利邊緣也很常見。
?
diffImage = cv2.absdiff(grayImage_F1,grayImage_F2) #獲取差異——這很酷 blurImage = cv2.GaussianBlur(diffImage, (5,5), 0) _, thresholdImage = cv2.threshold(blurImage, 20,255,cv2.THRESH_BINARY) dilatedImage = cv2.dilate(thresholdImage,kernal,iterations=5)
?
下一步是找到計數器并檢查每個計數器的面積,通過找到面積,我們可以算出運動有多大。如果該區域大于變量motion_detected中的指定值,則我們將其視為活動并在更改周圍繪制一個框以向用戶突出顯示它。
?
contours, _ = cv2.findContours (dilatedImage, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) #查找輪廓是一個神奇的函數 對于輪廓中的輪廓:#對于檢測到的每個變化 ??? (x,y,w,h) = cv2.boundingRect(contour) #獲取發現變化的位置 ??? 如果 cv2.contourArea(contour) > motion_threshold: ??????? cv2.rectangle(frame1, (x, y), (x + w, y + h), (255, 255, 0), 1) ??????? display_screen = find_screen()
?
函數find_screen()用于查找活動在四個攝像頭中發生的位置。我們可以發現,因為我們知道運動的 x 和 y 值。我們將這些 x 和 y 值與每個屏幕的位置進行比較,以找出哪個屏幕產生了活動,然后我們再次裁剪該特定屏幕,以便我們可以將其顯示在 pi 觸摸屏上。
?
定義查找屏幕(): ??? 如果(x < cam_width): ??????? 如果(y < cam_height): ??????????? 屏幕 = frame1 [0:cam_height, 0:cam_width] ??????????? print("凸輪屏幕 1 中的活動") ??????? 別的: ??????????? 屏幕 = frame1[cam_height:cam_height*2, :cam_width] ??????????? print("凸輪屏幕 2 中的活動") ??? 別的: ??????? 如果(y < cam_height): ??????????? 屏幕 = frame1[:cam_height, cam_width:cam_width*2] ??????????? print("凸輪屏幕 3 中的活動") ??????? 別的: ??????????? 屏幕 = frame1[cam_height:cam_height*2, cam_width:cam_width*2] ??????????? print("凸輪屏幕 4 中的活動") ??? 返回(屏幕)
?
為移動偵測設置警報
一旦我們知道在哪個屏幕上檢測到運動,就很容易添加我們需要的任何類型的警報。在這里,我們將通過連接到 GPIO 3 的蜂鳴器發出蜂鳴聲。if語句檢查是否在屏幕 3 中檢測到運動并增加一個名為trig_alarm的變量。您可以檢測您選擇的任何屏幕,甚至可以檢測多個屏幕。
?
如果 ((x>cam_width) 和 (y?
如果trig_alarm的值達到 3 以上,我們將蜂鳴一次。這個計數的原因是有時我注意到陰影或鳥兒制造了假警報。所以這種方式只有在連續活動 3 幀的情況下,我們才會收到警報。
?
if (trig_alarm>=3):#wait for conts 3 動作 #按蜂鳴器 GPIO.輸出(蜂鳴器,1) time.sleep(0.02) GPIO.輸出(蜂鳴器,0) 觸發警報 =0?
監控 CPU 溫度和使用情況
該系統旨在 24x7 全天候工作,因此 Pi 會變得非常熱,因此我決定通過在屏幕上顯示這些值來監控溫度和 CPU 使用率。我們已經使用 gpiozero 庫獲得了這些信息。
?
cpu = CPU溫度() 負載 = 平均負載() cpu_temperature = str((cpu.temperature)//1) load_average = str(load.load_average) #print (cpu.溫度) #print(load.load_average) cv2.putText(display_screen, cpu_temperature, (250,250), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0,0,255), 1) cv2.putText(display_screen, load_average, (300,250), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0,255,0), 2)?
啟動 Pi CCTV 運動探測器
我已經測試了好幾天來收集它,它每次都能正常工作,這真的是一個有趣的構建,直到我損壞了一臺相機,更多關于它的內容以及下面的視頻中的詳細工作。但是這個系統是可靠的,我很驚訝地看到 pi 能順利處理所有這些黃油。
#!/usr/bin/env python3
導入簡歷2
將 numpy 導入為 np
進口時間
導入 RPi.GPIO 作為 GPIO
從 gpiozero 導入 CPUTemperature, LoadAverage
#輸入 CCTV 的憑據
rtsp_username = "管理員"
rtsp_password = "aswinth347653"
rtsp_IP = "192.168.29.100"
cam_width = 352 #設置為來自 DVR 的傳入視頻的分辨率
cam_height = 288 #設置為來自 DVR 的傳入視頻的分辨率
motion_threshold = 1000 #降低此值以增加靈敏度
cam_no = 1
觸發警報 =0
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings (假)
蜂鳴器 = 3
GPIO.setup(蜂鳴器,GPIO.OUT)
def create_camera(頻道):
rtsp = "rtsp://" + rtsp_username + ":" + rtsp_password + "@" + rtsp_IP + ":554/Streaming/channels/" + channel + "02" #更改 IP 以適合您的
cap = cv2.VideoCapture(rtsp, cv2.CAP_FFMPEG)
cap.set(3, cam_width) # 寬度的 ID 號為 3
cap.set(4, cam_height) # 高度的 ID 號是 480
cap.set(10, 100) # 亮度 ID 號為 10
返回帽
def read_camera ():
成功,current_screen = cam1.read()
Main_screen [:cam_height, :cam_width, :3] = current_screen
成功,current_screen = cam2.read()
Main_screen[cam_height:cam_height*2, :cam_width, :3] = current_screen
成功,current_screen = cam3.read()
Main_screen[:cam_height, cam_width:cam_width*2, :3] = current_screen
成功,current_screen = cam4.read()
Main_screen[cam_height:cam_height*2, cam_width:cam_width*2, :3] = current_screen
返回(主屏幕)
定義查找屏幕():
如果(x < cam_width):
如果(y < cam_height):
屏幕 = frame1 [0:cam_height, 0:cam_width]
print("凸輪屏幕 1 中的活動")
別的:
屏幕 = frame1[cam_height:cam_height*2, :cam_width]
print("凸輪屏幕 2 中的活動")
別的:
如果(y < cam_height):
屏幕 = frame1[:cam_height, cam_width:cam_width*2]
print("凸輪屏幕 3 中的活動")
別的:
屏幕 = frame1[cam_height:cam_height*2, cam_width:cam_width*2]
print("凸輪屏幕 4 中的活動")
返回(屏幕)
#打開所有四個相機Framers
cam1 = create_camera(str(1))
cam2 = create_camera(str(2))
cam3 = create_camera(str(3))
cam4 = create_camera(str(4))
print ("讀取相機成功")
Main_screen = np.zeros(( (cam_height*2), (cam_width*2), 3) , np.uint8) # 創建所有四個攝像頭都將被縫合的屏幕
display_screen = np.zeros(( (cam_height*2), (cam_width*2), 3) , np.uint8) # 創建要在 5 英寸 TFT 顯示器上顯示的屏幕
kernal = np.ones((5,5),np.uint8) #形成一個5x5矩陣,全1范圍為8位
而真:
frame1 = read_camera() #讀取第一幀
grayImage_F1 = cv2.cvtColor(frame1, cv2.COLOR_BGR2GRAY) # 轉換為灰色
frame2 = read_camera() #讀取第2幀
grayImage_F2 = cv2.cvtColor(frame2, cv2.COLOR_BGR2GRAY)
diffImage = cv2.absdiff(grayImage_F1,grayImage_F2) #獲取差異——這很酷
blurImage = cv2.GaussianBlur(diffImage, (5,5), 0)
_, thresholdImage = cv2.threshold(blurImage, 20,255,cv2.THRESH_BINARY)
dilatedImage = cv2.dilate(thresholdImage,kernal,iterations=5)
contours, _ = cv2.findContours (dilatedImage, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) #查找輪廓是一個神奇的函數
對于輪廓中的輪廓:#對于檢測到的每個變化
(x,y,w,h) = cv2.boundingRect(contour) #獲取發現變化的位置
如果 cv2.contourArea(contour) > motion_threshold:
cv2.rectangle(frame1, (x, y), (x + w, y + h), (255, 0, 0), 1)
display_screen = find_screen()
如果 ((x>cam_width) 和 (y觸發警報+=1
別的:
觸發警報 =0
if (trig_alarm>=3):#wait for conts 3 動作
#按蜂鳴器
GPIO.輸出(蜂鳴器,1)
time.sleep(0.02)
GPIO.輸出(蜂鳴器,0)
觸發警報 =0
cpu = CPU溫度()
負載 = 平均負載()
cpu_temperature = str((cpu.temperature)//1)
load_average = str(load.load_average)
#print (cpu.溫度)
#print(load.load_average)
cv2.putText(display_screen, cpu_temperature, (250,250), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0,0,255), 1)
cv2.putText(display_screen, load_average, (300,250), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0,255,0), 2)
打印(trig_alarm)
暗淡 = (800, 480)
Full_frame = cv2.resize (display_screen,dim,interpolation=cv2.INTER_AREA)
cv2.namedWindow("AISHA", cv2.WINDOW_NORMAL)
cv2.setWindowProperty('AISHA', cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)
cv2.imshow("AISHA",Full_frame)
如果 cv2.waitKey(1) & 0xFF == ord('p'):
cam1.release()
cam2.release()
cam3.release()
cam4.release()
cv2.destroyAllWindows()
休息
評論
查看更多