接上篇,OpenCV视频目标跟踪及背景分割器,本篇介绍OpenCV—python目标跟踪==》光流法
回顾:
目标跟踪是对摄像头视频中的移动目标进行定位的过程。实时目标跟踪是许多计算机视觉应用的重要任务,如监控、基于感知的用户界面、增强现实、基于对象的视频压缩以及辅助驾驶等。关于实现视频目标跟踪的方法有很多,当跟踪所有移动目标时,帧之间的差异会变的有用;当跟踪视频中移动的手时,基于皮肤颜色的均值漂移方法是最好的解决方案;当知道跟踪对象的一方面时,模板匹配是不错的技术。OpenCV中常用的运动物体检测有背景差法、帧差法、光流法,运动物体检测广泛应用于视频安全监控、车辆检测等方面。
常用的目标跟踪方法有:
背景差法:将一幅图作为背景,让后和每一帧对比;就是用原图像减去背景模型,剩下的就是前景图像,即运动目标,缺点是一开始存入的背景可能随光照变法而造成错误,但是可以用在光照环境稳定的地方,优点是可以检测之前背景没有的景象;
背景减法基本步骤:原图-背景------阈值处理------去除噪声(腐蚀滤波)------膨胀连通-----查找轮廓-----外接矩形(椭圆/圆)
帧差法:将前一帧和后一帧进行对比;缺点是无法对运动后突然又静止的景象进行识别,优点是光照不影响;
就是利用相邻的两帧或者三帧图像,利用像素之间的差异性,判断是否有运动目标
(视频就是一帧一帧图像组成的、求图像差异最基本的就是图像减法--suntract,absdiff)
目标
在这一章当中,
- 我们将使用Lucas-Kanade方法理解光流的概念及其估计。
- 我们将使用像cv2.calcOpticalFlowPyrLK()这样的函数来跟踪视频中的特征点。
光学流程
光流是由物体或相机的运动引起的图像对象在两个连续帧之间的视在运动模式。它是2D矢量场,其中每个矢量是一个位移矢量,显示点从第一帧到第二帧的移动。考虑下面的图片(图片提供:维基百科有关光流的文章)。
它显示了一个连续5帧移动的球。箭头显示其位移矢量。光流在以下领域有许多应用:
- 运动结构
- 视频压缩
- 视频稳定...
光流在几个假设下工作:
- 物体的像素强度在连续帧之间不会改变。
- 相邻像素具有相似的运动。
考虑第一帧中的像素(检查一个新的维度,时间,在这里添加,之前我们只处理图像,所以不需要时间)。它
在
时间之后的下一帧中按距离移动。所以,由于这些像素相同,强度不变,我们可以说,
然后采用泰勒级数近似的右手边,去除常用项并除以得到以下等式:
其中:
以上等式称为光流方程。在它中,我们可以找到并且
它们是图像渐变。同样
是沿着时间的梯度。但
未知。我们无法用两个未知变量解决这个方程。所以有几种方法可以解决这个问题,其中之一就是Lucas-Kanade。
光流法主要用于寻找不同图片间的特征点对应关系。特别是应用在视频中,因为对于视频,可以合理地认为当前帧中的许多点能够在下一帧中找到。
一个理想的光流算法输出应该是图中每个像素的速度预测集合,或是表示每个像素在相邻帧间相对位置的位移向量。当对图中每个像素求解时,就是密集光流法(dense optical flow)。相对的,也存在稀疏光流法(sparse optical flow)。稀疏光流法通过只追踪图中的特征点,同时达到快速和准确的目的。
Lucas-Kanade原理
基本假设:
亮度恒定假设:目标物体像素的强度值在帧间亮度不变。对于灰度图,即对于追踪的像素点,帧间亮度不变。
时间连续:相邻帧间运动微小
空间一致性:图中临近的点属于相同的表面,具有相似的移动
我们之前已经看到一个假设,即所有相邻的像素都会有相似的运动。卢卡斯 - 卡纳德方法在这点上需要3x3的补丁。所有9点都有相同的动作。我们可以找到这9点。所以现在我们的问题变成了求解9个有两个未知变量的方程,这些变量是超定的。使用最小二乘拟合法可以获得更好的解决方案。以下是最终的解决方案,它是两个方程 - 两个未知问题并解决得到解决方案。
(检查逆矩阵与哈里斯角点检测器的相似性,它表示角点是更好的跟踪点。)
所以从用户的角度来看,想法很简单,我们给出一些跟踪点,我们收到这些点的光流向量。但也有一些问题。直到现在,我们正在处理小的议案。所以当运动很大时就失败了。我们再一次去金字塔。当我们在金字塔上走时,小的运动被移除,大的运动变成小的运动。因此,在那里应用卢卡斯 - 卡纳德,我们可以得到光流和规模。
卢卡斯- 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 函数计算出两帧图像中兴趣点的移动情况。
删除未移动的兴趣点。
在两次移动的点之间绘制一条线段。
"""
calcOpticalFlowPyrLK.py:
由于目标对象或者摄像机的移动造成的图像对象在 续两帧图像中的移动 被称为光流。
它是一个 2D 向量场 可以用来显示一个点从第一帧图像到第二 帧图像之间的移动。
光 流在很多领域中都很有用
• 由运动重建结构
• 视频压缩
• Video Stabilization 等
"""
'''
重点函数解读:
1. cv2.goodFeaturesToTrack(old_gray, mask=None, **feature_params)
用于获得光流估计所需要的角点
参数说明:
old_gray表示输入图片,
mask表示掩模,
feature_params:maxCorners=100角点的最大个数,
qualityLevel=0.3角点品质,minDistance=7即在这个范围内只存在一个品质最好的角点
2. 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表示空间金字塔的层数
3. cv2.add(frame, mask) # 将两个图像的像素进行加和操作
参数说明:frame表示输入图片,mask表示掩模
光流估计:通过当前时刻与前一时刻的亮度不变的特性
I(x, y, t) = I(x+?x, y+?y, t+?t) 使用lucas-kanade算法进行求解问题, 我们需要求得的是x,y方向的速度
'''
import numpy as np
import cv2
# 第一步:视频的读入
cap = cv2.VideoCapture('../data/slow.flv')
# 第二步:构建角点检测所需参数
# params for ShiTomasi corner detection
feature_params = dict(maxCorners=100,
qualityLevel=0.3,
minDistance=7,
blockSize=7)
# Parameters for lucas kanade optical flow
# maxLevel 为使用的图像金字塔层数
lk_params = dict(winSize=(15, 15),
maxLevel=2,
criteria=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03))
# Create some random colors
color = np.random.randint(0, 255, (100, 3))
# 第三步:拿到第一帧图像并灰度化作为前一帧图片
# Take first frame and find corners in it
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)
# 第五步:创建一个mask, 用于进行横线的绘制
# Create a mask image for drawing purposes
mask = np.zeros_like(old_frame)
while True:
# 第六步:读取图片灰度化作为后一张图片的输入
ret, frame = cap.read()
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 第七步:进行光流检测需要输入前一帧和当前图像及前一帧检测到的角点
# calculate optical flow能够获取点的新位置
p1, st, err = cv2.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lk_params)
# 第八步:读取运动了的角点st == 1表示检测到的运动物体,即v和u表示为0
# Select good points
good_new = p1[st == 1]
good_old = p0[st == 1]
# 第九步:绘制轨迹
# draw the tracks
for i, (new, old) in enumerate(zip(good_new, good_old)):
a, b = new.ravel()
c, d = old.ravel()
mask = cv2.line(mask, (a, b), (c, d), color[i].tolist(), 2)
frame = cv2.circle(frame, (a, b), 5, color[i].tolist(), -1)
# 第十步:将两个图片进行结合,并进行图片展示
img = cv2.add(frame, mask)
cv2.imshow('frame', img)
k = cv2.waitKey(30) #& 0xff
if k == 27:
break
# 第十一步:更新前一帧图片和角点的位置
# Now update the previous frame and previous points
old_gray = frame_gray.copy()
p0 = good_new.reshape(-1, 1, 2)
cv2.destroyAllWindows()
cap.release()
'''
代码:
第一步:使用cv2.capture读入视频
第二步:构造角点检测所需参数, 构造lucas kanade参数
第三步:拿到第一帧图像,并做灰度化, 作为光流检测的前一帧图像
第四步:使用cv2.goodFeaturesToTrack获得光流检测所需要的角点
第五步:构造一个mask用于画直线
第六步:读取一张图片,进行灰度化,作为光流检测的后一帧图像
第七步:使用cv2.caclOpticalFlowPyrLK进行光流检测
第八步:使用st==1获得运动后的角点,原始的角点位置
第九步:循环获得角点的位置,在mask图上画line,在后一帧图像上画角点
第十步:使用cv2.add()将mask和frame的像素点相加并进行展示
第十一步:使用后一帧的图像更新前一帧的图像,同时使用运动的后一帧的角点位置来代替光流检测需要的角点
'''
OpenCV中的密集光流
Lucas-Kanade方法计算稀疏特征集的光流(在我们的例子中,使用Shi-Tomasi算法检测拐角)。OpenCV提供了另一种算法来查找密集的光流。它计算帧中所有点的光流。它基于Gunner Farneback的算法,该算法在Gunner Farneback于2003年在“基于多项式展开的两帧运动估计”中进行了解释。
以下示例显示了如何使用上述算法找到密集的光流。我们得到了一个带有光流矢量的双通道阵列。我们发现它们的规模和方向。我们对结果进行颜色编码以实现更好的可视化 方向对应于图像的色调值。大小对应于数值平面。请参阅下面的代码:
import cv2
import numpy as np
cap = cv2.VideoCapture("../data/vtest.avi")
# cap = cv2.VideoCapture("../data/slow.flv")
ret, frame1 = cap.read()
prvs = cv2.cvtColor(frame1, cv2.COLOR_BGR2GRAY)
hsv = np.zeros_like(frame1)
hsv[..., 1] = 255
while True:
ret, frame2 = cap.read()
next = cv2.cvtColor(frame2, cv2.COLOR_BGR2GRAY)
flow = cv2.calcOpticalFlowFarneback(prvs, next, None, 0.5, 3, 15, 3, 5, 1.2, 0)
mag, ang = cv2.cartToPolar(flow[..., 0], flow[..., 1])
hsv[..., 0] = ang * 180 / np.pi / 2
hsv[..., 2] = cv2.normalize(mag, None, 0, 255, cv2.NORM_MINMAX)
bgr = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
cv2.imshow('frame2', frame2)
cv2.imshow('flow', bgr)
k = cv2.waitKey(1) & 0xff
if k == 27:
break
elif k == ord('s'):
cv2.imwrite('opticalfb.png', frame2)
cv2.imwrite('opticalhsv.png', bgr)
prvs = next
cap.release()
cv2.destroyAllWindows()
参考文献:
1.https://blog.csdn.net/koibiki/article/details/80225827
2.https://my.oschina.net/u/3702502/blog/1815343
3.https://blog.csdn.net/wsp_1138886114/article/details/84400392
4.http://www.mamicode.com/info-detail-2624792.html