基于光流法的目标跟踪检查

Opencv,LK稀疏光流法,GF稠密光流法

  • 因为疫情的原因,许多考试都取消了线下考试,变成了各类小论文的撰写。
    本文为数字图像处理课程的大作业,对内容做了一些简单的整理,博君一笑V●π●V

摘要

  目标跟踪是当前计算机视觉领域的研究热点之一。本文对于光流算法的原理进行简要介绍,再使用OpenCV视觉库与python语言进行了Lucas-Kanade稀疏光流算法与Gunner-Farneback稠密光流算法的程序编写。实现了对物体的移动轨迹进行了跟踪,并对移动物体进行了识别检测,检测效果达到预期要求。

  • 目标跟踪检测;OpenCV视觉库;LK稀疏光流;GF稠密光流

研究背景

  现代社会计算机和其它数码产品的快速发展,让图像和视频变得更容易获取,也对图像的智能处理提出了更高的要求。数字图像处理技术来帮助人们分析图像和视频内容,并根据实际需求对图像和视频进行自动处理。当前的计算机视觉领域有许多方向,而基于视觉的目标跟踪检测一直是其中的一个热门分支。
  视觉目标跟踪是在视频或连续图像序列中找到感兴趣的目标并计算出其在每一帧中的位置,进而将结果输出以完成其它高级任务的一种技术。随着计算机性能的逐渐提高和摄像终端的推广,目标跟踪受到了越来越多的关注,也应用到了更多的领域。当前基于视觉的目标跟踪检测主要应用在如下范围:
 ⑴军事方面:近些年来精密制导火器与无人机在军事任务中得到了较多应用,如精密制导武器通过机器视觉追踪打击目标、无人机对目标进行侦察和跟踪等,这其中应用了目标跟踪技术。
 ⑵安防监控:智能视频安防监控系统被广泛用于银行、超市,小区等各类场所,以监控可能出现的异常情况。相较于人工监视和事后取证的传统监控系统,智能监控系统对视频中的运动目标进行跟踪和识别分析,并在出现非正常情况时及时发出警报,从而减少人力成本并提高效率。
 ⑶智能交通:当前城市交通流量越来越大,智能化的交通流量控制和监控也越来越重要。交通流量监测、车辆定位等都需要目标跟踪的参与,相关的典型系统主要有VISATRAM、VIPS等。
 ⑷机器人自主视觉工业机器人利用相机获取的视觉信息对目标进行定位和跟踪以完成各种工业任务。在各类恶劣工作环境如喷漆、放射污染、高温高压等情况下,有视觉功能的机器人可以代替人类完成危险劳动。

光流法理论基础

光流的定义

  所谓光流(optical flow),即是空间运动物体在观察成像平面上的像素运动的瞬时速度。通常将二维图像平面特定坐标点上的灰度瞬时变化率定义为光流矢量。
  光流法是利用图像序列中像素在时间域上的变化以及相邻帧之间的相关性来找到上一帧跟当前帧之间存在的对应关系,从而计算出相邻帧之间物体的运动信息的一种方法。所谓光流就是瞬时速率,在时间间隔很小(比如视频的连续前后两帧之间)时,也等同于目标点的位移。

光流的物理意义

  光流是由于场景中前景目标本身的移动、相机的运动,或者两者的共同运动所产生的。当人的眼睛观察运动物体时,物体的景象在人眼的视网膜上形成一系列连续变化的图像,这一系列连续变化的信息不断“流过”视网膜(即图像平面),好像一种光的“流”,故称之为光流。光流表达了图像的变化,由于它包含了目标运动的信息,因此可被观察者用来确定目标的运动情况。
  而利用光流场的二维矢量场,即带有灰度的像素点在图像平面上运动而产生的瞬时速度场,则可近似反映出现实世界中物体的运动场,进而得到物体在实际空间中的运动轨迹。

光流法的基本原理

基本假设条件

  (1)亮度恒定不变。即同一目标在不同帧间运动时,其亮度不会发生改变。这是基本光流法的假定(所有光流法变种都必须满足),用于得到光流法基本方程;
  (2)时间连续或运动是“小运动”。即时间的变化不会引起目标位置的剧烈变化,相邻帧之间位移要比较小。同样也是光流法不可或缺的假定。

基本约束方程

  考虑一个像素I(x,y,t)I(x,y,t)在第一帧的光强度(其中t代表其所在的时间维度)。它移动了(dx,dy)(dx,dy)的距离到下一帧,用了dtdt时间。因为是同一个像素点,依据上文提到的第一个假设我们认为该像素在运动前后的光强度是不变的,即:

I(x,y,t)=I(x+dx,y+dy,t+dt)(1)I(x,y,t)=I(x+dx,y+dy,t+dt) \qquad (1)

  将等式右端进行泰勒展开,得:

I(x,y,t)=I(x,y,t)+Ixdx+Iydy+Itdt+ε(2)I(x,y,t)=I(x,y,t)+ \frac {\partial I}{\partial x} \cdot dx + \frac{\partial I} { \partial y} \cdot dy+ \frac{\partial I} {\partial t} \cdot dt+ε \qquad (2)

  其中εε代表二阶无穷小项,可忽略不计。再将(2)代人(1)后同除dtdt,可得:

Ixdxdt+Iydydt+Itdtdt=0(3)\frac{∂I}{∂x}\cdot \frac{dx}{dt} + \frac{∂I}{∂y} \cdot \frac{dy}{dt}+ \frac{∂I}{∂t}\cdot \frac{dt}{dt}=0 \qquad (3)

  设uu,vv分别为光流分别为沿XX轴与YY轴的速度矢量,得:

u=dxdt,v=dydt(4)u=\frac{dx}{dt},v=\frac{dy}{dt} \qquad (4)

综上,式(3)可以写为:

Ixu+Iyv+It=0(5)I_x u+I_y v+I_t=0 \qquad (5)

  其中,Ix,Iy,ItIx,Iy,It均可由图像数据求得,而(u,v)(u,v)即为所求光流矢量。
  约束方程只有一个,而方程的未知量有两个,这种情况下无法求得uuvv的确切值。此时需要引入另外的约束条件,从不同的角度引入约束条件,导致了不同光流场计算方法。按照理论基础与数学方法的区别把它们分成四种:基于梯度的方法、基于匹配的方法、基于能量(频率)的方法和基于相位的方法。

稠密光流与稀疏光流

  除根据原理的不同来区分光流法外,还可以根据所形成的光流场中二维矢量的疏密程度将光流法分为稠密光流与稀疏光流两种。
  稠密光流是一种针对图像或指定的某一片区域进行逐点匹配的图像配准方法,它计算图像上所有的点的偏移量,从而形成一个稠密的光流场。通过这个稠密的光流场,可以进行像素级别的图像配准。Horn-Schunck算法以及基于区域匹配的大多数光流法都属于稠密光流的范畴。
  与稠密光流相反,稀疏光流并不对图像的每个像素点进行逐点计算。它通常需要指定一组点进行跟踪,这组点最好具有某种明显的特性,例如Harris角点等,那么跟踪就会相对稳定和可靠。稀疏跟踪的计算开销比稠密跟踪小得多。

算法介绍

Lucas-Kanade稀疏光流法

  在计算机视觉中,Lucas–Kanade光流算法是一种两帧差分的光流估计算法。它由Bruce D. Lucas和Takeo Kanade提出。基于先前已经介绍到的光流法的基本假设,即图像亮度恒定,物体是一个小运动和空间一致。该算法利用利用时变图像灰度(或其滤波形式)的时空微分(即时空梯度函数)来计算像素的速度矢量。
  假设流(Vx,Vx,Vx)(Vx,Vx,Vx)在一个大小为M*M的窗中,那么从图像像素1N1…N中可以得到方程(6):

Ix1Iy1Iz1Ix2Iy2Iz2IxnIynIznVxVyVz=I1I2In(6)\begin{matrix} I_x1 & I_y1 & I_z1 \\ I_x2 & I_y2 & I_z2 \\ \vdots & \vdots & \vdots \\ I_xn & I_yn & I_zn \\ \end{matrix} \cdot \begin{matrix} V_x \\ V_y \\ V_z \end{matrix} = \begin{matrix} -I_1\\ -I_2 \\\vdots \\ -I_n \end{matrix} \qquad (6)

  使用最小二乘法,即可求出该方程的解,带入光流法方程(5)Ixu+Iyv+It=0I_x u+I_y v+I_t=0,即可求解光流方程。
  但LK算法天生的不足在于无法产生一个密度很高的流向量,例如在运动的边缘和黑大的同质区域中的微小移动方面流信息会很快的褪去。它的优点在于有噪声存在的鲁棒性还是可以的。且由于该方法基于物体是一个小运动这一前提,故当物体运动速度较快时,假设不成立,那么后续的假设就会有较大的偏差,使得最终求出的光流值有较大的误差。Jean-Yves Bouguet提出一种基于金字塔分层,针对仿射变换的改进Lucas-Kanade算法。
  考虑物体的运动速度较大时,算法会出现较大的误差。那么就希望能减少图像中物体的运动速度。一个直观的方法就是,缩小图像的尺寸。假设当图像为400×400400×400时,物体速度为[1616][16 16],那么图像缩小为200×200200×200时,速度变为[8,8][8,8]。缩小为100100100*100时,速度减少到[4,4][4,4]。所以光流可以通过生成 原图像的金字塔图像,逐层求解,不断精确来求得。简单来说上层金字塔(低分辨率)中的一个像素可以代表下层的两个。
  总体来讲,金字塔特征跟踪算法描述如下:首先,光流和仿射变换矩阵在最高一层的图像上计算出;将上一层的计算结果作为初始值传递给下一层图像,这一层的图像在这个初始值的基础上,计算这一层的光流和仿射变化矩阵;再将这一层的光流和仿射矩阵作为初始值传递给下一层图像,直到传递给最后一层,即原始图像层,这一层计算出来的光流和仿射变换矩阵作为最后的光流和仿射变换矩阵的结果。最终的光流和就是在所有层的分段光流d的叠加。使用金字塔图像计算光流的一个明显的好处是,对于一个有着较大的像素偏移的矢量d,可以通过计算几个比较小的残余光流来得到。这里就是金字塔跟踪算法的核心。
  这种算法最明显的优势在于对于每一层的光流都会保持很小,但是最终计算来的光流可以进行累积,便于有效地跟踪特征点。

Gunner-Farneback稠密光流法

  针对稀疏光流法存在的问题,2003年 Gunner-Farneback稠密光流法被正式提出。Gunner-Farneback稠密光流法可将图像上的所有像素点的光流都计算出来,从而获得一个高密度的流向量。
  Gunner-Farneback首先将图像视为二维信号的函数(输出图像是灰度图像),因变量是二维坐标位置X=(x,y)TX=(x,y)^T,并且利用二次多项式对于图像进行近似建模的话,可得到公式(7)

f(x)xTAx+bT+c(7)f(x) \rightarrow x^T Ax+b^T+c\qquad (7)

  其中,$A $是一个2×22×2的对称矩阵(是通过像素的邻域信息的最小二乘加权拟合得到的,权重系数与邻域的像素大小和位置有关);bb是一个2×12×1的矩阵向量;cc为标量。
  再将原有(笛卡尔坐标系)图像的二维信号空间转换到以(1,x,y,x2,y2,xy)(1,x,y,x^2,y^2,xy)作为基函数的空间,则表示图像需要一个六维向量作为稀疏,带入不同像素点的坐标值求得像素点的灰度值。

具体实现

软件环境

Python语言

  Python是一种跨平台的计算机程序设计语言,是一个高层次的结合了解释性、编译性、互动性和面向对象的脚本语言,在各个平台均有良好的兼容性。且Python配套了丰富的程序库,如用于科学计算的Numpy、用于画图的Matplotlib、用于数据分析的Pandas等,非常适合我们用来进行演示demo的快速实现和解决日常工作的问题。

OpenCV视觉库

  虽然 python 很强大,而且也有自己的图像处理库 PIL,但是相对于OpenCV 来讲,还是弱小很多。OpenCV是一个开源发行的跨平台计算机视觉和机器学习软件库。跟很多开源软件一样 OpenCV 也提供了完善的 python 接口,非常便于调用。OpenCV包含了超过 2500个算法和函数,几乎任何一个能想到的成熟算法都可以通过调用 OpenCV 的函数来实现,非常适合用于数字图像处理算法的实现。

实现L-K稀疏光流算法

  在OpenCV中,将Lucas-Kanade算法的实现打包成了一个函数:cv2.calcOpticalFlowPyrLK(),我们可以使用这个函数创建一个小程序来跟踪视频中的一些点。
  首先,我们先使用函数cv2.goodFeatureToTrack()来确定要跟踪的点。通过从视频的第一帧中检测一些Shi-Tomasi 角点,然后我们使用 Lucas-Kanade 算法迭代跟踪这些角点。
  接着,将前一帧图像,下一帧图像和检测到的角点传入到函数cv2.calcOpticlaFlowPyrLK()中,函数将返回带有状态数的点,如果状态数是 1,那说明在下一帧图像中找到了这个点(上一帧中角点),如果状态数是 0,就说明没有在下一帧图像中找到这个点。我们再把这些点作为参数传给函数,如此迭代下去实现对于目标的追踪跟踪。
  当然,在这个检测过程中,并未对返回的角点正确性进行校验,图像中的一些特征点甚至在丢失以后,光流还会找到一个预期相似的点。所以为了实现稳定的跟踪,应该每个一定间隔就要进行一次角点检测,如此往复,不断跟踪运动目标。
  具体跟踪效果如下图所示,可以看到,在第一组测试结果总,当我将头部总左侧摆动到右侧时,L-K稀疏光流算法很好的捕捉到了我的面部特征点,并追踪到了头部的摆动轨迹,能够实现很好的检测。

  具体程序代码如下:

import numpy as np
import cv2
cap = cv2.VideoCapture(0)
# params for ShiTomasi corner detection
feature_params = dict(maxCorners=100,
                      qualityLevel=0.3,
                      minDistance=7,
                      blockSize=7)
# Parameters for lucas kanade optical flow
# maxLevel 为使用的图像金字塔层数
lk_params = dict(winSize=(15, 15),
                 maxLevel=2,
                 criteria=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03))
# Create some random colors
color = np.random.randint(0, 255, (100, 3))
# Take first frame and find corners in it
ret, old_frame = cap.read()
old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)
p0 = cv2.goodFeaturesToTrack(old_gray, mask=None, **feature_params)
# Create a mask image for drawing purposes
mask = np.zeros_like(old_frame)
while (1):
    ret, frame = cap.read()
    frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    # calculate optical flow 能够获取点的新位置
    p1, st, err = cv2.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lk_params)
    # Select good points
    good_new = p1[st == 1]
    good_old = p0[st == 1]
    # draw the tracks
    for i, (new, old) in enumerate(zip(good_new, good_old)):
        a, b = new.ravel()
        c, d = old.ravel()
        mask = cv2.line(mask, (a, b), (c, d), color[i].tolist(), 2)
        frame = cv2.circle(frame, (a, b), 5, color[i].tolist(), -1)
    img = cv2.add(frame, mask)
    cv2.imshow('frame', img)
    k = cv2.waitKey(30) & 0xff
    if k == 27:
        break
    # Now update the previous frame and previous points
    old_gray = frame_gray.copy()
    p0 = good_new.reshape(-1, 1, 2)
cv2.destroyAllWindows()
cap.release()

实现G-F稠密光流算法

  同样的,OpenMV也将Gunner-Farneback算法打包成了一个函数:cv2.calcOpticalFlowFarneback()。我们可以使用这个函数创建一个小程序来跟踪运动目标。
  将视频数据转换为BGR2GRAY色域,再同样将视频的前一帧数据和后一帧的图像数据传送给函数cv2.calcOpticalFlowFarneback()。函数会返回一个带有光流向量的(u,v)双通道数组,分别代表光流的大小与方向。方向对应于 H(Hue)通道,大小对应于 V(Value)通道。接着,再使用边缘检测算法,即使用cv2.Canny()函数对稠密光流检测数据进行边缘检测,从而更好的实现对于移动目标的检测。
  具体检测效果如下,上方的两张图为灰度图与L-K稀疏光流算法检测图,用于更好的显示物体的实时运动状态。下方的两张图分别为G-F稠密算法检测图和对检测结果进行边缘检测处理后的结果。可以看出,当我的头部从左向右摆动时,Gunner-Farneback稠密光流算法精确的计算出了我头部图像的光流大小与方向,清晰的标识出了场景中的运动目标,再对其使用边缘检测算法,则可以很好的获得运动目标的外形轮廓,从而实现运动目标的实时检测。

  具体程序代码如下:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import cv2
import numpy as np
cap = cv2.VideoCapture(0)
ret, frame1 = cap.read()
prvs = cv2.cvtColor(frame1,cv2.COLOR_BGR2GRAY)
hsv = np.zeros_like(frame1)
hsv[...,1] = 255
while(1):
    ret, frame2 = cap.read()
    next = cv2.cvtColor(frame2,cv2.COLOR_BGR2GRAY)
    flow = cv2.calcOpticalFlowFarneback(prvs,next, None, 0.5, 3, 15, 3, 5, 1.2, 0)
    #cv2.cartToPolar Calculates the magnitude and angle of 2D vectors.
    mag, ang = cv2.cartToPolar(flow[...,0], flow[...,1])
    hsv[...,0] = ang*180/np.pi/2
    hsv[...,2] = cv2.normalize(mag,None,0,255,cv2.NORM_MINMAX)
    rgb = cv2.cvtColor(hsv,cv2.COLOR_HSV2BGR)
    edges = cv2.Canny(rgb, 50, 150)
    #cv2.imshow('flame0',next)
    cv2.imshow('frame1', rgb)
    cv2.imshow('frame2',edges)

    k = cv2.waitKey(30) & 0xff
    if k == 27:
        break
    elif k == ord('s'):
        cv2.imwrite('opticalfb.png',frame2)
        cv2.imwrite('opticalhsv.png',rgb)
    prvs = next
cap.release()
cv2.destroyAllWindows()

总结

  本文针对目标跟踪检测这一实际问题,采用光流法进行目标检测。结合光流算法的相关理论知识,使用OpenCV视觉库与Python语言,实现了Lucas-Kanade稀疏光流算法与Gunner-Farneback稠密光流算法,并对检测效果进行了初步验证。结果表明,Lucas-Kanade稀疏光流算法与Gunner-Farneback稠密光流算法均能较好的实现对于目标的检测跟踪。但Lucas-Kanade稀疏光流算法在面临复杂场景或高速移动物体时,可能会出现检测率较低或目标跟踪丢失的情况。Gunner-Farneback稠密光流算法面对各种场景均能够较好的检测识别运动目标,但相对而言计算较慢,需要使用更多的算力。