二.算法原理

1、camshift利用目标的颜色直方图模型将图像转换为颜色概率分布图,初始化一个搜索窗的大小和位置,并根据上一帧得到的结果自适应调整搜索窗口的位置和大小,从而定位出当前图像中目标的中心位置。camshift的核心步骤仍然是Meanshift,只是在距离相似性度量的基础之上,又增加了图像灰度相似性的度量。两者共同作用,实现了目标的跟踪。

2、camshift算法目标跟踪其具体步骤可以理解为三步:

鼠标响应圈出初始化目标位置----对目标框内生成对应的颜色概率直方图(反向投影)------对当前帧运行meanshift算法对目标框进行密度函数梯度估计,找到密度最大处,定义目标框的中心和大小------以上一帧的meanshift算法返回的目标框的大小和中心值作为本帧目标初始化位置继续第二步,实现跟踪。

Meanshift算法原理图

3、camshift算法原理总结

总体来说,该算法通过meanshift算法的特征空间分析方法不断地比较目标框的中心和其密度梯度分析的质心来更新目标框的大小和位置,然后再通过目标的颜色直方图模型反向投影将RGB颜色空间转化为颜色概率密度分布图,二者结合更准确的定位了目标的位置,即选取颜色的区域再在每帧图片里进行颜色的匹配跟踪,而且将RGB空间转化为HSV空间,减少了对光照亮度变化的敏感,提高了跟踪效果。

camshift能有效解决目标变形和遮挡的问题,对系统资源要求不高,时间复杂度低,在简单背景下能够取得良好的跟踪效果。但当背景较为复杂,或者有许多与目标颜色相似像素干扰的情况下,会导致跟踪失败。因为它单纯的考虑颜色直方图,忽略了目标的空间分布特性,所以这种情况下需加入对跟踪目标的预测算法。

4、运行情况

opencv 图像处理智能小车追踪 opencv追踪算法_反向投影

opencv 图像处理智能小车追踪 opencv追踪算法_camshift_02

第一个图运行情况可以看到,这个是视频捕捉的半自动跟踪,跟踪的画面很清晰也比较流畅,而且在小范围内时,捕捉框能精准的捕捉到对象,大小变化很灵活。当调节上面三个参数时,会导致捕捉框的大小变化,代码给出的原始数值是最佳的捕捉情况。

第二个图为改过之后的捕捉视频的运行情况,读取帧的速度快于原视频的速度,且跟踪效果在该情况下比较精准。当对象运动过快或者变化太多时无法检测到相应的颜色匹配区域时,该算法的处理情况是保持原状,所以会出现偶尔跟丢的情况,但是它在每一帧都会运行Meanshift算法和颜色匹配,所以跟丢之后还是会在较短时间内重新找到对象。

三.代码分析

  1、Onmouse()鼠标回调函数


void onMouse( int event, int x, int y, int flags, void*param ),其中event表示各种鼠标消息,x,y 表示鼠标在当前图像坐标系中的位置,flags是EVENT FLAG的组合,param是用户定义的传递到SETMOUSECALLBACK函数调用的参数。鼠标消息机制如CV_EVENT_LBUTTONDOWN,CV_EVENT_LBUTTONUP,CV_EVENT_MOUSEMOVE分别代表鼠标左键按下,左键抬起,鼠标移动这些鼠标消息机制,通过不同的鼠标消息来进入不同的响应函数完成所需要的鼠标响应操作,在该程序中在左键按下的case语句下通过设置标志位selectObject = true标记认为已选定目标,然后作为判断条件来通过函数得到矩形区域,一旦左键按下,那么进入矩形选择区域得到所选的目标框,当消息机制为左键抬起时,selectObject = false;没有框定目标,无跟踪对象。在官方给出的示例中在该鼠标消息响应函数中并没有画出相应的矩形框的函数,导致实际运行的时候并不能准确框住对象。


2、命令解析器函数CommandLineParser parser(argc, argv, keys)


const char* keys = {"{1|  | 0 | camera number}"};


CommandLineParser parser(argc, argv, keys);//命令解析器函数int camNum = parser.get<int>("1"); 


这个类的出现主要是方便用户在命令行使用过程中减少工作量,parser(argc, argv, keys)前两个参数是命令行传过来的,第三个参数keys的结构有一定规律,比如说"{c |camera|false| use camera or not}" 都是用大括号和双引号引起来,然后中间的内容分成4断,用”|”分隔开,分别表示简称,文件来源,文件值和帮助语句,在该函数中主要是用来打开摄像头和关闭摄像头的。大概可以看出来用这个类的好处就是很方便,因为以前版本没这个类时,如果要运行带参数的.exe,必须在命令行中输入文件路径以及各种参数,并且输入的参数格式要与代码中的if语句判断内容格式一样,一不小心就输错了,很不方便。另外如果想要更改输入格式的话在主函数文件中要相应更改很多地方。现在有了这个类,只需要改keys里面的内容就可以了。


3、cvCvtColor(src,dst_gray,CV_BGR2GRAY);颜色空间转化函数


void cvCvtColor( const CvArr* src, CvArr* dst, int code ); code为色彩空间转换的模式,该code来实现不同类型的颜色空间转换。比如CV_BGR2GRAY表示转换为灰度图,CV_BGR2HSV将图片从RGB空间转换为HSV空间。其中当code选用CV_BGR2GRAY时,dst需要是单通道图片。当code选用CV_BGR2HSV时,对于8位图,需要将RGB值归一化到0-1之间。这样得到HSV图中的H范围才是0-360,S和V的范围是0-1。在该算法中,cvtColor(image, hsv, CV_BGR2HSV)用于将rgb摄像头帧转化成hsv空间的。


4、void cvInRangeS( const CvArr* src, CvScalar lower, CvScalar upper, CvArr* dst );src第一个原数组;lower包括进的下边界;upper不包括进的上边界;dst输出数组必须是 8u 或 8s 类型.当src在所在范围内时,dst被置1,否则置0;


5、calcBackProject(&hue, 1, 0, hist, backproj, &phranges)反向投影函数


参数分别是输入图像,输入图像的数量,用于计算反向投影的通道列表,输入直方图,目标反向投影输出图像,直方图中每个维度bin的取值范围。因此该语句为计算hue图像0通道直方图hist的反向投影,并输入backproj中。


6、TermCriteria( CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 10, 1 )迭代终止条件


这个类是作为迭代算法的终止条件的,该类变量需要3个参数,一个是类型,第二个参数为迭代的最大次数,最后一个是特定的阈值。类型有CV_TERMCRIT_ITER、CV_TERMCRIT_EPS、CV_TERMCRIT_ITER+CV_TERMCRIT_EPS,分别代表着迭代终止条件为达到最大迭代次数终止,迭代到阈值终止,或者两者都作为迭代终止条件。这里是指迭代到最大迭代次数10或者达到特定的阈值就停止迭代。


和之前了解的KCF,TLD等算法完全不一样的体验,之前的算法是在在opencv3.3中已经封装好的,在调用的时候只需要直接调用相应的类函数就行了,没有其他的操作,而camshift算法没有那么智能,官方给出的源码中是通过代码指令来进行相应的计算,比如提取颜色直方图,进行RGB空间到HSV的反向投影等都是通过代码完成的。理解起来也是需要一定的基础,仔仔细细的百度了每个函数的相应功能之后大致动力函数整体的实现架构以及camshift算法的实现情况,但是对于算大内部的矩阵运算,直方图提取还是不能理解。就个人体验来说,camshift算法在跟踪方面性能比较优良,但是没有其他的附加功能,只是单纯的在做颜色匹配和meanshift算法,而其他的目标跟踪算法还有检测和学习的功能,不需要手动圈出对象,同时在跟踪过程中会一直根据当前提取到的信息来完善跟踪器,使后期的跟踪更加的精准。个人想法是,跟踪原理比较简单的camshift算法可以加上一个离线库(或者在线库)保存一些图片对象的颜色直方图,然后在后期检测的时候在视频里一旦有匹配的颜色分布,就可以依据此将相应对象圈出来。不过这个算法对于背景较复杂还有对象颜色分布较相近的情况是有很大的漏洞的。只能作为后期更加优良的跟踪算法的基础算法。