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()
休息
评论
查看更多