视频分析是计算机视觉中最贴近实际应用的方向之一。无论是智能监控、自动驾驶,还是人机交互,都离不开对视频中运动信息的提取与理解。本文基于 OpenCV 实现的三段代码,深入讲解背景建模(MOG2)光流估计(Lucas-Kanade)、**目标跟踪(CSRT)**三种技术的核心原理、代码实现,以及各自的优缺点与适用场景。


一、技术概览与选型思路

三种技术的本质差异在于"问的问题不同":

背景建模  → "画面中有哪些地方在动?"   (运动检测)
光流估计  → "这些特征点往哪个方向走?"  (运动追踪)
目标跟踪  → "我框住的这个东西在哪?"   (特定目标追踪)

在这里插入图片描述


二、背景建模(MOG2):运动检测的核心

2.1 核心原理

MOG2(Mixture of Gaussians 2)是基于混合高斯模型的背景建模方法。它将视频中每个像素位置的像素值建模为多个高斯分布的加权混合,其中稳定的背景对应权重高、方差小的高斯分布,而运动前景则偏离这些分布,从而被检测出来。

核心思想:
    像素值历史 → 多个高斯分布混合 → 背景层 + 前景层

OpenCV 封装了完整的 MOG2 接口,一行代码即可创建背景建模器:

# 创建混合高斯模型背景建模器
fgbg = cv2.createBackgroundSubtractorMOG2()

2.2 代码逐段解析

# 创建卷积核,用于后续形态学处理
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))

# 创建混合高斯模型
fgbg = cv2.createBackgroundSubtractorMOG2()

while True:
    ret, frame = cap.read()
    if not ret:
        break

    # 第1步:MOG2 前景检测
    fgmask = fgbg.apply(frame)

    # 第2步:形态学开运算——去除前景中的噪声(去毛刺)
    fgmask_new = cv2.morphologyEx(fgmask, cv2.MORPH_OPEN, kernel)

    # 第3步:轮廓检测,仅取外轮廓
    contours = cv2.findContours(fgmask_new, cv2.RETR_EXTERNAL,
                                 cv2.CHAIN_APPROX_SIMPLE)[-2]

    # 第4步:周长筛选,过滤微小轮廓
    for c in contours:
        perimeter = cv2.arcLength(c, True)
        if perimeter > 188:               # 阈值根据场景调整
            x, y, w, h = cv2.boundingRect(c)
            cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2)

    cv2.imshow('frame', frame)

2.3 处理流程总览

在这里插入图片描述

四步缺一不可

  • fgbg.apply() 将视频帧转为二值前景掩膜(白色=运动区域)
  • MORPH_OPEN(先腐蚀后膨胀)去除椒盐噪声
  • RETR_EXTERNAL 只保留最外层轮廓,排除嵌套噪声
  • arcLength > 188 是经验阈值,可根据目标大小调整

2.4 优点与缺点

优点 缺点
计算量小,适合实时视频流 光照突变时误检严重
无需事先知道目标位置 动态背景(树枝晃动、水面波纹)会产生误检
背景自动学习更新 无法区分不同的运动目标(无ID追踪)
对静止目标检测效果稳定 目标影子会被误判为前景
实现简单,一行API即可启用 无法感知运动方向和速度

三、光流估计(Lucas-Kanade):运动追踪的眼睛

3.1 光流的基本假设

光流(Optical Flow)描述的是图像中像素点随时间的运动速度矢量。Lucas-Kanade 算法基于两个核心假设:

假设1:亮度恒定 —— 同一物理点在连续帧间亮度基本不变
假设2:小运动    —— 相邻帧间像素位移很小
假设3:空间一致 —— 邻域内像素具有相似的运动

光流方程:I(x, y, t) = I(x+dx, y+dy, t+dt),通过泰勒展开求解 (dx, dy)。

3.2 goodFeaturesToTrack 特征点检测

在追踪之前,需要先用角点检测找出适合追踪的特征点。Shi-Tomasi 算法(goodFeaturesToTrack)挑出图像中对比度高、分布均匀的角点:

feature_params = dict(
    maxCorners = 100,       # 最大检测角点数
    qualityLevel = 0.3,     # 质量阈值(特征值 < qualityLevel * maxValue 会被丢弃)
    minDistance = 7,        # 两角点间最小欧氏距离
)

# 检测初始帧的特征点
p0 = cv2.goodFeaturesToTrack(old_gray, mask=None, **feature_params)

3.3 金字塔 LK 光流追踪

直接用 LK 光流在小运动上很准,但运动大了会失败。金字塔光流在多尺度图像上逐层计算:先在大尺度(模糊图像)估算大位移,再在细尺度上精修,从而支持更大的运动范围。

lk_params = dict(
    winSize = (15, 15),    # 搜索窗口大小,越大越鲁棒但越慢
    maxLevel = 2,          # 金字塔层数(0=原图,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
    )

    # status=1 表示该点被成功追踪到
    good_new = p1[st == 1]
    good_old = p0[st == 1]

    # 绘制运动轨迹
    for new, old in zip(good_new, good_old):
        a, b = new.ravel()
        c, d = old.ravel()
        mask = cv2.line(mask, (a, b), (c, d), color[i].tolist(), 2)

    img = cv2.add(frame, mask)    # 叠加轨迹到原图

    # 迭代:当前帧变为"上一帧"
    old_gray = frame_gray.copy()
    p0 = good_new.reshape(-1, 1, 2)   # 重新整理特征点形状

3.4 光流原理图解

在这里插入图片描述

金字塔 LK 的精妙之处:将"大运动"拆解为"小运动的组合"。从顶层(模糊小图)到底层(原始图),每一层都在前一层估计的基础上做精细调整,最终得到精确的位移向量。

3.5 优点与缺点

优点 缺点
可获取运动速度和方向(光流向量的模长/角度) 小运动假设限制了直接应用大运动场景
不需要背景建模,适用于相机也在运动的场景 光照剧烈变化时特征点容易丢失
特征点轨迹可叠加在原图上,直观展示运动 只能追踪"点",无法给出目标框或ID
可扩展为稠密光流(Farneback)分析整体运动 特征点跟丢后需要重新检测(漂移问题)
金字塔层数可调,精度与速度可权衡 计算量比MOG2大,需要逐帧处理

四、目标跟踪(CSRT):框住它,跟住它

4.1 CSRT 原理概述

CSRT(Channel and Spatial Reliability Tracking)是基于**判别相关滤波器(DCF)**的跟踪算法。它在初始化时学习目标的外观模型,然后在后续帧中通过相关滤波找到与目标最相似的区域。

CSRT 核心思路:
    初始化:框选ROI → 学习目标外观模板
    更新:提取候选区域 → 相关滤波打分 → 找到最高分位置

CSRT 相比 KCF / MOSSE 的优势在于引入了通道可靠性权重,对目标形变和部分遮挡有更好的鲁棒性。

4.2 代码逐段解析

# 创建 CSRT 跟踪器
tracker = cv2.TrackerCSRT_create()
tracking = False

cap = cv2.VideoCapture(0)          # 打开摄像头

while True:
    ret, frame = cap.read()
    if not ret:
        break

    key = cv2.waitKey(1)

    # 按 's' 键,手动框选要跟踪的目标
    if key == ord('s'):
        tracking = True
        # selectROI:鼠标框选,返回 (x, y, w, h)
        roi = cv2.selectROI('Tracking', frame, showCrosshair=False)
        # 用初始帧和ROI初始化跟踪器
        tracker.init(frame, roi)

    # 跟踪模式:逐帧更新目标位置
    if tracking:
        success, box = tracker.update(frame)
        if success:
            x, y, w, h = [int(v) for v in box]
            cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2)

    cv2.imshow('Tracking', frame)
    if key == 27:       # ESC 退出
        break

cap.release()
cv2.destroyAllWindows()

4.3 工作流程图解

在这里插入图片描述

四步闭环:框选 ROI → tracker.init()tracker.update() 逐帧循环 → 绘制跟踪框。

4.4 优点与缺点

优点 缺点
可持续追踪特定目标,保留目标ID 需人工初始化(第一帧手动框选)
支持目标缩放、旋转、形变 跟踪失败(遮挡/出画面)后无法自动恢复
相比 MOG2 不依赖背景建模 严重遮挡时容易跟丢(模板漂移)
CSRT 精度高,适合对精度要求高的场景 计算量较大,实时性略逊于 KCF
可在摄像头下实时运行 无法主动检测画面中新出现的目标

五、三技术深度对比

在这里插入图片描述

5.1 横向对比表

对比维度 MOG2 背景建模 LK 光流 CSRT 目标跟踪
核心任务 发现运动区域 追踪特征点运动 跟踪特定目标
是否需要初始化 ❌ 不需要 ❌ 不需要 ✅ 需要框选ROI
运动方向感知 ❌ 不知道方向 ✅ 速度+方向 ✅ 目标框中心
目标ID能力 ❌ 无 ❌ 无(点轨迹) ✅ 有(同一目标)
光照突变鲁棒性 ⭐⭐ 较弱 ⭐⭐ 较弱 ⭐⭐⭐ 较好
遮挡处理能力 ⭐⭐⭐ 自动覆盖 ⭐⭐⭐ 依赖特征点 ⭐⭐ 跟丢风险大
计算复杂度 ⭐ 最低 ⭐⭐ 中等 ⭐⭐⭐ 较高
实时性(摄像头) ✅ 流畅 ✅ 流畅 ⚠️ 依赖硬件

5.2 优缺点核心总结

背景建模 MOG2 — “发现运动”

优点:轻量、无需初始化、背景自动学习
缺点:光照敏感、无ID追踪、影子误检

光流 LK — “看得见方向”

优点:运动向量直观、适于相机运动场景、可视化轨迹
缺点:小运动假设、无目标ID、特征点漂移

CSRT — “跟住这个”

优点:目标ID持久化、支持形变遮挡、精度高
缺点:需手动初始化、遮挡易跟丢、无新目标检测

六、实战:如何组合使用三种技术?

三种技术并非互斥,实际项目中经常组合使用,取长补短:

方案1:监控告警系统
    MOG2(运动检测)→ 触发告警区域
        ↓
    CSRT(目标跟踪)→ 持续追踪入侵者
        ↓
    LK光流(速度分析)→ 计算逃跑速度

方案2:行为分析系统
    LK光流(密集光流)→ 分析人群流动方向
        ↓
    MOG2(区域统计)→ 统计各区域人数
        ↓
    CSRT(单人追踪)→ 锁定特定人员进行行为分析

方案3:智能驾驶感知
    MOG2(运动车辆检测)→ 粗筛运动目标
        ↓
    CSRT(车道跟踪)→ 持续跟踪前车
        ↓
    LK光流(TTC计算)→ 估算碰撞时间

七、完整代码汇总

7.1 背景建模(MOG2)

import cv2
import numpy as np

cap = cv2.VideoCapture('test.avi')
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
fgbg = cv2.createBackgroundSubtractorMOG2()

while True:
    ret, frame = cap.read()
    if not ret:
        break

    # MOG2 前景检测
    fgmask = fgbg.apply(frame)
    # 形态学开运算去噪
    fgmask_new = cv2.morphologyEx(fgmask, cv2.MORPH_OPEN, kernel)
    # 轮廓检测 + 周长筛选
    contours = cv2.findContours(fgmask_new, cv2.RETR_EXTERNAL,
                                 cv2.CHAIN_APPROX_SIMPLE)[-2]
    for c in contours:
        perimeter = cv2.arcLength(c, True)
        if perimeter > 188:
            x, y, w, h = cv2.boundingRect(c)
            cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2)

    cv2.imshow('frame', frame)
    if cv2.waitKey(60) == 27:
        break

cap.release()
cv2.destroyAllWindows()

7.2 光流估计(LK金字塔)

import numpy as np
import cv2

cap = cv2.VideoCapture("test.avi")
color = np.random.randint(0, 255, (100, 3))
ret, frame = cap.read()
old_gray = cv2.cvtColor(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(frame)

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(), 2)

    img = cv2.add(frame, mask)
    cv2.imshow('frame', img)
    if cv2.waitKey(150) == 27:
        break

    old_gray = frame_gray.copy()
    p0 = good_new.reshape(-1, 1, 2)

cap.release()
cv2.destroyAllWindows()

7.3 CSRT 目标跟踪

import cv2

tracker = cv2.TrackerCSRT_create()
tracking = False
cap = cv2.VideoCapture(0)

while True:
    ret, frame = cap.read()
    if not ret:
        break
    key = cv2.waitKey(1)

    if key == ord('s'):
        tracking = True
        roi = cv2.selectROI('Tracking', frame, showCrosshair=False)
        tracker.init(frame, roi)

    if tracking:
        success, box = tracker.update(frame)
        if success:
            x, y, w, h = [int(v) for v in box]
            cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2)

    cv2.imshow('Tracking', frame)
    if key == 27:
        break

cap.release()
cv2.destroyAllWindows()

八、总结与选型建议

遇到视频分析任务,先问自己三个问题:

① "我要检测画面里有没有运动?" → MOG2 背景建模
② "我要知道某个点/区域的运动方向?" → LK 光流估计
③ "我要一直跟着一个特定目标?" → CSRT 目标跟踪

三者组合使用效果更佳——MOG2 触发、光流分析、CSRT 追踪,形成完整的视频理解链路。
场景 推荐技术
人数统计、入侵检测 MOG2
手势识别、车道偏移检测 LK 光流
智能跟随、目标锁定 CSRT
综合监控告警系统 MOG2 + CSRT + LK 组合
cv2.imshow('Tracking', frame)
if key == 27:
    break

cap.release()
cv2.destroyAllWindows()


---

## 八、总结与选型建议

遇到视频分析任务,先问自己三个问题:

① “我要检测画面里有没有运动?” → MOG2 背景建模
② “我要知道某个点/区域的运动方向?” → LK 光流估计
③ “我要一直跟着一个特定目标?” → CSRT 目标跟踪

三者组合使用效果更佳——MOG2 触发、光流分析、CSRT 追踪,形成完整的视频理解链路。


| 场景 | 推荐技术 |
|------|---------|
| 人数统计、入侵检测 | MOG2 |
| 手势识别、车道偏移检测 | LK 光流 |
| 智能跟随、目标锁定 | CSRT |
| 综合监控告警系统 | MOG2 + CSRT + LK 组合 |
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐