目标追踪是对摄像头或者视频中的移动目标进行检测和定位的过程,它有着广泛的应用.实时目标追踪是许多计算机视觉应用的重要任务,例如监控(surveillance), 基于感知的(perceptual)的用于界面,增强现实,基于对象的视频压缩以及辅助驾驶等
可以使用多种方式实现目标追踪,而最优的跟踪技术在很大程度上跟具体任务有关,为了跟踪视频中的所有目标,首先要完成的任务是识别视频帧中那些可能包含移动目标的区域.
有很多实现视频目标跟踪的方法,这些方法的目的稍微不同.例如,**当跟踪所有移动目标时,帧之间的差异会变的有用**;当跟踪视频中移动的手时,基于皮肤颜色的均值漂移是最好的解决方案;当知道跟踪对象的一方面时,模板匹配会是不错的技术.
因此,我们第一次笔记用来介绍最基本的运动检测方法--帧差法,即计算帧之间的差异,或考虑"背景"帧与其他帧之间的差异.
为了更直观有效的解释这个方法,先进行程序的细致讲解,最后进行总结概括.
目录
3.1 阈值化处理 mode = THRESH_BINARY
cv2.findContours,有3个返回值,分别是img, countours, hierarchy
-
程序
#-*- coding: utf-8 -*-
import cv2
import numpy as np
'''
帧差法实现目标追踪
@author 2019-1-22 19:56
'''
camera = cv2.VideoCapture(0)
es = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (9,4))
kernel = np.ones((5,5), np.uint8)
background = None
while(True):
ret, frame = camera.read()
if background is None:
background = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
background = cv2.GaussianBlur(background, (21,21), 0)
continue
gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
gray_frame = cv2.GaussianBlur(gray_frame, (21, 21), 0)
diff = cv2.absdiff(background, gray_frame)
diff = cv2.threshold(diff, 25, 255, cv2.THRESH_BINARY)[1]
diff = cv2.dilate(diff, es, iterations=2)
image, cnts, hierarchy = cv2.findContours(diff.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for c in cnts:
if cv2.contourArea(c) < 1500:
continue
(x, y, w, h) = cv2.boundingRect(c)
cv2.rectangle(frame, (x,y), (x+w,y+h), (0, 255, 0), 2)
cv2.imshow("contours", frame)
cv2.imshow("diff", diff)
if cv2.waitKey(50) & 0xff == ord("q"):
break
cv2.destroyAllWindows()
camera.release()
-
程序解读
1. 定义结构元素
camera = cv2.VideoCapture(0) #调用摄像头
#获取常用的结构元素的形状 椭圆>>长轴为9,短轴为4
#参数介绍MORPH_RECT(矩形), MORPH_ELLIPSE(椭圆),MORPH_CROSS(十字形)
es = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (9,4)) #结构化元素获取
kernel = np.ones((5,5), np.uint8) #卷积核设定
background = None
主要使用函数: cv2.getStructuringElement()
Python: cv2.getStructuringElement(shape, ksize[, anchor]) → retval
此函数用于返回固定大小和尺寸的结构化元素进行形态学操作,形态学操作即改变物体的形状,诸如腐蚀 膨胀之类.
2. 背景和其他帧 预处理
#进入死循环
while(True):
ret, frame = camera.read() #摄像头读取
if background is None: #背景初始化设定,以第一帧作为背景
background = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) #背景灰度化处理
background = cv2.GaussianBlur(background, (21,21), 0) #背景高斯模糊处理
continue #跳出本次循环
(21, 21)表示高斯矩阵的长与宽都是21,标准差取0时OpenCV会根据高斯矩阵的尺寸自己计算。
概括地讲,高斯矩阵的尺寸越大,标准差越大,处理过的图像模糊程度越大。
gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) #frame二值化处理
gray_frame = cv2.GaussianBlur(gray_frame, (21, 21), 0) #frame模糊化处理
主要使用函数:cv2.cvtColor() cv2.GaussianBlur()
Python: cv2.cvtColor(src, code[, dst[, dstCn]]) → dst
Python: cv2.GaussianBlur(src, ksize, sigmaX[, dst[, sigmaY[, borderType]]]) → dst
在导入模块之后,打开系统默认的摄像头获得视频图像,并将第一帧设置为整个输入的背景.在进行其他帧和背景之间的差异对比之前需要对帧和背景都进行同样的预处理.
首先就是灰度化处理将帧转换为灰阶,并进行一下模糊处理.
高斯模糊本质上是低通滤波器,输出图像的每个像素点是原图像上对应像素点与周围像素点的加权和,原理并不复杂。做久了卷积神经网络看这个分外亲切,就是用高斯分布权值矩阵与原始图像矩阵做卷积运算而已。
至于高斯分布权重矩阵,就是对二维正态分布的密度函数(也就是高斯函数)采样再做归一化的产物。
进行模糊处理的原因:每个输入的视频都会因为自然震动,光照变化或者摄像头本身等原因而产生噪声.对噪声进行平滑是为了避免在运动和跟踪时将噪声检测处理,造成误检测.
3. 计算背景和其他帧 差异
diff = cv2.absdiff(background, gray_frame) #计算背景和其他帧的差异(取差值得绝对值)
value<25 -->0 黑色 value>25 -->255白色
diff = cv2.threshold(diff, 25, 255, cv2.THRESH_BINARY)[1] #阈值化处理 帧差结果二值化
使用椭圆结构化元素进行膨胀操作,膨胀次数为2
diff = cv2.dilate(diff, es, iterations=2) #膨胀操作 帧差结果膨胀处理
主要使用函数 cv2.absdiff() cv2.threshold() cv2.dilate()
Python: cv2.absdiff(src1, src2[, dst]) → dst
Python: cv2.threshold(src, thresh, maxval, type[, dst]) → retval, dst
Python: cv2.dilate(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]]) → dst
在完成对帧和背景的灰度变换和平滑后,就可以计算与背景帧的差异,并得到一个差分图(differentence map), 但不止这样处理,还需要应用阈值来得到一副黑白图像,并进行膨胀(dilate)图像,从而对孔(hole)和缺陷(imperfection)进行归一化处理.
3.1 阈值化处理 mode = THRESH_BINARY
3.2 膨胀处理
形态学操作其实就是改变物体的形状,比如腐蚀就是"变瘦",膨胀就是"变胖",看下图就明白了:
形态学操作一般作用于二值化图,来连接相邻的元素或分离成独立的元素。腐蚀和膨胀是针对图片中的白色部分!
腐蚀的效果是把图片"变瘦",其原理是在原图的小区域内取局部最小值。因为是二值化图,只有0和255,所以小区域内有一个是0该像素点就为0:
这样原图中边缘地方就会变成0,达到了瘦身目的(小胖福利(●ˇ∀ˇ●))
膨胀与腐蚀相反,取的是局部最大值,效果是把图片"变胖":
4. 绘图并显示
#寻找轮廓
image, cnts, hierarchy = cv2.findContours(diff.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
#对得到的轮廓进行外接矩形绘制
for c in cnts:
if cv2.contourArea(c) < 1500: #计算边界区域面积
continue
#(x,y)即矩形框左上点坐标,w,h为矩形框的宽带和高度
(x, y, w, h) = cv2.boundingRect(c)
#绘制矩形框 (x,y)是左上角端点 (x+w,y+h)是右下角端点 绿色 线宽2
cv2.rectangle(frame, (x,y), (x+w,y+h), (0, 255, 0), 2)
#显示
cv2.imshow("contours", frame)
cv2.imshow("diff", diff)
#视频延时设定 按"q"退出
if cv2.waitKey(50) & 0xff == ord("q"):
break
#摄像头释放 关闭所有窗口
cv2.destroyAllWindows()
camera.release()
主要使用函数 cv2.findContours cv2.contourArea() cv2.boundingRect() cv2.rectangle()
Python: cv2.findContours(image, mode, method[, contours[, hierarchy[, offset]]]) → image, contours, hierarchy
Python: cv2.contourArea(contour[, oriented]) → retval
Python: cv2.boundingRect(points) → retval
Python: cv2.rectangle(img, pt1, pt2, color[, thickness[, lineType[, shift]]]) → img
轮廓检测也是图像处理中经常用到的。OpenCV-Python接口中使用cv2.findContours()函数来查找检测物体的轮廓。
需要注意的是cv2.findContours()函数接受的参数为二值图,即黑白的(不是灰度图),所以读取的图像要先转成灰度的,再转成二值图.
4.1 cv2.findContours,有3个返回值,分别是img, countours, hierarchy
第一个参数是寻找轮廓的图像;
第二个参数表示轮廓的检索模式,有四种(本文介绍的都是新的cv2接口):
cv2.RETR_EXTERNAL表示只检测外轮廓
cv2.RETR_LIST检测的轮廓不建立等级关系
cv2.RETR_CCOMP建立两个等级的轮廓,上面的一层为外边界,里面的一层为内孔的边界信息。如果内孔内还有一个连通物体,这个物体的边界也在顶层。
cv2.RETR_TREE建立一个等级树结构的轮廓。
第三个参数method为轮廓的近似办法
cv2.CHAIN_APPROX_NONE存储所有的轮廓点,相邻的两个点的像素位置差不超过1,即max(abs(x1-x2),abs(y2-y1))==1
cv2.CHAIN_APPROX_SIMPLE压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标,例如一个矩形轮廓只需4个点来保存轮廓信息
cv2.CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS使用teh-Chinl chain 近似
-
总结
现在基本上使用帧差法构建了一个基本的运动检测器,该检测器会在目标周围画一个矩形框.结果不再展示.
对于这个简单的技术,其结果相当准确.但是,也有一些缺点使得这种方法不能满足所有的商业需求.最明显的问题是:该技术需要提前设置"默认"帧作为背景.在一些情况下(例如室外),由于光照变化频繁,这种处理方法就显得相当不灵活,所以需要引入更智能的方法,下次再讲.
-
参考文章
3 python-opencv2利用cv2.findContours()函数来查找检测物体的轮廓