CamShift算法
简介
CamShift算法,即"ContinuouslyApative Mean-Shift"算法,是一种运动跟踪算法。它主要通过视频图像中运动物体的颜色信息来达到跟踪的目的。我把这个算法分解成三个部分,便于理解:
Back Projection计算。
Mean Shift算法
CamShift算法
1 )Back Projection计算(反向投影)
计算Back Projection的步骤是这样的:
1. 计算被跟踪目标的色彩直方图。在各种色彩空间中,只有HSI空间(或与HSI类似的色彩空间)中的H分量可以表示颜色信息。所以在具体的计算过程中,首先将其他的色彩空间的值转化到HSI空间,然后会其中的H分量做1D直方图计算。
2. 根据获得的色彩直方图将原始图像转化成色彩概率分布图像,这个过程就被称作"BackProjection"。
即将图像的cvCalcHist(&h_plane, hist); 之后,得到所计算的直方图
并且对生成的新的图像的H分量进行提取后,用cvCalcBackProject(&h_plane_dst, dst, hist );
得到概率图像的分布,即根据区间和bins的个数,得到每一个bins的范围,即区间/bins,然后得到每一个数据所在的直方图区间的位置,直方图表示的是在区间中的数据的个数
比如
[1 2 3
4 5 6
7 8 9
4 5 8]
Bins = 3, 我们定的范围时0-10,
则每个小区间 10/3
所以bins[0] = 3, bins[1] = 5, bins[2] = 4;
这样我们对新的图像进行反向投影所得到的结果就是,如果新的数据点位于bins[2]则此处的值就为3,这样我们就得到了,opencv中的最后那句话,用肤色直方图来推导肤色图像的概率。(补充,如果直方图是归一化的,则此反向投影值与一个条件概率相关)。
2)mean-shift算法
我的理解过程,通过在通过在hue维度上,找到我们所要的颜色的范围,之后进行的是跟踪即可,跟踪过程:
逐级寻找窗口的重心,复杂的公式用到了核函数的知识,如果大家有兴趣,可以看下,挺好的!
这里只介绍其简化形式:
分析一幅图像的密度分布!!!!!!!!!!!!!物理含义
3) 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; }