OpenCV 光流估计

发布于:2025-04-13 ⋅ 阅读:(31) ⋅ 点赞:(0)

一、引言

在计算机视觉领域,光流估计是一项重要的技术,它描述了图像中物体在连续帧之间的运动信息。光流可以理解为图像中每个像素点在相邻帧之间的运动速度和方向,通过分析光流,我们能够获取物体的运动轨迹、检测物体的运动方向等。OpenCV 作为一个强大的计算机视觉库,提供了多种光流估计的方法,本文将详细介绍光流估计的原理、OpenCV 中的实现方法以及实际应用场景。

二、光流估计的原理

光流估计基于三个基本假设:

1. 亮度恒定假设:在相邻帧之间,同一物体上的像素点的亮度保持不变。即对于像素点

2.时间连续性假设:时间间隔 Δt足够小,使得像素点的运动是连续的。

3.空间一致性假设:相邻像素点具有相似的运动。

由于一个方程无法求解两个未知数 u和 v,因此需要引入额外的约束条件,这就产生了不同的光流估计算法。

三、代码实现

视频获取方式:通过网盘分享的文件:test.avi
链接: https://pan.baidu.com/s/1VP25W9YboeNe9ngE1w8T7w 提取码: vqh1

1. 导入库与打开视频文件

import numpy as np
import cv2
cap = cv2.VideoCapture('test.avi')

导入了 numpy 和 cv2 库。numpy 是用于科学计算的基础库,cv2 是 OpenCV 库,提供了丰富的计算机视觉功能。
cv2.VideoCapture('test.avi') 打开了名为 test.avi 的视频文件,返回一个视频捕获对象 cap,后续将通过它读取视频的每一帧。

2. 生成随机颜色

color = np.random.randint(0, 255, (100, 3))

借助 numpy 的 random.randint 函数生成了一个形状为 (100, 3) 的二维数组 color。
数组里的元素是 0 到 255 之间的随机整数,这代表着 100 种随机的 RGB 颜色,之后会用于绘制特征点的运动轨迹。

3. 读取首帧并转换为灰度图

ret, old_frame = cap.read()
old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)

cap.read() 从视频中读取第一帧图像。ret 是一个布尔值,用于判断是否成功读取到帧;old_frame 则是读取到的帧图像。
cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY) 把读取到的彩色帧图像 old_frame 转换为灰度图像 old_gray,因为光流计算通常在灰度图像上进行。

4. 检测特征点

feature_params = dict(maxCorners=100,
                      qualityLevel=0.3,
                      minDistance=7)
p0 = cv2.goodFeaturesToTrack(old_gray, mask=None, **feature_params)

feature_params 是一个字典,包含了角点检测的参数。maxCorners 规定了最多检测的角点数量;qualityLevel 是角点质量水平,用于筛选角点;minDistance 是角点之间的最小距离。
cv2.goodFeaturesToTrack 函数用于检测图像中的角点。old_gray 是输入的灰度图像,mask 是可选的掩码,这里设为 None 表示检测整个图像。**feature_params 把 feature_params 字典中的参数解包并传递给函数。p0 是检测到的角点的坐标

5. 创建掩码图像

mask = np.zeros_like(old_frame)

创建了一个与第一帧图像 old_frame 形状相同的全零数组 mask。
这个数组会作为掩码图像,用于绘制特征点的运动轨迹。

6. 设置光流计算参数

lk_params = dict(winSize=(15, 15),
                 maxLevel=2)

lk_params 是一个字典,包含了 Lucas - Kanade 光流算法的参数。
winSize 是搜索窗口的大小,用于在当前帧和下一帧中匹配特征点;maxLevel 是金字塔的最大层数,用于多尺度光流计算。

7. 主循环:逐帧处理视频

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)
        cv2.imshow('mask', mask)
    img = cv2.add(frame, mask)
    cv2.imshow('frame', img)
    k = cv2.waitKey(150)
    if k == 27:
        break
    old_gray = frame_gray.copy()
    p0 = good_new.reshape(-1, 1, 2)

读取帧并转换为灰度图:cap.read() 从视频中读取下一帧图像,若读取失败(如视频结束),ret 为 False,则跳出循环。cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) 将当前帧图像 frame 转换为灰度图像 frame_gray。
计算光流:cv2.calcOpticalFlowPyrLK 利用 Lucas - Kanade 金字塔光流算法计算特征点在相邻帧之间的运动。p1 是当前帧中特征点的新坐标,st 是一个布尔数组,表明每个特征点是否被成功跟踪,err 是每个特征点的跟踪误差。
筛选成功跟踪的特征点:st == 1 筛选出跟踪成功的特征点,good_new 和 good_old 分别是当前帧和前一帧中成功跟踪的特征点的坐标。
绘制特征点的运动轨迹:通过 cv2.line 函数在掩码图像 mask 上绘制从旧坐标到新坐标的线段,线段颜色为 color[i],线宽为 2。
合并原始帧和掩码图像并显示:cv2.add(frame, mask) 将原始帧图像 frame 和绘制了运动轨迹的掩码图像 mask 相加,得到最终的显示图像 img,并使用 cv2.imshow 显示。
等待按键并更新前一帧信息:cv2.waitKey(150) 等待 150 毫秒,检测是否有按键事件。若按下 Esc 键(键码为 27),则跳出循环。old_gray = frame_gray.copy() 将当前帧的灰度图像复制给 old_gray,作为下一帧处理的前一帧图像。p0 = good_new.reshape(-1, 1, 2) 将当前帧中成功跟踪的特征点的坐标 good_new 重新调整形状,作为下一帧处理的初始特征点。

8. 释放资源

cv2.destroyAllWindows()
cap.release()

cv2.destroyAllWindows() 关闭所有 OpenCV 窗口。
cap.release() 释放视频捕获对象,关闭视频文件。

完整代码:

import numpy as np
import cv2
cap = cv2.VideoCapture('test.avi')
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,
                    minDistance=7)
p0 = cv2.goodFeaturesToTrack(old_gray, mask=None, **feature_params)
mask = np.zeros_like(old_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()  # 获取新点的坐标或者[a,b]= new
        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)
        cv2.imshow('mask',mask)
    img=cv2.add(frame,mask)
    cv2.imshow('frame',img)
    k=cv2.waitKey(150)
    if k==27:
        break
    old_gray=frame_gray.copy()
    p0 = good_new.reshape(-1,1,2)
cv2.destroyAllWindows()
cap.release()

实现结果:四、总结

光流估计是计算机视觉中的一个重要技术,它可以帮助我们理解图像中物体的运动信息。通过 OpenCV 提供的光流估计函数,我们可以方便地实现光流估计的功能。希望本文能够帮助你入门光流估计,并在实际项目中应用这一技术。