Optical Flow(光流)
- 光流的概念是指在连续的两帧图像中由于图像中的物体移动或者摄像头的移动导致的图像中目标像素的移动
- 由观察者和场景之间的相对运动引起的视觉场景中物体表面和边缘的明显运动模式
- 光流是二维矢量场,表示了一个点从第一帧到第二帧的位移
- 上面的图表示了一个球在连续的5帧图像中的运动。箭头表示了它的位移矢量
- 光流法的工作原理基于如下假设:
- 场景的像素强度在相邻帧之间基本不变
- 相邻像素具有相似的运动
- 第一帧中的像素 I(x,y,t) 表示在时刻 t 时像素 I(x,y) 的值,在经过 dt 时间后,该像素在下一帧中移动了 (dx,dy),由于这些像素是相同的且强度不变,因此可以表示成:
- 假设移动很小,使用泰勒公式可以表示成:
- $ H.O.T $ 是高阶无穷小
- 由第一个假设和使用泰勒公式展开的式子可以得到:
- 除以 $ \partial t $ 得
- 其中
- 上述方程称为光流方程,$ f_x $ 和 $ f_y $ 是图像的梯度,$ f_t $ 是沿时间的梯度。但是 u 和 v 是未知的,我们无法用一个方程解两个未知数,那么就有 Lucas-Kanade 方法来解决这个问题
Lucas-Kanade 算法
- 基于第二条假设,就是所有的相邻像素都有相同的移动 Lucas-Kanade 方法使用了一个 3x3 的窗口,在这个窗口中的 9 个像素点满足方程
- 将点代入方程,现在的问题就变成了使用9个点求解两个未知量,解的个数大于未知数的个数,这是个超定方程,使用最小二乘的方法来求解最优值。如下为计算得到的结果
- 检测逆矩阵与 Harris 角点检测很像,说明角点是适合用来做跟踪的- 想法很简单,给出一些点用来追踪,从而获得点的光流向量。但是有另外一个问题需要解决,目前讨论的运动都是小步长的运动,如果有幅度大的运动出现,本算法就会失效- 使用的解决办法是利用图像金字塔。在金字塔顶端的小尺寸图片当中,大幅度的运动就变成了小幅度的运动。所以使用LK算法,可以得到尺度空间上的光流
OpenCV 中的 LK 光流
- OpenCV 库提供函数 cv.calcOpticalFlowPyrLK()- 一个简单应用示例:我们要跟踪视频中某些点,先使用 cv.goodFeaturesToTrack() 来获取这些点- 首先取第一帧,检测其中的 Shi-Tomasi 角点,然后使用 Lucas-Kanade 算法来迭代地跟踪这些特征点。迭代的方式就是向 cv.calcOpticalFlowPyrLK() 函数传入上一帧图片和其中的特征点以及当前帧图片,函数会返回当前帧的特征点,每个点都带有一个状态值(0 或 1),如果在当前帧找到了上一帧中的点,这个点的状态值就是 1,否则就是 0。将状态值为 1 的点作为下次特征点的输入,不停迭代
nextPts, status, err = cv.calcOpticalFlowPyrLK(prevImg, nextImg, prevPts, nextPts[, status[, err[, winSize[, maxLevel[, criteria[, flags[, minEigThreshold]]]]]]])
- 返回值:
- nextPts:二维点的输出矢量(具有单精度浮点坐标),包含第二图像中输入特征的计算新位置; 当传递 OPTFLOW_USE_INITIAL_FLOW 标志时,向量必须与输入中的大小相同
- status:状态值(无符号字符型)
- err:向量中的每个特征对应的错误率
- 输入值:
- prevImg:上一帧图片
- nextImg:当前帧图片
- prevPts:上一帧找到的特征点向量
- winSize:在图像金字塔中计算局部连续运动的窗口的尺寸
- maxLevel:图像金字塔层数,0 代表不使用金字塔(单极)
- criteria:指定迭代搜索算法的终止条件(在指定的最大迭代次数criteria.maxCount之后或当搜索窗口移动小于criteria.epsilon时)
- flags:选择计算方法:
- OPTFLOW_USE_INITIAL_FLOW:使用初始估计,存储在nextPts中; 如果未设置标志,则将prevPts复制到nextPts并将其视为初始估计
- OPTFLOW_LK_GET_MIN_EIGENVALS:使用最小特征值作为误差测量; 如果未设置标志,则将原始位置和移动点之间的色块之间的 L1 距离除以窗口中的像素数,用作误差测量
- minEigThreshold:该算法计算光流方程的2×2正常矩阵的最小特征值,除以窗口中的像素数,如果此值小于 minEigThreshold,则会过滤掉相应的功能并且不会处理该光流,因此它允许删除坏点并获得性能提升