云计算百科
云计算领域专业知识百科平台

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

一、光流估计概念

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

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

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

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

    二、函数详解

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

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

    参数说明:

    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方法。

    函数原型:

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

    参数详细说明:

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

    • nextImg:当前帧的灰度图像

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

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

    # 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. 初始化阶段

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

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

    2. 光流计算循环

    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窗口内搜索最佳匹配位置。

    五、应用场景

  • 运动检测与分析

  • 视频稳定

  • 目标跟踪

  • 动作识别

  • 三维重建

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

  • 六、优缺点

    优点:

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

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

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

    局限性:

    • 依赖于亮度恒定假设

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

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

    七、实际运用

    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对象

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

    关键属性和方法:

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

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

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

    2)逐帧读取循环

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

    # ret: 布尔值,表示是否成功读取
    # frame: 当前帧的图像数据(numpy数组)

    if not ret: # 视频结束或读取失败
    break

    # 处理当前帧(光流计算、显示等)

    2. 对比

    1)数据结构不同

    # 视频读取 – 连续帧序列
    cap = cv2.VideoCapture('video.avi')
    ret, frame = cap.read() # 每次读取一帧

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

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

    2) 读取方式不同

    # 视频 – 循环逐帧读取
    while True:
    ret, frame = cap.read() # 需要手动控制读取
    if not ret: # 检查是否读取成功
    break
    # 处理每一帧

    # 图片 – 一次性读取
    img = cv2.imread('image.jpg') # 单次调用读取所有数据

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

    1) 时间连续性要求

    # 光流需要连续帧的时间序列
    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) 特征点追踪的持续性

    # 需要保持特征点的连续性
    p0 = good_new.reshape(-1, 1, 2) # 为下一帧更新特征点

    # 在视频中,特征点可以持续跟踪多帧
    # 在静态图片对中,只能计算单次运动

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » opencv计算机视觉--光流估计&视频读取方法
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!