opencv目标跟踪-Camshift应用

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),直到搜索窗的中心与质心间的移动距离小于预设的固定阈值,或者循环运算的次数达到某一最大值,停止计算。关于meanshift的收敛性证明可以google相关文献。

3--camshift
将meanshift算法扩展到连续图像序列,就是camshift算法。它将视频的所有帧做meanshift运算,并将上一帧的结果,即搜索窗的大小和中心,作为下一帧meanshift算法搜索窗的初始值。如此迭代下去,就可以实现对目标的跟踪。
算法过程为:
(1).初始化搜索窗
(2).计算搜索窗的颜色概率分布(反向投影)
(3).运行meanshift算法,获得搜索窗新的大小和位置。
(4).在下一帧视频图像中用(3)中的值重新初始化搜索窗的大小和位置,再跳转到(2)继续进行。

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


对运动物体的跟踪: 如果背景固定,可用帧差法 然后在计算下连通域 将面积小的去掉即可 ;如果背景单一,即你要跟踪的物体颜色和背景色有较大区别 可用基于颜色的跟踪 如CAMSHIFT 鲁棒性都是较好的 ;如果背景复杂,如背景中有和前景一样的颜色 就需要用到一些具有预测性的算法 如卡尔曼滤波等 可以和CAMSHIFT结合

     下面是一个opencv自带的CamShift算法使用工程实例。该实例的作用是跟踪摄像头中目标物体,目标物体初始位置用鼠标指出,其跟踪窗口大小和方向随着目标物体的变化而变化。其代码及注释大概如下:




#include 
#include 
#include 
#include 

/*Declare some namespace*/
using namespace cv;
using namespace std;

/*Define some global vars*/
Mat image;
bool backprojMode = false; /*backproject mode switch*/
bool selectObject = false; /*object selcetion indicator*/
int trackObject = 0; 
bool showHist = true; /*show hist or not*/
Point origin; /*record point info when mouse click in the first*/
Rect selection; /*save selection box of mouse*/
int vmin = 10, vmax = 256, smin = 30;

/*Callback function on mouse action*/
static void onMouse( int event, int x, int y, int, void* )
{
	if( selectObject ) /*it is assigned when the left key of mouse is pressed, when releasing from pressing come here*/
	{
		selection.x = MIN(x, origin.x); /*the released point of mouse*/
		selection.y = MIN(y, origin.y);
		selection.width = std::abs(x - origin.x); /*the width of selection box*/
		selection.height = std::abs(y - origin.y); /*the height of selection box*/
		selection &= Rect(0, 0, image.cols, image.rows); /*make sure selection area in the range of image */
	}

	switch( event )
	{
		case CV_EVENT_LBUTTONDOWN: /*pressure event*/
			origin = Point(x,y);  /*record the prssed point of mouse*/
			selection = Rect(x,y,0,0); /*init a rect when the left key is pressed*/
			selectObject = true;
			break;
		case CV_EVENT_LBUTTONUP:
			selectObject = false;
			if( selection.width > 0 && selection.height > 0 )
			  trackObject = -1;
			break;
	}
}

/*show help information*/
static void ShowHelpText()
{
	cout <<"\n\n\t\t\tThis a demo that shows came-shift based tracking\n"
		<< "\n\n\t\t\tThe opencv version is:" << CV_VERSION 
		<<"\n\n  ----------------------------------------------------------------------------" ;
	cout << "\n\n\tPlease select an object by rect box to track it\n"
	cout << "\n\n\tHot keys: \n"
		"\t\tESC - quit the program\n"
		"\t\tc - stop the tracking\n"
		"\t\tb - switch to/from backprojection view\n"
		"\t\th - show/hide histogram\n"
		"\t\tp - pause video\n";
}
const char* keys =
{
	"{1|  | 0 | camera number}"
};

/*MAIN function*/
int main( int argc, const char** argv )
{
	ShowHelpText();

	VideoCapture cap; /*Define an instance object based on class VideoCapture*/
	Rect trackWindow; /*Define track window*/
	int hsize = 16; /*划分直方图bins个数*/
	float hranges[] = {0,180};
	const float* phranges = hranges;

	cap.open(0); /*open camera device*/

	if( !cap.isOpened() )
	{
		cout << "open failed\n";
	}

	namedWindow( "Histogram", 0 );
	namedWindow( "CamShift Demo", 0 );

	setMouseCallback( "CamShift Demo", onMouse, 0 ); /*bind callback function for window*/

	/*Create trackbar on the window*/
	createTrackbar( "Vmin", "CamShift Demo", &vmin, 256, 0 );
	createTrackbar( "Vmax", "CamShift Demo", &vmax, 256, 0 );
	createTrackbar( "Smin", "CamShift Demo", &smin, 256, 0 );

        /*用hsv中hue分量跟踪*/
	Mat frame, hsv, hue, mask, hist, histimg = Mat::zeros(200, 320, CV_8UC3), backproj;

	bool paused = false;

	for(;;)
	{
		if( !paused )
		{
			cap >> frame; /*capture a frame image and save to frame*/
			if( frame.empty() )
			  break;
		}

		frame.copyTo(image);

		if( !paused )
		{
			cvtColor(image, hsv, COLOR_BGR2HSV); /*image from rgb to hsv */

			/*click left key:-1, ctrl-c:0, original value:0*/
			if( trackObject )
			{
				int _vmin = vmin, _vmax = vmax;

				/*制作掩模只出来range h(0,180),s(smin,256), v(MIN(_vmin,_vmax), MAX(vmin,vmax))*/
				inRange(hsv, Scalar(0, smin, MIN(_vmin,_vmax)),
							Scalar(180, 256, MAX(_vmin, _vmax)), mask);

				int ch[] = {0, 0};

				/*init hue by hsv*/
				hue.create(hsv.size(), hsv.depth());
                /*copy hsv value to hue in the channel 1*/
				mixChannels(&hsv, 1, &hue, 1, ch, 1);

				if( trackObject < 0 ) /*once object is selected by rect box*/
				{
					/*此处的构造函数roi用的是Mat hue的矩阵头,且roi的数据指针指向hue,即共用相同的数据,select为其感兴趣的区域*/
					Mat roi(hue, selection), maskroi(mask, selection);
					/*calcHist()函数第一个参数为输入矩阵序列,第2个参数表示输入的矩阵数目,第3个参数表示将被计算直方
					 *图维数通道的列表,第4个参数表示可选的掩码函数,第5个参数表示输出直方图,
					 * 第6个参数表示直方图的维数,第7个参数为每一维直方图数组的大小,第8个参数为每一维直方图bin的边界
					 * 将roi的0通道计算直方图并通过mask放入hist中,hsize为每一维直方图的大小*/
					calcHist(&roi, 1, 0, maskroi, hist, 1, &hsize, &phranges);

					/*将hist矩阵进行数组范围归一化,都归一化到0~255*/
					normalize(hist, hist, 0, 255, CV_MINMAX);
					trackWindow = selection;
                    /*只要鼠标选完区域松开后,且没有按键盘清0键'c'
					 *,则trackObject一直保持为1,因此该if函数只能执行一次,除非重新选择跟踪区域*/
					trackObject = 1;
                    /*与按下'c'键是一样的,这里的all(0)表示的是标量全部清0*/
					histimg = Scalar::all(0);
					/*histimg是一个200*300的矩阵,hsize应该是每一个bin的宽度,也就是histimg矩阵能分出几个bin出来*/
					int binW = histimg.cols / hsize;
					/*定义一个缓冲单bin矩阵*/
					Mat buf(1, hsize, CV_8UC3);
                    /*saturate_cast函数为从一个初始类型准确变换到另一个初始类型,Vec3b为3个char值向量*/
					for( int i = 0; i < hsize; i++ )
					  buf.at(i) = Vec3b(saturate_cast(i*180./hsize), 255, 255);
					/*hsv to rgb*/
					cvtColor(buf, buf, CV_HSV2BGR);
                                        /*画直方图到图像空间*/
                                        for( int i = 0; i < hsize; i++ )
					{
						int val = saturate_cast(hist.at(i)*histimg.rows/255);
						rectangle( histimg, Point(i*binW,histimg.rows),
									Point((i+1)*binW,histimg.rows - val),
									Scalar(buf.at(i)), -1, 8 );
					}
				}
                                /*计算直方图的反向投影,计算hue图像0通道直方图hist的反向投影,并入backproj中*/
				calcBackProject(&hue, 1, 0, hist, backproj, &phranges);
                                backproj &= mask;/*得到掩模内反向投影*/
                                 /*用camshift算法搜索backproj内容,返回搜索结果*/
                                 RotatedRect trackBox = CamShift(backproj, trackWindow,TermCriteria( CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 10, 1 ));
				if( trackWindow.area() <= 1 )
				{
					int cols = backproj.cols, rows = backproj.rows, r = (MIN(cols, rows) + 5)/6;
					trackWindow = Rect(trackWindow.x - r, trackWindow.y - r,
								trackWindow.x + r, trackWindow.y + r) &
						Rect(0, 0, cols, rows);
				}
				if( backprojMode )
				  cvtColor( backproj, image, COLOR_GRAY2BGR );

                                /*跟踪的时候以椭圆为代表目标,画出跟踪目标*/
				ellipse( image, trackBox, Scalar(0,0,255), 3, CV_AA );
			}
		}
		else if( trackObject < 0 )
		  paused = false;

		if( selectObject && selection.width > 0 && selection.height > 0 )
		{
			Mat roi(image, selection);
			bitwise_not(roi, roi); /*bitwise_not为将每一个bit位取反*/
		}

		imshow( "CamShift Demo", image );
		imshow( "Histogram", histimg );
		char c = (char)waitKey(10);

		if( c == 27 )
		  break;

		switch(c)
		{
			case 'b':
				backprojMode = !backprojMode;
				break;
			case 'c':
				trackObject = 0;
				histimg = Scalar::all(0);
				break;
			case 'h':
				showHist = !showHist;
				if( !showHist )
				  destroyWindow( "Histogram" );
				else
				  namedWindow( "Histogram", 1 );
				break;
			case 'p':
				paused = !paused;
				break;
			default:
				;
		}
	}
	return 0;
}

meanShift算法的声明:int meanShift(InputArray probImage, Rect& window, TermCriteriacriteria)

   与CamShift函数不同的一点是,它返回的不是一个矩形框,而是一个int型变量。该int型变量应该是代表找到目标物体的个数。特别需要注意的是参数window,它不仅是目标物体初始化的位置,还是实时跟踪目标后的位置,所以其实也是一个返回值。由于meanShift好像主要不是用于目标跟踪上,很多应用是在图像分割上。

接口解释:

createTrackbar滑动条

CV_EXPORTS int createTrackbar( const string& trackbarname, const string& winname,
                               int* value, int count,
                               TrackbarCallback onChange=0,
                               void* userdata=0);

前两个参数分别指定了滑动条的名字以及滑动条附属窗口的名字。当滑动条被创建后,滑动条会被创建在窗口的顶部或者底部。另外,滑动条不会遮挡窗口中的图像。

随后的两个为value,它是一个整数指针,当滑动条被拖动时,opencv会自动将当前位置所代表的值创递给指针指向的整数;另外一个参数count是一个整数数值,为滑动条所能表示的最大值。

最后一个参数是一个指向回调函数的指针,当滑动条被拖动时,回调函数会自动被调用。这跟鼠标事件的回调函数实现类似。回调函数必须为CvTrackbarCallback格式,定义如下:

void (*callback)(int position)

这个回调函数不是必须的,所以如果不需要一个回调函数,可以将参数设置为NULL,没有回调函数,当滑动条被拖动时,唯一的影响就是改变指针value所指向的整数值。

highgui还提供了两个函数分别用来读取与设置滑动条的value值,不过前提是必须知道滑动条的名字。

int cvGetTrackbarPos(const char* trackbar_name,const char * window_name);

void cvSetTrackbarPos(const char* trackbar_name, const char * window_name,int pos);


原文链接:http://blog.csdn.net/ss19890125/article/details/2266381

你可能感兴趣的:(OpenCV)