opencv计算机视觉--光流估计&视频读取方法

一、光流估计概念

光流(Optical Flow) 是计算机视觉中用于描述图像中像素点运动模式的技术。它基于以下两个基本假设:

  1. 亮度恒定假设:同一物体点在连续帧中的亮度保持不变

  2. 时空连续性假设:相邻点具有相似的运动

光流估计的核心思想是:在视频序列中,通过分析相邻帧之间像素强度的变化来估计像素的运动速度和方向。

二、函数详解

1. cv2.goodFeaturesToTrack() - Shi-Tomasi角点检测

用于在图像中检测角点(特征点),这些点适合进行光流跟踪。

参数说明:

python 复制代码
p0 = cv2.goodFeaturesToTrack(image=old_gray, mask=None, **feature_params)
  • image:输入的单通道灰度图像

  • maxCorners=100:返回的最大角点数量(≤0表示无限制)

  • qualityLevel=0.3:角点质量阈值(0-1之间),值越大检测到的角点质量越高但数量越少

  • minDistance=7:角点之间的最小欧氏距离(像素),避免角点过于密集

  • mask=None:可选掩码,指定检测区域

  • blockSize=3(默认):计算角点时考虑的邻域大小

  • useHarrisDetector=False(默认):是否使用Harris角点检测器(False表示使用Shi-Tomasi)

返回值

  • 角点坐标数组,形状为(n, 1, 2)

2. cv2.calcOpticalFlowPyrLK() - Lucas-Kanade金字塔光流法

这是代码中核心的光流计算函数,使用金字塔Lucas-Kanade方法。

函数原型

python 复制代码
p1, st, err = cv2.calcOpticalFlowPyrLK(
    prevImg=old_gray, 
    nextImg=frame_gray,
    prevPts=p0,
    nextPts=None,
    **lk_params
)

参数详细说明:

输入参数:
  • prevImg:前一帧的灰度图像

  • nextImg:当前帧的灰度图像

  • prevPts:前一帧中需要跟踪的特征点坐标

  • nextPts:初始化为None,函数会计算并返回这些点在当前帧中的位置

python 复制代码
# Lucas-Kanade光流法参数
lk_params = dict(
    winSize=(15, 15),  # 窗口大小
    maxLevel=2,  # 金字塔层数
    # criteria=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03)
)
lk_params字典中的参数:
  • winSize=(15, 15):搜索窗口大小

    • 较大的窗口可以处理更大的运动,但计算更慢

    • 较小的窗口对快速运动敏感但可能丢失跟踪

  • maxLevel=2:金字塔层数

    • 金字塔用于处理大位移运动

    • 层数0表示不使用金字塔,只使用原始图像

    • 每增加一层,图像尺寸减半,可以处理更大的运动

  • criteria(代码中已注释):迭代终止准则

    • 例如:(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03)

    • 表示最大迭代次数10或变化小于0.03时停止

可选参数(代码中未使用):
  • flags:计算选项

    • 0:普通方法

    • cv2.OPTFLOW_LK_GET_MIN_EIGENVALS:使用最小特征值

  • minEigThreshold:特征值阈值,用于判断跟踪质量

返回值:

  • p1:当前帧中估计的特征点位置

  • st:状态向量(与p0同大小)

    • 1:成功跟踪

    • 0:跟踪失败

  • err:误差向量,表示每个点的跟踪误差

三、算法流程解析

1. 初始化阶段

python 复制代码
# 读取第一帧并转为灰度
old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)

# 检测角点作为特征点
p0 = cv2.goodFeaturesToTrack(old_gray, maxCorners=100, qualityLevel=0.3, minDistance=7)

2. 光流计算循环

python 复制代码
    p1, st, err = cv2.calcOpticalFlowPyrLK(prevImg=old_gray, nextImg=frame_gray,prevPts=p0,nextPts=None,**lk_params)

    # 选择成功的点(状态为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  # 新点坐标
        c, d = old  # 旧点坐标

        # 转换为整数
        a, b, c, d = int(a), int(b), int(c), int(d)

        # 在掩模上绘制线段,连接新点和旧点
        mask = cv2.line(mask, (a, b), (c, d),
                        color[i].tolist(), thickness=2)
        # 显示掩模
        cv2.imshow('mask', mask)

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

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

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

    # 更新灰度图和特征点
    old_gray = frame_gray.copy()
    p0 = good_new.reshape(-1, 1, 2) 

四、金字塔Lucas-Kanade算法原理

1. 基本Lucas-Kanade方程

对于每个特征点,假设在一个小的邻域窗口内,所有像素具有相同的运动:

复制代码
I(x,y,t) = I(x+dx, y+dy, t+dt)

通过泰勒展开得到光流方程:

复制代码
I_x * u + I_y * v + I_t = 0

其中:

  • I_x, I_y:图像在x,y方向的梯度

  • I_t:时间梯度

  • u, v:x,y方向的速度

2. 金字塔方法

  • 构建图像金字塔:从原始图像开始,每层尺寸减半

  • 从顶层开始计算:在低分辨率层计算粗略光流

  • 逐层细化:将粗略结果作为下一层的初始值

  • 最终在原始分辨率层得到精确结果

3. 窗口搜索

对于每个特征点,在其周围的winSize窗口内搜索最佳匹配位置。

五、应用场景

  1. 运动检测与分析

  2. 视频稳定

  3. 目标跟踪

  4. 动作识别

  5. 三维重建

  6. 自动驾驶(车辆/行人运动估计)

六、优缺点

优点:

  • 计算效率高,适合实时应用

  • 对亮度变化有一定鲁棒性

  • 金字塔结构可以处理较大位移

局限性:

  • 依赖于亮度恒定假设

  • 对快速运动或遮挡处理不佳

  • 需要好的特征点(角点)

七、实际运用

python 复制代码
import cv2
import numpy as np

# 读取视频文件
cap = cv2.VideoCapture('renwushipin.avi')

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

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

# Shi-Tomasi角点检测参数
feature_params = dict(
    maxCorners=100,  # 最大角点数量
    qualityLevel=0.3,  # 角点质量的阈值
    minDistance=7  # 最小距离,用于分散角点
)

# Shi-Tomasi角点检测
p0 = cv2.goodFeaturesToTrack(image=old_gray,mask=None,**feature_params)

# 创建用于绘制轨迹的掩模
mask = np.zeros_like(old_frame)

# Lucas-Kanade光流法参数
lk_params = dict(
    winSize=(15, 15),  # 窗口大小
    maxLevel=2,  # 金字塔层数
    # criteria=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03)
)
# 生成随机颜色用于绘制轨迹
color = np.random.randint(0, 255, (100, 3))

while True:
    # 读取下一帧
    ret, frame = cap.read()
    if not ret:
        break

    # 转换为灰度图
    frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

    # 计算光流
    p1, st, err = cv2.calcOpticalFlowPyrLK(prevImg=old_gray, nextImg=frame_gray,prevPts=p0,nextPts=None,**lk_params)

    # 选择成功的点(状态为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  # 新点坐标
        c, d = old  # 旧点坐标

        # 转换为整数
        a, b, c, d = int(a), int(b), int(c), int(d)

        # 在掩模上绘制线段,连接新点和旧点
        mask = cv2.line(mask, (a, b), (c, d),
                        color[i].tolist(), thickness=2)
        # 显示掩模
        cv2.imshow('mask', mask)

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

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

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

    # 更新灰度图和特征点
    old_gray = frame_gray.copy()
    p0 = good_new.reshape(-1, 1, 2)  # 重新整理特征点为适合下次计算的形状(n,2)->(n,1,2)

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

这个代码示例完整展示了如何使用OpenCV实现基于特征点的光流跟踪,包括特征点检测、光流计算、轨迹绘制等完整流程。从代码众,可以看到,这个代码使用的是视频,和之前使用图片时的代码读取方式有所不同,那么有什么不同呢?

八、视频读取方法及与图片的区别

1.视频读取的详细机制

1) VideoCapture对象
python 复制代码
# 创建视频捕获对象
cap = cv2.VideoCapture('renwushipin.avi')
# 或从摄像头读取
# cap = cv2.VideoCapture(0)  # 0表示默认摄像头

关键属性和方法:

  • cap.isOpened(): 检查是否成功打开

  • cap.get(propId): 获取视频属性(帧率、尺寸等)

  • cap.set(propId, value): 设置视频属性

2)逐帧读取循环
python 复制代码
while True:
    # 读取一帧
    ret, frame = cap.read()
    
    # ret: 布尔值,表示是否成功读取
    # frame: 当前帧的图像数据(numpy数组)
    
    if not ret:  # 视频结束或读取失败
        break
        
    # 处理当前帧(光流计算、显示等)

2. 对比

1)数据结构不同
python 复制代码
# 视频读取 - 连续帧序列
cap = cv2.VideoCapture('video.avi')
ret, frame = cap.read()  # 每次读取一帧

# 图片读取 - 单张图像
img = cv2.imread('image.jpg')  # 一次性读取整个图像

如果想使用摄像头,只需要将cap = cv2.VideoCapture('video.avi')改为cap = cv2.VideoCapture(0)

2) 读取方式不同
python 复制代码
# 视频 - 循环逐帧读取
while True:
    ret, frame = cap.read()  # 需要手动控制读取
    if not ret:  # 检查是否读取成功
        break
    # 处理每一帧
    
# 图片 - 一次性读取
img = cv2.imread('image.jpg')  # 单次调用读取所有数据

3.在光流估计中的特殊意义

1) 时间连续性要求
python 复制代码
# 光流需要连续帧的时间序列
old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)

while True:
    ret, frame = cap.read()
    frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    
    # 计算相邻帧之间的运动
    p1, st, err = cv2.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lk_params)
    
    # 更新为下一帧准备
    old_gray = frame_gray.copy()
2) 特征点追踪的持续性
python 复制代码
# 需要保持特征点的连续性
p0 = good_new.reshape(-1, 1, 2)  # 为下一帧更新特征点

# 在视频中,特征点可以持续跟踪多帧
# 在静态图片对中,只能计算单次运动
相关推荐
何中应5 小时前
PyCharm报`Invalid Python SDK`错误
ide·python·pycharm
学步_技术5 小时前
食品计算-Multimodal Food Learning
人工智能·深度学习·计算机视觉·语言模型
月明长歌5 小时前
全栈测试修炼指南:从接口策略到 Python+Pytest+Allure 企业级架构
python·架构·pytest
Go_Zezhou5 小时前
render快速部署网站和常见问题解决
运维·服务器·开发语言·python·github·状态模式
爱学习的阿磊5 小时前
Python深度学习入门:TensorFlow 2.0/Keras实战
jvm·数据库·python
m0_736919105 小时前
构建一个桌面版的天气预报应用
jvm·数据库·python
ctyshr5 小时前
实战:用OpenCV和Python进行人脸识别
jvm·数据库·python
七夜zippoe5 小时前
NumPy高级:结构化数组与内存布局优化实战指南
python·架构·numpy·内存·视图
AIGCmitutu13 小时前
Ps怎么把图片2D转3D?新手图文详细教程!
计算机视觉·photoshop·ps·美工