上一个教程中我们介绍了一个视频跟踪的算法,但是通过实验我们发现,在视频或者是摄像头当中,如果被追踪的物体迎面过来,由于****效果,物体会放大,之前设置好的窗口区域大小会不合适。
OpenCV实现了一个Camshift算法,首先使用meanshift算法找到目标,然后调整窗口大小,而且还会计算目标对象的的最佳外接圆的角度,并调整窗口。并使用调整后的窗口对物体继续追踪。
使用方法与meanShift算法一样,不过返回的是一个带有旋转角度的矩形。
Camshift原理
camshift利用目标的颜色直方图模型将图像转换为颜色概率分布图,初始化一个搜索窗的大小和位置,并根据上一帧得到的结果自适应调整搜索窗口的位置和大小,从而定位出当前图像中目标的中心位置。
分为三个部分:
1、色彩投影图(反向投影):
(1).RGB颜色空间对光照亮度变化较为敏感,为了减少此变化对跟踪效果的影响,首先将图像从RGB空间转换到HSV空间。
(2).然后对其中的H分量作直方图,在直方图中代表了不同H分量值出现的概率或者像素个数,就是说可以查找出H分量大小为h的概率或者像素个数,即得到了颜色概率查找表。
(3).将图像中每个像素的值用其颜色出现的概率对替换,就得到了颜色概率分布图。这个过程就叫反向投影,颜色概率分布图是一个灰度图像。
2、meanshift
meanshift算法是一种密度函数梯度估计的非参数方法,通过迭代寻优找到概率分布的极值来定位目标。
算法过程为:
(1).在颜色概率分布图中选取搜索窗W
(2).计算零阶距:
计算一阶距:
计算搜索窗的质心:
(3).调整搜索窗大小,宽度为:
长度为1.2s;
(4).移动搜索窗的中心到质心,如果移动距离大于预设的固定阈值,则重复2)3)4),直到搜索窗的中心与质心间的移动距离小于预设的固定阈值,或者循环运算的次数达到某一最大值,停止计算。
3–camshift
将meanshift算法扩展到连续图像序列,就是camshift算法。它将视频的所有帧做meanshift运算,并将上一帧的结果,即搜索窗的大小和中心,作为下一帧meanshift算法搜索窗的初始值。如此迭代下去,就可以实现对目标的跟踪。
算法过程为:
(1)初始化搜索窗
(2)计算搜索窗的颜色概率分布(反向投影)
(3)运行meanshift算法,获得搜索窗新的大小和位置。
(4)在下一帧视频图像中用(3)中的值重新初始化搜索窗的大小和位置,再跳转到(2)继续进行。
camshift能有效解决目标变形和遮挡的问题,对系统资源要求不高,时间复杂度低,在简单背景下能够取得良好的跟踪效果。但当背景较为复杂,或者有许多与目标颜色相似像素干扰的情况下,会导致跟踪失败。因为它单纯的考虑颜色直方图,忽略了目标的空间分布特性,所以这种情况下需加入对跟踪目标的预测算法。
OpenCV中的camshift算法
Camshift算法的使用在OpenCV中类似于上一个教程的Meanshift算法的使用,我们来看代码(将给出详细的代码解释):
view plaincopy to clipboardprint?
def camshift():
cap = cv2.VideoCapture(0)
# ret判断是否读到图片
# frame读取到的当前帧的矩阵
# 返回的是元组类型,所以也可以加括号
ret, frame = cap.read()
# print(type(ret), ret)
# print(type(frame), frame)
# 设置跟踪框参数
r, h, c, w = 0, 100, 0, 100 # simply hardcoded the values
track_window = (c, r, w, h)
# 从当前帧中框出一个小框
roi = frame[r:r + h, c:c + w]
# RGB转为HSV更好处理
hsv_roi = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
# inRange函数设置亮度阈值
# 去除低亮度的像素点的影响
# eg. mask = cv2.inRange(hsv, lower_red, upper_red)
# 将低于和高于阈值的值设为0
mask = cv2.inRange(hsv_roi, np.array((0., 60., 32.)), np.array((180., 255., 255.)))
# 然后得到框中图像的直方图
# cv2.calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate ]])
# mask 即上文的阈值设置
# histSize表示这个直方图分成多少份(即多少个直方柱)
# range是表示直方图能表示像素值的范围
# 返回直方图
roi_hist = cv2.calcHist([hsv_roi], [0], mask, [180], [0, 180])
# 归一化函数cv2.normalize(src[, dst[, alpha[, beta[, norm_type[, dtype[, mask]]]]]])
# 返回dst类型
# 归一化就是要把需要处理的数据经过处理后(通过某种算法)限制在你需要的一定范围内
# src - 输入数组
# dst - 与src大小相同的输出数组
# alpha - 范围值, 以便在范围归一化的情况下归一化到较低范围边界
# beta - 范围归一化时的上限范围; 它不用于标准规范化
# normType - 规范化类型 这里的NORM_MINMAX是数组的数值被平移或缩放到一个指定的范围,线性归一化。
# dtype - 当为负数时,输出数组与src的类型相同;否则,它具有与src相同的通道数;深度=CV_MAT_DEPTH(dtype)
# mask - 可选的操作掩码。
cv2.normalize(roi_hist, roi_hist, 0, 255, cv2.NORM_MINMAX)
# 设置迭代的终止标准,最多十次迭代
term_crit = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 1)
while (1):
ret, frame = cap.read()
if ret == True:
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
# 反向投影函数(特征提取函数)
# 反向投影是一种记录给定图像中的像素点如何适应直方图模型像素分布的方式
# 反向投影就是首先计算某一特征的直方图模型,然后使用模型去寻找图像中存在的特征
# cv2.calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate]])
# images:待处理的图像,图像格式为uint8或float32
# channels:对应图像需要统计的通道,若是灰度图则为0,彩色图像B、G、R对应0、1、2
# mask:掩膜图像。如果统计整幅图像就设置为None,否则这里传入设计的掩膜图像。
# histSize表示这个直方图分成多少份(即多少个直方柱)
# ranges:像素量化范围,通常为0 - 255。
dst = cv2.calcBackProject([hsv], [0], roi_hist, [0, 180], 1)
# RotatedRect CamShift(InputArray probImage, Rect&window, TermCriteria criteria)。
# probImage为输入图像直方图的反向投影图,
# window为要跟踪目标的初始位置矩形框,
# criteria为算法结束条件。
# 函数返回一个有方向角度的矩阵。
#
ret, track_window = cv2.CamShift(dst, track_window, term_crit)
# Draw it on image
pts = cv2.boxPoints(ret)
# 类型转换int0()用于索引的整数(same as C ssize_t; normally either int32 or int64)
pts = np.int0(pts)
# 非填充多边形:polylines()
# cv2.polylines(img, pts, isClosed, color[, thickness[, lineType[, shift]]])
# img – 要画的图片
# pts – 多边形的顶点
# isClosed – 是否闭合线段
# color – 颜色
img2 = cv2.polylines(frame, [pts], True, 255, 2)
cv2.imshow('img2', img2)
# 停止追踪按钮
k = cv2.waitKey(60) & 0xff
if k == 27:
break
else:
cv2.imwrite(chr(k) + ".jpg", img2)
else:
break
cv2.destroyAllWindows()
cap.release()
大致看一下结果:
查看文章汇总页