前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >学习—用 Python 和 OpenCV 检测和跟踪运动对象

学习—用 Python 和 OpenCV 检测和跟踪运动对象

作者头像
十四君
修改2019-11-26 10:45:43
2.8K0
修改2019-11-26 10:45:43
举报
文章被收录于专栏:UrlteamUrlteam

学习了pyimagesearch 的《PyImageSearch Gurus course》。现在记录下代码的分析。

在运动检测中,做出如下的假设:

我们视频流中的背景在连续的视频帧内,多数时候应该是静止不变的,因此如果我们可以建立背景模型,我们的就可以监视到显著的变化。如果发生了显著的变化,我们就可以检测到它——通常这些变化和我们视频中的运动有关。

代码放在github

代码语言:javascript
复制
#coding:utf-8
# 导入必要的软件包
import argparse
import datetime
import imutils
import time
import cv2
 
# 创建参数解析器并解析参数
ap = argparse.ArgumentParser()
ap.add_argument("-v", "--video", help="path to the video file")
ap.add_argument("-a", "--min-area", type=int, default=500, help="minimum area size")
args = vars(ap.parse_args())
  
# 如果video参数为None,那么我们从摄像头读取数据
if args.get("video", None) is None:
    camera = cv2.VideoCapture(0)
    time.sleep(0.25)
           
# 否则我们读取一个视频文件
else:
    camera = cv2.VideoCapture(args["video"])
                
# 初始化视频流的第一帧
firstFrame = None

# 遍历视频的每一帧
while True:
    # 获取当前帧并初始化occupied/unoccupied文本
    (grabbed, frame) = camera.read()
    text = "Unoccupied"
 
    # 如果不能抓取到一帧,说明我们到了视频的结尾
    if not grabbed:
        break
 
    # 调整该帧的大小,转换为灰阶图像并且对其进行高斯模糊
    frame = imutils.resize(frame, width=500)
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    gray = cv2.GaussianBlur(gray, (21, 21), 0)
 
    # 如果第一帧是None,对其进行初始化
    if firstFrame is None:
        firstFrame = gray
        continue
# 计算当前帧和第一帧的不同
    frameDelta = cv2.absdiff(firstFrame, gray)
    thresh = cv2.threshold(frameDelta, 25, 255, cv2.THRESH_BINARY)[1]
 
    # 扩展阀值图像填充孔洞,然后找到阀值图像上的轮廓
    thresh = cv2.dilate(thresh, None, iterations=2)
    (cnts, _) = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
        cv2.CHAIN_APPROX_SIMPLE)
 
    # 遍历轮廓
    for c in cnts:
        # if the contour is too small, ignore it
        if cv2.contourArea(c) < args["min_area"]:
            continue
 
        # compute the bounding box for the contour, draw it on the frame,
        # and update the text
        # 计算轮廓的边界框,在当前帧中画出该框
        (x, y, w, h) = cv2.boundingRect(c)
        cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
        text = "Occupied"
# draw the text and timestamp on the frame
    # 在当前帧上写文字以及时间戳
    cv2.putText(frame, "Room Status: {}".format(text), (10, 20),
        cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)
    cv2.putText(frame, datetime.datetime.now().strftime("%A %d %B %Y %I:%M:%S%p"),
        (10, frame.shape[0] - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.35, (0, 0, 255), 1)
 
   # 显示当前帧并记录用户是否按下按键
    cv2.imshow("Security Feed", frame)
    cv2.imshow("Thresh", thresh)
    cv2.imshow("Frame Delta", frameDelta)
    key = cv2.waitKey(1) & 0xFF
 
    # 如果q键被按下,跳出循环
    if key == ord("q"):
        break
 
# 清理摄像机资源并关闭打开的窗口
camera.release()
cv2.destroyAllWindows()

2-6行导入了我们必要的软件包。这些看上去都很熟悉,除了imutils这个包,它提供了一组由我编写的非常方便的函数,来让我们更简单的进行图像处理。如果你还没有安装 imutils到你的系统,你可以通过pip来安装:pip install imutils(如果这样无法下载如笔者,那就直接pull下他imutils的git仓库,然后运行sudo python setup.py install 这样就可以安装了)

下一步,我们在9-12行解析了命令行参数。我们定义了两个选项。第一个,--video,是可选的。它会指定一个路径,指向一个预先录制好的视频文件,我们可以检测该视频中的运动。如果你不提供视频的路径,那么OpenCV会从你的摄像头中来检测运动。

我们同时还定义了--min-area,它表示一个图像区域被看做实际运动的最小尺寸(以像素为单位)。正如我接下来要讲的那样,我们会发现图像中比较小的区域变化会比较显著,可能是因为噪点或是光线的变化。在实际中,这些小区域并不是实际的运动——所以我们定义一个最小的尺寸来对付和过滤掉这些假阳性(false-positives)结果。

15-21行获取一个我们摄像机对象的引用。在这个例子中,没有提供视频路径(15-17行),我们会取得一个摄像头的引用。如果提供了一个视频文件路径,那么我们会在20-21行建立一个指向它的指针。

最后,我们以一个变量来结束这段代码,这个变量是firstFrame。 能猜到firstFrame 是什么吗?

假设视频的第一帧不会包含运动,而仅仅是背景——因此我们可以使用第一帧来建立背景模型。 显然我们此处建立的假设有些太大了。但是再说一次,我们的目标是要在树莓派上运行这个系统,所以我们不能做的太复杂。正如你会在本文的结果一节所看到的那样,当有人在屋里走动的时候,我们可以轻易的检测到运动并追踪他们。

现在我们已经获取了视频文件/摄像头数据流的引用,我们可以在第一行(原文第27行)开始遍历每一帧了。

调用camera.read()为我们返回一个2元组。元组的第一个值是grabbed,表明是否成功从缓冲中读取了frame。元组的第二个值就是frame它本身。

我们同时还定义了一个叫做 text 的字符串,并对其进行初始化来表明我们正在监控的这个房间“没有被占领”(Unoccupied)。如果这个房间确实有活动,我们可以更新这个字符串。

在这个例子中,如果没有成功从视频文件中读取一帧,我们会在10-11行(原文35-36行)跳出循环。

我们可以开始处理帧数据并准备进行运动分析(15-17行)。我们首先会调整它的大小到500像素宽——没有必要去直接处理视频流中的大尺寸,原始图像。我们同样会把图片转换为灰阶图像,因为彩色数据对我们的运动检测算法没有影响。最后,我们会使用高斯模糊来平滑我们的图像。

认识到即使是相邻帧,也不是完全相同的这一点很重要!

由于数码相机传感器的微小变化,没有100%相同的两帧数据——一些像素肯定会有不同的强度值。也就是说,我们需要,并应用高斯平滑对一个11X11的区域的像素强度进行平均。这能帮我们滤除可能使我们运动检测算法失效的高频噪音。

正如我在上面提到的,我们需要通过某种方式对我们的图像进行背景建模。再一次的,我们会假设视频的第一帧不包含任何运动,它是一个很好的例子,表明我们的背景是如何的。如果firstFrame没有初始化,我们会把它保存然后继续处理视频的下一帧。(20-22行

这里有一个关于示例视频第一帧的例子:

上面这一帧满足我们的假设,视频的第一帧仅仅是一个静止的背景——没有运动。

有了这个静止的背景图片,我们已经准备好实时运动检测和追踪了:

现在我们已经从firstFrame变量对背景进行了建模,我们可以利用它来计算起始帧和视频流数据中后续新帧之间的不同。

计算两帧的不同是一个简单的减法,我们使用两方相应的像素强度差的绝对值。(第二行

delta = |background_model – current_frame|

两帧差值图例如下:

识别移动
识别移动

再一次,注意到图片的背景是黑色的,而前景(运动发生的位置)是白色的。 有了这个阀值化的图片,只要简单的进行实施轮廓检测来找到白色区域的外轮廓线(第7行

我们在第14行开始对轮廓线进行遍历,在15行滤掉小的,不相关的轮廓。 如果轮廓面积比我们提供的--min-area值大,我们会在前景和移动区域画边框线。(23-25行)。我们同样会更新text状态字符串来表示这个房间”被占领“(Occupied)了

11-13行显示了我的工作成果,运行我们可以在视频中看到是否检测到了运动,使用帧差值和阀值图像我们可以调试我们的脚本。

注意:如果你下载了本文的源代码并打算应用到你自己的视频文件上,你可能需要改变cv2.threshold 的值和--min-area 参数来获得你所在光照环境下的最佳效果。

最后,22行和23行清理并释放了视频流的指针。

结果

显然,我要确定我们的运动监测系统可以在James那个偷酒贼再次造访的之前能够正常工作——我们将在本系列第二篇文章中谈到他。为了测试我们使用Python和OpenCV搭建的运动监测系统,我录制了两个视频文件。

第一个文件是example_01.mp4 ,监视了我公寓的正门,当门被打开时完成检测。第二个文件是example_02.mp4 使用安装在橱柜上的树莓派录制的。它监控厨房和客厅,当有人在其中走动的时候完成检测。

让我们给我们简单的探测器一次尝试的机会,打开终端并执行下面指令:

python motion_detector.py —video videos/example_01.mp4

下图是一个 gif 图,显示来自探测器的一些静止帧数据。

注意到在门被打开前没有进行运动检测——然后我们可以检测到我自己从门中走过。你可以在这里看到全部视频:

http://www.youtube.com/embed/fi4LORwk8Fc?feature=oembed

现在,我安装在用于监视厨房和客厅的摄像机表现如何呢?然我们一探究竟。输入下面命令:

python motion_detector.py —video videos/example_02.mp4

同样,这里是我们运动检测结果的完整视频:

http://www.youtube.com/embed/36j238XtcIE?feature=oembed

正如你看到的,我们的运动检测系统尽管非常简单,但表现还不错!我们可以正常检测到我进入客厅和离开房间。

然而,现实来讲,结果还远远谈不上完美。尽管只有一个人在屋内走动,我们却得到了多个外框——这和理想状态相差甚远。而且我可以看到,微小的光线变化,比如阴影和墙面反射,都触发了假阳性的运动检测结果。

最后,如果你想要利用你的摄像头的原始视频流来进行运动检测,空着--video选项即可。

python 1.py

原创文章,转载请注明: 转载自URl-team

本文链接地址: 学习—用 Python 和 OpenCV 检测和跟踪运动对象

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2015-12-222,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 结果
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档