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叠加在一起显示。效果:视频上保留彩色运动轨迹。

结果展示:

相关推荐
驭渊的小故事8 分钟前
NC229023 题解:二分答案求解“最大化最小值“问题
java·算法
朝朝辞暮i21 分钟前
C++ 第 36 课:Action——机械臂/VLA 非常重要
开发语言·c++·算法·ros2
ra90fy27 分钟前
B3873 [GESP202309 六级] 小杨买饮料
c++·算法
richard_yuu32 分钟前
OpenCV 实战第 5 篇:threshold 全家族 + Otsu + 自适应阈值,参数怎么定
人工智能·opencv
Omics Pro36 分钟前
北理工李荣华:AI虚拟细胞证据多模态推理基准
数据库·人工智能·算法·机器学习·自然语言处理
xieliyu.41 分钟前
LeetCode 1419 数青蛙:状态模拟求最少青蛙数量|Java 题解
java·算法·leetcode
acd1200943 分钟前
线上偶发 499 错误,并非后端服务异常
算法
Nil2081 小时前
leetcode 45跳跃游戏Ⅱ
算法·leetcode·游戏
黑妹天下第一乖1 小时前
第 10 讲:阿加犀 AidVoice 端侧语音交互与语音识别实战
图像处理·人工智能·数码相机·opencv·交互·语音识别·xcode
小蒋观天下1 小时前
端侧大模型在安防摄像头部署实操(下篇)|模型量化、推理加速、视频接入与量产调优
大数据·人工智能·算法·安全·机器学习·计算机视觉·ai大模型