本次试验可以分成两个步骤完成。
- 利用颜色阈值取出跟踪目标。
- 使用均移(Meanshift and Camshift)算法进行目标跟踪。
首先,什么是均移(Meanshift and Camshift)算法?
均移首先是建立一个窗口(window),然后不断地将目标在窗口内移动搜索,并计算相关位置的最大值(也可以说是窗口内概率密度)。如图,实正方形是窗口的中心,实圆形是目标的质心。目标移动,其质心也会移动,我们的目的就是计算出目标的质心然后将窗口中心移动到质心位置这样就完成了对目标的跟踪。opencv的给的例子meanshif窗口大小固定,camshift窗口实自适应的。
详细请查看python-opencv手册
https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_video/py_meanshift/py_meanshift.html#meanshift
其次,什么是颜色阈值分割?
目标的颜色是目标的一个非常明显的特征,也是比较容易察觉,彩色图像的三基色是红(RED),绿(GREEN),蓝(BLUE),在此基础上建立的模型是RGB模型,然而我们常常将RGB模型 转换成HSV模型,H(明色调),S(饱和度),V(明度)。色度决定图像色彩,饱和度是颜色的深浅,明度是图像的明亮程度。图像阈值分割就是利用HSV这三个特性,设置一个范围将我们所需要(区别于背景)的目标颜色提取出来。基于颜色阈值分割的优点在于易于理解,很直观。缺点在于阈值的选取比较麻烦,且容易受到背景颜色的干扰。
python+opencv的方式给图像处理带来的极大的简易性,python是脚本语言所以在图像处理操作时可将代码写成脚本方便理解。
接下来开始进行试验。
import numpy as np#导入numpy库
import cv2#导入opencv库
cap = cv2.VideoCapture(0)#开启摄像头
ret,frame = cap.read()#读取第一帧视频
# take first frame of the video
lower_blue = np.array([20,0,0]) #设置颜色阈值下限
upper_blue = np.array([50,200,200])#设置颜色阈值上限
在这段代码中读取摄像头捕捉的第一帧作为预处理图像。并设置我们所需要捕捉目标颜色的范围,这里我捕捉的是黄色,范围大概是(20-50)。根据HSV模型,彩色图像的色调(H)在8bit下是(0-180)三基色的值大概是红(0),黄(30),绿(60),青(90),蓝(120)。饱和度(H)的范围是(0-255),明度(V)的范围是(0-255)。所以我给定的黄色杯盖的HSV范围如下H(20-50),S(0-200),V(0-200)。具体的opencv颜色阈值选取详细内容参见
https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_colorspaces/py_colorspaces.html#converting-colorspaces
mask = cv2.inRange(frame, lower_blue, upper_blue)#建立一个遮掩mask
kernel = np.ones((5,5),np.uint8)#建立一个核心
opening = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)#开启
dilation = cv2.dilate(opening,kernel,iterations = 1)#膨胀
在这段代码中主要是对我们摄像头获取的第一帧图像进行了取遮掩,取遮掩的目的是提取出第一帧图像中的目标。opening(开启)和dilation(膨胀)都是形态学处理,开启用于把结构元素(可以理解为每个像素)小的突刺滤掉,切断细长的搭接而起到分离作用。膨胀用于扩张比背景亮的区域,压缩比背景暗的区域。其中kernel创建的就是大小为5 * 5的结构元素。关于形态学的知识详见
https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_morphological_ops/py_morphological_ops.html
image, contours, hierarchy = cv2.findContours(dilation,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)#取轮廓
cnt = contours[1]#轮廓集合中第二个轮廓
M = cv2.moments(cnt)#图像矩
#重心
cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])
这段代码目的是对开启和膨胀后的目标图像进行取轮廓操作,并对取到的轮廓取图像矩取重心和取中心点坐标。轮廓contours是包含着目标的轮廓点。因为获得的目标图像可能会有空洞所以因该会有很多的轮廓点集,这里cnt = contours[1]取的是取第二个点集。图像中计算出来的矩通常描述了图像不同种类的几何特征如:大小、灰度、方向、形状、重心等特征。这里cx和cy是轮廓的重心。关于opencv提取轮廓参见[findContours]https://docs.opencv.org/3.0-last-rst/modules/imgproc/doc/structural_analysis_and_shape_descriptors.html?highlight=cv2.findcontour#cv2.findContours 关于轮廓特性如图像矩参见[CONTOUR_FEATURES]https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_contours/py_contour_features/py_contour_features.html
r,h,c,w = np.int(np.abs(cy-250)),250,np.int(np.abs(cx-250)),250# 根据获得的重心建立一个窗口(window)
track_window = (c,r,w,h)
roi = frame[r:r+h, c:c+w]#取出图像中的目标图像
hsv_roi = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV)#RGB模型转换成HSV模型
mask = cv2.inRange(hsv_roi, lower_blue, upper_blue)#创建遮掩mask
roi_hist = cv2.calcHist([hsv_roi],[0],mask,[30],[0,30])#获取灰度直方图
cv2.normalize(roi_hist,roi_hist,0,30,cv2.NORM_MINMAX)#归一化处理
# Setup the termination criteria, either 10 iteration or move by atleast 1 pt
term_crit = ( cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 1 )#设置起始条件
这段代码主要就是以获取到的轮廓重心为中心建立一个矩形窗口,并对这个窗口取灰度直方图,进行归一化处理,以及设置起始条件为直方图反向映射做准备。直方图反向映射是什么?它用于图像分割或在图像中查找感兴趣的对象。简单地说,它创建的图像大小(但单通道)与我们的输入图像,其中每个像素对应的概率,该像素属于我们的对象。在更简单的世界中,与其余部分相比,输出图像将使我们感兴趣的对象更白。关于直方图反向映射的原理参见
https://blog.csdn.net/michaelhan3/article/details/73550643
while(1):
ret ,frame = cap.read()#读取图片
if ret == True:
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
dst = cv2.calcBackProject([hsv],[0],roi_hist,[0,30],1)#直方图反向映射
# apply meanshift to get the new location
ret, track_window = cv2.CamShift(dst, track_window, term_crit)#自适应均移算法
# Draw it on image
pts = cv2.boxPoints(ret)
pts = np.int0(pts)
img2 = cv2.polylines(frame, [pts], True, 255, 2)#绘制跟踪窗口
cv2.imshow('img2', img2)#显示图像
k = cv2.waitKey(60) & 0xff#设置按下esc退出程序
if k == 27:
break
cv2.destroyAllWindows()#摧毁窗口
cap.release()#卸载摄像头
以上实现了一个获取黄色的目标并进行跟踪的程序。结果如下gif图。
代码托管在码云https://gitee.com/capone/testt/attach_files