文章目录
光流估计
光流估计 是计算机视觉中的一种重要技术,它通过计算图像中像素点在连续帧之间的运动向量来估计物体的运动情况。传统光流算法和基于深度学习的光流算法都有各自的优势和应用领域。随着计算机视觉技术的不断发展,光流估计将在更多领域发挥重要作用。
本篇我们来介绍,如何通过光流估计来描绘移动物体的运动情况:
一、基本原理
- 亮度恒定假设:在相邻两帧图像中,同一物体上的像素点在时间上的变化主要由其在图像平面上的运动引起,而不受光照条件的影响。即,同一点随着时间的变化,其亮度不发生变化。
- 小运动假设:随着时间的变化,不会引起位置的剧烈变化,只有小运动情况下才能用前后帧之间单位位置变化引起的灰度变化去近似灰度对位置的偏导数。
- 空间一致性:一个场景上临近的点投影到图像上也是临近点,且临近点速度一致。
二、计算步骤
光流估计的计算过程主要分为两个步骤:特征提取和光流计算。
- 特征提取:在图像中选择合适的像素点作为特征点。一般选择具有较大亮度梯度的像素点(如边缘、角点等)作为特征点,因为这些点在图像中更容易被识别和跟踪。
- 光流计算:通过比较相邻两帧图像中特征点的亮度变化,计算出这些点的运动速度和方向。在实际计算中,通常会使用一些优化算法来提高计算精度和效率。常用的优化算法包括Lucas-Kanade算法、Horn-Schunck算法和金字塔光流算法等
三、实现步骤
由上所述,我们的实现过程需要找到特征点,然后找到亮度变换的点,从而找到移动物体,描绘其轨迹。
步骤:
1.读取视频,得到第一帧的特征点 -----> 2. 创建全零掩膜,用于在其上面绘制轨迹 -----> 3. 主循环,计算下一帧的特征点 ----> 4. 将连续帧之间的特征点连线绘制在掩膜上
1. 处理第一帧
将第一帧转换为灰度图,便于接下来进行特征检测。
python
cap = cv2.VideoCapture('test.avi')
# 读取视频第一帧
ret,old_frame = cap.read()
# 将第一帧转换为灰度图像
old_gray = cv2.cvtColor(old_frame,cv2.COLOR_BGR2GRAY)
2. 寻找特征点
使用cv2.goodFeaturesToTrack()函数,在图像中检测"好"的特征点,这些特征点通常是在图像中容易识别和跟踪的点,如角点、边缘等。
python
cv2.goodFeaturesToTrack(image,maxCorners=100,qualityLevel=0.01,minDistance=10)
参数:
python
-- image:输入的灰度图像。注意,这个函数需要灰度图像作为输入,因此如果输入是彩色图像,需要先转换为灰度图像。
-- maxCorners:要返回的最大角点数量。如果检测到的角点数量超过这个值,则只返回响应值最强的 maxCorners 个角点。
-- qualityLevel:接受检测到的角点的最低质量水平参数。该参数表示最低可接受的角点质量,以最强角点的质量为基准,低于此值的角点会被 拒绝。通常设置为 0.01 到 0.1 之间的值。
-- minDistance:角点之间的最小欧氏距离。在检测过程中,如果两个角点之间的距离小于这个值,则较弱的角点会被拒绝。这有助于避免检测到 的角点过于集中。
- 返回值是一个形状为 (-1, 1, 2) 的 NumPy 数组,其中 -1 表示行数(根据检测到的角点数量动态确定),1 表示每个角点是一个单独的"批次",2 表示每个角点的 (x, y) 坐标。
python
"""-----寻找特征点-----"""
# 定义特征点检测参数
feature_params = dict(maxCorners = 100,# 最大角点数量
qualityLevel = 0.3,# 角点质量的阈值
minDistance = 7)# 最小距离,用于分散角点
p0 = cv2.goodFeaturesToTrack(old_gray,mask=None,**feature_params)# **:关键字参数解包,用于将字典解包为关键字参数
3. 创建全零掩膜
创建一个与当前帧大小相同的全零掩膜,用于绘制轨迹,随机生成颜色,用于后续绘制不同轨迹。
python
"""-----创建一个与当前帧大小相同的全零掩膜,用于绘制轨迹-----"""
mask = np.zeros_like(old_frame)
# 随机生成颜色,用于绘制轨迹
color = np.random.randint(0,255,(100,3))
4. 流光估计函数介绍
cv2.calcOpticalFlowPyrLK()是 OpenCV 中用于计算稀疏光流的函数,具体实现的是 Lucas-Kanade 方法的金字塔版本。这个函数通过比较两帧图像(通常是连续的视频帧)之间的特征点位置,来估计这些特征点的运动。
python
p1, st, err = cv2.calcOpticalFlowPyrLK(prevImg, nextImg, prevPts, nextPts=None, winSize=(15,15), maxLevel=0)
参数:
python
-- prevImg:前一帧图像,通常是灰度图。
-- nextImg:当前帧图像,也是灰度图。
-- prevPts:前一帧图像中的特征点坐标,通常是二维点集。
-- nextPts:输出参数,用于存储计算得到的当前帧中的特征点坐标。如果传入 None,则函数会分配一个新的数组来存储结果。
-- winSize:搜索窗口的大小,用于在每个金字塔层级上搜索特征点的对应点。
-- maxLevel:金字塔的最大层级数。0 表示只使用原始图像,不使用金字塔。
返回值:
python
-- p1:当前帧中特征点的坐标,形状为 (-1, 1, 2) 的 NumPy 数组,其中 -1 表示行数(根据输入特征点的数量动态确定),1 表示每个特 征点是一个单独的"批次",2 表示每个特征点的 (x, y) 坐标。
-- st:状态数组,表示每个特征点是否成功跟踪到。值为 1 表示成功,0 表示失败。
-- err:每个特征点的误差数组,表示光流估计的误差大小。误差越小,表示估计越准确。
5. 主循环处理视频的每一帧
5.1 流光估计
通过连续的两帧图像,使用流光估计找到跟踪成功的点:
python
while (True):
# 读取下一帧
ret,frame = cap.read()
# 检查是否成功读取到帧
if not ret:
break
# 将当前帧转换为灰度图像
frame_gray = cv2.cvtColor(frame,cv2.COLOR_RGB2GRAY)
# cv2.calcOpticalFlowPyrLK()这个函数通过比较两帧图像(通常是连续的视频帧)之间的特征点位置,来估计这些特征点的运动。
p1,st,err = cv2.calcOpticalFlowPyrLK(old_gray,frame_gray,p0,None,**lk_params)
# 选好的点(状态为1的点)
good_new = p1[st == 1]
good_old = p0[st == 1]
5.2 绘制轨迹
使用cv2.line()函数 ,在掩膜图片上将每一帧跟踪的点连线:
python
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)
5.3 生成最终图像
将掩膜添加到当前帧上,生成最终图像:
python
img = cv2.add(frame,mask)
# 显示结果图像
cv2.imshow('frame',img)
5.4 更新旧灰度图和旧特征点
因为,每次进行光流估计跟踪特征点,都是连续的两帧,所以上一帧的图像old_gray 要不停的更新,每次结束之后更新为当前帧,用于下一次计算:
python
# 等待150ms,检测是否按下Esc健
k = cv2.waitKey(150) & 0xff # 按下Esc健,退出循环
if k == 27:
break
# 更新旧灰度图和旧特征点
old_gray = frame_gray.copy()
p0 = good_new.reshape(-1,1,2) # 重新整理特征点为适合下次计算的形状,(38,2) --> (
6. 释放资源
python
"""-----释放资源-----"""
cv2.destroyAllWindows()
cap.release()
总结
本篇介绍了,如何利用光流估计来绘制移动物体的轨迹。
注意!!:每两个连续帧之间计算完特征点并连线之后,都需要将上一帧的图像更新为当前帧,用于下次计算时称为它的上一帧。