计算机视觉——Opencv(光流估计实现目标追踪)

一、光流估计是什么?

是空间运动物体在观测成像平面上的像素运动的"瞬时速度",根据各个像素点的速度矢量特征,可以对图像进行动态分析,例如目标跟踪。

二、光流估计的前提

(1)亮度恒定:同一点随着时间的变化,其亮度不会发生改变。

(2)小运动:随着时间的变化不会引起位置的剧烈变化,只有小运动情况下才能用前后帧之间单位位置变化引起的灰度变化去近似灰度对位置的偏导数。

(3)空间一致:一个场景上邻近的点投影到图像上也是邻近点,且邻近点速度一致。因为光流法基本方程约束只有一个,而要求x,y方向的速度,有两个未知变量。所以需要连立n多个方程求解。

三、Lucas-Kanade 光流法

光流法分为稠密光流 (追踪所有像素)和稀疏光流(仅追踪特征点)两类。其中,Lucas-Kanade(LK)光流法是稀疏光流的经典算法,因其计算效率高、实时性好,成为视频追踪的首选方案。

LK 光流法的核心思路是:

  1. 选取图像中的角点(特征点)作为追踪目标(角点具有明显的灰度变化,易于识别和追踪);

  2. 对每个特征点构建局部邻域窗口(通常为 15×15、21×21 等),利用窗口内像素的亮度恒定假设建立方程组;

  3. 通过最小二乘法求解方程组,得到特征点的运动矢量(x、y 方向的位移);

  4. 迭代更新特征点位置,实现连续帧的轨迹追踪。

与稠密光流相比,LK 光流法仅追踪少量特征点,计算量大幅降低,更适合实时视频处理场景。

四、代码详解

1、导入相关库和视频读取

python 复制代码
import numpy as np
import cv2

# 打开视频文件
cap = cv2.VideoCapture(r"E:\xwechat_files\wxid_qi43v1w2nqcb12_e432\msg\file\2026-01\test(1).avi")

cv2.VideoCapture用于打开视频文件,参数可以是本地视频路径(绝对路径需加r避免转义),也可以是摄像头编号(如0表示默认摄像头)

2、初始化轨迹颜色与第一帧处理

python 复制代码
# 随机生成颜色,用于绘制轨迹
color = np.random.randint(0, 255, (100, 3))

# 读取视频的第一帧
ret, old_frame = cap.read()

# 将第一帧转换为灰度图像
old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)

np.random.randint(0, 255, (100, 3))生成 100 组随机 RGB 颜色(范围 0-255),用于为不同特征点的轨迹分配唯一颜色,便于区分

光流计算基于灰度图像(减少计算量,且亮度是核心特征),因此将第一帧转换为灰度图old_gray,作为追踪的 "基准帧"。

3、特征点检测(角点检测)

python 复制代码
# 定义特征点检测参数
feature_params = dict(maxCorners=100,  # 最大角点数量
                      qualityLevel=0.3,  # 角点质量的阈值
                      minDistance=7)  # 最小距离,用于分散角点

# 使用角点检测方法找到特征点
p0 = cv2.goodFeaturesToTrack(old_gray, mask=None, **feature_params)

cv2.goodFeaturesToTrack是 OpenCV 的角点检测函数(Shi-Tomasi 算法),专门用于筛选适合追踪的特征点:

maxCorners=100:最多检测 100 个角点(避免过多特征点导致计算缓慢);

qualityLevel=0.3:角点质量阈值(取值 0-1),低于该值的角点会被过滤(值越小,检测到的角点越多,但质量越低);

minDistance=7:角点之间的最小像素距离(避免角点聚集在同一区域,保证特征点分布均匀);

输出p0是形状为(N, 1, 2)的数组(N 为检测到的角点数量),每个元素表示角点的 (x, y) 坐标。

4、初始化轨迹掩模与光流参数

python 复制代码
# 创建一个与当前帧大小相同的全零掩模,用于绘制轨迹
mask = np.zeros_like(old_frame)

# 定义Lucas-Kanade光流参数
lk_params = dict(winSize=(15, 15),  # 窗口大小
                 maxLevel=2)  # 金字塔层数

lk_params定义 LK 光流的核心参数:

winSize=(15, 15):光流计算的窗口大小(窗口越大,对运动的鲁棒性越强,但计算量越大);

maxLevel=2:金字塔层数(多尺度光流,通过降采样图像计算光流,适应不同速度的运动目标)。

5、主循环:逐帧处理与光流计算

python 复制代码
# 主循环,处理视频的每一帧
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)

cv2.calcOpticalFlowPyrLK是 LK 光流计算函数,参数说明:

  • old_gray:基准帧(灰度);

  • frame_gray:当前帧(灰度);

  • p0:基准帧的特征点;

  • None:无初始猜测的特征点位置;

  • **lk_params:光流参数;

输出结果:

  • p1:当前帧中特征点的新位置(与p0形状相同);

  • st:状态数组(1 表示特征点追踪成功,0 表示追踪失败);

  • err:误差数组(追踪的像素误差,值越小追踪越准确)。

6、筛选有效特征点并绘制轨迹

python 复制代码
    # 选择好的点(状态为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()  # 获取旧点的坐标
        a, b, c, d = int(a), int(b), int(c), int(d)  # 转换为整数
        # 在掩模上绘制线段,连接新点和旧点
        mask = cv2.line(mask, pt1=(a, b), pt2=(c, d), color=color[i].tolist(), thickness=2)
        cv2.imshow('mask', mask)

st == 1筛选出追踪成功的特征点,good_new为当前帧的有效特征点,good_old为基准帧的对应特征点;

ravel()将特征点的坐标数组展平(如[x, y]),便于提取坐标值;

cv2.line在掩模上绘制线段,连接基准帧的特征点(old)和当前帧的特征点(new),形成轨迹:

color[i].tolist():为每个特征点分配随机颜色(保证轨迹颜色唯一);

7、合成图像与交互控制

python 复制代码
    # 将掩模添加到当前帧上,生成最终图像
    img = cv2.add(frame, mask)

    # 显示结果图像
    cv2.imshow('frame', img)

    # 等待150ms,检测是否按下了Esc键(键码为27)
    k = cv2.waitKey(150)
    if k == 27:  # 按下Esc键,退出循环
        break

8、更新基准帧与特征点

python 复制代码
    # 更新旧灰度图和旧特征点
    old_gray = frame_gray.copy()
    p0 = good_new.reshape(-1, 1, 2)  # 重新整理特征点为适合下次计算的形状
  • 将当前帧的灰度图frame_gray赋值给old_gray,作为下一帧的基准帧;

  • good_new.reshape(-1, 1, 2)将有效特征点的形状还原为(N, 1, 2)(符合cv2.calcOpticalFlowPyrLK的输入要求),作为下一帧的基准特征点p0

9、释放资源

python 复制代码
# 释放资源
cap.release()
cv2.destroyAllWindows()

五、运行结果

运行代码后,会弹出两个窗口:

mask窗口:显示黑色背景上的彩色轨迹线(仅轨迹);

frame窗口:显示视频原画面叠加轨迹线的效果,特征点的运动轨迹以彩色线段呈现,按下 Esc 键可退出。

相关推荐
飞Link19 小时前
梯度下降的优化算法中,动量算法和指数加权平均的区别对比
人工智能·深度学习·算法
1941s19 小时前
02-LangChain 框架入门:模型抽象与 Prompt 模板
人工智能·langchain·prompt
MoRanzhi120319 小时前
pillow 图像合成、透明叠加与蒙版处理
python·计算机视觉·pillow·图片处理·图像合成·透明叠加·多图层叠加
猫咪老师199519 小时前
Claude Code从零开始不敲代码使用若依java框架开发-第1节部署篇
人工智能·claude code
冬奇Lab19 小时前
OpenClaw 实战:SKILL安装极简指南,让你的 Agent 真正干活
人工智能·aigc
泥壳AI19 小时前
[特殊字符] OpenClaw + 飞书集成超详细教程
人工智能·python·深度学习·阿里云·飞书
xifangge202519 小时前
2026最新教程:Windows 10 部署 OpenClaw 智能体 附带一键修复环境脚本+ 豆包 API
人工智能
尘觉19 小时前
OpenClaw 入门:本地 AI 助手架构、功能与使用场景说明(2026-3月最新版)
人工智能·架构·openclaw
weixin_4462608519 小时前
win11本地部署openclaw实操第9集-配置 OpenClaw 连接本地模型参数
人工智能