光流(Optical Flow)是空间运动物体在观测成像平面上的像素运动的"瞬时速度",根据各个像素点的速度矢量特征,可以对图像进行动态分析,例如目标跟踪。
-
亮度恒定:同一点随着时间的变化,其亮度不会发生改变。
-
小运动:随着时间的变化不会引起位置的剧烈变化,只有小运动情况下才能用前后帧之间单位位置变化引起的灰度变化去近似灰度对位置的偏导数。
-
空间一致:一个场景上邻近的点投影到图像上也是邻近点,且邻近点速度一致。因为光流法基本方程约束只有一个,而要求x,y方向的速度,有两个未知变量。所以需要连立n多个方程求解。


Lucas-Kanade 算法算法原理推导
光流估计的核心是求解像素的运动矢量(u, v),其中 u 表示 x 轴方向的移动分量,v 表示 y 轴方向的移动分量。Lucas-Kanade 算法是求解该问题的经典方法,其推导过程围绕上述三个假设展开,步骤如下:
1.建立约束方程
基于亮度恒定假设,对于任意像素点 (x, y),其在 t 时刻的灰度值 I (x, y, t) 与 t+Δt 时刻的灰度值 I (x+uΔt, y+vΔt, t+Δt) 满足:I(x,y,t)=I(x+uΔt,y+vΔt,t+Δt)其中 Δt 为帧间时间间隔(通常取 1,即相邻帧),u 和 v 是待求的运动矢量分量。


泰勒展开简化方程
由于满足小运动假设,对右侧 I (x+uΔt, y+vΔt, t+Δt) 进行一阶泰勒展开(忽略高阶无穷小项):
其中:
-
Ix=∂x∂I(x 方向灰度梯度)
-
Iy=∂y∂I(y 方向灰度梯度)
-
It=∂t∂I(时间维度灰度变化率)
结合亮度恒定假设,左右两侧 I (x, y, t) 抵消,两边除以 Δt 后得到核心方程:Ix⋅u+Iy⋅v=−It
1.已知量求解
上述方程中,Ix、Iy、It均可通过图像数据直接计算:
-
Ix和Iy:通过索贝尔算子(Sobel Operator)等梯度计算方法,从单帧图像中求解;
-
It:相邻帧间灰度变化率,直接通过后一帧灰度值减去前一帧灰度值得到(It≈I(x,y,t+Δt)−I(x,y,t))。
2.空间一致性解决欠定问题
核心方程Ix⋅u+Iy⋅v=−It中,包含两个未知数(u, v)但仅一个方程,属于欠定问题,无法直接求解。此时空间一致性假设发挥作用:
我们选取一个局部窗口(如 3×3、5×5),窗口内所有像素共享相同的运动矢量(u, v)。若窗口大小为 5×5,则可得到 25 个类似的约束方程,形成超定方程组:

其中 N 为窗口内像素数(如 25)。
3.最小二乘法求解超定方程组
对于超定方程组A⋅[u,v]T=B(其中 A 为 N×2 矩阵,B 为 N×1 矩阵),采用最小二乘法求解最优解,即使得误差平方和最小。最终通过矩阵运算推导得到:

这里需要注意:矩阵AT⋅A可逆的前提是其两个特征值λ1和λ2均较大 ------ 这恰好对应图像中的角点(角点的两个方向梯度均较大)。因此,光流估计通常围绕角点展开,这也是后续实战中先进行角点检测的原因。
三、OpenCV 光流估计实战
基于 Lucas-Kanade 算法,OpenCV 提供了成熟的光流估计接口,实战核心分为 "角点检测" 和 "帧间光流跟踪" 两步,以下是详细实现流程:
1.实战核心思路
-
读取视频流,获取第一帧图像并转换为灰度图;
-
对第一帧进行角点检测(筛选稳定的跟踪特征点);
-
循环读取后续帧,逐帧计算角点的光流矢量;
-
绘制跟踪轨迹和运动矢量,可视化跟踪结果;
-
处理跟踪丢失问题(如遮挡导致的角点消失)。
2.关键函数与参数说明
(1)角点检测:cv2.goodFeaturesToTrack ()
该函数用于筛选适合跟踪的角点(特征点),核心参数如下:
-
maxCorners:最大角点数量(如 100,避免过多角点影响实时性); -
qualityLevel:品质因子(如 0.3,值越大筛选越严格,角点数量越少); -
minDistance:角点间最小距离(如 7,避免相邻角点重复跟踪)。
(2)光流估计:cv2.calcOpticalFlowPyrLK ()
该函数实现金字塔 LK 光流跟踪(改进版 LK 算法,提升尺度适应性),核心参数如下:
-
输入:前一帧灰度图、当前帧灰度图、前一帧角点坐标;
-
关键参数:
winSize(跟踪窗口大小,如 (25,25))、maxLevel(金字塔最大层数,如 0 表示不使用金字塔); -
输出:当前帧角点坐标、跟踪状态(1 表示跟踪成功,0 表示丢失)。
3.实战效果与问题分析
成功跟踪视频中第一帧检测到的角点,绘制出角点的移动轨迹,直观呈现目标的运动状态(如行人、车辆的移动方向和速度)。
python
import numpy as np
import cv2
cap = cv2.VideoCapture('test.avi')
# 角点检测所需参数
feature_params = dict( maxCorners = 100,
qualityLevel = 0.3,
minDistance = 7)
# lucas kanade参数
lk_params = dict( winSize = (15,15),
maxLevel = 2)
# 随机颜色条
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
mask = np.zeros_like(old_frame)
while(True):
ret,frame = cap.read()
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 需要传入前一帧和当前图像以及前一帧检测到的角点
p1, st, err = cv2.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lk_params)
# st=1表示
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(150) & 0xff
if k == 27:
break
# 更新
old_gray = frame_gray.copy()
p0 = good_new.reshape(-1,1,2)
cv2.destroyAllWindows()
cap.release()

四、总结
光流估计的核心是通过 "亮度恒定、小运动、空间一致" 三大假设,将像素运动问题转化为数学方程求解,而 Lucas-Kanade 算法通过局部窗口和最小二乘法,高效解决了欠定方程的求解问题。在实战中,OpenCV 的封装接口简化了实现流程,但需注意角点检测的筛选逻辑和跟踪丢失的处理。
光流估计的典型应用场景包括目标跟踪、运动分割、视频防抖等,掌握其原理和实战技巧,能为计算机视觉相关任务提供核心技术支撑。后续可进一步优化跟踪效果,如定期重检测角点、结合目标检测算法动态更新跟踪点等。