Python-OpenCV 处理视频(三)(四)(五): 标记运动轨迹 运动检测 运动方向判断

0x00. 光流

光流是进行视频中运动对象轨迹标记的一种很常用的方法,在OpenCV中实现光流也很容易。

CalcOpticalFlowPyrLK 函数计算一个稀疏特征集的光流,使用金字塔中的迭代 Lucas-Kanade 方法。

简单的实现流程:

  1. 加载一段视频。

  2. 调用GoodFeaturesToTrack函数寻找兴趣点。

  3. 调用CalcOpticalFlowPyrLK函数计算出两帧图像中兴趣点的移动情况。

  4. 删除未移动的兴趣点。

  5. 在两次移动的点之间绘制一条线段。

代码示例:

import cv2.cv as cv

capture = cv.CaptureFromFile('img/myvideo.avi')

nbFrames = int(cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FRAME_COUNT))
fps = cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FPS)
wait = int(1/fps * 1000/1)
width = int(cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FRAME_WIDTH))
height = int(cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FRAME_HEIGHT))

prev_gray = cv.CreateImage((width,height), 8, 1) #Will hold the frame at t-1
gray = cv.CreateImage((width,height), 8, 1) # Will hold the current frame

prevPyr = cv.CreateImage((height / 3, width + 8), 8, cv.CV_8UC1) #Will hold the pyr frame at t-1
currPyr = cv.CreateImage((height / 3, width + 8), 8, cv.CV_8UC1) # idem at t

max_count = 500
qLevel= 0.01
minDist = 10
prev_points = [] #Points at t-1
curr_points = [] #Points at t
lines=[] #To keep all the lines overtime

for f in xrange( nbFrames ):

    frame = cv.QueryFrame(capture) #Take a frame of the video

    cv.CvtColor(frame, gray, cv.CV_BGR2GRAY) #Convert to gray
    output = cv.CloneImage(frame)

    prev_points = cv.GoodFeaturesToTrack(gray, None, None, max_count, qLevel, minDist) #Find points on the image

    #Calculate the movement using the previous and the current frame using the previous points
    curr_points, status, err = cv.CalcOpticalFlowPyrLK(prev_gray, gray, prevPyr, currPyr, prev_points, (10, 10), 3, (cv.CV_TERMCRIT_ITER|cv.CV_TERMCRIT_EPS,20, 0.03), 0)


    #If points status are ok and distance not negligible keep the point
    k = 0
    for i in range(len(curr_points)):
        nb =  abs( int(prev_points[i][0])-int(curr_points[i][0]) ) + abs( int(prev_points[i][1])-int(curr_points[i][1]) )
        if status[i] and  nb > 2 :
            prev_points[k] = prev_points[i]
            curr_points[k] = curr_points[i]
            k += 1

    prev_points = prev_points[:k]
    curr_points = curr_points[:k]
    #At the end only interesting points are kept

    #Draw all the previously kept lines otherwise they would be lost the next frame
    for (pt1, pt2) in lines:
        cv.Line(frame, pt1, pt2, (255,255,255))

    #Draw the lines between each points at t-1 and t
    for prevpoint, point in zip(prev_points,curr_points):
        prevpoint = (int(prevpoint[0]),int(prevpoint[1]))
        cv.Circle(frame, prevpoint, 15, 0)
        point = (int(point[0]),int(point[1]))
        cv.Circle(frame, point, 3, 255)
        cv.Line(frame, prevpoint, point, (255,255,255))
        lines.append((prevpoint,point)) #Append current lines to the lines list


    cv.Copy(gray, prev_gray) #Put the current frame prev_gray
    prev_points = curr_points

    cv.ShowImage("The Video", frame)
    #cv.WriteFrame(writer, frame)
    cv.WaitKey(wait)

直接调用摄像头使用该方法:

import cv2.cv as cv

capture = cv.CaptureFromCAM(0)

width = int(cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FRAME_WIDTH))
height = int(cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FRAME_HEIGHT))

prev_gray = cv.CreateImage((width,height), 8, 1)
gray = cv.CreateImage((width,height), 8, 1)

prevPyr = cv.CreateImage((height / 3, width + 8), 8, cv.CV_8UC1) #Will hold the pyr frame at t-1
currPyr = cv.CreateImage((height / 3, width + 8), 8, cv.CV_8UC1) # idem at t

max_count = 500
qLevel= 0.01
minDist = 10
prev_points = [] #Points at t-1
curr_points = [] #Points at t
lines=[] #To keep all the lines overtime

while True:
    frame = cv.QueryFrame(capture)
    cv.CvtColor(frame, gray, cv.CV_BGR2GRAY) #Convert to gray
    output = cv.CloneImage(frame)

    prev_points = cv.GoodFeaturesToTrack(gray, None, None, max_count, qLevel, minDist)
    curr_points, status, err = cv.CalcOpticalFlowPyrLK(prev_gray, gray, prevPyr, currPyr, prev_points, (10, 10), 3, (cv.CV_TERMCRIT_ITER|cv.CV_TERMCRIT_EPS,20, 0.03), 0)

    #If points status are ok and distance not negligible keep the point
    k = 0
    for i in range(len(curr_points)):
        nb =  abs( int(prev_points[i][0])-int(curr_points[i][0]) ) + abs( int(prev_points[i][1])-int(curr_points[i][1]) )
        if status[i] and  nb > 2 :
            prev_points[k] = prev_points[i]
            curr_points[k] = curr_points[i]
            k += 1

    prev_points = prev_points[:k]
    curr_points = curr_points[:k]
    #At the end only interesting points are kept

    #Draw all the previously kept lines otherwise they would be lost the next frame
    for (pt1, pt2) in lines:
        cv.Line(frame, pt1, pt2, (255,255,255))

    #Draw the lines between each points at t-1 and t
    for prevpoint, point in zip(prev_points,curr_points):
        prevpoint = (int(prevpoint[0]),int(prevpoint[1]))
        cv.Circle(frame, prevpoint, 15, 0)
        point = (int(point[0]),int(point[1]))
        cv.Circle(frame, point, 3, 255)
        cv.Line(frame, prevpoint, point, (255,255,255))
        lines.append((prevpoint,point)) #Append current lines to the lines list


    cv.Copy(gray, prev_gray) #Put the current frame prev_gray
    prev_points = curr_points

    cv.ShowImage("The Video", frame)
    #cv.WriteFrame(writer, frame)
    c = cv.WaitKey(1)
    if c == 27: #Esc on Windows
        break

0x01. 寻找最大特征值的角点

cv.GoodFeaturesToTrack 函数可以检测出图像中最大特征值的角点,使用这个函数可以对图像中的特征点进行跟踪,从而绘制出运动轨迹。

直接加载视频:

import cv2.cv as cv

capture = cv.CaptureFromFile('img/myvideo.avi')

#-- Informations about the video --
nbFrames = int(cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FRAME_COUNT))
fps = cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FPS)
wait = int(1/fps * 1000/1)
width = int(cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FRAME_WIDTH))
height = int(cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FRAME_HEIGHT))
#For recording
#codec = cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FOURCC)
#writer=cv.CreateVideoWriter("img/output.avi", int(codec), int(fps), (width,height), 1) #Create writer with same parameters
#----------------------------------

prev_gray = cv.CreateImage((width,height), 8, 1) #Will hold the frame at t-1
gray = cv.CreateImage((width,height), 8, 1) # Will hold the current frame

output = cv.CreateImage((width,height), 8, 3)

prevPyr = cv.CreateImage((height / 3, width + 8), 8, cv.CV_8UC1)
currPyr = cv.CreateImage((height / 3, width + 8), 8, cv.CV_8UC1)

max_count = 500
qLevel= 0.01
minDist = 10

begin = True

initial = []
features = []
prev_points = []
curr_points = []

for f in xrange( nbFrames ):

    frame = cv.QueryFrame(capture)

    cv.CvtColor(frame, gray, cv.CV_BGR2GRAY) #Convert to gray
    cv.Copy(frame, output)


    if (len(prev_points) <= 10): #Try to get more points
        #Detect points on the image
        features = cv.GoodFeaturesToTrack(gray, None, None, max_count, qLevel, minDist)
        prev_points.extend(features) #Add the new points to list
        initial.extend(features) #Idem

    if begin:
        cv.Copy(gray, prev_gray) #Now we have two frames to compare
        begin = False

    #Compute movement
    curr_points, status, err = cv.CalcOpticalFlowPyrLK(prev_gray, gray, prevPyr, currPyr, prev_points, (10, 10), 3, (cv.CV_TERMCRIT_ITER|cv.CV_TERMCRIT_EPS,20, 0.03), 0)

    #If points status are ok and distance not negligible keep the point
    k = 0
    for i in range(len(curr_points)):
        nb =  abs( int(prev_points[i][0])-int(curr_points[i][0]) ) + abs( int(prev_points[i][1])-int(curr_points[i][1]) )
        if status[i] and  nb > 2 :
            initial[k] = initial[i]
            curr_points[k] = curr_points[i]
            k += 1

    curr_points = curr_points[:k]
    initial = initial[:k]
    #At the end only interesting points are kept

    #Draw the line between the first position of a point and the
    #last recorded position of the same point
    for i in range(len(curr_points)):
        cv.Line(output, (int(initial[i][0]),int(initial[i][1])), (int(curr_points[i][0]),int(curr_points[i][1])), (255,255,255))
        cv.Circle(output, (int(curr_points[i][0]),int(curr_points[i][1])), 3, (255,255,255))


    cv.Copy(gray, prev_gray)
    prev_points = curr_points


    cv.ShowImage("The Video",  output)
    cv.WriteFrame(writer, output)
    cv.WaitKey(wait)

调用摄像头绘制:

import cv2.cv as cv

capture = cv.CaptureFromCAM(0)

width = int(cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FRAME_WIDTH))
height = int(cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FRAME_HEIGHT))

prev_gray = cv.CreateImage((width,height), 8, 1) #Will hold the frame at t-1
gray = cv.CreateImage((width,height), 8, 1) # Will hold the current frame

output = cv.CreateImage((width,height), 8, 3)

prevPyr = cv.CreateImage((height / 3, width + 8), 8, cv.CV_8UC1)
currPyr = cv.CreateImage((height / 3, width + 8), 8, cv.CV_8UC1)

max_count = 500
qLevel= 0.01
minDist = 10

begin = True

initial = []
features = []
prev_points = []
curr_points = []

while True:

    frame = cv.QueryFrame(capture)

    cv.CvtColor(frame, gray, cv.CV_BGR2GRAY) #Convert to gray
    cv.Copy(frame, output)


    if (len(prev_points) <= 10): #Try to get more points
        #Detect points on the image
        features = cv.GoodFeaturesToTrack(gray, None, None, max_count, qLevel, minDist)
        prev_points.extend(features) #Add the new points to list
        initial.extend(features) #Idem

    if begin:
        cv.Copy(gray, prev_gray) #Now we have two frames to compare
        begin = False

    #Compute movement
    curr_points, status, err = cv.CalcOpticalFlowPyrLK(prev_gray, gray, prevPyr, currPyr, prev_points, (10, 10), 3, (cv.CV_TERMCRIT_ITER|cv.CV_TERMCRIT_EPS,20, 0.03), 0)

    #If points status are ok and distance not negligible keep the point
    k = 0
    for i in range(len(curr_points)):
        nb =  abs( int(prev_points[i][0])-int(curr_points[i][0]) ) + abs( int(prev_points[i][1])-int(curr_points[i][1]) )
        if status[i] and  nb > 2 :
            initial[k] = initial[i]
            curr_points[k] = curr_points[i]
            k += 1

    curr_points = curr_points[:k]
    initial = initial[:k]
    for i in range(len(curr_points)):
        cv.Line(output, (int(initial[i][0]),int(initial[i][1])), (int(curr_points[i][0]),int(curr_points[i][1])), (255,255,255))
        cv.Circle(output, (int(curr_points[i][0]),int(curr_points[i][1])), 3, (255,255,255))


    cv.Copy(gray, prev_gray)
    prev_points = curr_points

    cv.ShowImage("The Video", output)
    c = cv.WaitKey(1)
    if c == 27: #Esc on Windows
        break
 
 
 
————————————————————————————————————分割线来了————————————————————————————————————


0x00. 平均值法

通过计算两帧图像之间变化了的像素点占的百分比,来确定图像中是否有动作产生。

这里主要用到 Absdiff 函数,比较两帧图像之间有差异的点,当然需要将图像进行一些处理,例如平滑处理,灰度化处理,二值化处理,经过处理之后的二值图像上的点将更有效。

代码示例:

import cv2.cv as cv

capture=cv.CaptureFromCAM(0)

frame1 = cv.QueryFrame(capture)
frame1gray = cv.CreateMat(frame1.height, frame1.width, cv.CV_8U)
cv.CvtColor(frame1, frame1gray, cv.CV_RGB2GRAY)

res = cv.CreateMat(frame1.height, frame1.width, cv.CV_8U)

frame2gray = cv.CreateMat(frame1.height, frame1.width, cv.CV_8U)

w= frame2gray.width
h= frame2gray.height
nb_pixels = frame2gray.width * frame2gray.height

while True:
    frame2 = cv.QueryFrame(capture)
    cv.CvtColor(frame2, frame2gray, cv.CV_RGB2GRAY)

    cv.AbsDiff(frame1gray, frame2gray, res)
    cv.ShowImage("After AbsDiff", res)

    cv.Smooth(res, res, cv.CV_BLUR, 5,5)
    element = cv.CreateStructuringElementEx(5*2+1, 5*2+1, 5, 5,  cv.CV_SHAPE_RECT)
    cv.MorphologyEx(res, res, None, None, cv.CV_MOP_OPEN)
    cv.MorphologyEx(res, res, None, None, cv.CV_MOP_CLOSE)
    cv.Threshold(res, res, 10, 255, cv.CV_THRESH_BINARY_INV)

    cv.ShowImage("Image", frame2)
    cv.ShowImage("Res", res)

    #-----------
    nb=0
    for y in range(h):
        for x in range(w):
            if res[y,x] == 0.0:
                nb += 1
    avg = (nb*100.0)/nb_pixels
    #print "Average: ",avg, "%\r",
    if avg >= 5:
        print "Something is moving !"
    #-----------


    cv.Copy(frame2gray, frame1gray)
    c=cv.WaitKey(1)
    if c==27: #Break if user enters 'Esc'.
        break

0x01. 背景建模与前景检测

背景建模也是检测运动物体的一种办法,下面是代码示例:

import cv2.cv as cv

capture = cv.CaptureFromCAM(0)
width = int(cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FRAME_WIDTH))
height = int(cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FRAME_HEIGHT))

gray = cv.CreateImage((width,height), cv.IPL_DEPTH_8U, 1)

background = cv.CreateMat(height, width, cv.CV_32F)
backImage = cv.CreateImage((width,height), cv.IPL_DEPTH_8U, 1)
foreground = cv.CreateImage((width,height), cv.IPL_DEPTH_8U, 1)
output = cv.CreateImage((width,height), 8, 1)

begin = True
threshold = 10

while True:
  frame = cv.QueryFrame( capture )

  cv.CvtColor(frame, gray, cv.CV_BGR2GRAY)

  if begin:
      cv.Convert(gray, background) #Convert gray into background format
      begin = False

  cv.Convert(background, backImage) #convert existing background to backImage

  cv.AbsDiff(backImage, gray, foreground) #Absdiff to get differences

  cv.Threshold(foreground, output, threshold, 255, cv.CV_THRESH_BINARY_INV)

  cv.Acc(foreground, background,output) #Accumulate to background

  cv.ShowImage("Output", output)
  cv.ShowImage("Gray", gray)
  c=cv.WaitKey(1)
  if c==27: #Break if user enters 'Esc'.
    break

0x02. 我的方法

上面的几种办法我都试了下,基本上能识别出运动的物体,但是发现总是有点瑕疵,所以又比对了几种别人的方案,然后合成了一个自己的方案:

具体处理思路:

  • 对两帧图像做一个absdiff得到新图像。

  • 对新图像做灰度和二值化处理。

  • 使用findContours函数获取二值化处理之后的图片中的轮廓。

  • 使用contourArea()过滤掉自己不想要的面积范围的轮廓。

这个办法基本上能够检测出物体的图像中物体的移动,而且我觉得通过设定contourArea()函数的过滤范围,可以检测距离摄像头不同距离范围的运动物体。

以下是代码示例:

#!usr/bin/env python
#coding=utf-8

import cv2
import numpy as np

camera = cv2.VideoCapture(0)
width = int(camera.get(3))
height = int(camera.get(4))

firstFrame = None

while True:
  (grabbed, frame) = camera.read()
  gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
  gray = cv2.GaussianBlur(gray, (21, 21), 0)

  if firstFrame is None:
    firstFrame = gray
    continue

  frameDelta = cv2.absdiff(firstFrame, gray)
  thresh = cv2.threshold(frameDelta, 25, 255, cv2.THRESH_BINARY)[1]
  # thresh = cv2.adaptiveThreshold(frameDelta,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\
            # cv2.THRESH_BINARY,11,2)
  # thresh = cv2.adaptiveThreshold(frameDelta,255,cv2.ADAPTIVE_THRESH_MEAN_C,\
  # cv2.THRESH_BINARY,11,2)
  thresh = cv2.dilate(thresh, None, iterations=2)
  (_, cnts, _) = cv2.findContours(thresh.copy(), cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
  for c in cnts:
     if cv2.contourArea(c) < 10000:
       continue
     (x, y, w, h) = cv2.boundingRect(c)

     cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)

   cv2.imshow("Security Feed", frame)
  
  firstFrame = gray.copy()
camera.release()
cv2.destroyAllWindows()



——————————————————————————————————汪星人说这是分割线——————————————————————————————————



注意,我使用的OpenCV 版本是 3.0, 低版本就有可能出现第一条评论里的报错

在检测出运动的物体之后,我还需要知道运动的方向,使用了上一节中的办法检测运动我发现很难去计算运动方向,开始考虑通过计算轮廓的中点的变化来实现,但是因为每次检测出得轮廓的数量不稳定,所以这个办法会让误差不可控。

这时我发现了 goodFeaturesToTrack 函数,简直是救了我,goodFeaturesToTrack 函数可以获取图像中的最大特征值的角点,以下是我的思路:

Tips: 看代码之前请先看看我下面写的实现思路,另外还有代码里的注释也对于理解代码会有所帮助

  • 对两帧图像做一个 absdiff 得到新图像。

  • 对新图像做灰度和二值化处理。

  • 使用 goodFeaturesToTrack 函数得到最大特征值的角点。

  • 计算角点的平均点,写入队列。(通过计算平均点的解决办法类似物理中刚体问题抽象成质点解决的思路)

  • 维护一个长度为 10 的队列,队列满时计算队列中数据的增减情况,来确定运动方向。

代码示例(只实现了左右移动的判断):

#!usr/bin/env python
#coding=utf-8

import cv2
import numpy as np
import Queue

camera = cv2.VideoCapture(0)
width = int(camera.get(3))
height = int(camera.get(4))

firstFrame = None
lastDec = None
firstThresh = None

feature_params = dict( maxCorners = 100,
                       qualityLevel = 0.3,
                       minDistance = 7,
                       blockSize = 7 )

lk_params = dict( winSize  = (15,15),
                  maxLevel = 2,
                  criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03))

color = np.random.randint(0,255,(100,3))
num = 0

q_x = Queue.Queue(maxsize = 10)
q_y = Queue.Queue(maxsize = 10)

while True:
  (grabbed, frame) = camera.read()
  gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
  gray = cv2.GaussianBlur(gray, (21, 21), 0)

  if firstFrame is None:
    firstFrame = gray
    continue

  # 对两帧图像进行 absdiff 操作
  frameDelta = cv2.absdiff(firstFrame, gray)
  # diff 之后的图像进行二值化
  thresh = cv2.threshold(frameDelta, 25, 255, cv2.THRESH_BINARY)[1]
  # 下面的是几种不同的二值化的方法,感觉对我来说效果都差不多
  # thresh = cv2.adaptiveThreshold(frameDelta,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\
            # cv2.THRESH_BINARY,11,2)
  # thresh = cv2.adaptiveThreshold(frameDelta,255,cv2.ADAPTIVE_THRESH_MEAN_C,\
  # cv2.THRESH_BINARY,11,2)
  thresh = cv2.dilate(thresh, None, iterations=2)
  # 识别角点
  p0 = cv2.goodFeaturesToTrack(thresh, mask = None, **feature_params)
  if p0 is not None:
    x_sum = 0
    y_sum = 0
    for i, old in enumerate(p0):
      x, y = old.ravel()
      x_sum += x
      y_sum += y
    # 计算出所有角点的平均值
    x_avg = x_sum / len(p0)
    y_avg = y_sum / len(p0)
    
    # 写入固定长度的队列
    if q_x.full():
      # 如果队列满了,就计算这个队列中元素的增减情况
      qx_list = list(q_x.queue)
      key = 0
      diffx_sum = 0
      for item_x in qx_list:
        key +=1
        if key < 10:
          # 下一个元素减去上一个元素
          diff_x = item_x - qx_list[key]
          diffx_sum += diff_x
      # 加和小于0,表明队列中的元素在递增
      if diffx_sum < 0:
        print "left"
        cv2.putText(frame, "some coming form left", (100,100), 0, 0.5, (0,0,255),2)
      else:
        print "right"

      print x_avg
      q_x.get()
    q_x.put(x_avg)
    cv2.putText(frame, str(x_avg), (300,100), 0, 0.5, (0,0,255),2)
    frame = cv2.circle(frame,(int(x_avg),int(y_avg)),5,color[i].tolist(),-1)
  
  cv2.imshow("Security Feed", frame)
  firstFrame = gray.copy()

camera.release()
cv2.destroyAllWindows()

总的来讲作为一个图像处理的小白,不断地折腾和尝试,终于搞出了自己想要的东西,OpenCV绝对是喜欢折腾的人必要掌握的一个库了,以后肯定还会继续研究这块东西。



from: https://segmentfault.com/a/1190000003804820
https://segmentfault.com/a/1190000003804835
https://segmentfault.com/a/1190000003804867

你可能感兴趣的:(python,opencv,运动检测,处理视频,运动轨迹)