一、引言
在计算机视觉领域,光流估计是一项重要的技术,它描述了图像中物体在连续帧之间的运动信息。光流可以理解为图像中每个像素点在相邻帧之间的运动速度和方向,通过分析光流,我们能够获取物体的运动轨迹、检测物体的运动方向等。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 提供的光流估计函数,我们可以方便地实现光流估计的功能。希望本文能够帮助你入门光流估计,并在实际项目中应用这一技术。