魏老师学生——Cecil:学习OpenCV-机器视觉之旅
- 光流
- Lucas-Kanade法
- OpenCV中的Lucas-Kanade光流
- 代码演示
- OpenCV中的稠密光流
- 代码演示
Aim:学习光流概念和Lucas-Kanade光流法;使用cv2.calcOpticalFlowPyrLK( ) 对图像中的特征点进行跟踪。
光流
- 概念:由于目标对象或摄像机移动造成的图像对象在连续两帧图像中的移动。是2D向量场,用来显示一个点从第一帧图像到第二帧图像的移动。
- 上图显示一个点在连续五帧图像间的移动,箭头表示光流场向量。
- 光流应用领域:通过运动重建结构;视频压缩;视频增强稳定。
- 光流依据的假设:连续两帧图像间目标对象的像素灰度值不变;相邻像素具有相同运动。
- 第一帧图像中的像素 I (x,y,t) 在时间 dt 后移动到第二帧图像的(x+dx,y +dy)处。根据第一条假设:灰度值不变。所以我们可以得到:I (x,y,t)=I (x + dx,y + dy,t + dt)
对等号右侧泰勒级数展开,消去相同项,两边除以 dt,得到如下方程:fxu + fyv + ft =0
上图等式为光流方程。fx,fy:图像梯度。ft:时间方向梯度。(u,v)未知,通过Lucas-Kanade法解决。
Lucas-Kanade法
- 基于邻域内所有点运动相似。
- 本方法利用3×3邻域中的9个点具有相同的运动。从而找到9个点的光流方程,组成一个具有两未知数9个等式的方程组。(过约束的方程组) 解决办法:最小二乘拟合。
- 角点适合用于跟踪。
- 如何处理大幅运动? 使用图像金字塔的顶层,移除小幅运动,将大幅运动转换为小幅运动,再使用Lucas-Kanade算法,从而得到尺度空间上的光流。
OpenCV中的Lucas-Kanade光流
- 使用cv2.hoodFeatureToTrack() 确定要跟踪的点;(在视频第一帧图像中检测一些Shi-Tomasi角点,再使用Lucas-Kanade算法迭代跟踪角点。)
- cv2.calcOpticalaFlowPyrLK() :传入前一帧图像和其中的点以及下一帧图像。函数返回带有状态数的点。状态数=1,在下一帧图像中找到对应点。状态数=0,未找到。继续传入得到的参数,不断迭代实现跟踪。
- 预防图中特征点丢失后误将相似点作为对象使用,隔一段间隔应该进行一次角点检测。
代码演示
#coding=utf-8
import cv2
import numpy as np
cap=cv2.VideoCapture(0)
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))
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()
frame_gray=cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
pl,st,err=cv2.calcOpticalFlowPyrLK(old_gray,frame_gray,p0,None,**lk_params)
good_new=pl[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('img',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()
OpenCV中的稠密光流
- Gunner_Farneback()算法 :计算稠密光流的方法,返回带有光流向量(u,v)的双通道数组。
- 光流的大小和方向通过计算得到,使用颜色对结果编码便于观察。方向对应H(Hue)通道,大小对应V(Value)通道。
代码演示
#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)
#cv2.calcOpticalFlowFarneback(prev,next,pyr_scale,levels,winsize,iterations,poly_n,poly_sigma,flags)
#pyr_scale 参数,为每张图描述图片比例以构建金字塔。取0.5意味经典金字塔,下一层比上一层小两倍。
#poly_n 使用像素邻域大小查找每个像素的多项式展开,经验值5,7.
#poly_sigma 高斯分布的标准偏差,用来平滑的导数用于多项式展开的基础。经验值1.1或1.5.
#flag 取0计算快,取1计算慢但准确。
flow=cv2.calcOpticalFlowFarneback(prvs,next,None,0.5,3,15,3,5,1.2,0)
#cv2.cartToPolar 计算二维向量的大小和角度。
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)
cv2.imshow('img2',rgb)
k=cv2.waitKey(40)&0xFF
if k==27:
break
elif k==ord('s'):
cv2.imshow('frame2',frame2)
cv2.imshow('rgb',rgb)
prvs=next
cap.release()
cv2.destroyAllWindows()