任务描述
本关任务:编写一个光流法进行目标检测的程序
相关知识
光流法
光流
光流是空间运物体在观察成像平面上的像素运动的瞬时速度,一般来说,光流的产生是由于场景中前景目标本身的移动、相机的运动,或者两者的共同运动所产生的
光流法基本原理
光流法的基本假设条件
亮度恒定不变。即统一目标在不同帧间运动的时候,亮度不会发生改变。这是光流法的基本假定,必须满足这一条件,用于得到光流法基本方程; 时间连续或运动是小运动。即时间的变化不会引起目标位置的剧烈变化,相邻帧之间位移也比较小,这也是光流法需要满足的条件之一
基本约束方程
一个像素I(x, y, t)在第一帧的光强度。(t代表所在的时间维度)。当移动了(dx,dy),的距离到了下一帧,用了dt时间。因为 是同一个像素点,依据上文提到的第一个假设像素在运动的前后光强度是不变的 I(x,y,t)=I(x+dx,y+dy,t+dt) =I(x,y,t)+∂x∂Idx+∂y∂Idy+∂t∂Idt+ϵ 其中ε代表二阶无穷小项,可以忽略不计,可得: ∂xdt∂Idx+∂ydt∂Idy+∂tdt∂Idt=0
设u,v为光流沿X轴和Y轴的速度矢量,可得: u=dtdx,v=dtdy
令Ix=∂x∂I,Iy=∂y∂I,It=∂t∂I分别为图像中像素点的灰度沿X,Y,T方向的偏导数,最终可以得到 Ixu+Iyv+It=0
其中,Ix,Iy,It都可以从图像数据中求出,(u,v)即为所求光流矢量,我们无法用两个未知变量解决这个方程。所以有几种方法可以解决这个问题,其中之一就是Lucas-Kanade。
卢卡斯- Kanade光流OpenCV中 OpenCV在一个函数cv2.calcOpticalFlowPyrLK()
中提供了所有这些。在这里,我们创建一个简单的应用程序来跟踪视频中的某些点。为了决定点,我们使用cv2.goodFeaturesToTrack()
。我们采用第一帧,检测一些Shi-Tomasi角点,然后使用Lucas-Kanade光流迭代地跟踪这些点。对于函数cv2.calcOpticalFlowPyrLK()
,我们传递前一帧,前一点和下一帧。如果找到下一个点,它将返回下一个点以及一些状态值为1的值,否则为零。我们迭代地将这些下一点作为下一步中的前几点。
实现原理
首先选取第一帧,在第一帧图像中检测Shi-Tomasi角点,然后使用LK算法来迭代的跟踪这些特征点。迭代的方式就是不断向cv2.calcOpticalFlowPyrLK()
中传入上一帧图片的特征点以及当前帧的图片。 函数会返回当前帧的点,这些点带有状态1或者0,如果在当前帧找到了上一帧中的点,那么这个点的状态就是1,否则就是0。
实现流程
加载视频。 调用 GoodFeaturesToTrack 函数寻找兴趣点(关键点)。 调用 CalcOpticalFlowPyrLK 函数计算出两帧图像中兴趣点的移动情况。 删除未移动的兴趣点。 在两次移动的点之间绘制一条线段。
opencv中重要函数的说明
cv2.goodFeaturesToTrack(old_gray, mask=None, **feature_params)
用于获得光流估计所需要的角点
参数说明: old_gray:表示输入图片, mask表示掩模, feature_params:maxCorners=100:角点的最大个数, qualityLevel=0.3:角点品质,minDistance=7:即在这个范围内只存在一个品质最好的角点
pl, st, err = cv2.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lk_params)
用于获得光流检测后的角点位置
参数说明: pl:表示光流检测后的角点位置, st:表示是否是运动的角点, err:表示是否出错, old_gray:表示输入前一帧图片,frame_gray:表示后一帧图片, p0:表示需要检测的角点, lk_params: winSize:表示选择多少个点进行u和v的求解, maxLevel:表示空间金字塔的层数
cv2.add(frame, mask)
将两个图像的像素进行加和操作
参数说明: frame表:示输入图片 mask:表示掩模 光流估计:通过当前时刻与前一时刻的亮度不变的特性,根据基本约束方程 I(x,y,t)=I(x+dx,y+dy,t+dt)使用lucas-kanade算法进行求解问题, 我们需要求得的是x,y方向的速度
具体代码部分实现
# 视频的读入
cap = cv2.VideoCapture('test.mp4')
# 构建角点检测所需参数
feature_params = dict(maxCorners=100,
qualityLevel=0.3,
minDistance=7,
blockSize=7)
# maxLevel 为使用的图像金字塔层数
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))
# 拿到第一帧图像并灰度化作为前一帧图片
ret, old_frame = cap.read()
old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)
# 返回所有检测特征点,需要输入图片,角点的最大数量,品质因子,minDistance=7如果这个角点里有比这个强的就不要这个弱的
p0 = cv2.goodFeaturesToTrack(old_gray, mask=None, **feature_params)
编程要求
根据提示,在右侧编辑器补充代码,完善代码,通过已经完成的步骤,补全后面的步骤,完成程序
测试说明
平台会对你编写的代码进行测试:
预期输出: 测试通过
参考代码:
import numpy as np
import pandas as pd
import cv2
np.random.seed(10)
this_answer = None
# 视频的读入
cap = cv2.VideoCapture(r'step2/video_20210506_183642.mp4')
# 构建角点检测所需参数
feature_params = dict(maxCorners=100,
qualityLevel=0.3,
minDistance=7,
blockSize=7)
# maxLevel 为使用的图像金字塔层数
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))
# 拿到第一帧图像并灰度化作为前一帧图片
ret, old_frame = cap.read()
old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)
# 返回所有检测特征点,需要输入图片,角点的最大数量,品质因子,minDistance=7如果这个角点里有比这个强的就不要这个弱的
p0 = cv2.goodFeaturesToTrack(old_gray, mask=None, **feature_params)
tmp_time = 0
while True:
ret, frame = cap.read()
if ret:
# ********** Begin ********** #
# 读取图片灰度化作为后一张图片的输入
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 进行光流检测需要输入前一帧和当前图像及前一帧检测到的角点
p1, st, err = cv2.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lk_params)
# 读取运动了的角点st == 1表示检测到的运动物体,即v和u表示
good_new = p1[st == 1]
good_old = p0[st == 1]
# ********** end ********** #
cv2.waitKey(30)
tmp_time += 30
if tmp_time == 300:
this_answer = good_new.ravel() - good_old.ravel()
# ********** Begin ********** #
# 更新前一帧图片和角点的位置
old_gray = frame_gray.copy()
p0 = good_new.reshape(-1, 1, 2)
# ********** end ********** #
else:
break
cv2.destroyAllWindows()
cap.release()
df = pd.read_csv(r"step2/answer.csv", index_col=0)
mse = np.sum((df.values - this_answer.reshape(-1, 1)) ** 2)
if mse < 10:
print("测试通过")
else:
print("测试失败")
可以参考4-光流估计实战_哔哩哔哩_bilibili,这个up的视频做的很不错,讲的也很清楚
提问:
- # 更新前一帧图片和角点的位置
- old_gray = frame_gray.copy()
- p0 = good_new.reshape(-1, 1, 2)
第三排更新角点的位置为什么是good_new.reshape(-1, 1, 2)?