基于MeanShift算法的目标跟踪解析
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
基于MeanShift算法的目标跟踪
1 算法描述
1.1 meanshift算法背景
meanShift这个概念最早是由Fukunage在1975年提出的,Fukunage等人在一篇关于概率密度梯度函数的估计中提出这一概念。
其最初的含义正如其名:偏移的均值向量;但随着理论的发展,meanShift的含义已经发生了很多变化。
如今,我们说的meanShift算法,一般是指一个迭代的步骤,即先算出当前点的偏移均值,然后以此为新的起始点,继续移动,直到满足一定的结束条件。
在很长一段时间内,meanShift算法都没有得到足够的重视,直到1995年另一篇重要论文的发表。
该论文的作者Yizong Cheng定义了一族核函数,使得随着样本与被偏移点的距离不同,其偏移量对均值偏移向量的贡献也不同。
其次,他还设定了一个权重系数,使得不同样本点的重要性不一样,这大大扩展了meanShift的应用范围。
此外,还有研究人员将非刚体的跟踪问题近似为一个meanShift的最优化问题,使得跟踪可以实时进行。
目前,利用meanShift进行跟踪已经相当成熟。
1.2 meanshift算法原理
Meanshift可以应用在很多领域,比如聚类,图像平滑,图像分割,还在目标跟踪领域有重要的应用。
Meanshift跟踪算法是通过计算候选目标与目标模板之间相似度的概率密度分布,然后利用概率密度梯度下降的方向来获取匹配搜索的最佳路径,加速运动目标的定位和降低搜索的时间,因此其在目标实时跟踪领域有着很高的应用价值。
该算法由于采用了统计特征,因此对噪声具有很好的鲁棒性;由于是一个蛋参数算法,容易作为一个模块和其他算法集成;采用核函数直方图建模,对边缘阻挡、目标的旋转、变形以及背景运动都不敏感;同时该算法构造了一个可以用meanshift算法进行寻优的相似度函数。
Meanshift本质上是最陡下降法,因此其求解过程收敛速度快,使得该算法具有很好的实用性。
Meanshift算法的思想是利用概率密度的梯度爬升来寻找局部最优。
它要做的就是输入一个在图像的范围,然后一直朝着重心迭代,直到满足你的要求或者达到迭代最大次数结束。
在opencv中,进行meanshift其实很简单,输入一张图像(imgProb),再输入一个开始迭代的方框(windowIn)和一个迭代条件(criteria),输出的是迭代完成的位置(comp )。
这是函数原型:
int cvMeanShift( const void* imgProb, CvRect windowIn,
CvTermCriteria criteria, CvConnectedComp* comp )
参数介绍:
imgProb:目标直方图的反向投影
windowIn:初试的搜索框
Criteria:确定搜索窗口的终止条件
Comp:生成的结构,包含收敛的搜索窗口坐标(comp->rect 字段) 与窗口内部所有像素的和(comp->area 字段)
1.3 meanshift算法跟踪目标实现步骤
1.首先从摄像头或者本地文件读入视频
2.选择要跟踪的物体,程序读取一帧视频
3.计算视频帧的色调直方图
4.计算视频帧的反向投影图
5.输入反向投影图和跟踪矩形框,调用meanshift算法迭代,寻找局部最优解。
根据重心的移动,调整跟踪矩形框
6.读取下一帧视频,用当前矩形框作为输入,重复执行步骤2-5
1.4 meanshift算法实现过程
1 在颜色概率分布图中选取搜索窗W
2 计算零阶距:
计算一阶距:
计算搜索窗的质心:
3 调整搜索窗大小
宽度为
长度为1.2s
4 移动搜索窗的中心到质心,如果移动距离大于预设的固定阈值,则重复2)3)4),直到搜索窗的中心与质心间的移动距离小于预设的固定阈值,或者循环运算的次数达到某一最大值,停止计算。
1.5 meanshift算法跟踪效果
使用摄像头跟踪人的肤色效果如图1.5.1所示。
图 1.5.1 对应于图1.5.1的反向投影图如下图1.5.2所示。
图 1.5.2
点选区域的像素点直方图如图1.5.3所示。
图 1.5.3
从以上效果图可以发现,meanshift算法处理的是HSV中的色调分量。
换句话说,就是通过追踪相同的颜色,而达到追踪物体的功能。
程序首先计算点选框里的有效像素点,通过统计获得像素点分布直方图;然后计算出视频的反向投影图,即是像素点的概率分布图。
如上图1.5.2所示,越亮的点就是与原物体越匹配的点,大量的亮点的聚集处就极有可能是需要跟踪的物体;最后用矩形框框住当前帧中带跟踪的物体,如上图1.5.1所示的结果。
当被跟踪的物体的色调与背景相似时,跟踪就会失效,如图1.5.4所示。
图 1.5.4
显然,当背景与被跟踪物体颜色难以区分时跟踪会失败。
原因很简单,meanshift算法实现跟踪的原理就是通过计算色调的概率分布达到跟踪的效果,所以待跟踪的物体一定要与背景在色调上有区分度。
类似的,本程序也可以通过设置:调试->命令参数,使用本地的视频文件来实现视频中特定物体的跟踪,具体方法如图1.5.5所示。
由于实验的效果与使用摄像头跟踪物体差不多,所以就不一一截图显示了。
图 1.5.5
2 程序源码与数据结构
#include<stdlib.h>
#include"stdafx.h"
#ifdef _CH_
#pragma package <opencv>
#endif
#ifndef _EiC
#include<stdio.h>
#include"cv.h"
#include"highgui.h"
#include<ctype.h>
#endif
IplImage *image = 0, *hsv = 0, *hue = 0, *mask = 0, *backproject = 0, *histimg = 0; //用HSV中的Hue分量进行跟踪
CvHistogram *hist = 0;
//直方图类
int backproject_mode = 0;
int select_object = 0;
int track_object = 0;
int show_hist = 1;
CvPoint origin;
CvRect selection;
CvRect track_window;
CvBox2D track_box;
//Meanshift跟踪算法返回的Box类
//typedef struct CvBox2D{
//CvPoint2D32f center; /* 盒子的中心*/
//CvSize2D32f size; /* 盒子的长和宽*/
//float angle; /* 水平轴与第一个边的夹角,用弧度表示*/
//}CvBox2D;
CvConnectedComp track_comp;
//连接部件
//typedef struct CvConnectedComp{
//double area; /* 连通域的面积*/
//float value; /* 分割域的灰度缩放值*/
//CvRect rect; /* 分割域的ROI */
//} CvConnectedComp;
int hdims = 32;
//划分直方图bins的个数,越多越精确
float hranges_arr[] = {0,180};
//像素值的范围
float* hranges = hranges_arr;
//用于初始化CvHistogram类
int vmin = 10, vmax = 256, smin = 30;
//用于设置滑动条
int cvmeanshift(const void *imgProb,CvRect windowIn, CvTermCriteria criteria, CvConnectedComp* comp) //实现meanshift算法
{
CvMoments moments; //CvMoments用来计算矩形的重心,面积等形状特征
int i=0,eps;
CvMat stub, *mat=(CvMat*)imgProb;
CvMat cur_win;
CvRect cur_rect=windowIn; //当前矩形框
CV_FUNCNAME("cvmeanshift");
if(comp) //初始化跟踪矩形
{
comp->rect=windowIn;
}
moments.m00=moments.m01 =moments.m10 =0; //将阶矩和阶矩置
__BEGIN__;
CV_CALL(mat=cvGetMat(mat,&stub));
if( CV_MA T_CN( mat->type ) > 1 ) //通道数选择出错
{
CV_ERROR( CV_BadNumChannels, cvUnsupportedFormat );
}
if( windowIn.height <= 0 || windowIn.width <= 0 ) //点选的窗口无效
{
CV_ERROR( CV_StsBadArg, "Input window has non-positive sizes" );
}
if( windowIn.x < 0 || windowIn.x + windowIn.width > mat->cols ||windowIn.y < 0 || windowIn.y + windowIn.height > mat->rows )
{
CV_ERROR( CV_StsBadArg, "Initial window is not inside the image ROI" );
}
CV_CALL( criteria = cvCheckTermCriteria( criteria, 1., 100 )); //迭代的标准,精度=1.0,迭代次数=100
eps = cvRound( criteria.epsilon * criteria.epsilon ); //精度eps=1
for( i = 0; i < criteria.max_iter; i++ )
{
int dx, dy, nx, ny;
double inv_m00;
//选取搜索区域,对该矩形区域计算它的,1阶矩
CV_CALL( cvGetSubRect( mat, &cur_win, cur_rect ));
CV_CALL( cvMoments( &cur_win, &moments ));
/* Calculating center of mass */
if( fabs(moments.m00) < DBL_EPSILON )
{
break;
}
//搜索区域的质量m00
inv_m00 = moments.inv_sqrt_m00*moments.inv_sqrt_m00;
//搜索区域的水平重心偏移dx
dx = cvRound( moments.m10 * inv_m00 - windowIn.width*0.5 ); //搜索区域的垂直重心偏移dy
dy = cvRound( moments.m01 * inv_m00 - windowIn.height*0.5 ); //搜索区域的重心坐标(nx,ny)
nx = cur_rect.x + dx;
ny = cur_rect.y + dy;
//跟踪目标处于图像边缘时进行一些对应的处理
if( nx < 0 )
{
nx = 0;
}
else if( nx + cur_rect.width > mat->cols )
{
nx = mat->cols - cur_rect.width;
}
if( ny < 0 )
{
ny = 0;
}
else if( ny + cur_rect.height > mat->rows )
{
ny = mat->rows - cur_rect.height;
}
dx = nx - cur_rect.x;
dy = ny - cur_rect.y;
cur_rect.x = nx;
cur_rect.y = ny;
/* Check for coverage centers mass & window */
//精度达到要求时就可以退出循环
if( dx*dx + dy*dy < eps )
{
break;
}
}
__END__;
if(comp)
{
comp->rect=cur_rect;
comp->area=(float)moments.m00; //图像面积}
return i;
}
void on_mouse( int event, int x, int y, int flags, void* param )
//鼠标回调函数,该函数用鼠标进行跟踪目标的选择
{
if( !image )
return;
if( image->origin )
y = image->height - y;
//如果图像原点坐标在左下,则将其改为左上
if( select_object )
//select_object为,表示在用鼠标进行目标选择
//此时对矩形类selection用当前的鼠标位置进行设置
{
selection.x = MIN(x,origin.x);
selection.y = MIN(y,origin.y);
selection.width = selection.x + CV_IABS(x - origin.x);
selection.height = selection.y + CV_IABS(y - origin.y);
selection.x = MAX( selection.x, 0 );
selection.y = MAX( selection.y, 0 );
selection.width = MIN( selection.width, image->width );
selection.height = MIN( selection.height, image->height );
selection.width -= selection.x;
selection.height -= selection.y;
}
switch( event )
{
case CV_EVENT_LBUTTONDOWN:
//鼠标按下,开始点击选择跟踪物体
origin = cvPoint(x,y);
selection = cvRect(x,y,0,0);
select_object = 1;
break;
case CV_EVENT_LBUTTONUP:
//鼠标松开,完成选择跟踪物体
select_object = 0;
if( selection.width > 0 && selection.height > 0 )
//如果选择物体有效,则打开跟踪功能
track_object = -1;
break;
}
}
CvScalar hsv2rgb( float hue )
//用于将Hue量转换成RGB量
{
int rgb[3], p, sector;
static const int sector_data[][3]=
{{0,2,1}, {1,2,0}, {1,0,2}, {2,0,1}, {2,1,0}, {0,1,2}};
hue *= 0.033333333333333333333333333333333f;
sector = cvFloor(hue);
p = cvRound(255*(hue - sector));
p ^= sector & 1 ? 255 : 0;
rgb[sector_data[sector][0]] = 255;
rgb[sector_data[sector][1]] = 0;
rgb[sector_data[sector][2]] = p;
return cvScalar(rgb[2], rgb[1], rgb[0],0);
}
int main( int argc, char** argv )
{
CvCapture* capture = 0;
if( argc == 1 || (argc == 2 && strlen(argv[1]) == 1 && isdigit(argv[1][0]))) //打开摄像头
capture = cvCaptureFromCAM( argc == 2 ? argv[1][0] - '0' : 0 );
else if( argc == 2 )
//打开avi
capture = cvCaptureFromA VI( argv[1] );
if( !capture )
//打开视频流失败
{
fprintf(stderr,"Could not initialize capturing...\n");
return -1;
}
printf( "Hot keys: \n"
"\tESC - quit the program\n"
"\tc - stop the tracking\n"
"\tb - switch to/from backprojection view\n"
"\th - show/hide object histogram\n"
"To initialize tracking, select the object with mouse\n" );
//打印程序功能列表
cvNamedWindow( "Histogram", 1 );
//用于显示直方图
cvNamedWindow( "MeanShiftDemo", 1 );
//用于显示视频
cvSetMouseCallback( "MeanShiftDemo", on_mouse, 0 );
//设置鼠标回调函数
cvCreateTrackbar( "Vmin", "MeanShiftDemo", &vmin, 256, 0 ); cvCreateTrackbar( "Vmax", "MeanShiftDemo", &vmax, 256, 0 ); cvCreateTrackbar( "Smin", "MeanShiftDemo", &smin, 256, 0 );
//设置滑动条
for(;;)
//进入视频帧处理主循环
{
IplImage* frame = 0;
int i, bin_w, c;
frame = cvQueryFrame( capture ); //读取一帧图像
if( !frame )
break;
if( !image )
//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 );
//分配直方图空间
histimg = cvCreateImage( cvSize(320,200), 8, 3 );
//分配用于直方图显示的空间
cvZero( histimg );
//置背景为黑色
}
cvCopy( frame, image, 0 );
cvCvtColor( image, hsv, CV_BGR2HSV );
//把图像从RGB表色系转为HSV表色系
if( track_object )
//track_object非零,表示有需要跟踪的物体
{
int _vmin = vmin, _vmax = vmax;
cvInRangeS( hsv, cvScalar(0,smin,MIN(_vmin,_vmax),0),
cvScalar(180,256,MAX(_vmin,_vmax),0), mask );
//制作掩膜板,只处理像素值为H:~180,S:smin~256,V:vmin~vmax之间的部分cvSplit( hsv, hue, 0, 0, 0 );
//分离H分量
if( track_object < 0 )
//如果需要跟踪的物体还没有进行属性提取,则进行选取框类的图像属性提取
{
float max_val = 0.f;
cvSetImageROI( hue, selection );
//设置原选择框为ROI
cvSetImageROI( mask, selection );
//设置掩膜板选择框为ROI
cvCalcHist( &hue, hist, 0, mask );
//得到选择框内且满足掩膜板内的直方图
cvGetMinMaxHistValue( hist, 0, &max_val, 0, 0 );
cvConvertScale( hist->bins, hist->bins, max_val ? 255. / max_val : 0., 0 );
// 对直方图的数值转为~255
cvResetImageROI( hue );
//去除ROI
cvResetImageROI( mask );
//去除ROI
track_window = selection;
track_object = 1;
//置track_object为,表明属性提取完成
cvZero( histimg );
bin_w = histimg->width / hdims;
for( i = 0; i < hdims; i++ )
//画直方图到图像空间
{
int val = cvRound( cvGetReal1D(hist->bins,i)*histimg->height/255 );
CvScalar color = hsv2rgb(i*180.f/hdims);
cvRectangle( histimg, cvPoint(i*bin_w,histimg->height),
cvPoint((i+1)*bin_w,histimg->height - val),
color, -1, 8, 0 );
}
}
cvCalcBackProject( &hue, backproject, hist );
//计算hue的反向投影图
cvAnd( backproject, mask, backproject, 0 );
//得到掩膜内的反向投影
//cvCamShift( backproject, track_window,
// cvTermCriteria( CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 10, 1 ),
// &track_comp, &track_box );
cvmeanshift( backproject, track_window,
cvTermCriteria( CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 10, 1 ),
&track_comp );
//使用MeanShift算法对backproject中的内容进行搜索,返回跟踪结果
track_window = track_comp.rect;
//得到跟踪结果的矩形框
if( backproject_mode )
cvCvtColor( backproject, image, CV_GRAY2BGR );
if( image->origin )
track_box.angle = -track_box.angle;
//cvEllipseBox( image, track_box, CV_RGB(255,0,0), 3, CV_AA, 0 );
//画出跟踪结果的位置
cvRectangle(image, cvPoint(track_comp.rect.x , track_comp.rect.y),
cvPoint(track_comp.rect.x+track_comp.rect.width , track_comp.rect.y+track_comp.rect.height) , CV_RGB(255,0,0), 1, CV_AA, 0);
}
if( select_object && selection.width > 0 && selection.height > 0 )
//如果正处于物体选择,画出选择框
{
cvSetImageROI( image, selection );
cvXorS( image, cvScalarAll(255), image, 0 );
cvResetImageROI( image );
}
cvShowImage( "MeanShiftDemo", image );
cvShowImage( "Histogram", histimg );
c = cvWaitKey(100);
if( (char) c == 27 )
break;
switch( (char) c )
//按键切换功能
{
case'b':
backproject_mode ^= 1;
break;
case'c':
track_object = 0;
cvZero( histimg );
break;
case'h':
show_hist ^= 1;
if( !show_hist )
cvDestroyWindow( "Histogram" );
else
cvNamedWindow( "Histogram", 1 );
break;
default:
;
}
}
cvReleaseCapture( &capture );
cvDestroyWindow("MeanShiftDemo");
return 0;
}
#ifdef _EiC
main(1,"camshiftdemo.c");
#endif
3 实验总结
meanshift算法在跟踪中的优势:
1 算法计算量较小,在目标区域已知的情况下完全可以做到实时跟踪
2 采用核函数直方图模型,对边缘遮挡、目标旋转、变形和背景运动不敏感
3 对于慢速运动物体,同时色调与背景有很好区分度时,跟踪效果很好
同时,meanShift算法也存在着以下一些缺点:
1 缺乏必要的模板更新;
2 跟踪过程中由于窗口宽度大小保持不变,当目标尺度有所变化时,跟踪就会失败;
3 当目标速度较快时,跟踪效果不好;
4 直方图特征在目标颜色特征描述方面略显匮乏,缺少空间信息;
5 不能跟踪与背景颜色类似的物体。