用Python的OpenCV检测运动

8

我的目标是检测IP摄像机流中特定区域的运动。我已经写出了可行的代码,但是它基于我的个人理解。

import cv2
import numpy as np
import os
import time
import datetime
import urllib
import pynotify

stream=urllib.urlopen('http://user:pass@192.168.198.120/video.mjpg')
bytes=''
fgbg = cv2.createBackgroundSubtractorMOG2()

while True:
    bytes+=stream.read(16384)
    a = bytes.find('\xff\xd8')
    b = bytes.find('\xff\xd9')
    if a!=-1 and b!=-1:
        jpg = bytes[a:b+2]
        bytes= bytes[b+2:]
        img = cv2.imdecode(np.fromstring(jpg, dtype=np.uint8),cv2.IMREAD_COLOR)
        rows,cols,c = img.shape
        mask = np.zeros(img.shape, dtype=np.uint8)
        roi_corners = np.array([[(940,220),(1080,240), (1080,310), (940,290)]], dtype=np.int32)
        channel_count = img.shape[2]
        ignore_mask_color = (255,)*channel_count
        cv2.fillPoly(mask, roi_corners, ignore_mask_color)
        masked_image = cv2.bitwise_and(img, mask)

        fgmask = fgbg.apply(masked_image)
        iii = fgmask[220:310,940:1080]

        hist,bins = np.histogram(iii.ravel(),256,[0,256])

        black, white, cnt1, cnt2 = 0,0,0,0


        for i in range(0,127):
            black += hist[i]
            cnt1+=1
        bl = float(black / cnt1)

        for i in range(128,256):
            white += hist[i]
            cnt2+=1
        wh = float(white / cnt2)

        finalResult = ((bl+1) / (wh+1))/10

    if finalResult < 1.0:
        pynotify.init("cv2alert")
            notice = pynotify.Notification('Alert', 'Alert text')
            try:
                notice.show()
            except gio.Error:
                print "Error"

这段代码是有效的,但由于我对直方图的理解不太好,因此无法直接获取值,但通过一些“技巧”(例如左侧为黑色,右侧为白色),black/white给出了我想要的结果。 我知道这并不完全正确,但它给出了ROI中没有元素时4-9的结果,当有人进入这个ROI时给出0.5-2.0的结果。

我的问题是: 是否有其他方法读取直方图并比较数据,或者其他方法? 阅读文档并没有帮助我。


感兴趣的区域是否预先定义并稳定?如果我理解正确,您正在处理灰度图片/视频,并且希望检测像“像素值的快速变化”这样的运动? - Nikolas Rieble
是的,这个区域是预定义的。我试图从直方图中提取值,因为只有两种颜色(黑色和白色),但没有成功。所以,选择直方图的前半部分作为黑色,后半部分作为白色。我的问题是,是否存在另一种实现这个目标的方法?我的主要目标是尽可能简单地检测特定区域的运动,并通知用户。 - Aleksandar
我认为有无数种不同的方法。例如,您可以记录最后x帧的得分,并计算一个“平均”图像,然后使用它来计算差异图像。如果任何区域(如n * m补丁)中的差异高于阈值,则可以称其为运动并报告它。这也可以用于识别运动区域,而无需明确定义ROI。 - Nikolas Rieble
3个回答

4
一种检测运动的方法是使用cv2.accumulateWeighted对场景进行一个运行平均值,然后使用cv2.absdiff将每个新帧与平均值进行比较,以获取表示场景变化的图像。
在我的视频处理项目中,我就是这样做的。请查看文件diffavg1.py中的主循环,其中我运行了累加器并执行了差分操作。
(该项目的研究旨在利用多核CPU架构实现实时视频处理,因此后续版本diffavg2.pydiffavg3.pydiffavg4.py是逐步提高性能的实现,但基础的累加-差分算法是相同的。)

不错。我成功地获取了一些值,但需要做一些小工作来解决我的问题。我注意到 CPU 和内存使用率低于我的自定义脚本。 - Aleksandar

2
差分图像是两幅图像相减的结果。因此,差分图像显示了两个图像之间的差异。通过这些图像,您可以使运动可见。
在以下脚本中,我们使用从三个连续图像计算出的差分图像和。这样做的好处是从结果中删除了无趣的背景。
OpenCV提供了使用absdiff()相互减去两个图像的可能性。同时,已经实现了两个图像的逻辑操作。我们使用方法bitwise_and()来实现最终的差分图像。在Python中,它看起来像这样:
def diffImg(t0, t1, t2):
  d1 = cv2.absdiff(t2, t1)
  d2 = cv2.absdiff(t1, t0)
  return cv2.bitwise_and(d1, d2)

我们需要做的最后一件事是将差分图像函数引入到我们之前的脚本中。在循环开始之前,我们读取第一张图片t_minus、t和t_plus,并将它们转换为灰度图像,因为我们不需要颜色信息。有了这些图像,就可以开始计算差分图像。在展示差分图像之后,我们只需要摆脱最旧的图像并读取下一张图像。最终的脚本如下所示:
import cv2

def diffImg(t0, t1, t2):
  d1 = cv2.absdiff(t2, t1)
  d2 = cv2.absdiff(t1, t0)
  return cv2.bitwise_and(d1, d2)

cam = cv2.VideoCapture(0)

winName = "Movement Indicator"
cv2.namedWindow(winName, cv2.WINDOW_AUTOSIZE)

# Read three images first:
t_minus = cv2.cvtColor(cam.read()[1], cv2.COLOR_RGB2GRAY)
t = cv2.cvtColor(cam.read()[1], cv2.COLOR_RGB2GRAY)
t_plus = cv2.cvtColor(cam.read()[1], cv2.COLOR_RGB2GRAY)

while True:
  cv2.imshow( winName, diffImg(t_minus, t, t_plus) )

  # Read next image
  t_minus = t
  t = t_plus
  t_plus = cv2.cvtColor(cam.read()[1], cv2.COLOR_RGB2GRAY)

  key = cv2.waitKey(10)
  if key == 27:
    cv2.destroyWindow(winName)
    break

print("Goodbye")

在这里,您将找到更详细的答案,以回答您所寻找的问题。


1
在修改了Adrian的代码并删除了不必要的内容后,我设法让它工作了。简单而有效,对资源影响较小(1%的CPU和30 Mb的内存)。在屏幕上显示图像的代替方法是,在他的脚本的第69行中通知功能非常好用。感谢这个。我认为这段代码可以针对我的目的进行优化,但现在它已经能够使用了。 - Aleksandar

-2

可以使用 ecapture 完成。

安装

pip install ecapture

代码

from ecapture import motion as md

md.motion_detect(0,"x")
print("detected")

这段代码将会打印出

detected

一旦摄像机视野中有运动


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接