OpenCV 光流估计实战:Lucas-Kanade 算法实现运动目标跟踪

一、光流估计核心概念

1. 什么是光流?

光流是空间运动物体在观测成像平面上的像素运动 "瞬时速度" ,它描述了图像序列中像素点随时间的运动变化。通过分析这些速度矢量,我们可以实现动态场景分析,比如目标跟踪、行为识别、视频 stabilization 等。

2. 光流估计的三大前提假设

光流算法的有效性依赖于以下三个核心假设:

  • 亮度恒定 :同一点在不同帧中的亮度保持不变,即
  • 小运动:时间变化不会引起位置剧烈变化,仅能通过前后帧灰度变化近似偏导数
  • 空间一致:场景中邻近点在图像上也是邻近点,且运动速度一致(用于解决单方程多未知数问题)

3. 光流基本方程推导

由亮度恒定假设展开泰勒级数并忽略高阶项,可得光流约束方程:

其中:

  • :图像在 x,y 方向的梯度
  • :像素在 x,y 方向的运动速度
  • :图像随时间的变化梯度

二、Lucas-Kanade 光流算法原理

Lucas-Kanade 算法是稀疏光流的代表,仅跟踪图像中特征明显的点(如角点),效率更高、更适合实时场景。

核心思想

利用空间一致假设,对每个特征点周围的局部窗口(如 15×15)建立超定方程组,通过最小二乘法求解 u,v,解决单个约束方程无法求解两个未知数的问题。

金字塔光流优化

为了处理大运动 场景,算法引入图像金字塔

  1. 先在低分辨率(高层金字塔)跟踪大位移
  2. 逐步向高分辨率(底层金字塔)细化,最终得到精确光流

完整代码

python 复制代码
import numpy as np
import cv2

# 打开视频文件(也可改为0调用摄像头)
cap = cv2.VideoCapture('test.avi')

# 随机生成100种颜色,用于绘制不同特征点的运动轨迹
color = np.random.randint(0, 255, (100, 3))

# 读取第一帧并转为灰度图
ret, old_frame = cap.read()
old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)

# ---------------------- 1. 特征点检测参数 ----------------------
feature_params = dict(
    maxCorners=100,    # 最多检测100个角点
    qualityLevel=0.3,  # 角点质量阈值(保留质量前30%的点)
    minDistance=7      # 角点之间最小距离,避免特征点过于密集
)

# 检测Shi-Tomasi角点作为待跟踪的特征点
p0 = cv2.goodFeaturesToTrack(old_gray, mask=None, **feature_params)

# 创建与帧大小相同的掩模,用于绘制轨迹
mask = np.zeros_like(old_frame)

# ---------------------- 2. Lucas-Kanade光流参数 ----------------------
lk_params = dict(
    winSize=(15, 15),  # 局部搜索窗口大小
    maxLevel=2         # 金字塔层数(0表示只用原始图像)
)

# ---------------------- 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
    )

    # 筛选成功跟踪的特征点(st=1表示跟踪成功)
    good_new = p1[st == 1]
    good_old = p0[st == 1]

    # ---------------------- 4. 绘制运动轨迹 ----------------------
    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(), 2)
        # 在当前帧上标记新特征点
        frame = cv2.circle(frame, (a, b), 3, color[i].tolist(), -1)

    # 叠加掩模与当前帧,显示最终结果
    img = cv2.add(frame, mask)
    cv2.imshow('Lucas-Kanade Optical Flow', img)

    # 按ESC键退出
    k = cv2.waitKey(150) & 0xff
    if k == 27:
        break

    # ---------------------- 5. 更新状态,准备下一帧 ----------------------
    old_gray = frame_gray.copy()
    p0 = good_new.reshape(-1, 1, 2)  # 重塑为OpenCV要求的格式

# 释放资源
cap.release()
cv2.destroyAllWindows()

代码详解

python 复制代码
# 随机生成100种颜色,用于绘制不同特征点的运动轨迹
color = np.random.randint(0, 255, (100, 3))

# 读取第一帧并转为灰度图
ret, old_frame = cap.read()
old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)

生成 100 行 3 列的随机颜色数组(RGB),每个特征点用不同颜色画轨迹,方便区分。

  • 读取视频第一帧
  • 转为灰度图(光流算法只需要灰度信息,速度更快)
  • old_gray 保存为 "上一帧",用于后续对比计算运动
python 复制代码
# ---------------------- 1. 特征点检测参数 ----------------------
feature_params = dict(
    maxCorners=100,    # 最多检测100个角点
    qualityLevel=0.3,  # 角点质量阈值(保留质量前30%的点)
    minDistance=7      # 角点之间最小距离,避免特征点过于密集
)

# 检测Shi-Tomasi角点作为待跟踪的特征点
p0 = cv2.goodFeaturesToTrack(old_gray, mask=None, **feature_params)

这一步是找要跟踪的点

  • 使用 goodFeaturesToTrack 检测角点(边缘交点,最适合跟踪)
  • maxCorners=100:最多跟踪 100 个点
  • p0 存储第一帧所有要跟踪的特征点坐标
python 复制代码
# 创建与帧大小相同的掩模,用于绘制轨迹
mask = np.zeros_like(old_frame)

创建一张和视频帧一样大的全黑图片 mask 。作用:在上面画轨迹,不会覆盖原视频画面

python 复制代码
# ---------------------- 2. Lucas-Kanade光流参数 ----------------------
lk_params = dict(
    winSize=(15, 15),  # 局部搜索窗口大小
    maxLevel=2         # 金字塔层数(0表示只用原始图像)
)

金字塔 LK 光流参数:

  • winSize=(15,15):以点为中心,搜索周围 15×15 区域
  • maxLevel=2:使用 2 层金字塔,能跟踪更快、更大的运动
python 复制代码
# ---------------------- 3. 主循环:逐帧计算光流 ----------------------
while True:
    ret, frame = cap.read()
    if not ret:
        break
    frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

循环读取每一帧:

  • ret:是否读到帧
  • frame:当前彩色图
  • frame_gray:当前灰度图(用于光流计算)
python 复制代码
    # 计算光流:获取特征点在当前帧的新位置
    p1, st, err = cv2.calcOpticalFlowPyrLK(
        old_gray, frame_gray, p0, None, **lk_params
    )

输入:上一帧、当前帧、上一帧特征点输出:

  • p1:当前帧点的新位置
  • st:状态(1 = 跟踪成功,0 = 丢失)
  • err:误差
python 复制代码
    # 筛选成功跟踪的特征点(st=1表示跟踪成功)
    good_new = p1[st == 1]
    good_old = p0[st == 1]

只保留跟踪成功的点,丢掉丢失的点。

  • good_new:当前帧成功点
  • good_old:上一帧成功点
python 复制代码
    # ---------------------- 4. 绘制运动轨迹 ----------------------
    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(), 2)
        # 在当前帧上标记新特征点
        frame = cv2.circle(frame, (a, b), 3, color[i].tolist(), -1)

遍历每一对新旧点,画线 + 画点:

  • cv2.line:在 mask 上画运动轨迹线
  • cv2.circle:在当前帧画当前特征点
  • 每个点用不同颜色,轨迹更清晰
python 复制代码
    # 叠加掩模与当前帧,显示最终结果
    img = cv2.add(frame, mask)
    cv2.imshow('Lucas-Kanade Optical Flow', img)

原视频轨迹 mask叠加在一起显示。效果:视频上保留彩色运动轨迹。

结果展示:

相关推荐
计算机安禾2 小时前
【数据结构与算法】第12篇:栈(二):链式栈与括号匹配问题
c语言·数据结构·c++·学习·算法·visual studio code·visual studio
散峰而望2 小时前
【数据结构】单调栈与单调队列深度解析:从模板到实战,一网打尽
开发语言·数据结构·c++·后端·算法·github·推荐算法
qwehjk20082 小时前
内存泄漏自动检测系统
开发语言·c++·算法
tankeven2 小时前
HJ153 实现字通配符*
c++·算法
旖-旎3 小时前
位运算(两整数之和)(3)
c++·算法·leetcode·位运算
2301_816651223 小时前
C++与Rust交互编程
开发语言·c++·算法
ab1515173 小时前
3.28完成9、16、20、98、100、55、57
算法
带娃的IT创业者3 小时前
营养食谱推荐引擎:基于规则与协同过滤的混合算法
算法·规则引擎·协同过滤·健康管理·食谱推荐·营养搭配·家庭饮食
扶摇接北海1763 小时前
洛谷:P1307 [NOIP 2011 普及组] 数字反转
c++·算法·洛谷