LK光流和特征点的关系
uv方程
光流有两个假设:
1.亮度恒定,即图像相同位置的灰度短时不变。两帧中对应像素灰度/亮度相同
2.时间持续性(微小移动),这意味着时间的变化不会引起像素位置的剧烈变化,这样像素的灰度值才能对位置求对应的偏导数。
将图像I看作是有三个自变量(坐标和时间)的函数:,那么对它泰勒展开:
舍去constant,认为(x,y)位置的点在t经过偏移后和初始值相同(对应假设1,偏移之后灰度值不变):
,
这样得到
左右同除以,得到
,
当逼近0,由极限定理可以得:
这样方程就可以使用梯度和速度u,v来表示:
其中也是梯度,只不过是时间坐标方向上的。因为假设2的存在可以使用两帧的差分来表示。xy方向的梯度是同一帧中x和y方向相邻像素的差值表示,那么t方向的梯度就是两帧图对应像素的差值:
Lucas-Kanade 法
第三个假设
从这个方程中可以看到,每个像素位置有u,v两个变量要求,但是只有一个方程怎么能有两个未知数呢?LK又增加了第三个假设:空间一致性:场景中相同表面的相邻点具有相似的运动,这样就可以联立多个方程去求解u,v。
所以两个假设都是有用的:第一个亮度不变的假设让可以可以得到泰勒展开中的偏导和为0,进一步得到了光流方程;第二个假设让我们可以联立方程组,进而求解出当前邻域的光流值。
Lucas-Kanade法就是利用一个3x3的领域中的9个像素点具有相同的运动,就可以得到9个点的光流方程(即上述公式),用这些方程来求得(u, v)这两个未知数。相当于多个uv方程共有同一个u,v:
显然这是个约束条件过多的方程组(超正定方程),不能解得精确解,一个好的解决方法就是使用最小二乘来拟合。
矩阵形式
上面方程组的矩阵形式为:
这就是光流约束方程,类型是Ax=b。x的解通过广义逆矩阵来表示:
和角点的关系
可以看到G是一个hessian矩阵,和harris角点的检测如出一辙。为了解得光流中的uv,就要求G首先是可逆的,而可逆就要求G的两个特征值都非零,即最小特征值尽可能大。这就表明了只有在角点的位置才可以求得uv。或者反证一下,如果M描述的是平坦区域或者边缘,那么总会有一个方向的差分是0,就无法满足可逆的条件:
这限制了LK光流法的使用范围,这也是被称为稀疏光流法的主要原因。
和特征点匹配的关系
LK光流不需要描述子,也不需要匹配。
LK光流只计算前一帧的特征点,再结合两帧的差分就可以计算光流,所以可以看作是特征点追踪。
特征点的选择,opencv的cv::goodFeaturesToTrack函数中,默认shi-tomasi角点,可选Harris角点检测。shi-tomasi是在论文《Good_Features_to_Track》中提出的,相比于Harris修改了R的计算,只关注较小的特征值,这样就可以省去超参数k的设定:
Harris | shi-tomasi |
![]() | ![]() |
金字塔
因为假设2的存在,进一步限制了光流的使用,因为输入帧之间的运动往往是很大的。所以一个思路就是把图像下采样,下采样2倍,那么运动也会缩小为原来的2倍,这样就又可以满足假设2.
具体使用的金字塔是高斯金字塔。所以在下采样前要先进行高斯模糊:
下采样是真正的采样,而不是resize,个人猜测这样一方面是简单,因为2倍的下采样就只需要对行列进行奇数或者偶数抽样就可以了。另外一方面是避免了resize带来的中心偏移。
因为金字塔的使用,上一层的光流*2被传递到下一层,所以就使得光流可以处理更大的运动。
代码
import numpy as np
import cv2
"""
LK for image
"""
img_old = cv2.imread('../asset/image/1.bmp')
img_new = cv2.imread('../asset/image/2.bmp')
old_frame = cv2.cvtColor(img_old, cv2.COLOR_BGR2GRAY)
new_frame = cv2.cvtColor(img_new, cv2.COLOR_BGR2GRAY)
feature_params = dict(maxCorners=100, qualityLevel=0.3, minDistance=7)
lk_params = dict(winSize=(30, 30), maxLevel=6)
p0 = cv2.goodFeaturesToTrack(old_frame, mask=None, **feature_params)
p1, st, err = cv2.calcOpticalFlowPyrLK(old_frame, new_frame, p0, None, ) #p1表示当前帧对应的特征点,st标志是否是运动的角点,err表示错误率
p11 = p1[st == 1]
p00 = p0[st == 1]
for i, (new, old) in enumerate(zip(p11, p00)):
image_old = cv2.circle(img_old, (old[0], old[1]), 5, (0, 0, 255), -1)
image_new = cv2.circle(img_new, (new[0], new[1]), 5, (0, 0, 255), -1)
cv2.imshow('new', image_new)
cv2.imshow('old', image_old)
reference:
总结:光流--LK光流--基于金字塔分层的LK光流--中值流_mini猿要成长QAQ的博客-CSDN博客
Fast Optical Flow using Dense Inverse Search - 知乎
OpenCV: Optical Flow
光流(Optical flow)-视频分析基础概念-CSDN博客
光流法(optical flow)_推导光流方程-CSDN博客
https://zhuanlan.zhihu.com/p/384651830
【SLAM】光流 - LK光流 - 金字塔分层LK光流_tlk光流-CSDN博客
LK光流法---金字塔改进_lk金字塔光流法-CSDN博客
http://robots.stanford.edu/cs223b04/algo_tracking.pdf