【深度学习】OpenCV 光流估计:视频特征点跟踪(代码可完成复现)
·
文章目录
完整代码一览
import numpy as np
import cv2
# 打开视频文件
cap = cv2.VideoCapture('test.avi')
# 随机生成颜色,用于绘制轨迹
color = np.random.randint(0, 255, (100, 3))
# 读取视频的第一帧
ret, old_frame = cap.read()
# 将第一帧转换为灰度图像
old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)
# 定义特征点检测参数
feature_params = dict(maxCorners=100,
qualityLevel=0.3,
minDistance=7)
# 检测第一帧中的角点
p0 = cv2.goodFeaturesToTrack(old_gray, mask=None, **feature_params)
# 创建一个与当前帧大小相同的全黑掩模,用于绘制轨迹
mask = np.zeros_like(old_frame)
# 定义Lucas-Kanade光流参数
lk_params = dict(winSize=(15, 15),
maxLevel=2)
# 主循环
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)
# 选择成功跟踪的点
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()
a, b, c, d = int(a), int(b), int(c), int(d)
mask = cv2.line(mask, (a, b), (c, d), color[i].tolist(), thickness=2)
cv2.imshow('mask', mask)
# 将掩模叠加到当前帧
img = cv2.add(frame, mask)
cv2.imshow('frame', img)
k = cv2.waitKey(150)
if k == 27: # ESC键退出
break
# 更新旧帧和特征点
old_gray = frame_gray.copy()
p0 = good_new.reshape(-1, 1, 2)
cap.release()
cv2.destroyAllWindows()
导入库、打开视频、生成颜色
import numpy as np
import cv2 # 用于数组操作和随机数生成。
cap = cv2.VideoCapture('test.avi')
color = np.random.randint(0, 255, (100, 3))
# 生成一个形状为 (100, 3) 的随机整数数组,值域 0~255,代表 100 种不同的 RGB 颜色
读取第一帧,检测初始角点
ret, old_frame = cap.read()
old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)
# 光流计算通常基于灰度图,因为颜色信息对运动估计帮助不大,且灰度图计算量更小。
定义角点检测参数
feature_params = dict(maxCorners=100, # 最多检测 100 个角点
qualityLevel=0.3,# 角点质量阈值,取值 0~1
minDistance=7)# 两个角点之间的最小欧氏距离,避免角点过于集中。
检测角点
p0 = cv2.goodFeaturesToTrack(old_gray, mask=None, **feature_params)
# 将跟踪的特征点集合赋值给p0
cv2.goodFeaturesToTrack 是 Shi-Tomasi 角点检测器,它返回一个形状为 (N, 1, 2) 的数组,N 是检测到的角点数,每个角点用 (x, y) 坐标表示。
mask=None 表示在全图检测;**feature_params 将字典解包为关键字参数。
创建掩模与设置光流参数
# 创建掩模
mask = np.zeros_like(old_frame)# 生成一个与 old_frame 同尺寸的全黑图像(三通道),用于绘制轨迹线。每个新帧都会在这个 mask 上画线,然后叠加到当前帧上,形成运动轨迹。
# 定义光流参数
lk_params = dict(winSize=(15, 15),# 使用局部窗口,窗口大小为 15×15 像素
maxLevel=2)# 使用图像金字塔的层数(从 0 到 2,共 3 层)
主程序
转为灰度用于光流计算。
# 读取下一帧并转为灰度
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)
# old_gray:前一帧灰度图。
# frame_gray:当前帧灰度图。
# p0:前一帧的特征点坐标。
# None:下一帧的初始坐标估计
# **lk_params:解包窗口大小和金字塔层数。
calcOpticalFlowPyrLK(prevImg, nextImg, prevPts, nextPts, status=None, err=None, winSize=None, maxLevel=None, criteria=None, flags=None, minEigThreshold=None)
prevImg: 前一帧图像
nextImg: 当前帧图像
prevPts: 前一帧图像中特征点坐标
nextPts: 当前帧图像中特征点坐标,可以为None
winSize: 搜索窗口的大小
maxLevel: 金字塔层数
criteria: 停止迭代的准则
返回值:
p1 (nextPts):当前帧中对应的特征点坐标,形状与 p0 相同。
st(status):状态向量,1 表示成功跟踪,0 表示失败。
err:误差向量(平均平方误差)。
筛选出成功跟踪的点
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()
a, b, c, d = int(a), int(b), int(c), int(d) # 转换为整数坐标,因为 cv2.line 要求整数坐标。
mask = cv2.line(mask, (a, b), (c, d), color[i].tolist(), thickness=2) # cv2.line 在掩模 mask 上从旧点画到新点
cv2.imshow('mask', mask)
叠加掩模到当前帧并显示
img = cv2.add(frame, mask)# cv2.add 将当前帧和掩模(轨迹线)按像素相加,实现叠加效果。
cv2.imshow('frame', img)
控制播放速度与退出
k = cv2.waitKey(150) # 等待 150 毫秒,控制视频播放速度
if k == 27:# 检测键盘按键,如果按下 ESC(ASCII 27),则退出循环。
break
更新旧帧和特征点
old_gray = frame_gray.copy()# 将当前帧作为下一次迭代的“旧帧”
p0 = good_new.reshape(-1, 1, 2) # reshape(-1, 1, 2) 中 -1 表示自动计算第一维大小(即点的个数)
将 good_new 重塑为 (N, 1, 2) 形状,因为 calcOpticalFlowPyrLK 要求 prevPts 是这种形状
释放资源
cv2.destroyAllWindows()
释放视频捕获对象,关闭所有 OpenCV 窗口。
运行结果:

更多推荐




所有评论(0)