OpenCv高阶(十)——光流估计

文章目录


前言

在计算机视觉领域,光流估计是捕捉图像序列中像素点运动信息的核心技术。它描述了图像中每个像素点在相邻帧间的运动方向和速度,能从静态图像帧中解析出动态变化。

光流估计基于像素亮度不变和运动平滑两个关键假设,通过数学建模解算出像素运动场,为视频分析、目标跟踪等任务提供基础。OpenCV 集成了稀疏光流、稠密光流等计算方法,广泛应用于无人机避障、电影特效等场景。

接下来,我们将深入解析光流估计原理、OpenCV 函数使用及实战应用,探索这项技术的奥秘。

一、光流估计

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

光流估计的前提:

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

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

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

二、使用步骤

1、导库读取视频、随机初始化颜色

python 复制代码
# 导入数值计算库和计算机视觉库
import numpy as np
import cv2

# 创建视频捕获对象,读取测试视频
cap = cv2.VideoCapture('../data/test.avi')

# 生成100种随机颜色,用于不同特征点的轨迹绘制
# 格式为(100,3)的数组,每个元素代表BGR颜色值
color = np.random.randint(0, 255, (100, 3))

2、初始化光流跟踪

python 复制代码
# 读取视频第一帧
ret, old_frame = cap.read()

# 将第一帧转换为灰度图像(光流算法需要灰度输入)
old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)

# 设置特征点检测参数字典
feature_params = dict(
    maxCorners = 100,      # 检测的最大特征点数量
    qualityLevel = 0.3,    # 特征点质量阈值(0-1,值越大质量越高)
    minDistance = 7        # 特征点之间的最小像素距离
)

# 使用Shi-Tomasi算法检测角点特征
# goodFeaturesToTrack:在灰度图像中寻找适合跟踪的强角点
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          # 金字塔层数(0表示不使用金字塔)
)

3、视频帧处理循环

python 复制代码
while True:
    # 读取新帧
    ret, frame = cap.read()
    
    # 视频结束或读取失败时退出循环
    if not ret:
        break
    
    # 将当前帧转换为灰度图像
    frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

4、光流计算与可视化

python 复制代码
     # 使用Lucas-Kanade金字塔光流法计算特征点运动
    # p1:新帧中特征点位置
    # st:状态标记(1表示成功跟踪,0表示丢失)
    # err:跟踪误差
    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().astype(int)
        c, d = old.ravel().astype(int)
        
        # 在mask图像上绘制运动轨迹线
        # 参数:目标图像,起点,终点,颜色,线宽
        mask = cv2.line(mask, (a, b), (c, d), color[i].tolist(), 2)
        
        # 显示纯轨迹图像
        cv2.imshow("mask", mask)

    # 将轨迹叠加到原始帧上
    img = cv2.add(frame, mask)
    
    # 显示叠加后的结果帧
    cv2.imshow('frame', img)

5、循环控制与资源释放

python 复制代码
    # 等待150ms并检测ESC按键(ASCII 27)
    k = cv2.waitKey(150)
    if k == 27:
        break

    # 更新前一帧数据
    old_gray = frame_gray.copy()  # 更新灰度图像
    
    # 更新特征点(只保留成功跟踪的点)
    # reshape(-1,1,2)保持与原始p0相同的维度结构
    p0 = good_new.reshape(-1, 1, 2)

# 释放视频资源并销毁所有窗口
cv2.destroyAllWindows()
cap.release()

效果:

完整代码

python 复制代码
# 导入必要的库
import numpy as np
import cv2

# 创建视频捕获对象,读取视频文件
cap = cv2.VideoCapture('../data/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)

# 设置特征点检测参数
feature_params = dict(
    maxCorners=100,   # 最大特征点数量
    qualityLevel=0.3, # 特征点质量等级(0-1之间,越大质量越高)
    minDistance=7     # 特征点之间的最小欧氏距离
)

# 使用Shi-Tomasi方法检测初始特征点(角点检测)
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         # 金字塔层数(0表示仅当前层)
)

# 主循环处理视频帧
while True:
    # 读取新的一帧
    ret, frame = cap.read()
    if not ret:  # 如果读取失败(如视频结束)则退出循环
        break

    # 将当前帧转换为灰度图像
    frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

    # 计算光流(Lucas-Kanade方法)
    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]

    # 绘制特征点运动轨迹
    for i, (new, old) in enumerate(zip(good_new, good_old)):
        # 将浮点坐标转换为整数
        a, b = new.ravel().astype(int)
        c, d = old.ravel().astype(int)
        
        # 在mask上绘制运动轨迹线(使用随机颜色)
        mask = cv2.line(mask, (a, b), (c, d), color[i].tolist(), 2)
        
    # 将轨迹mask与当前帧叠加显示
    img = cv2.add(frame, mask)
    cv2.imshow('frame', img)
    cv2.imshow('mask', mask)  # 单独显示轨迹mask

    # 等待按键(150ms延迟),ESC键退出
    k = cv2.waitKey(150)
    if k == 27:
        break

    # 更新前一帧的灰度图像和特征点
    old_gray = frame_gray.copy()
    p0 = good_new.reshape(-1, 1, 2)  # 更新为当前帧的有效特征点

# 释放资源并关闭所有窗口
cv2.destroyAllWindows()
cap.release()

总结

光流估计的应用:

视频增强与创作:光流不仅用于运动补偿压缩(如MPEG标准),还被应用于视频插帧(生成中间帧提升流畅度)和特效合成(如电影中动态背景替换),英伟达SDK已展示其商业化潜力。

医疗精准化:在超声影像中,光流估计校正探头移动导致的图像偏移,辅助心脏手术的实时导航;还可量化器官运动参数(如心肌应变率),为疾病诊断提供动态指标。

工业智能化升级:生产线中,光流实时监测机械臂运动轨迹,结合异常检测算法预防故障;在精密装配场景,通过微位移分析提升质检精度。

相关推荐
前端双越老师2 分钟前
30 行代码 langChain.js 开发你的第一个 Agent
人工智能·node.js·agent
东坡肘子18 分钟前
高温与奇怪的天象 | 肘子的 Swift 周报 #092
人工智能·swiftui·swift
KaneLogger36 分钟前
视频转文字,别再反复拖进度条了
前端·javascript·人工智能
度假的小鱼38 分钟前
从 “人工编码“ 到 “AI 协同“:大模型如何重塑软件开发的效率与范式
人工智能
zm-v-159304339862 小时前
ArcGIS 水文分析升级:基于深度学习的流域洪水演进过程模拟
人工智能·深度学习·arcgis
拓端研究室3 小时前
视频讲解|核密度估计朴素贝叶斯:业务数据分类—从理论到实践
人工智能·分类·数据挖掘
灵智工坊LingzhiAI3 小时前
人体坐姿检测系统项目教程(YOLO11+PyTorch+可视化)
人工智能·pytorch·python
昨日之日20063 小时前
Video Background Remover V3版 - AI视频一键抠像/视频换背景 支持50系显卡 一键整合包下载
人工智能·音视频
SHIPKING3934 小时前
【机器学习&深度学习】什么是下游任务模型?
人工智能·深度学习·机器学习
子燕若水8 小时前
Unreal Engine 5中的AI知识
人工智能