OpenCV计算机视觉实战(20)——光流法运动分析
0. 前言
在视频分析与动态视觉系统中,运动信息的提取与理解是许多关键任务的基础,例如目标跟踪、动作识别、交通监控以及增强现实等。而其中最具代表性的技术之一,就是基于图像序列的光流 (Optical Flow
) 分析。光流技术试图通过连续帧之间像素强度的变化来估计图像中每个像素的运动方向与速度,从而重建出视觉世界中的动态场景。
1. Lucas–Kanade 稀疏光流
使用 Lucas–Kanade
算法在图像中选择少量高角点,通过金字塔跟踪估计它们在下一帧中的位置,适合实时目标跟踪。
1.1 应用场景
- 手势识别:跟踪手部关键点进行动作分类
- 工业检测:监控流水线工件轻微抖动
- 运动分析:球类或飞行物的轨迹捕捉
为了在特征点丢失或遮挡时保持鲁棒性,常在跟踪点减少到一定阈值后重新检测新角点。
1.2 实现过程
- 读取视频或摄像头,获取首帧并灰度化
- 基于
Shi–Tomasi
在首帧选取若干角点 (cv2.goodFeaturesToTrack
) - 迭代读取下一帧,灰度化后用
cv2.calcOpticalFlowPyrLK
计算这些角点的新位置 - 筛选成功跟踪的点 (
status==1
),在画布上绘制连线表示位移并更新上一帧 - 实时显示跟踪结果
import cv2
import numpy as np
# 功能:Lucas–Kanade 稀疏光流跟踪 Shi–Tomasi 角点
cap = cv2.VideoCapture('test.mp4') # 或者 cap = cv2.VideoCapture(0)
# 1. 读取首帧,灰度化并检测特征点
ret, prev = cap.read()
prev_gray = cv2.cvtColor(prev, cv2.COLOR_BGR2GRAY)
p0 = cv2.goodFeaturesToTrack(
prev_gray,
maxCorners=200,
qualityLevel=0.3,
minDistance=7,
blockSize=7
)
# 2. 准备画布与 LK 参数
mask = np.zeros_like(prev)
lk_params = dict(winSize=(15,15), maxLevel=2,
criteria=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03))
while True:
ret, frame = cap.read()
if not ret:
break
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 3. 计算稀疏光流
p1, st, err = cv2.calcOpticalFlowPyrLK(
prev_gray, frame_gray, p0, None, **lk_params)
if p1 is None:
break
# 4. 筛选跟踪成功的点
good_new = p1[st==1]
good_old = p0[st==1]
# 5. 绘制轨迹
for new, old in zip(good_new, good_old):
x1, y1 = new.ravel()
x0, y0 = old.ravel()
cv2.line(mask, (int(x1), int(y1)), (int(x0), int(y0)), (0,255,0), 2)
cv2.circle(frame, (int(x1), int(y1)), 3, (0,0,255), -1)
img = cv2.add(frame, mask)
cv2.imshow('Sparse LK Flow', img)
if cv2.waitKey(30) & 0xFF == 27:
break
# 更新上一帧和点集
prev_gray = frame_gray.copy()
p0 = good_new.reshape(-1,1,2)
cap.release()
cv2.destroyAllWindows()
关键函数解析:
cv2.goodFeaturesToTrack(gray, maxCorners, qualityLevel, minDistance)
:Shi–Tomasi
角点检测,选择最显著的若干特征点cv2.calcOpticalFlowPyrLK(prev, next, p0, None, **params)
:金字塔Lucas–Kanade
算法,返回新点位置、状态与误差矩阵cv2.add(frame, mask)
:叠加轨迹画布和当前帧,实现持续轨迹可视化
2. Farneback 稠密光流
Farneback
算法估计每个像素的运动矢量,生成稠密光流场,适合整体运动场分析与背景补偿。
2.1 应用场景
- 交通监控:计算道路信号灯前后车流速度场
- 流体仿真:视觉化水流或烟雾运动
- 动作识别:用全图运动模式区分不同人体动作
可以对光流幅度进行阈值过滤,仅保留高速运动区域。
2.2 实现过程
- 读取视频,对每帧灰度化
- 使用
cv2.calcOpticalFlowFarneback
计算上一帧到当前帧的稠密光流场 - 将流场转换为
HSV
映射:角度映射色相,幅度映射亮度,生成伪彩可视化 - 实时显示稠密光流图
import cv2
import numpy as np
# 功能:Farneback 稠密光流并伪彩可视化
cap = cv2.VideoCapture('r2.mp4')
ret, prev = cap.read()
prev_gray = cv2.cvtColor(prev, cv2.COLOR_BGR2GRAY)
# 用于 HSV 映射
hsv = np.zeros_like(prev)
hsv[...,1] = 255
while True:
ret, frame = cap.read()
if not ret:
break
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 1. 计算稠密光流
flow = cv2.calcOpticalFlowFarneback(
prev_gray, gray,
None,
pyr_scale=0.5, levels=3, winsize=15,
iterations=3, poly_n=5, poly_sigma=1.2, flags=0
)
# 2. 转换极坐标
mag, ang = cv2.cartToPolar(flow[...,0], flow[...,1])
hsv[...,0] = ang * 180/np.pi/2
hsv[...,2] = cv2.normalize(mag, None, 0, 255, cv2.NORM_MINMAX)
bgr = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
cv2.imshow('Dense Farneback Flow', bgr)
if cv2.waitKey(30) & 0xFF == 27:
break
prev_gray = gray
cap.release()
cv2.destroyAllWindows()
关键函数解析:
cv2.calcOpticalFlowFarneback(prev, next, None, pyr_scale, levels, winsize, iterations, poly_n, poly_sigma, flags)
:Farneback
稠密光流参数众多,可调节金字塔比例、窗口大小、拟合多项式阶数等cv2.cartToPolar(dx, dy)
:笛卡尔坐标转极坐标,得到幅度与方向HSV
映射:H
表示方向、V
表示幅度,使运动方向与强度一目了然
3. 运动轨迹可视化
结合稀疏光流跟踪结果,记录每个特征点的历史位置并绘制完整轨迹,直观呈现目标在视频中的运动路径。
3.1 应用场景
- 多人运动:为不同对象分配不同颜色轨迹
- 迁徙研究:可视化动物或车辆群的运动路径
- 交互式分析:在轨迹上叠加时间戳或速度信息
用 deque
控制每条轨迹长度,并为每个轨迹随机分配颜色。
3.2 实现过程
- 使用
Lucas–Kanade
初始化一组特征点 - 建立一个列表 存储多帧跟踪结果,每帧将新位置追加到各自轨迹
- 在每帧渲染 所有轨迹线段,并为最新点打标
- 支持清除过旧轨迹或限制轨迹长度
import cv2
import numpy as np
# 视频读取
cap = cv2.VideoCapture('r2.mp4') # 替换为你的输入视频路径
# Shi-Tomasi 角点检测参数
feature_params = dict(maxCorners=100, qualityLevel=0.3, minDistance=7, blockSize=7)
# Lucas-Kanade 光流参数
lk_params = dict(winSize=(15, 15), maxLevel=2,
criteria=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03))
# 随机颜色用于不同轨迹
color = np.random.randint(0, 255, (100, 3))
# 读取第一帧并检测角点
ret, old_frame = cap.read()
if not ret:
print("无法读取视频")
cap.release()
exit()
old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)
p0 = cv2.goodFeaturesToTrack(old_gray, mask=None, **feature_params)
# 创建一个掩膜图像用于绘制轨迹
mask = np.zeros_like(old_frame)
# 初始化轨迹列表
trajectories = [[] for _ in range(len(p0))]
while True:
ret, frame = cap.read()
if not ret:
break
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 计算光流
p1, st, err = cv2.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lk_params)
if p1 is None or st is None:
break
# 选择跟踪成功的点
good_new = p1[st == 1]
good_old = p0[st == 1]
# 更新轨迹
new_trajectories = []
for i, (new, old) in enumerate(zip(good_new, good_old)):
a, b = new.ravel()
c, d = old.ravel()
trajectories[i].append((a, b))
# 绘制轨迹线段
for j in range(1, len(trajectories[i])):
cv2.line(mask, tuple(np.int32(trajectories[i][j - 1]).ravel()), tuple(np.int32(trajectories[i][j]).ravel()), color[i].tolist(), 2)
# 绘制当前点
cv2.circle(frame, (int(a), int(b)), 5, color[i].tolist(), -1)
img = cv2.add(frame, mask)
cv2.imshow('Optical Flow Tracking', img)
k = cv2.waitKey(30) & 0xff
if k == 27:
break
# 更新前一帧和前一帧的点
old_gray = frame_gray.copy()
p0 = good_new.reshape(-1, 1, 2)
cap.release()
cv2.destroyAllWindows()
小结
在视频分析与动态视觉系统中,光流 (Optical Flow
) 技术是一种关键的运动估计方法。它通过分析连续帧之间像素强度的变化,估计图像中每个像素的运动方向与速度,从而重建出视觉世界中的动态场景。光流技术主要分为稀疏光流和稠密光流两种类型。稀疏光流(如 Lucas–Kanade
方法)关注图像中的少量特征点,适合实时目标跟踪;而稠密光流(如 Farneback
方法)则估计每个像素的运动矢量,适合整体运动场分析与背景补偿。
系列链接
OpenCV计算机视觉实战(1)——计算机视觉简介
OpenCV计算机视觉实战(2)——环境搭建与OpenCV简介
OpenCV计算机视觉实战(3)——计算机图像处理基础
OpenCV计算机视觉实战(4)——计算机视觉核心技术全解析
OpenCV计算机视觉实战(5)——图像基础操作全解析
OpenCV计算机视觉实战(6)——经典计算机视觉算法
OpenCV计算机视觉实战(7)——色彩空间详解
OpenCV计算机视觉实战(8)——图像滤波详解
OpenCV计算机视觉实战(9)——阈值化技术详解
OpenCV计算机视觉实战(10)——形态学操作详解
OpenCV计算机视觉实战(11)——边缘检测详解
OpenCV计算机视觉实战(12)——图像金字塔与特征缩放
OpenCV计算机视觉实战(13)——轮廓检测详解
OpenCV计算机视觉实战(14)——直方图均衡化
OpenCV计算机视觉实战(15)——霍夫变换详解
OpenCV计算机视觉实战(16)——图像分割技术
OpenCV计算机视觉实战(17)——特征点检测详解
OpenCV计算机视觉实战(18)——视频处理详解
OpenCV计算机视觉实战(19)——特征描述符详解