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;
}
与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