光流(Optical Flow)作为计算机视觉中的核心概念,用于描述视频帧之间像素的移动情况,通过分析连续帧中像素的位置变化,生成二维运动矢量图。其中,Lucas-Kanade方法是较为经典的一种稀疏光流算法,它主要通过追踪图像中的特征点(例如Shi-Tomasi角点)来估算运动路径,被广泛应用于目标追踪、运动分割和视频稳定等领域。
本文将利用Python和OpenCV,逐步演示如何实现Lucas-Kanade光流的计算及其可视化,旨在帮助读者快速掌握光流的基本原理并进行实际操作。
在深入编程之前,我们先了解光流的基本假设以及Lucas-Kanade方法的求解思路。
Lucas-Kanade方法依赖于以下两个基本假设:
基于亮度不变假设,通过泰勒级数展开(忽略高阶项),我们可以得到以下近似式:
\(I(x+u, y+v, t+1) ≈ I(x, y, t) + uI_x + vI_y + I_t\)
结合亮度不变假设 \(I(x+u, y+v, t+1) = I(x, y, t)\),简化后得到光流约束方程:
\(uI_x + vI_y + I_t = 0\)
其中,\(I_x = \frac{\partial I}{\partial x}\) 表示x方向的梯度,\(I_y = \frac{\partial I}{\partial y}\) 表示y方向的梯度,\(I_t = \frac{\partial I}{\partial t}\) 表示时间维度的梯度。
由于光流约束方程是一个不定方程(包含两个未知数 \(u, v\) 和一个方程),因此不能直接求解。Lucas-Kanade方法通过在一个局部窗口(例如15×15像素的窗口)内选取多个像素点(通常是窗口内的所有像素点)来建立超定方程组,然后使用最小二乘法求解。
对于窗口内的每个像素点 \((x_i, y_i)\),光流约束方程可以表示为:
\(\begin{bmatrix} I_x(x_i, y_i) & I_y(x_i, y_i) \end{bmatrix} \begin{bmatrix} u \\ v \end{bmatrix} = -I_t(x_i, y_i)\)
将窗口内所有点的方程组合,形成线性方程组:
\(M \begin{bmatrix} u \\ v \end{bmatrix} = b\)
其中,矩阵 \(M = \sum_{i} \begin{bmatrix} I_x^2 & I_xI_y \\ I_xI_y & I_y^2 \end{bmatrix}\) 表示窗口内梯度的协方差矩阵;向量 \(b = -\sum_{i} \begin{bmatrix} I_xI_t \\ I_yI_t \end{bmatrix}\) 表示窗口内梯度与时间差的乘积之和。
通过最小二乘法求解,最终得到位移 \((u, v)\):
\(\begin{bmatrix} u \\ v \end{bmatrix} = (M^T M)^{-1} M^T b\)
接下来,我们将通过一系列步骤——下载测试视频、编写代码、运行并可视化——来完成光流实验。
首先,下载官方提供的测试视频(该视频场景为缓慢移动的交通,非常适合观察运动轨迹)。
下载视频:wget https://www.bogotobogo.com/python/OpenCV_Python/images/mean_shift_tracking/slow_traffic_small.mp4
我们将编写的代码命名为optical_flow_tracker.py,其核心逻辑包括以下几个步骤:
以下是经过优化的代码,其中变量名更具语义性,注释也更加清晰:
import numpy as np
import cv2
import argparse
def main():
# 打开视频文件
video_path = "slow_traffic_small.mp4"
video_capture = cv2.VideoCapture(video_path)
if not video_capture.isOpened():
print(f"无法打开视频文件: {video_path}")
return
# 获取视频分辨率,初始化视频写入器(用于保存结果)
frame_width = int(video_capture.get(cv2.CAP_PROP_FRAME_WIDTH))
frame_height = int(video_capture.get(cv2.CAP_PROP_FRAME_HEIGHT))
output_video = cv2.VideoWriter(
'optical_flow_result.mp4',
cv2.VideoWriter_fourcc(*'mp4v'),
30, # 输出视频的帧率
(frame_width, frame_height)
)
# 1. 配置Shi-Tomasi角点检测参数(用于提取初始特征点)
shi_tomasi_params = {
'maxCorners': 100, # 最多检测100个角点
'qualityLevel': 0.3, # 角点质量阈值,用于过滤低质量点
'minDistance': 7, # 角点间的最小距离,防止点过于密集
'blockSize': 7 # 计算梯度的窗口大小
}
# 2. 配置Lucas-Kanade光流算法参数
lucas_kanade_params = {
'winSize': (15, 15), # 追踪窗口大小,提供局部平滑效果
'maxLevel': 2, # 金字塔层数,处理不同尺度的变化
'criteria': (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03) # 终止条件:迭代10次或误差小于0.03
}
# 生成随机颜色,用于标记不同的追踪轨迹
colors = np.random.randint(0, 255, (100, 3)) # 100个角点对应100种颜色
# 3. 处理第一帧:提取初始角点
success, previous_frame = video_capture.read()
if not success:
print("无法读取视频帧")
return
previous_gray = cv2.cvtColor(previous_frame, cv2.COLOR_BGR2GRAY) # 将图像转换为灰度图,以便进行光流计算
initial_corners = cv2.goodFeaturesToTrack(previous_gray, mask=None, **shi_tomasi_params) # 提取角点
tracking_mask = np.zeros_like(previous_frame) # 创建掩码图像,用于绘制追踪轨迹
# 4. 循环处理后续帧
while video_capture.isOpened():
success, current_frame = video_capture.read()
if not success:
break # 视频结束
current_gray = cv2.cvtColor(current_frame, cv2.COLOR_BGR2GRAY)
# 计算光流:追踪initial_corners在current_frame中的位置
new_corners, status, errors = cv2.calcOpticalFlowPyrLK(
previous_gray, current_gray, initial_corners, None, **lucas_kanade_params
)
# 过滤出有效的追踪点(status=1表示追踪成功)
if new_corners is not None:
valid_new = new_corners[status == 1]
valid_old = initial_corners[status == 1]
# 绘制追踪轨迹:使用掩码记录所有轨迹
for i, (new, old) in enumerate(zip(valid_new, valid_old)):
new_x, new_y = new.ravel()
old_x, old_y = old.ravel()
# 绘制从old到new的轨迹线
optical_flow_tracker.py
mask = cv2.line(mask, (int(curr_x), int(curr_y)), (int(prev_x), int(prev_y)), color[i].tolist(), 2)
# 在当前帧上绘制当前角点(实心圆)
curr_frame = cv2.circle(curr_frame, (int(curr_x), int(curr_y)), 5, color[i].tolist(), -1)
# 将当前帧与轨迹掩模合并
result_frame = cv2.add(curr_frame, mask)
# 保存结果帧到输出文件
out.write(result_frame)
# 更新前一帧和前一个点,以便在下一个循环中使用
prev_gray = curr_gray.copy()
prev_points = valid_curr.reshape(-1, 1, 2) # 调整点的形状以满足 calcOpticalFlowPyrLK 的要求
# 释放使用的资源
cap.release()
out.release()
cv2.destroyAllWindows()
print("光流计算完成,结果已保存为:optical_flow_result.mp4")
if __name__ == "__main__":
main()
cv2.goodFeaturesToTrack
该步骤从图像中提取角点,这些角点作为光流追踪的“锚点”。由于角点在运动过程中相对稳定,因此适合用于光流追踪。
cv2.calcOpticalFlowPyrLK
这是 OpenCV 实现的 Lucas-Kanade 光流算法,采用金字塔技术来处理不同尺度的变化。该函数返回当前帧的角点位置
curr_points
以及追踪状态
status
(1 表示成功,0 表示失败)。
使用
mask
图像来记录所有的轨迹线,避免每帧都重新绘制。通过
cv2.add
将轨迹与当前帧合并,生成可视化的结果。
Lucas-Kanade 光流作为一种经典的稀疏光流方法,在计算机视觉领域有广泛的应用:
本文介绍了如何使用 Python 和 OpenCV 实现 Lucas-Kanade 光流的计算与可视化。主要步骤包括:
光流是视频分析的基础工具,掌握它可以让你更好地理解视频中的运动信息。如果你希望进一步深入研究,可以尝试以下方向:
希望本文能对你的光流实践有所帮助!
扫码加好友,拉您进群



收藏
