OpenCV是一個(gè)強(qiáng)大的工具,結(jié)合RaspberryPi可以打開(kāi)許多便攜式智能設(shè)備的大門,我們將學(xué)習(xí)如何利用OpenCV的強(qiáng)大功能并在我們的實(shí)時(shí)閉路電視畫(huà)面上構(gòu)建一個(gè)RaspberryPi運(yùn)動(dòng)檢測(cè)系統(tǒng)。
我們將編寫(xiě)一個(gè) Python 腳本,它可以同時(shí)監(jiān)控所有四個(gè)閉路電視攝像機(jī)的任何活動(dòng)(運(yùn)動(dòng))。如果在任何攝像頭上檢測(cè)到活動(dòng),我們的 Raspberry Pi 將自動(dòng)切換到該特定攝像頭屏幕并突出顯示發(fā)生了哪些活動(dòng),所有這些都是實(shí)時(shí)的,只有 1.5 秒的延遲。我還添加了一個(gè)警報(bào)功能,例如蜂鳴器,如果檢測(cè)到活動(dòng),它可以通過(guò)蜂鳴聲提醒用戶。但是您可以輕松地將其放大以發(fā)送消息或電子郵件或其他什么!令人興奮的權(quán)利!讓我們開(kāi)始吧
使用 Buster 和 OpenCV 設(shè)置 Raspberry Pi
我正在使用運(yùn)行 Buster OS 的 Raspberry Pi 3 B+,OpenCV 的版本是 4.1。如果您是新手,請(qǐng)先按照以下教程開(kāi)始操作。
樹(shù)莓派入門
在樹(shù)莓派上安裝 OpenCV
Raspberry Pi 上的 RTSP CCTV 錄像監(jiān)控
目標(biāo)是讓您的 Pi 啟動(dòng)并準(zhǔn)備好進(jìn)行開(kāi)發(fā)??梢栽谀?Pi 上安裝任何版本的 Raspbian OS,但請(qǐng)確保 OpenCV 的版本為 4.1 或更高版本。您可以按照上面的教程編譯您的 OpenCV,這將花費(fèi)數(shù)小時(shí),但對(duì)于繁重的項(xiàng)目更可靠,或者直接使用以下命令從 pip 安裝它。
?
$ pip install opencv-contrib-python==4.1.0.25
?
如果您是第一次使用 pip 安裝 OpenCV,則還必須安裝其他依賴項(xiàng)。為此,請(qǐng)使用以下命令。
?
$ 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
?
我們已經(jīng)構(gòu)建了許多Raspberry Pi OpenCV 項(xiàng)目,您也可以查看它們以獲得更多靈感。
為 Raspberry Pi 5 英寸顯示屏添加蜂鳴器
在硬件方面,除了 5 英寸顯示屏和蜂鳴器外,我們沒(méi)有太多東西。將5 英寸顯示器與樹(shù)莓派接口后,我們可以直接將蜂鳴器安裝在顯示器的背面,為我們擴(kuò)展了一些 GPIO 引腳。我已經(jīng)連接了我的蜂鳴器,如下所示-
如果您對(duì)使用更多 I/O 引腳感興趣,那么下面的引腳描述將很有用。正如您在擴(kuò)展引腳中看到的那樣,大多數(shù)引腳都被顯示器本身用于觸摸屏界面。但是,我們?nèi)匀挥袥](méi)有連接的引腳 3、5、7、8、10、11、12、13、15、16 和 24,我們可以將其用于我們自己的應(yīng)用程序。在本教程中,我將蜂鳴器連接到 GPIO 3。
為閉路電視運(yùn)動(dòng)檢測(cè)編程樹(shù)莓派
這個(gè)項(xiàng)目的完整 python 腳本可以在這個(gè)頁(yè)面的底部找到,但是讓我們討論代碼的每個(gè)部分以了解它是如何工作的。
使用 RTSP 在 Raspberry Pi 上無(wú)延遲監(jiān)控多個(gè)攝像頭
完成這項(xiàng)工作的挑戰(zhàn)性部分是減少 Raspberry pi 上的負(fù)載以避免流式傳輸延遲。最初,我嘗試在所有四個(gè)攝像機(jī)之間切換以尋找運(yùn)動(dòng),但它非常滯后(大約 10 秒)。所以我將所有四個(gè)攝像頭組合成一個(gè)圖像,并在該圖像上進(jìn)行所有運(yùn)動(dòng)檢測(cè)活動(dòng)。我寫(xiě)了兩個(gè)函數(shù),分別是創(chuàng)建相機(jī)和讀取相機(jī)。
創(chuàng)建相機(jī)功能用于打開(kāi)帶有相應(yīng)通道號(hào)的凸輪。請(qǐng)注意,RTSP URL 以“02”結(jié)尾,這意味著我正在使用分辨率較低的子流視頻源,因此閱讀速度更快。此外,您使用的視頻編解碼器類型也有助于提高速度,我嘗試了不同的編碼,發(fā)現(xiàn) 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 號(hào)為 3 cap.set(4, cam_height) # 高度的 ID 號(hào)是 480 cap.set(10, 100) # 亮度 ID 號(hào)為 10 返回帽
?
在讀取相機(jī)功能中,我們將讀取所有四個(gè)凸輪,即 cam1、cam2、cam3 和 cam4,以將它們?nèi)拷M合成一個(gè)名為Main_screen的圖像。一旦這個(gè)主屏幕準(zhǔn)備好,我們將在這個(gè)圖像上完成我們所有的 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 返回(主屏幕)
?
組合了所有四個(gè)凸輪的主屏幕圖像將如下圖所示。
使用 Raspberry Pi 在 OpenCV 上進(jìn)行運(yùn)動(dòng)檢測(cè)
現(xiàn)在我們已經(jīng)準(zhǔn)備好圖像,我們可以從運(yùn)動(dòng)檢測(cè)開(kāi)始。在while 循環(huán)中,我們首先讀取兩個(gè)不同的幀,即 frame1 和 frame2,然后將它們轉(zhuǎn)換為灰度
?
frame1 = read_camera() #讀取第一幀 grayImage_F1 = cv2.cvtColor(frame1, cv2.COLOR_BGR2GRAY) # 轉(zhuǎn)換為灰色 frame2 = read_camera() #讀取第2幀 grayImage_F2 = cv2.cvtColor(frame2, cv2.COLOR_BGR2GRAY)
?
然后我們對(duì)這兩個(gè)圖像進(jìn)行比較,看看發(fā)生了什么變化,并使用一個(gè)閾值,將所有發(fā)生變化的地方分組,有點(diǎn)像一團(tuán)。模糊和擴(kuò)大圖像以避免銳利邊緣也很常見(jiàn)。
?
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)
?
下一步是找到計(jì)數(shù)器并檢查每個(gè)計(jì)數(shù)器的面積,通過(guò)找到面積,我們可以算出運(yùn)動(dòng)有多大。如果該區(qū)域大于變量motion_detected中的指定值,則我們將其視為活動(dòng)并在更改周圍繪制一個(gè)框以向用戶突出顯示它。
?
contours, _ = cv2.findContours (dilatedImage, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) #查找輪廓是一個(gè)神奇的函數(shù) 對(duì)于輪廓中的輪廓:#對(duì)于檢測(cè)到的每個(gè)變化 ??? (x,y,w,h) = cv2.boundingRect(contour) #獲取發(fā)現(xiàn)變化的位置 ??? 如果 cv2.contourArea(contour) > motion_threshold: ??????? cv2.rectangle(frame1, (x, y), (x + w, y + h), (255, 255, 0), 1) ??????? display_screen = find_screen()
?
函數(shù)find_screen()用于查找活動(dòng)在四個(gè)攝像頭中發(fā)生的位置。我們可以發(fā)現(xiàn),因?yàn)槲覀冎肋\(yùn)動(dòng)的 x 和 y 值。我們將這些 x 和 y 值與每個(gè)屏幕的位置進(jìn)行比較,以找出哪個(gè)屏幕產(chǎn)生了活動(dòng),然后我們?cè)俅尾眉粼撎囟ㄆ聊?,以便我們可以將其顯示在 pi 觸摸屏上。
?
定義查找屏幕(): ??? 如果(x < cam_width): ??????? 如果(y < cam_height): ??????????? 屏幕 = frame1 [0:cam_height, 0:cam_width] ??????????? print("凸輪屏幕 1 中的活動(dòng)") ??????? 別的: ??????????? 屏幕 = frame1[cam_height:cam_height*2, :cam_width] ??????????? print("凸輪屏幕 2 中的活動(dòng)") ??? 別的: ??????? 如果(y < cam_height): ??????????? 屏幕 = frame1[:cam_height, cam_width:cam_width*2] ??????????? print("凸輪屏幕 3 中的活動(dòng)") ??????? 別的: ??????????? 屏幕 = frame1[cam_height:cam_height*2, cam_width:cam_width*2] ??????????? print("凸輪屏幕 4 中的活動(dòng)") ??? 返回(屏幕)
?
為移動(dòng)偵測(cè)設(shè)置警報(bào)
一旦我們知道在哪個(gè)屏幕上檢測(cè)到運(yùn)動(dòng),就很容易添加我們需要的任何類型的警報(bào)。在這里,我們將通過(guò)連接到 GPIO 3 的蜂鳴器發(fā)出蜂鳴聲。if語(yǔ)句檢查是否在屏幕 3 中檢測(cè)到運(yùn)動(dòng)并增加一個(gè)名為trig_alarm的變量。您可以檢測(cè)您選擇的任何屏幕,甚至可以檢測(cè)多個(gè)屏幕。
?
如果 ((x>cam_width) 和 (y?
如果trig_alarm的值達(dá)到 3 以上,我們將蜂鳴一次。這個(gè)計(jì)數(shù)的原因是有時(shí)我注意到陰影或鳥(niǎo)兒制造了假警報(bào)。所以這種方式只有在連續(xù)活動(dòng) 3 幀的情況下,我們才會(huì)收到警報(bào)。
?
if (trig_alarm>=3):#wait for conts 3 動(dòng)作 #按蜂鳴器 GPIO.輸出(蜂鳴器,1) time.sleep(0.02) GPIO.輸出(蜂鳴器,0) 觸發(fā)警報(bào) =0?
監(jiān)控 CPU 溫度和使用情況
該系統(tǒng)旨在 24x7 全天候工作,因此 Pi 會(huì)變得非常熱,因此我決定通過(guò)在屏幕上顯示這些值來(lái)監(jiān)控溫度和 CPU 使用率。我們已經(jīng)使用 gpiozero 庫(kù)獲得了這些信息。
?
cpu = CPU溫度() 負(fù)載 = 平均負(fù)載() 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)?
啟動(dòng) Pi CCTV 運(yùn)動(dòng)探測(cè)器
我已經(jīng)測(cè)試了好幾天來(lái)收集它,它每次都能正常工作,這真的是一個(gè)有趣的構(gòu)建,直到我損壞了一臺(tái)相機(jī),更多關(guān)于它的內(nèi)容以及下面的視頻中的詳細(xì)工作。但是這個(gè)系統(tǒng)是可靠的,我很驚訝地看到 pi 能順利處理所有這些黃油。
#!/usr/bin/env python3
導(dǎo)入簡(jiǎn)歷2
將 numpy 導(dǎo)入為 np
進(jìn)口時(shí)間
導(dǎo)入 RPi.GPIO 作為 GPIO
從 gpiozero 導(dǎo)入 CPUTemperature, LoadAverage
#輸入 CCTV 的憑據(jù)
rtsp_username = "管理員"
rtsp_password = "aswinth347653"
rtsp_IP = "192.168.29.100"
cam_width = 352 #設(shè)置為來(lái)自 DVR 的傳入視頻的分辨率
cam_height = 288 #設(shè)置為來(lái)自 DVR 的傳入視頻的分辨率
motion_threshold = 1000 #降低此值以增加靈敏度
cam_no = 1
觸發(fā)警報(bào) =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 號(hào)為 3
cap.set(4, cam_height) # 高度的 ID 號(hào)是 480
cap.set(10, 100) # 亮度 ID 號(hào)為 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 中的活動(dòng)")
別的:
屏幕 = frame1[cam_height:cam_height*2, :cam_width]
print("凸輪屏幕 2 中的活動(dòng)")
別的:
如果(y < cam_height):
屏幕 = frame1[:cam_height, cam_width:cam_width*2]
print("凸輪屏幕 3 中的活動(dòng)")
別的:
屏幕 = frame1[cam_height:cam_height*2, cam_width:cam_width*2]
print("凸輪屏幕 4 中的活動(dòng)")
返回(屏幕)
#打開(kāi)所有四個(gè)相機(jī)Framers
cam1 = create_camera(str(1))
cam2 = create_camera(str(2))
cam3 = create_camera(str(3))
cam4 = create_camera(str(4))
print ("讀取相機(jī)成功")
Main_screen = np.zeros(( (cam_height*2), (cam_width*2), 3) , np.uint8) # 創(chuàng)建所有四個(gè)攝像頭都將被縫合的屏幕
display_screen = np.zeros(( (cam_height*2), (cam_width*2), 3) , np.uint8) # 創(chuàng)建要在 5 英寸 TFT 顯示器上顯示的屏幕
kernal = np.ones((5,5),np.uint8) #形成一個(gè)5x5矩陣,全1范圍為8位
而真:
frame1 = read_camera() #讀取第一幀
grayImage_F1 = cv2.cvtColor(frame1, cv2.COLOR_BGR2GRAY) # 轉(zhuǎn)換為灰色
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) #查找輪廓是一個(gè)神奇的函數(shù)
對(duì)于輪廓中的輪廓:#對(duì)于檢測(cè)到的每個(gè)變化
(x,y,w,h) = cv2.boundingRect(contour) #獲取發(fā)現(xiàn)變化的位置
如果 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觸發(fā)警報(bào)+=1
別的:
觸發(fā)警報(bào) =0
if (trig_alarm>=3):#wait for conts 3 動(dòng)作
#按蜂鳴器
GPIO.輸出(蜂鳴器,1)
time.sleep(0.02)
GPIO.輸出(蜂鳴器,0)
觸發(fā)警報(bào) =0
cpu = CPU溫度()
負(fù)載 = 平均負(fù)載()
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()
休息
評(píng)論
查看更多