opencv+python 稀疏光流估计算法 代码实现 目标跟踪

光流估计算法

光流的概念是Gibson在1950年首先提出来的。它是空间运动物体在观察成像平面上的像素运动的瞬时速度,是利用图像序列中像素在时间域上的变化以及相邻帧之间的相关性来找到上一帧跟当前帧之间存在的对应关系,从而计算出相邻帧之间物体的运动信息的一种方法。一般而言,光流是由于场景中前景目标本身的移动、相机的运动,或者两者的共同运动所产生的。

假设条件

(1)亮度恒定,就是同一点随着时间的变化,其亮度不会发生改变。这是基本光流法的假定(所有光流法变种都必须满足),用于得到光流法基本方程;

(2)小运动,这个也必须满足,就是时间的变化不会引起位置的剧烈变化,这样灰度才能对位置求偏导(换句话说,小运动情况下我们才能用前后帧之间单位位置变化引起的灰度变化去近似灰度对位置的偏导数),这也是光流法不可或缺的假定;

(3)空间一致,一个场景上邻近的点投影到图像上也是邻近点,且邻近点速度一致。这是Lucas-Kanade光流法特有的假定,因为光流法基本方程约束只有一个,而要求x,y方向的速度,有两个未知变量。我们假定特征点邻域内做相似运动,就可以连立n多个方程求取x,y方向的速度(n为特征点邻域总点数,包括该特征点)。

研究光流场的目的就是为了从图片序列中近似得到不能直接得到的运动场。运动场,其实就是物体在三维真实世界中的运动在图像上的二位速度矢量投影;光流场,是三维物体的运动在二维图像平面上(人的眼睛或者摄像头)亮度模式的投影。

那通俗的讲就是通过一个图片序列,把每张图像中每个像素的运动速度和运动方向找出来就是光流场。那怎么找呢?咱们直观理解肯定是:第t帧的时候A点的位置是(x1, y1),那么我们在第t+1帧的时候再找到A点,假如它的位置是(x2,y2),那么我们就可以确定A点的运动了:(ux, vy) = (x2, y2) - (x1,y1)。

那怎么知道第t+1帧的时候A点的位置呢? 这就存在很多的光流计算方法了。

1981年,Horn和Schunck创造性地将二维速度场与灰度相联系,引入光流约束方程,得到光流计算的基本算法。人们基于不同的理论基础提出各种光流计算方法,算法性能各有不同。Barron等人对多种光流计算技术进行了总结,按照理论基础与数学方法的区别把它们分成四种:基于梯度的方法、基于匹配的方法、基于能量的方法、基于相位的方法。近年来神经动力学方法也颇受学者重视。

L-K光流估计方法的基本原理:

L-K光流估计方法属于一种基于匹配的光流计算方法,其包括基于特征和区域的两种。基于特征的方法不断地对目标主要特征进行定位和跟踪,对目标大的运动和亮度变化具有鲁棒性(robustness)。存在的问题是光流通常很稀疏,而且特征提取和精确匹配也十分困难。基于区域的方法先对类似的区域进行定位,然后通过相似区域的位移计算光流。这种方法在视频编码中得到了广泛的应用。然而,它计算的光流仍不稠密。另外,这两种方法估计亚像素精度的光流也有困难,计算量很大。在考虑光流精度和稠密性时,基于匹配的方法适用。

根据上面的光流估计基本模型得出方程IxΔx+IyΔy=−It,任务:求(u,v)=(Δx,Δy)

困难:一个方程包含两个未知数。

Lucas-Kanade方法采用基于领域的计算方法,假设在一个小方格里的所有像素位移相同。则会得到一组光流估计方程。用矩阵表示如下:

opencv+python 稀疏光流估计算法 代码实现 目标跟踪_第1张图片

既:

opencv+python 稀疏光流估计算法 代码实现 目标跟踪_第2张图片

将上述问题转化为最优化问题(超定方程求解)

输入图片说明

最小二乘解:

输入图片说明

区域像素只有2个时,就是2元1次方程组求解!

多个像素,比如3*3时,则是求上述最小二乘解
进一步,L-K方法假定在一个小的图像邻域内速度近似一致

约束:

opencv+python 稀疏光流估计算法 代码实现 目标跟踪_第3张图片

对应:

opencv+python 稀疏光流估计算法 代码实现 目标跟踪_第4张图片

类似前述求解,可得

输入图片说明

可信度判断:矩阵求逆是否能实现?

输入图片说明

我们在计算光流的时候,我们要求图像对应位置灰度变化充分(具有充分特征),假如位置灰度变化平缓,那么沿x和y方向的偏倒就可能为0,那么上述式子不可求逆,也就无法计算光流。

通过特征值判断是否计算可信
在数学上我们要判断矩阵是否可逆,我们可以通过计算特征值来判断。如果两个特征值远大于0,那么矩阵求逆是可靠的。假如有一个特征值接近于0的话,那么矩阵求逆是不可靠的。

opencv代码实现

import numpy as np
import cv2
import time
import datetime

cap = cv2.VideoCapture("vtest.avi")#打开一个视频

fourcc = cv2.VideoWriter_fourcc(*'XVID')#设置保存图片格式
out = cv2.VideoWriter(datetime.datetime.now().strftime("%A_%d_%B_%Y_%I_%M_%S%p")+'.avi',fourcc, 10.0, (768,576))#分辨率要和原视频对应

# ShiTomasi 角点检测参数
feature_params = dict( maxCorners = 100,
                       qualityLevel = 0.3,
                       minDistance = 7,
                       blockSize = 7 )

# lucas kanade光流法参数
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))

# 获取第一帧,找到角点
ret, old_frame = cap.read()
#找到原始灰度图
old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)

#获取图像中的角点,返回到p0中
p0 = cv2.goodFeaturesToTrack(old_gray, mask = None, **feature_params)

# 创建一个蒙版用来画轨迹
mask = np.zeros_like(old_frame)

while(1):
    ret,frame = cap.read() #读取图像帧
    frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) #灰度化

    # 计算光流
    p1, st, err = cv2.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lk_params)
    # 选取好的跟踪点
    good_new = p1[st==1]
    good_old = p0[st==1]

    # 画出轨迹
    for i,(new,old) in enumerate(zip(good_new,good_old)):
        a,b = new.ravel()#多维数据转一维,将坐标转换后赋值给a,b
        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)  #显示图像

    out.write(img)#保存每一帧画面

    k = cv2.waitKey(30) & 0xff #按Esc退出检测
    if k == 27:
        break

    # 更新上一帧的图像和追踪点
    old_gray = frame_gray.copy()
    p0 = good_new.reshape(-1,1,2)


out.release()#释放文件
cap.release()
cv2.destoryAllWindows()#关闭所有窗口

opencv+python 稀疏光流估计算法 代码实现 目标跟踪_第5张图片

height="600" width="720" src="https://v.qq.com/x/page/b0851nvrlym.html" allowfullscreen="">

检测效果视频:稀疏光流估计算法的视频展示-点这里!

光流估计函数详解:

void calcOpticalFlowPyrLK(
InputArray prevImg, InputArray nextImg, InputArray prevPts, InputOutputArray nextPts, 
OutputArray status, OutputArray err, 
Size winSize=Size(21,21), int maxLevel=3, 
TermCriteria criteria=TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, 0.01), 
int flags=0, double minEigThreshold=1e-4 
)
  • prevImg:第一幅8位输入图像 或 由buildOpticalFlowPyramid()构造的金字塔。
  • nextImg:第二幅与preImg大小和类型相同的输入图像或金字塔。
  • prevPts:光流法需要找到的二维点的vector。点坐标必须是单精度浮点数。
  • nextPts:包含输入特征在第二幅图像中计算出的新位置的二维点(单精度浮点坐标)的输出vector。当使用OPTFLOW_USE_INITIAL_FLOW 标志时,nextPts的vector必须与input的大小相同。
  • status:输出状态vector(类型:unsigned chars)。如果找到了对应特征的流,则将向量的每个元素设置为1;否则,置0。
  • err:误差输出vector。vector的每个元素被设置为对应特征的误差,可以在flags参数中设置误差度量的类型;如果没有找到流,则未定义误差(使用
  • status:参数来查找此类情况)。
  • winSize:每级金字塔的搜索窗口大小。
  • maxLevel:基于最大金字塔层次数。如果设置为0,则不使用金字塔(单级);如果设置为1,则使用两个级别,等等。如果金字塔被传递到input,那么算法使用的级别与金字塔同级别但不大于MaxLevel。
  • criteria:指定迭代搜索算法的终止准则(在指定的最大迭代次数标准值(criteria.maxCount)之后,或者当搜索窗口移动小于criteria.epsilon。)
  • flags:操作标志,可选参数:
  • OPTFLOW_USE_INITIAL_FLOW:使用初始估计,存储在nextPts中;如果未设置标志,则将prevPts复制到nextPts并被视为初始估计。
  • OPTFLOW_LK_GET_MIN_EIGENVALS:使用最小本征值作为误差度量(见minEigThreshold描述);如果未设置标志,则将原始周围的一小部分和移动的点之间的 L1 距离除以窗口中的像素数,作为误差度量。
  • minEigThreshol:算法所计算的光流方程的2x2标准矩阵的最小本征值(该矩阵称为[Bouguet00]中的空间梯度矩阵)÷ 窗口中的像素数。如果该值小于MinEigThreshold,则过滤掉相应的特征,相应的流也不进行处理。因此可以移除不好的点并提升性能。

你可能感兴趣的:(机器学习&计算机视觉)