CamShift源代码编程实现原理解释
CamShift算法,即"Continuously Apative Mean-Shift"算法,是一种运动跟踪算法。今年来在目标跟踪方面应用很广,可以尝试结合Camshift算法结合其他优化算法进行预测,将目标跟踪的实时性提高。本身它主要通过视频图像中运动物体的颜色信息(最近有人提出用HOG特征直方图作为目标实现跟踪初始化)来达到跟踪的目的。我把这个算法分解成三个部分,便于理解:
1) Back Projection计算
2) Mean Shift算法
3) CamShift算法
Back Projection:(这也是此算法改进之处。)
1. 计算被跟踪目标的色彩直方图。在各种色彩空间中,只有HSI空间(或与HSI类似的色彩空间)中的H分量可以表示颜色信息。所以在具体的计算过程中,首先将其他的色彩空间的值转化到HSI空间,然后会其中的H分量做1D直方图计算。
2. 根据获得的色彩直方图将原始图像转化成色彩概率分布图像,这个过程就被称作"Back Projection"。
在OpenCV中的直方图函数中,包含Back Projection的函数,函数原型是:
void cvCalcBackProject(IplImage** img, CvArr** backproject, const CvHistogram* hist);
传递给这个函数的参数有三个:
1. IplImage** img://存放原始图像,输入。 2. CvArr** backproject://存放Back Projection结果,输出。 3. CvHistogram* hist://存放直方图,输入
下面就给出计算Back Projection的OpenCV代码。
1.准备一张只包含被跟踪目标的图片,将色彩空间转化到HSI空间,获得其中的H分量:
IplImage* target=cvLoadImage("target.bmp",-1); //装载图片 IplImage* target_hsv=cvCreateImage( cvGetSize(target), IPL_DEPTH_8U, 3 ); IplImage* target_hue=cvCreateImage( cvGetSize(target), IPL_DEPTH_8U, 3 ); cvCvtColor(target,target_hsv,CV_BGR2HSV); //转化到HSV空间 cvSplit( target_hsv, target_hue, NULL, NULL, NULL ); //获得H分量
2.计算H分量的直方图,即1D直方图:
IplImage* h_plane=cvCreateImage( cvGetSize(target_hsv),IPL_DEPTH_8U,1 ); int hist_size[]={255}; //将H分量的值量化到[0,255] float* ranges[]={ {0,360} }; //H分量的取值范围是[0,360) CvHistogram* hist=cvCreateHist(1, hist_size, ranges, 1); cvCalcHist(&target_hue, hist, 0, NULL);
在这里需要考虑H分量的取值范围的问题,H分量的取值范围是[0,360),这个取值范围的值不能用一个byte来表示,为了能用一个byte表示,需要将H值做适当的量化处理,在这里我们将H分量的范围量化到[0,255].
4.计算Back Projection:
IplImage* rawImage; //---------------------------------------------- //get from video frame,unsigned byte,one channel //---------------------------------------------- IplImage* result=cvCreateImage(cvGetSize(rawImage),IPL_DEPTH_8U,1); cvCalcBackProject(&rawImage,result,hist);
算法分析
用在cvCalcBackProject处理中的模板是目标图像色调(HUE)的直方图,而直方图可以看作是一种概率分布图。在处理前,目标图像中的每一个象素的值描述的在这一点的颜色信息,而处理后,图像中每一个象素的值就变成了这个颜色信息出现在此处的可能性的一种离散化的度量,出现的可能性大,象素的值就大,反之则小。这样就为后面的匹配和跟踪提供了线索。
Meanshift:
通过分析meanshift的原理,其目的就是寻找的是局部最优点,这样很容易联想到其在图像分割或聚类上的应用。
对于其应用到图像处理上来,其样本可认为是(x,y),x值二维坐标,y是颜色空间(p维,maybe 1or3),那么总维数是 p+2。
meanshift也可用作图像平滑,但是对每一个像素点操作用迭代截止值代替,算法复杂度肯定很高了。
目前用meanshift的很多其实还是目标跟踪,先预先取一个目标矩形框,在目标动的时候,就会迭代更新密度中心点到当前目标的密度中心,这样只是一个固定的矩形框的局部范围搜索,算法复杂度肯定可以接受,实时性就不错了。但是通过分析发现用meanshift做跟踪的话,目标突然消失又突然出现,这个算法肯定傻逼了,所以换句话说,目标必须连续移动。
1.计算区域内0阶矩 for(int i=0;i<height;i++) for(int j=0;j<width;j++) M00+=I(i,j) 2.区域内1阶矩: for(int i=0;i<height;i++) for(int j=0;j<width;j++) { M10+=i*I(i,j); M01+=j*I(i,j); } 3.则Mass Center为: Xc=M10/M00; Yc=M01/M00
接下来,讨论Mean Shift算法的具体步骤,Mean Shift算法可以分为以下4步:
1.选择窗的大小和初始位置.
2.计算此时窗口内的Mass Center.
3.调整窗口的中心到Mass Center.
4.重复2和3,直到窗口中心"会聚",即每次窗口移动的距离小于一定的阈值。
在OpenCV中,提供Mean Shift算法的函数,函数的原型是:
int cvMeanShift(IplImage* imgprob,CvRect windowIn,
CvTermCriteria criteria,CvConnectedComp* out);
需要的参数为:
1.IplImage* imgprob:2D概率分布图像,传入;
2.CvRect windowIn:初始的窗口,传入;
3.CvTermCriteria criteria:停止迭代的标准,传入;
4.CvConnectedComp* out:查询结果,传出。
(注:构造CvTermCriteria变量需要三个参数,一个是类型,另一个是迭代的最大次数,最后一个表示特定的阈值。例如可以这样构造criteria:criteria=cvTermCriteria(CV_TERMCRIT_ITER|CV_TERMCRIT_EPS,10,0.1)。)
返回的参数:
1.int:迭代的次数。
Camshift:
1.原理
在了解了MeanShift算法以后,我们将MeanShift算法扩展到连续图像序列(一般都是指视频图像序列),这样就形成了CamShift算法。CamShift算法的全称是"Continuously Apaptive Mean-SHIFT",它的基本思想是视频图像的所有帧作MeanShift运算,并将上一帧的结果(即Search Window的中心和大小)作为下一帧MeanShift算法的Search Window的初始值,如此迭代下去,就可以实现对目标的跟踪。整个算法的具体步骤分5步:
Step 1:将整个图像设为搜寻区域。
Step 2:初始话Search Window的大小和位置。
Step 3:计算Search Window内的彩色概率分布,此区域的大小比Search Window要稍微大一点。
Step 4:运行MeanShift。获得Search Window新的位置和大小。
Step 5:在下一帧视频图像中,用Step 3获得的值初始化Search Window的位置和大小。跳转到Step 3继续运行。
2.实现
在OpenCV中,有实现CamShift算法的函数,此函数的原型是:
cvCamShift(IplImage* imgprob, CvRect windowIn,
CvTermCriteria criteria,
CvConnectedComp* out, CvBox2D* box=0);
其中:
imgprob:色彩概率分布图像。
windowIn:Search Window的初始值。
Criteria:用来判断搜寻是否停止的一个标准。
out:保存运算结果,包括新的Search Window的位置和面积。
box:包含被跟踪物体的最小矩形。
IplImage* frame = 0; int i, bin_w, c; frame = cvQueryFrame( capture ); if( !frame ) break; if( !image ) //如果此时的image 还是新的图时 { image = cvCreateImage( cvGetSize(frame), 8, 3 ); image->origin = frame->origin; hsv = cvCreateImage( cvGetSize(frame), 8, 3 ); hue = cvCreateImage( cvGetSize(frame), 8, 1 ); mask = cvCreateImage( cvGetSize(frame), 8, 1 ); backproject = cvCreateImage( cvGetSize(frame), 8, 1 ); hist = cvCreateHist( 1, &hdims, CV_HIST_ARRAY, &hranges, 1 ); //float hranges_arr[] = {0,180}; float* hranges = hranges_arr; //int hdims = 16; //构建直方图 histimg = cvCreateImage( cvSize(320,200), 8, 3 ); //????why the porpuse cvZero( histimg ); } cvCopy( frame, image, 0 ); cvCvtColor( image, hsv, CV_BGR2HSV ); //convert to hsv if( track_object ) //init the track_object is 0 { int _vmin = vmin, _vmax = vmax; // vmin = 10, vmax = 256 cvInRangeS( hsv, cvScalar(0,smin,MIN(_vmin,_vmax),0), //the function so cool, 在hsv颜色空间中,很酷 //更直观cvInRangeS(imgHSV, cvScalar(15, 234, 120), cvScalar(21, 234, 120), imgThreshed); cvScalar(180,256,MAX(_vmin,_vmax),0), mask ); //mask one channel //在hsv空间中,所要找的颜色位于特定的范围内,因为它是由皮肤颜色来确定的 //cvShowImage( "eg1", mask); //while(1); cvSplit( hsv, hue, 0, 0, 0 ); //将hsv中的一个通道h通道的图像提出出来 // h为色相,如红色,黄色 //cvShowImage( "eg", hue); if( track_object < 0 ) { float max_val = 0.f; cvSetImageROI( hue, selection ); //在hsv单通道的图像中找到自己所感兴趣的区域 cvSetImageROI( mask, selection ); //在经过cvInRangeS二值显示的那个图像 cvCalcHist( &hue, hist, 0, mask ); //mask是掩膜,是要计算直方图的区域, mask为0是表示整幅图像 cvGetMinMaxHistValue( hist, 0, &max_val, 0, 0 ); cvConvertScale( hist->bins, hist->bins, max_val ? 255. / max_val : 0., 0 ); //归一化的转化 cvResetImageROI( hue ); cvResetImageROI( mask ); //解除感兴趣的区域 track_window = selection; //CvRect selection; track_object = 1; cvZero( histimg ); bin_w = histimg->width / hdims; //int hdims = 16; for( i = 0; i < hdims; i++ ) //共有hdims个直方图的图驻 { //cout << cvGetReal1D(hist->bins,i) << endl; int val = cvRound( cvGetReal1D(hist->bins,i)*histimg->height/255 ); CvScalar color = hsv2rgb(i*180.f/hdims); //convertto RGB to display the hist cvRectangle( histimg, cvPoint(i*bin_w,histimg->height), cvPoint((i+1)*bin_w,histimg->height - val), color, -1, 8, 0 ); //plot a rectangle(矩形) //将统计的直方图柱画为矩形小块 } //cvShowImage( "eg", histimg); } cvCalcBackProject( &hue, backproject, hist ); //方向投影,将图像投影到16个柱的统计数目的图 //cvShowImage( "eg", backproject ); cvAnd( backproject, mask, backproject, 0 ); //进行位与运算 //cvShowImage( "eg", backproject ); cvCamShift( backproject, track_window, //backproject 代表可能位置的密度 //在框中寻找中心 cvTermCriteria( CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 10, 1 ), // use mean-shift to track the project &track_comp, &track_box ); //track_box由上一图的box包含的二阶矩计算出来的尺寸 ??为什么现在只是检测出了头 track_window = track_comp.rect; //现在找到的感兴趣的区域作为下一幅图的窗口 if( backproject_mode ) cvCvtColor( backproject, image, CV_GRAY2BGR ); //将方向投影图转化回RGB if( !image->origin ) // track_box.angle = -track_box.angle; //跟踪盒子和那个视频的矩的一个反向偏差 cvEllipseBox( image, track_box, CV_RGB(255,0,0), 3, CV_AA, 0 ); //draw 椭圆 // cvCamShift and cvFitEllipse do lose++; if(lose>=50) track_object=0; //50个一个循环 x_str = track_window.x + track_window.width/2; y_str = track_window.y + track_window.height/2; }
详细函数例子应用:http://blog.csdn.net/poiiy333/article/details/8922286