运动目标检测
运动目标的检测的其主要目的是获取目标对象的运动参数(位置、速度、加速度等)及运动轨迹,通过进一步分析处理,实现对目标行为更高层级上的理解。
运动目标检测技术目的是从序列图像中将变化区域从背景图像中提取出来,常用于视频监视、图像压缩、三维重构、异常检测等。
光流
背景介绍
运动目标检测主流方法有帧差法、背景差法、光流法等。光流法源于仿生学思想,更贴近于直觉,大量昆虫的视觉机理便是基于光流法。
二十世纪五十年代心理学家Gibson在他的著作“The Perception of Visual World”中首次提出了以心理学实验为基础的光流法基本概念,而直到八十年代才由Horn、Kanade、Lucash和Schunck创造性地将灰度与二维速度场相联系,引入光流约束方程的算法,对光流计算做了奠基性的工作。
光流(optical flow):由于目标对象或者摄像机的移动造成的图像对象在连续两帧图像中的移动。
- 它是一个2D向量场,用来显示一个点从第一帧图像到第二帧图像的移动,可以通过二维平面的光流场恢复三维空间和运动参数和结构参数。
- 人眼观察运动物体时,物体景象在人眼视网膜上形成一系列连续变化图像,如连续变化的信息流过视网膜,类似光的一种流,因此称为光流。
-
运动场为物体在真实三维世界的运动,光流场则是运动场在二维平面(如人眼或摄像头)上的投影,我们用图片序列的光流场还原不能直接得到的真实运动场。
通俗说,对于一个图片序列,把每张图像每个像素在连续帧之间的运动速度和方向(某像素点在连续两帧上的位移矢量)找出来就是光流场。
第t帧的时A点的位置是(x1, y1),第t+1帧时A点位置是(x2,y2),则像素点A的位移矢量:(ux, vy) = (x2, y2) - (x1,y1)
如何知道第t+1帧的时候A点的位置涉及到不同的光流计算方法,主要有四种:基于梯度的方法、基于匹配的方法、基于能量的方法、基于相位的方法。
光流法依赖于三个假设:
- 亮度恒定:目标像素强度在连续帧内不变
- 相邻帧间隔时间足够短。
- 相邻像素具有相似运动。
稠密光流与稀疏光流
根据所形成的光流场中二维矢量的疏密程度,光流法可分为稠密光流与稀疏光流。
1. 稠密光流
- 稠密光流计算图像上所有点的偏移量,得到稠密的光流场,可进行像素级别图像配准。
- Horn-Schunck算法以及基于区域匹配的大多数光流法都属于稠密光流的范畴
-
稠密光流配准效果精准到像素级,但计算量大、时效性差。
2.稀疏光流
稀疏光流只对有明显特征的组点(如角点)进行跟踪,计算开销小。
OpenCV中光流算法的API介绍
http://www.opencv.org.cn/opencvdoc/2.3.2/html/modules/video/doc/motion_analysis_and_object_tracking.html#calcopticalflowfarneback
(1)calcOpticalFlowPyrLK
基于金字塔LK光流算法,计算某些点集的稀疏光流。
参考论文《Pyramidal Implementation of the Lucas Kanade Feature TrackerDescription of the algorithm》
(2)calcOpticalFlowFarneback
基于Gunnar Farneback 的算法计算稠密光流。
参考论文《Two-Frame Motion Estimation Based on PolynomialExpansion》
(3)CalcOpticalFlowBM
通过块匹配的方法来计算光流
(4)CalcOpticalFlowHS
基于Horn-Schunck 的算法计算稠密光流。
参考论文《Determining Optical Flow》
(5)calcOpticalFlowSF
论文《SimpleFlow: A Non-iterative, Sublinear Optical FlowAlgo》的实现
代码实现
LK光流法
# 来自opencv\sources\samples\python\lk_track.py
import numpy as np
import cv2 as cv
cap = cv.VideoCapture("E:/opencv_vs/opencv/sources/samples/data/vtest.avi")
# params for ShiTomasi corner detection 设置 ShiTomasi 角点检测的参数
feature_params = dict(maxCorners=100,
qualityLevel=0.3,
minDistance=7,
blockSize=7)
# Parameters for lucas kanade optical flow 设置 lucas kanade 光流场的参数
# maxLevel 为使用的图像金字塔层数
lk_params = dict(winSize=(15, 15),
maxLevel=2,
criteria=(cv.TERM_CRITERIA_EPS | cv.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 = cv.cvtColor(old_frame, cv.COLOR_BGR2GRAY)
p0 = cv.goodFeaturesToTrack(old_gray, mask=None, **feature_params)
# Create a mask image for drawing purposes 创建一个掩膜为了后面绘制角点的光流轨迹
mask = np.zeros_like(old_frame)
# 视频文件输出参数设置
out_fps = 12.0 # 输出文件的帧率
fourcc = cv.VideoWriter_fourcc('M', 'P', '4', '2')
sizes = (int(cap.get(cv.CAP_PROP_FRAME_WIDTH)), int(cap.get(cv.CAP_PROP_FRAME_HEIGHT)))
out = cv.VideoWriter('E:/video/v5.avi', fourcc, out_fps, sizes)
while True:
(ret, frame) = cap.read()
frame_gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
# calculate optical flow 能够获取点的新位置
p1, st, err = cv.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lk_params)
# 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 = cv.line(mask, (a, b), (c, d), color[i].tolist(), 2)
frame = cv.circle(frame, (a, b), 5, color[i].tolist(), -1)
img = cv.add(frame, mask)
cv.imshow('frame', img)
out.write(img)
k = cv.waitKey(200) & 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)
out.release()
cv.destroyAllWindows()
cap.release()
Gunner_Farneback 算法计算稠密光流
# 来自opencv\sources\samples/python2/opt_flow.py
import cv2 as cv
import numpy as np
es = cv.getStructuringElement(cv.MORPH_ELLIPSE, (10, 10))
kernel = cv.getStructuringElement(cv.MORPH_ELLIPSE, (3, 3))
cap = cv.VideoCapture("E:/opencv_vs/opencv/sources/samples/data/vtest.avi")
frame1 = cap.read()[1]
prvs = cv.cvtColor(frame1, cv.COLOR_BGR2GRAY)
hsv = np.zeros_like(frame1)
hsv[..., 1] = 255
# 视频文件输出参数设置
out_fps = 12.0 # 输出文件的帧率
fourcc = cv.VideoWriter_fourcc('M', 'P', '4', '2')
sizes = (int(cap.get(cv.CAP_PROP_FRAME_WIDTH)), int(cap.get(cv.CAP_PROP_FRAME_HEIGHT)))
out1 = cv.VideoWriter('E:/video/v6.avi', fourcc, out_fps, sizes)
out2 = cv.VideoWriter('E:/video/v8.avi', fourcc, out_fps, sizes)
while True:
(ret, frame2) = cap.read()
next = cv.cvtColor(frame2, cv.COLOR_BGR2GRAY)
flow = cv.calcOpticalFlowFarneback(prvs, next, None, 0.5, 3, 15, 3, 5, 1.2, 0)
mag, ang = cv.cartToPolar(flow[..., 0], flow[..., 1])
hsv[..., 0] = ang * 180 / np.pi / 2
hsv[..., 2] = cv.normalize(mag, None, 0, 255, cv.NORM_MINMAX)
bgr = cv.cvtColor(hsv, cv.COLOR_HSV2BGR)
draw = cv.cvtColor(bgr, cv.COLOR_BGR2GRAY)
draw = cv.morphologyEx(draw, cv.MORPH_OPEN, kernel)
draw = cv.threshold(draw, 25, 255, cv.THRESH_BINARY)[1]
image, contours, hierarchy = cv.findContours(draw.copy(), cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
for c in contours:
if cv.contourArea(c) < 500:
continue
(x, y, w, h) = cv.boundingRect(c)
cv.rectangle(frame2, (x, y), (x + w, y + h), (255, 255, 0), 2)
cv.imshow('frame2', bgr)
cv.imshow('draw', draw)
cv.imshow('frame1', frame2)
out1.write(bgr)
out2.write(frame2)
k = cv.waitKey(200) & 0xff
if k == 27 or k == ord('q'):
break
elif k == ord('s'):
cv.imwrite('opticalfb.png', frame2)
cv.imwrite('opticalhsv.png', bgr)
prvs = next
out1.release()
out2.release()
cap.release()
cv.destroyAllWindows()
参考博客
https://blog.csdn.net/zhanghenan123/article/details/80853523
https://blog.csdn.net/tengfei461807914/article/details/80978947
https://blog.csdn.net/wsp_1138886114/article/details/84400392
https://wenku.baidu.com/view/7a2cb968ff00bed5b8f31d6c.html?from=search
https://zhuanlan.zhihu.com/p/36389954
https://zhuanlan.zhihu.com/p/42942198