完整代码一览

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)# 使用图像金字塔的层数(从 02,共 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 窗口。

运行结果:

在这里插入图片描述

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐