光流估计的基本概念
光流,顾名思义,光的流动。比如人眼感受到的夜空中划过的流星。在计算机视觉中,定义图像中对象的移动,这个移动可以是相机移动或者物体移动引起的。具体是指,视频图像的一帧中的代表同一对象(物体)像素点移动到下一帧的移动量,使用二维向量表示.
根据是否选取图像稀疏点进行光流估计,可以将光流估计分为稀疏光流和稠密光流
OpenCV中提供了光流估计的接口,包括稀疏光流估计算法cv2.calcOpticalFlowPyrLK(),和稠密光流估计cv2.calcOpticalFlowFarneback()。其中稀疏光流估计算法为Lucas-Kanade算法。
传统算法 Lucas-Kanade
为了将光流估计进行建模,Lucas-Kanade做了三个重要的假设:
- 亮度恒定:同一点随着时间的变化,其亮度不会发生改变。
- 小运动:随着时间的变化不会引起位置的剧烈变化,只有小运动情况下才能用前后帧之间单位位置变化引起的灰度变化去近似灰度对位置的偏导数。
- 空间一致:一个场景上邻近的点投影到图像上也是邻近点,且邻近点速度一致。因为光流法基本方程约束只有一个,而要求x,y方向的速度,有两个未知变量。所以需要连立n多个方程求解。
cv2.calcOpticalFlowPyrLK():
参数:
- prevImage 前一帧图像
- nextImage 当前帧图像
- prevPts 待跟踪的特征点向量
- winSize 搜索窗口的大小
- maxLevel 最大的金字塔层数
返回:
- nextPts 输出跟踪特征点向量
- status 特征点是否找到,找到的状态为1,未找到的状态为0
简单应用
import numpy as np
import cv2
cv2.goodFeaturesToTrack()函数是用来跟踪检测图像中的角点
输入的参数分别是:
- image : 输入图像,是八位的或者32位浮点型,单通道图像,所以有时候用灰度图
- maxCorners : 返回最大的角点数,是最有可能的角点数,如果这个参数不大于0,那么表示没有角点数的限制。
- qualityLevel : 图像角点的最小可接受参数,质量测量值乘以这个参数就是最小特征值,小于这个数的会被抛弃。
- minDistance : 返回的角点之间最小的欧式距离。
- mask : 检测区域。如果图像不是空的(它需要具有CV_8UC1类型和与图像相同的大小),它指定检测角的区域。
- blockSize : 用于计算每个像素邻域上的导数协变矩阵的平均块的大小。
- useHarrisDetector :选择是否采用Harris角点检测,默认是false.
- k : Harris检测的自由参数。
输出参数:
- corners: 输出为角点
# 读取视频
cap = cv2.VideoCapture('./test.avi')
# 读第一帧图片
ret, old_frame = cap.read()
# 灰度化
old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)
# 检测角点 -- 特征点
feature_params = dict(
maxCorners=100,
qualityLevel=0.3,
minDistance=7
)
# p0存储特征点,内部数据为float
p0 = cv2.goodFeaturesToTrack(old_gray, mask=None, **feature_params)
# 创建一个与原图同等大小的mask,初始为0
mask = np.zeros_like(old_frame)
# 随机颜色,形状为(100, 3)
color = np.random.randint(0, 255, (100, 3))
while True:
# 读取一帧画面
ret, frame = cap.read()
if frame is None:
break
# 对画面进行灰度化处理
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 光流估计 -- p0要float,返回新图片中,特征点的位置
p1, st,err = cv2.calcOpticalFlowPyrLK(old_gray, gray, p0, None, winSize=(15, 15), maxLevel=2)
# 只保留在上一画面和此画面中,都出现的特征点
good_new = p1[st==1] # 新画面中,特征点的位置
good_old = p0[st==1] # 原画面中,特征点的位置
# 绘制轨迹
for i, (new, old) in enumerate(zip(good_new, good_old)):
# 转换成int型,方便画图
a, b = new.ravel().astype(int)
c, d = old.ravel().astype(int)
# 画线
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)
key = cv2.waitKey(150)
if key == 27 or key == ord('q'):
break
# 保存上一个画面
old_gray = gray.copy()
# 更改旧特征点
p0 = good_new.reshape(-1, 1, 2)
cv2.destroyAllWindows()
cap.release()
cv2.waitKey(1)
-1