使用网络摄像头和Python中的OpenCV构建运动检测器(Translate)

2020-07-15 11:45:37 浏览数 (2)

本期我们将学习如何使用OpenCV实现运动检测

运动检测是指检测物体相对于周围环境的位置是否发生了变化。接下来,让我们一起使用Python实现一个运动检测器应用程序吧!

该运动检测器可以完成以下任务:

1)在家工作时在屏幕前查找时间

2) 监控孩子在屏幕前的时间

3) 在你的后院发现非法侵入

4) 在你的房间/房子/小巷周围找到不需要的公共/动物活动……。

想要实现该运动检测器程序我们需要具备以下条件:

1)硬件要求:装有网络摄像机或任何类型摄像机的计算机。

2)软件需求:Pyhton3或者更高版本。

3)附加要求:对运动检测有一定的兴趣。

接下来我们将一步步的完成该应用程序的构建。

首先,我们将通过网络摄像头捕获第一帧,并将它视为基准帧,如下图所示。通过计算该基准帧中的对象与新帧对象之间的相位差来检测运动。我们也将得到的结果称为Delta帧。

接下来,我们将使用像素强度来优化Delta帧,优化后的帧称为阈值帧。并且,我们将应用一些复杂的图像处理技术,例如阴影消除、扩张轮廓等,以完成在阈值帧上提取对象物体。以下是您要实现的目标:

被探测对象

当这个对象进入帧和退出帧时,我们能够很容易的捕获这两帧的时间戳。因此,将能够准确的在视频中找到相关片段。

我们希望小伙伴都能自己实现这个程序,因此我们就不直接嵌入代码了。

从最基本的安装开始,我们需要安装Python3或更高版本,并使用pip安装pandas和OpenCV这两个库。这些工作做好,我们的准备工作就完成了。

第一步:导入需要的库:

第二步:初始化变量,列表,data frame

在下面的代码中,我们将会了解到在什么时候需要使用上面涉及到的每一项。

第三步:使用网络摄像机捕获视频帧:

在OpenCV中有能够打开相机并捕获视频帧的内置函数。其中输入参数“0”表示计算机硬件端口号为0的摄像机。如果我们拥有了多个摄像头或闭路电视等设置,可以通过该参数提供相应的端口号。

第四步:将捕捉到的帧转换为灰度图像,并应用高斯模糊去除噪声:

由于彩色图片中每个像素均具有三个颜色通道,实际上我们并不需要使用这么多的信息,因此首先将彩色帧转换成灰度帧。再利用高斯模糊对图像进行平滑处理,进而提高检测精度。在高斯模糊函数中,我们利用第2个参数定义了高斯核的宽度和高度;利用第3个参数,定义了标准偏差值。在这里我们可以使用核大小为(21,21),标准偏差为0的标准值。想要了解有关高斯平滑的更多信息,请参考:

Smoothing Images - OpenCV 2.4.13.7 documentation In an analogous way as the Gaussian filter, the bilateral filter also considers the neighboring pixels with weights…

docs.opencv.org

第五步:捕获第一个灰度帧

第一帧是整个处理过程中的基准帧。通过计算此基准帧与新帧之间特定对象的相位差来检测运动。在拍摄第一帧时,特定对象相机前不应有任何移动。但是得到的第一帧并不需要后续处理,因此我们可以用continue语句跳过后续过程。

第六步:创建Delta帧和阈值帧

现在,我们需要找出第一帧和当前帧之间的区别。因此,我们使用absdiff函数并将得到的结果称为delta帧。对于我们的用例来说,仅仅找到一个差异是不够的,所以我们需要定义一个像素阈值,它可以被视为真实的对象。

我们可以选择30像素作为标准阈值,并将标准阈值的颜色定义为白色(颜色代码:255). 二元阈值函数THRESH_BINARY返回一个元组值,其中只有第二项([0]是第一项,[1]是第二项)包含生成的阈值帧。二元阈值函数用于处理含有2个离散值的非连续函数:如0或1。如果摄影机前面没有对象,我们将当前帧的状态视为0;如果摄影机前面存在对象,则将当前帧的状态视为1。

更多阈值图像处理相关知识,请参考:

Miscellaneous Image Transformations - OpenCV 2.4.13.7 documentation Performs a marker-based image segmentation using the watershed algorithm. The function implements one of the variants…

docs.opencv.org

第七步:膨胀阈值帧并在其中找到轮廓像素

“我们的眼睛总是被光线吸引,但阴影处有更多内容。”—格雷戈里·马奎尔

对象的每个部分都会在背景或自身的其他部分留下一定的阴影。这似乎总是让我们感到很困惑。例如,鼻子投射在嘴唇上的阴影,较大的静止物体在旁边的小物体上投射的阴影。飘动的光源,不同发光强度的多个光源,你房间的窗帘,光源的方向和视角等等都会对阴影造成一定的影响。

以下是在实时捕获的帧中发现的一些干扰。因此,为了使这些噪声最小化,我们需要对图像进行滤波。在膨胀函数Dilate中,我们可以通过设置迭代次数来设置平滑度。迭代次数越多,平滑度越高,处理时间也就越长。因此,建议保持标准化设置为3。膨胀函数中的“None”参数表示我们的应用中不需要元素结构。

关于膨胀的更多知识,你可以参考:

Image Filtering - OpenCV 2.4.13.7 documentation Functions and classes described in this section are used to perform various linear or non-linear filtering operations…

docs.opencv.org

完成过滤以后,我们需要在该帧中找到对象轮廓。我们用当前帧中的轮廓来识别对象的大小和位置。为了实现这一点,我们将该帧的一个副本传递到findCounters方法中,使用这个副本来查找轮廓。使用副本的原因是,我们不希望轮廓识别影响到原始过滤帧。

这里有个麻烦,因为我们必须将轮廓存储在一个元组中,并且只需要使用该元组的第一个值。请参阅Python3中声明元组的语法:(name,_)。

现在,我们只需要在过滤层上找到对象的外部轮廓。对于我们的用例来说,除了极端外部轮廓以外的其他轮廓都是无用的。因此我们必须使用一些近似方法来优化轮廓的提取过程。例如使用曲线近似或曲线插值,也可以使用简单链近似规则,即压缩水平、垂直和对角线线段,只保留其端点。因此,我们能够很快得到最佳拟合轮廓。

第八步:找到轮廓区域,并在矩形中形成端点:

实际上我们并不想捕捉像昆虫这样的小物体,而是要捕捉像人或动物这样的大物体。因此我们采用轮廓区域的概念,即跳过那些面积小于10000像素的对象。对于大于此区域的轮廓,我们将状态设置为1,即检测到对象。

想知道关于图像处理中的轮廓,可以参考:

Structural Analysis and Shape Descriptors - OpenCV 2.4.13.7 documentation Draws contours outlines or filled contours. The function draws contour outlines in the image if or fills the area…

docs.opencv.org

现在我们使用boundingRect函数捕捉轮廓的坐标。然后,我们使用这些坐标在彩色帧上绘制一个特定颜色、特定厚度的矩形。此矩形描述了实际检测到的对象。

第九步:捕获对象进入帧(场景)和退出帧(场景)时的时间戳

“状态”列表status_list存储值0:代表未检测到对象,1:代表检测到对象。此状态值从0更改为1的时刻就是对象进入帧的那一时刻。同样,此状态值从1变为0的时刻就是对象从帧中消失的那一时刻。因此,我们从状态列表的最后两个值可以获得这两个切换事件的时间戳。

第十步:显示所有不同的画面(帧)

使用imshow()方法,我们将在一个独立的窗口中显示每个帧并进行比较。

我们使用waitKey函数来延迟进程,直到按下某个键。在这里,我们使用waitKey(1)从摄像机获得连续的实时反馈。想停止拍摄视频时,只需按键盘上的“Q”键即可。

我们同时需要在按下“Q”的同时捕获最后一个时间戳,因为这将帮助程序结束从摄像机捕获视频的过程,并生成时间数据。

下面是使用该应用程序生成的实际图像输出。第一个图像表示基准帧的4个帧类型,第二个图像表示带有对象的帧的4种类型的帧。你能比较一下区别吗?

Baseline First Frame

Frame with a detected object

第十一步:生成时间数据

到目前为止,所有的时间戳都存储在pandas的data-frame变量中。为了从生成的数据中获得更多信息,我们将把data-frame变量导出到本地磁盘的csv文件中。

请不要忘记释放视频变量,因为它在内存中占用了不少空间。同时销毁所有窗口以避免出现不必要的错误

这就是生成的csv的样子。正如我们所看到的那样,在程序结束之前,这个对象已经被检测了3次。您可以查看开始时间和结束时间,并计算对象在摄影机前面的时间。

这个应用程序还不够令人兴奋吗?这个应用程序是不是远离了典型的无聊编程?物联网爱好者甚至可以把这个程序部署到树莓派服务器Raspberry Pi上,并创造奇迹!

0 人点赞