光流法
光流
由于目标对象或者摄像机的移动造成的图像对象在连续两帧图像中的移动被称为光流。如下图所示,它是一个 2D 向量场,可以用来显示一个点从第一帧图像到第二帧图像之间的移动,箭头表示光流场向量。
其中,光流包括稀疏光流与稠密光流。图像中的每个像素都使用这种方法,则通常将其称为“稠密光流”。有一种替代类算法被称为“稀疏光流”,仅仅跟踪图像中某些点的子集。
Lucas-Kanade稀疏光流法
假设先验
LK光流全称为Lucas-Kanade光流,算法原理比较好理解,首先,LK光流对应用场景提出了三个假设先验:
- 亮度恒定:假设像素在运动过程中亮度(灰度值)恒定,其实这是大部分计算机视觉任务都需要的一个先验。
- 像素偏移小:检测光流的两帧之间不能有过大的motion,否则LK光流会检测失败。
- 空间一致性:当前帧相邻的像素在下一帧应该也是相邻的,这样便于求解图像块的梯度进而寻找到匹配的像素。
算法流程
给定t
时刻的图像上的像素点I(x,y)
,算法的目标是找到在下一时刻该像素的在各个方向上的位移,用公式表达就是:
可以对等号右边的式子采用泰勒展开:
可以看到等号后边第一项与等号左边相等可以消去, 和 比较好理解,就是当前时刻图像在和方向的梯度了, 表示的的是时间方向上的梯度,也就是下一帧与当前帧的差分。是两帧时间差也就是1,而和就是我们要求解的像素运动。由此我们可以得到:
我们现在有两个未知数但是只有一个方程,无法求解,根据我们在最开始的第三个假设,此时我们可以使用需要求解的像素周围5×5的像素块来帮助我们得到更多的方程式:
此时便组成了一个超定方程组,也就是方程个数大于未知数个数,这是我们可以使用最小二乘法来求解这个方程组。
方程的解必须满足以下条件:
必须可逆
中的特征值和不能太小。如下图所示,当特征值过小,说明像素块选在平缓区域。
-
中不能太大。如下图所示,当过大时,说明素块选在边缘区域。
总结
像素块不能选在平缓区域和边缘区域。如下图所示,像素块最好选在纹理丰富的区域。
LK光流法存在的问题
在实际的拍摄的视频中,每一帧不一定都满足三个假设:亮度恒定、像素偏移小和空间一致性。
-
亮度恒定不满足,
解决方法:梯度恒定
-
像素偏移过大
解决方法:采样高斯金字塔方法,估计光流
- 像素块不满足空间一致性。
代码实现:
import numpy as np
import cv2
cap = cv2.VideoCapture("768x576.avi")
# ShiTomas角点检测的参数
feature_params = dict(maxCorners =100,qualityLevel=0.3,minDistance=7,blockSize=7)
# 金字塔LK算法参数
lk_params = dict(winSize=(15,15),maxLevel=2,criteria=(cv2.TERM_CRITERIA_EPS|cv2.TermCriteria_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 = cv2.goodFeaturesToTrack(old_gray,mask=None,**feature_params)
mask = np.zeros_like(old_frame)
while(1):
ret,frame = cap.read()
if ret is True:
frame_gray = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
else:
break
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()
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
old_gray = frame_gray.copy()
p0 = good_new.reshape(-1,1,2)
cv2.destroyAllWindows()
cap.release()
参考文献:
[1] B. Lucas and T. Kanade, “An iterative image registration technique with an application to stereo vision,” in Proc. of International Joint Conf. On Artificial Intelligence, pp.674-679, 1981.
[2] 浙江大学陆系群副教授,《Motion Estimation Optical Flow.PPT》
[3] 《OpenCV-Python 中文教程》
[4] LK稀疏光流法