前面有几篇博文介绍了在单张图像上的Mean Shift 迭代收敛过程,接下来将要为大家分享的是基于摄像头获取视频的视频目标跟踪,希望对大家有帮助!\(^o^)/~

基于核函数加权直方图的Mean Shift目标跟踪 主要包括三点内容:

一、以核函数加权直方图作为目标模型

二、用巴氏系数度量目标直方图和候选直方图的相似性,巴氏系数越大,越相似。

三、使用Mean Shift来最大化巴氏系数。

关于上面三点的理论分析可以参考我的博文

均值漂移原理 与 巴氏系数最大化和均值漂移的关系

使用Mean Shift迭代,要收敛的话有个前条件就是起始搜索位置要和当前帧上目标的真实位置有重叠,否则不收敛。也就是说当前帧上的初始搜索点在上一帧位置的邻域内。

下面请看程序,该程序基于二维颜色直方图分布,就是在红绿蓝三个通道中任选两个通道统计直方图(猪似的很详细):

  1. // KernalBasedMeanShiftTracking_2.cpp : 定义控制台应用程序的入口点。

  2. //

  3. #include "stdafx.h"

  4. #include

  5. #include

  6. #include

  7. using namespace std;

  8. using namespace cv;

  9. Mat image;

  10. bool selectObject = false; //selectObject的初始值为false,

  11. int trackObject = 0; //trackObject的初值是0

  12. bool showTrace = true; //是否显示MeanShift迭代收敛轨迹

  13. Point origin;

  14. Rect selection;

  15. //设定直方图中bin的数目

  16. int histSize[] = {16,16};

  17. vector

  18. select_channels(2);

  19. //设定取值范围

  20. float range[] = {0,256};//灰度特征的范围都是[0,256)

  21. const float* histRange[] = {range,range};

  22. bool uniform = true; //均匀分布,

  23. bool accumu = false;//无累加

  24. //定义一个核函数指针,该函数指针可以指向任意一个标准的核函数

  25. typedef float (*KernalFunc)(Point2f& center,Point2f& xy,int hx,int hy);

  26. int weight_method = 0; //选择核函数 ==0:Epanechnikov;==1:Gaussian

  27. KernalFunc DeriveFuncOfKernal ;//声明一个指向核函数的导函数的函数指针变量

  28. //用鼠标选择目标图像区域

  29. static void onMouse( int event, int x, int y, int, void* );

  30. //该函数用于计算给定矩形图像块的加权直方图

  31. static void CalculateWeightedHistogram(const Mat& img,vector

  32. & channels,Mat& _hist,int weighted_method); //该函数用于计算给定矩形图像块的非加权直方图 static void CalculateHistogram(const Mat& img , Mat& _hist); //自己写的计算图像一维直方图的函数 void myCalcHist2D( const Mat& image, vector

  33. & channels,OutputArray _hist, int dims, const int* histSize,const float** ranges); //计算图像一维加权直方图的函数 static void myCalcWeightedHist2D( const Mat& image, vector

  34. & channels,OutputArray _hist, int dims, const int* histSize,const float** ranges,KernalFunc kernal); /*核函数:Epanechnikov Kernal*/ static float EpanechnikovKernalFunc(Point2f& center,Point2f& xy,int hx,int hy); /*核函数的负导数:Derivation of Epanechnikov Kernal*/ static float DerivationOfEpanechnikovKernal(Point2f& center,Point2f& xy,int hx,int hy); /*核函数:Gaussian Kernal */ static float GaussianKernalFunc(Point2f& center,Point2f& xy,int hx,int hy); /*核函数的负导数:Derivation of Gaussian Kernal*/ static float DerivationOfGaussianKernal(Point2f& center,Point2f& xy,int hx,int hy); //计算两个直方图的巴式距离 static float ComputeBhattacharyyaDistance2D(const Mat& hist1 ,const Mat& hist2 ); //根据当前搜索框下的图像块,加权直方图以及目标模板直方图计算更新权值矩阵 static void UpdateWeightsMatrix2D(Mat& image,Mat& modal_hist,Mat& cur_hist,int* histSize, const float** ranges,OutputArray weights_matrix); //根据均值向量漂移到新的位置(公式26) static void ShiftNewLocationByMeanVector2D(Mat& weights_matrix,KernalFunc DeriveFuncOfKernal, Point& leftup,Point2f& old_center,Point2f& new_center); //自己编写的Mean Shift算法,isJudgeOverShift参数用于控制是否在迭代过程中判断漂移冲过了头; //最后两个参数用来保存迭代路径,用于显示搜索窗口的运动和记录窗口的巴氏系数 int MyMeanShift2D(Mat& image, Mat& modal_hist, Rect& initialWindow,TermCriteria criteria , bool isJudgeOverShift,vector

  35. & iterRects ,vector

  36. & iterBhattaCoeff); int _tmain(int argc, _TCHAR* argv[]) { VideoCapture cap; //声明一个读取视频流的类VideoCapture的对象 Rect trackWindow; //跟踪窗口 cap.open(1); //调用VideoCapture的对象的open函数打开摄像头 if( !cap.isOpened() )//判断相机是否被正常打开 { cout << "***无法初始化视频读入流...***\n"; cout << "当前命令行参数值: \n"; return -1; } namedWindow( "MeanShift Demo", 1 );//创建显示跟踪过程的窗口 setMouseCallback( "MeanShift Demo", onMouse, 0 );//为窗口添加鼠标响应函数,用于选择跟踪目标 //在大循环外预先声明重复使用的变量 Mat frame, colorFrame,modal_hist,ms_hist; vector

  37. IterRects;vector

  38. IterBhattaCoeff; //记录MeanShift迭代过程的数组 bool paused = false; //暂停标志 /**********控制算法运行行为的主要参数*******************************************************/ int MaxIterNum = 50; //该参数用于控制Mean Shift的最大迭代次数 bool isJudgeOverShift = true;//用于Mean Shift迭代过程中判断是否冲过头的标志 weight_method = 0; //选择加权核函数 ==0的话选择 Epanechnikov kernal;==1选择Gaussian kernal //选择特征通道 select_channels[0] = 0; select_channels[1] = 2; //选择blue和red通道构建直方图 TermCriteria criteria( CV_TERMCRIT_EPS | CV_TERMCRIT_ITER,MaxIterNum,1 );//MeanShift算法的迭代收敛条件 /**********控制算法运行行为的主要参数*******************************************************/ /*进入循环,处理视频图像序列,跟踪目标*/ for(;;) { if( !paused ) //如果没有暂停,则继续读入下一帧图像 { cap >> frame; //cap对象将从摄像头读入的原始图像保存在colorFrame中 if( frame.empty() ) //判断读入的图像数据是否为空 break; } frame.copyTo(image);//将读入的图像拷贝到image中,深度拷贝 if(!paused) //判断是否暂停处理 { frame.copyTo(colorFrame);//将读入的图像拷贝到image中,深度拷贝 if( trackObject )//直到选择了目标,trackObject才不为0,该if语句体才会执行 { if( trackObject < 0 ) //如果trackObject小于0,就表示刚刚选定目标, { //该if语句段就是在鼠标给出的矩形框中提取目标的特征直方图,显示直方图 //取出灰度图像和mask图像中的感兴趣区域 Mat roi_img(colorFrame, selection); //计算选中的roi图像块的加权直方图 CalculateWeightedHistogram(roi_img,select_channels,modal_hist,weight_method); //在第一帧中,跟踪窗口就是有鼠标指定的selection矩形区域 trackWindow = selection; trackObject = 1; //跟踪标志置1,所以下一次循环,该段if语句就不会执行了 }//该if语句只在第一帧选中目标后被执行一次,用于计算目标特征直方图hist和绘制histimg图像 //调用MeanShift算法搜索roi图像块 int IterNum = MyMeanShift2D(colorFrame,modal_hist,trackWindow,criteria, isJudgeOverShift,IterRects,IterBhattaCoeff); //MeanShift给出的最后目标位置框 trackWindow = IterRects[IterRects.size()-1];//继续作为下一帧的初始搜索位置 //在显示图像上绘制当前帧的跟踪结果 rectangle(image,trackWindow, Scalar(0,255,0),2); //将MeanShift收敛过程绘制到当前帧上 cout<<"当前帧所用迭代次数: "<

  39. <

  40. 0 && selection.height > 0 ) { Mat roi_img(image, selection); bitwise_not(roi_img, roi_img); } //显示当前要处理的图像 imshow( "MeanShift Demo", image ); char c = (char)waitKey(5);//每处理完一帧图像,等待10毫秒 if( c == 27 ) //按ESC键退出程序 break; switch(c) { case 't'://显示迭代轨迹 showTrace = !showTrace; break; case 'c': //停止跟踪 trackObject = 0; break; case 'p': //是否暂停 paused = !paused; break; default: ; } } return 0; } static void onMouse( int event, int x, int y, int, void* ) { if( selectObject ) //鼠标左键被按下后,该段语句开始执行 { //按住左键拖动鼠标的时候,该鼠标响应函数 //会被不断的触发,不断计算目标矩形的窗口 selection.x = MIN(x, origin.x); selection.y = MIN(y, origin.y); selection.width = std::abs(x - origin.x); selection.height = std::abs(y - origin.y); selection &= Rect(0, 0, image.cols, image.rows); } switch( event ) { case CV_EVENT_LBUTTONDOWN: origin = Point(x,y); selection = Rect(x,y,0,0); selectObject = true; //当在第一帧按下鼠标左键后,被置为true,拖动鼠标,开始选择目标的矩形区域 break; case CV_EVENT_LBUTTONUP: selectObject = false;//直到鼠标左键抬起,标志着目标区域选择完毕。selectObject被置为false if( selection.width > 0 && selection.height > 0 ) trackObject = -1; //当在第一帧用鼠标选定了合适的目标跟踪窗口后,trackObject的值置为 -1 break; } } //该函数用于计算给定矩形图像块的加权直方图 static void CalculateWeightedHistogram(const Mat& img ,vector

  41. & channels, Mat& _hist,int weighted_method) { //调用自己写的函数计算选中图像块的加权直方图 KernalFunc kernal;//声明一个KernalFunc类型的函数指针变量 //根据加权参数指定具体的核函数 if(weighted_method == 0) { kernal = EpanechnikovKernalFunc; //调用Epanechnikov核函数 DeriveFuncOfKernal = DerivationOfEpanechnikovKernal;//Epanechnikov的导函数 } else if(weighted_method == 1) { kernal = GaussianKernalFunc; //调用高斯Gaussian核函数 DeriveFuncOfKernal = DerivationOfGaussianKernal;//高斯核函数的导函数 } myCalcWeightedHist2D(img,channels,_hist,2,histSize,histRange,kernal); /// 将直方图归一化到范围 [ 0, 1] normalize(_hist, _hist, 0, 1, NORM_MINMAX, -1, Mat() ); } //该函数用于计算给定矩形图像块的直方图 static void CalculateHistogram(const Mat& img,vector

  42. & channels, Mat& _hist) { //调用OpenCV函数计算选中图像块的灰度直方图 //calcHist(&img,1,0,Mat(),_hist,1,&histSize,&histRange,uniform,accumu); //调用自己写的函数计算选中图像块的灰度直方图 myCalcHist2D(img,channels,_hist,2,histSize,histRange); /// 将直方图归一化到范围 [ 0, 1] normalize(_hist, _hist, 0, 1, NORM_MINMAX, -1, Mat() ); } /** 计算给定图像的二维直方图,图像类型必须是CV_8UCx的,x = 2, or 3 or ...., channels规定了通道索引顺序,对于2D直方图,channels数组只能有2个值,且必须都 < x */ void myCalcHist2D( const Mat& image, vector

  43. & channels,OutputArray _hist, int dims, const int* histSize,const float** ranges) { int calc_channels = channels.size();//image图像中指定要被统计计算的通道的个数 int img_channel = image.channels(); //image图像的总的通道个数 if(img_channel < channels[calc_channels - 1] + 1 ) { printf("channels中的最大通道索引数不能超过images的通道数\n"); getchar(); } if(image.depth() != CV_8U) { printf("该函数仅支持CV_8U类的图像\n"); getchar(); } if(dims != calc_channels) { printf("被索引的通道数目与直方图的维数不相等\n"); getchar(); } int img_width = image.cols; //图像宽度 int img_height = image.rows;//图像高度 //参数_hist是输出参数,保存着计算好的直方图,二维直方图是histSize[0]行histSize[1]列的矩阵 _hist.create(dims, histSize, CV_32F); //为输出直方图开辟空间 Mat hist = _hist.getMat(); hist.flags = (hist.flags & ~CV_MAT_TYPE_MASK)|CV_32S;//直方图矩阵的类型信息 hist = Scalar(0.f); //清空原来的数据,当前直方图不累加到原来的直方图上去 //被统计的通道索引,对应直方图的第一维和第二维 int ch1 = channels[dims-2], ch2 = channels[dims-1];//要统计的通道编号 ==: 0 or 1 or 2 //对应于直方图的第一维的区间范围,bin个数等 float low_range1 = ranges[0][0] ,high_range1 = ranges[0][1];//要计算的直方图第一维区间的上下限 float a1 = histSize[0]/(high_range1 - low_range1);/* 单位区间内直方图bin的数目*/ float b1 = -a1*low_range1; //对应于直方图的第二维的区间范围,bin个数等 float low_range2= ranges[1][0] ,high_range2 = ranges[1][1];//要计算的直方图第二维区间的上下限 float a2 = histSize[1]/(high_range2 - low_range2);/* 单位区间内直方图bin的数目*/ float b2 = -a2*low_range2; //外循环按行从上往下扫描 for(int y=0; y < img_height ; y++ ) { //指向图像第y行的指针 const uchar* ptr_y = image.ptr

  44. (y); //内循环从左往右扫描 for(int x=0; x < img_width ; x++) { //取出输入图像的第y行第x列第ch1通道和第ch2通道的像素值 uchar val1 = ptr_y[x*img_channel + ch1]; uchar val2 = ptr_y[x*img_channel + ch2]; //计算灰度值val在输入参数规定的灰度区间内的bin序号的索引值 int idx1 = cvFloor(val1*a1 + b1);//输出直方图第一维的bin索引号 int idx2 = cvFloor(val2*a2 + b2);//输出直方图第二维的bin索引号 //第一维idx1对应于直方图矩阵的行,第二维idx2对应于直方图矩阵的列 float* hist_ptr = hist.ptr

  45. (idx1) + idx2; //指向输出直方图的第idx1行第idx2列的bin的指针 (*hist_ptr)++; //累计(idx1,idx2)位置处的对应的直方图bin上的数量 } } return; } /*核函数:Epanechnikov Kernal center: 核函数中心坐标 xy : 在核区域内的某一个点 hx,hy : 核区域在x方向和y方向的半径(或叫 带宽) */ static float EpanechnikovKernalFunc(Point2f& center,Point2f& xy,int hx,int hy) { //计算点xy到中心center的归一化距离 float distx = (xy.x - center.x)/hx; float disty = (xy.y - center.y)/hy; float dist_square = distx*distx + disty*disty;//距离平方 float result = 0.f; //函数要返回的结果 //核区域就是以hx和hy为边长的矩形的内接圆,在该内接圆中的点 //的函数值都不为0,若距离超过核区域,则返回0, //距离center越近,函数值越大 if(dist_square>=1) { result = 0.f; } else { //float Cd = CV_PI; //单位圆面积 pi*R^2 ,R==1 float Cd_inv = 0.318309886f; // == 1.0/Cd;单位圆的面积的倒数 int dimension = 2; //坐标的维数 //Epanechnikov核函数的截面断层函数:profile result = 0.5f*Cd_inv*( dimension + 2 )*( 1 - dist_square ); } return result; } /*核函数的负导数:Derivation of Epanechnikov Kernal*/ static float DerivationOfEpanechnikovKernal(Point2f& center,Point2f& xy,int hx,int hy) { //计算点xy到中心center的归一化距离 float distx = (xy.x - center.x)/hx; float disty = (xy.y - center.y)/hy; float dist_square = distx*distx + disty*disty;//距离平方 float result = 0.f; //函数要返回的结果 if(dist_square>=1) { result = 0.f; } else { //float Cd = CV_PI; //单位圆面积 pi*R^2 ,R==1 //float Cd_inv = 0.318309886f; // == 1.0/Cd;单位圆的面积的倒数 //int dimension = 2; //坐标的维数 //result = 0.5f*Cd_inv*( dimension + 2 ) ; result = 0.63661977237f; //是个常数 = 0.5f*Cd_inv*( dimension + 2 ) } return result; } /*核函数:Gaussian Kernal center: 核函数中心坐标 xy : 在核区域内的某一个点 hx,hy : 核区域在x方向和y方向的半径(或叫 带宽) */ static float GaussianKernalFunc(Point2f& center,Point2f& xy,int hx,int hy) { //计算点xy到中心center的归一化距离 float distx = (xy.x - center.x)/hx; float disty = (xy.y - center.y)/hy; float dist_square = distx*distx + disty*disty;//距离平方 float result = 0.f; //函数要返回的结果 //核区域就是以hx和hy为边长的矩形的内接圆,在该内接圆中的点 //的函数值都不为0,若距离超过核区域,则返回0, //距离center越近,函数值越大,权重越高 if(dist_square>=1) { result = 0.f; } else { float Cd = 6.2831853072f; // ==2*CV_PI float Cd_inv = 1.0f/Cd; int dimension = 2; //坐标的维数 result = Cd_inv*expf( -0.5f*dist_square ); //高斯核函数的截面断层函数:profile } return result; } /*核函数的负导数:Derivation of Gaussian Kernal*/ static float DerivationOfGaussianKernal(Point2f& center,Point2f& xy,int hx,int hy) { //计算点xy到中心center的归一化距离 float distx = (xy.x - center.x)/hx; float disty = (xy.y - center.y)/hy; float dist_square = distx*distx + disty*disty;//距离平方 float result = 0.f; //函数要返回的结果 //核区域就是以hx和hy为边长的矩形的内接圆,在该内接圆中的点 //的函数值都不为0,若距离超过核区域,则返回0, //距离center越近,函数值越大,权重越高 if(dist_square>=1) { result = 0.f; } else { float Cd = 12.566370614f; // ==4*CV_PI float Cd_inv = 1.0f/Cd; int dimension = 2; //坐标的维数 result = Cd_inv*expf( -0.5f*dist_square ); //高斯核函数的导函数 } return result; } //计算图像二维加权直方图的函数 static void myCalcWeightedHist2D( const Mat& image, vector

  46. & channels,OutputArray _hist, int dims, const int* histSize,const float** ranges,KernalFunc kernal) { int calc_channels = channels.size();//image图像中指定要被统计计算的通道的个数 int img_channel = image.channels(); //image图像的总的通道个数 if(img_channel < channels[calc_channels - 1] + 1 ) { printf("channels中的最大通道索引数不能超过images的通道数\n"); getchar(); } if(image.depth() != CV_8U) { printf("该函数仅支持CV_8U类的图像\n"); getchar(); } if(dims != calc_channels) { printf("被索引的通道数目与直方图的维数不相等\n"); getchar(); } int img_width = image.cols; //图像宽度 int img_height = image.rows;//图像高度 //图像区域的中心,即核函数中心 Point2f center(0.5f*img_width,0.5f*img_height); int hx = img_width/2 , hy = img_height/2;//核函数带宽 float NormalConst =0.f; //用于归一化加权系数 //参数_hist是输出参数,保存着计算好的直方图,二维直方图是histSize[0]行histSize[1]列的矩阵 _hist.create(dims, histSize, CV_32F); //为输出直方图开辟空间 Mat hist = _hist.getMat(); hist.flags = (hist.flags & ~CV_MAT_TYPE_MASK)|CV_32S;//直方图矩阵的类型信息 hist = Scalar(0.f); //清空原来的数据,当前直方图不累加到原来的直方图上去 //被统计的通道索引,对应直方图的第一维和第二维 int ch1 = channels[dims-2], ch2 = channels[dims-1];//要统计的通道编号 ==: 0 or 1 or 2 //对应于直方图的第一维的区间范围,bin个数等 float low_range1 = ranges[0][0] ,high_range1 = ranges[0][1];//要计算的直方图第一维区间的上下限 float a1 = histSize[0]/(high_range1 - low_range1);/* 单位区间内直方图bin的数目*/ float b1 = -a1*low_range1; //对应于直方图的第二维的区间范围,bin个数等 float low_range2= ranges[1][0] ,high_range2 = ranges[1][1];//要计算的直方图第二维区间的上下限 float a2 = histSize[1]/(high_range2 - low_range2);/* 单位区间内直方图bin的数目*/ float b2 = -a2*low_range2; //外循环按行从上往下扫描 for(int y=0; y < img_height ; y++ ) { //指向图像第y行的指针 const uchar* ptr_y = image.ptr

  47. (y); //内循环从左往右扫描 for(int x=0; x < img_width ; x++) { //取出输入图像的第y行第x列第ch1通道和第ch2通道的像素值 uchar val1 = ptr_y[x*img_channel + ch1]; uchar val2 = ptr_y[x*img_channel + ch2]; //计算灰度值val在输入参数规定的灰度区间内的bin序号的索引值 int idx1 = cvFloor(val1*a1 + b1);//输出直方图第一维的bin索引号 int idx2 = cvFloor(val2*a2 + b2);//输出直方图第二维的bin索引号 //第一维idx1对应于直方图矩阵的行,第二维idx2对应于直方图矩阵的列 float* hist_ptr = hist.ptr

  48. (idx1) + idx2; //指向输出直方图的第idx1行第idx2列的bin的指针 //(*hist_ptr)++; //累计(idx1,idx2)位置处的对应的直方图bin上的数量 float weighted_value = kernal(center,Point2f(x,y),hx,hy); //(x,y)位置处的加权值 (*hist_ptr) += weighted_value; //加权累计直方图的每一个bin NormalConst += weighted_value; //加权系数归一化因子 } } //归一化因子 float NomalConst_inv = 1.0f/(NormalConst + DBL_EPSILON); //归一化加权直方图 for(int idx1=0; idx1 < histSize[0]; idx1++) { for(int idx2=0; idx2 < histSize[1]; idx2++) { //指向输出直方图的第idx1行第idx2列的bin的指针 float* hist_ptr = hist.ptr

  49. (idx1) + idx2; *hist_ptr *= NomalConst_inv; } } return; } //计算两个二维直方图的巴式距离 static float ComputeBhattacharyyaDistance2D(const Mat& hist1 ,const Mat& hist2 ) { //判断两个直方图的维数是否一致 Size2i hist_size1 = hist1.size(); int hist_height = hist_size1.height ; //第一维的bin个数 == histSize[0] int hist_width = hist_size1.width ; //第二维的bin个数 == histSize[1] Size2i hist_size2 = hist2.size(); if((hist_width != hist_size2.width)||(hist_height != hist_size2.height)) { printf("两个直方图的维数不一致,无法计算距离!!!\n");getchar(); } float distance = 0.0f; const float* phist1 =0,*phist2 =0; for(int idx1=0; idx1 < hist_height; idx1++) { for(int idx2=0; idx2 < hist_width; idx2++) { //指向 直方图的第idx1行第idx2列的bin的指针 phist1 = hist1.ptr

  50. (idx1) + idx2; phist2 = hist2.ptr

  51. (idx1) + idx2; distance += sqrtf((*phist1)*(*phist2));//累计距离值 } } return distance; } //根据当前搜索框下的图像块,加权直方图以及目标模板直方图计算更新权值矩阵 static void UpdateWeightsMatrix2D(Mat& cur_image,vector

  52. & channels,Mat& modal_hist,Mat& cur_hist, int* histSize,const float** ranges,OutputArray weights_matrix) { if((cur_image.channels() < 2 )||(cur_image.depth() != CV_8U)) { printf("该函数只支持CV_8U类型的多通道图像\n"); getchar(); } if((histSize[0] != modal_hist.rows)||(histSize[0] != cur_hist.rows)) { printf("直方图bin数目不一样\n"); getchar(); } int img_channel = cur_image.channels(); int rows = cur_image.rows; int cols = cur_image.cols; //给权值矩阵开辟空间,权值矩阵的大小与image的大小一样,每一个权值与image的一个像素位置一一对应 weights_matrix.create(rows,cols,CV_32FC1);//创建一个rows行cols列的单通道浮点数矩阵 Mat wei_mat = weights_matrix.getMat(); //从OutputArray类型的参数获取Mat类型的矩阵头索引 //遍历图像块的每一个像素位置,找到该像素点在直方图中的bin索引号,然后求两个直方图对应bin位置处的比值 int dims = 2; //直方图维数 //被统计的通道索引,对应直方图的第一维和第二维 int ch1 = channels[dims-2], ch2 = channels[dims-1];//要统计的通道编号 ==: 0 or 1 or 2 //对应于直方图的第一维的区间范围,bin个数等 float low_range1 = ranges[0][0] ,high_range1 = ranges[0][1];//要计算的直方图第一维区间的上下限 float a1 = histSize[0]/(high_range1 - low_range1);/* 单位区间内直方图bin的数目*/ float b1 = -a1*low_range1; //对应于直方图的第二维的区间范围,bin个数等 float low_range2= ranges[1][0] ,high_range2 = ranges[1][1];//要计算的直方图第二维区间的上下限 float a2 = histSize[1]/(high_range2 - low_range2);/* 单位区间内直方图bin的数目*/ float b2 = -a2*low_range2; //按照从上往下,从左往右的顺序填充权值矩阵 for(int row =0; row < rows; row++) { //指向image的第row行的指针 uchar* curimg_ptr = cur_image.ptr

  53. (row); //指向权重矩阵第row行的指针 float* weimat_ptr = wei_mat.ptr

  54. (row); for(int col =0; col < cols; col++) { //取出输入图像的第y行第x列第ch1通道和第ch2通道的像素值 uchar val1 = curimg_ptr[col*img_channel + ch1]; uchar val2 = curimg_ptr[col*img_channel + ch2]; //计算灰度值val在输入参数规定的灰度区间内的bin序号的索引值 int idx1 = cvFloor(val1*a1 + b1);//直方图第一维的bin索引号 int idx2 = cvFloor(val2*a2 + b2);//直方图第二维的bin索引号 //取出(idx1,idx2)对应位置的直方图的bin值 float* pmodal = modal_hist.ptr

  55. (idx1) + idx2 ; float* pcur = cur_hist.ptr

  56. (idx1) + idx2 ; //第row行第col列的权重值 float weight = sqrtf( (*pmodal) / (*pcur + DBL_EPSILON) ); *(weimat_ptr + col) = weight; } } return; } //根据均值向量漂移到新的位置(公式26) static void ShiftNewLocationByMeanVector2D(Mat& weights_matrix,KernalFunc DeriveFuncOfKernal, Point& leftup,Point2f& old_center,Point2f& new_center) { //权值矩阵的行列数 int rows = weights_matrix.rows; int cols = weights_matrix.cols; //核函数的横向半径和纵向半径 int hx = cols/2 , hy = rows/2; //计算新的搜索框中心位置 float x_sum=0.f,y_sum=0.f,const_sum=0.f; int x = 0 , y = 0; for(int row =0; row < rows; row++) { //指向权重矩阵第row行的指针 float* weimat_ptr = weights_matrix.ptr

  57. (row); y = leftup.y + row; for(int col =0; col < cols; col++) { x = leftup.x + col; //当前像素点在母图像中的坐标位置 Point2f point(x,y); //调用对应核函数的导函数 float g = DeriveFuncOfKernal(old_center,point,hx,hy); //取出对应位置(row,col)上的权重值 float weight = weimat_ptr[col]; //累加分子与分母 x_sum += x*weight*g ; y_sum += y*weight*g ; const_sum += weight*g ; } } //计算新的中心点 new_center.x = cvRound(x_sum/(const_sum+DBL_EPSILON)); new_center.y = cvRound(y_sum/(const_sum+DBL_EPSILON)); } //最后两个参数用来保存迭代路径,用于显示搜索窗口的运动和记录窗口的巴氏系数 int MyMeanShift2D(Mat& image, Mat& modal_hist, Rect& initialWindow,TermCriteria criteria , bool isJudgeOverShift,vector

  58. & iterRects ,vector

  59. & iterBhattaCoeff) { int i = 0, eps; Rect cur_rect ; //当前搜索窗口 Mat cur_win; //当前搜索窗口下的图像块 Mat cur_hist; //当前搜索窗口下图像块的加权直方图 Mat cur_weights;//当前搜索窗口下的权值矩阵 //初始化迭代路径, iterRects.clear(); iterBhattaCoeff.clear(); Mat mat = image; Rect windowIn(initialWindow); if( mat.channels()<2 )//目前只支持多通道图像 CV_Error( CV_BadNumChannels, "目前只支持多通道图像" ); if( windowIn.height <= 0 || windowIn.width <= 0 ) CV_Error( CV_StsBadArg, "窗口尺寸必须大于0" ); windowIn = Rect(windowIn) & Rect(0, 0, mat.cols, mat.rows); //边界检查 //检查迭代终止条件 criteria = cvCheckTermCriteria( criteria, 1., 100 ); eps = cvRound( criteria.epsilon * criteria.epsilon ); //初始搜索框 cur_rect = windowIn; float BhattaCoeff = 0.0f; //开始MeanShift迭代过程,实际迭代次数小于等于criteria.maxCount for( i = 0; i < criteria.maxCount; i++ ) { int dx, dy, nx, ny; //当前搜索窗口的中心 Point2f cur_center(cur_rect.x +0.5f* cur_rect.width, cur_rect.y + 0.5f* cur_rect.height); //当前搜索窗口的左上角顶点坐标 Point2i cur_leftup(cur_rect.x,cur_rect.y); //从当前帧图像中获取当前搜索窗口下的图像块 cur_win = mat(cur_rect); //计算当前窗口下的图像块的加权直方图 CalculateWeightedHistogram(cur_win,select_channels,cur_hist,0); //计算当前搜索窗口下的直方图与给定的目标模板直方图的巴氏系数 BhattaCoeff = ComputeBhattacharyyaDistance2D(modal_hist,cur_hist); //根据公式25计算当前窗口下的权值矩阵, UpdateWeightsMatrix2D(cur_win,select_channels,modal_hist,cur_hist, histSize,histRange,cur_weights); //公式--25 //根据公式26计算均值漂移向量,得到新的搜索窗口的中心位置 Point2f new_center;//新的搜索窗口的中心位置 ShiftNewLocationByMeanVector2D(cur_weights,DeriveFuncOfKernal, cur_leftup,cur_center,new_center); //公式--26 //新的搜索区域的左上角坐标 nx = cvRound(new_center.x - 0.5f*cur_rect.width); ny = cvRound(new_center.y - 0.5f*cur_rect.height); //x方向的边界检查 if( nx < 0 ) nx = 0; else if( nx + cur_rect.width > mat.cols ) nx = mat.cols - cur_rect.width; //y方向的边界检查 if( ny < 0 ) ny = 0; else if( ny + cur_rect.height > mat.rows ) ny = mat.rows - cur_rect.height; //计算漂移向量(dx,dy) dx = nx - cur_rect.x; dy = ny - cur_rect.y; //记录迭代过程 iterRects.push_back(cur_rect); iterBhattaCoeff.push_back(BhattaCoeff); //形成新的搜索框,移动左上角顶点坐标,长宽不变 Rect new_rect(cur_rect); new_rect.x = nx; new_rect.y = ny; //判断均值漂移是否冲过了头 if(isJudgeOverShift) { Mat new_win = mat(new_rect); Mat new_hist; //计算当前窗口下的图像块的加权直方图 CalculateWeightedHistogram(new_win,select_channels,new_hist,weight_method); //计算当前搜索窗口下的直方图与给定的目标模板直方图的巴氏系数 float new_BhattaCoeff = ComputeBhattacharyyaDistance2D(modal_hist,new_hist); //如果新位置上的巴氏系数小于旧位置上的的巴氏系数,说明冲过了头 if(new_BhattaCoeff < BhattaCoeff) { //若果冲过了头,就把两次的位置坐标平均一下,得出新的位置 new_rect.x = cvRound(0.5f*(cur_rect.x + new_rect.x)); new_rect.y = cvRound(0.5f*(cur_rect.y + new_rect.y)); //由于调整了新窗口的位置,所以要重新计算漂移向量(dx,dy) dx = new_rect.x - cur_rect.x; dy = new_rect.y - cur_rect.y; } } /* 检查窗口的移动距离是否小于eps,若小于,则算法收敛,退出循环 */ if( dx*dx + dy*dy < eps ) { cur_rect = new_rect; //如果在没有达到最大迭代次数之前该退出条件已经满足 //则iterRects中的最后一个元素就是最终的迭代结果 iterRects.push_back(cur_rect); iterBhattaCoeff.push_back(BhattaCoeff); break; } //进入下一次迭代 cur_rect = new_rect; //这个新的搜索框在循环退出时才被加入到迭代轨迹数组中 } //如果超过最大迭代次数的时候,循环退出,把最后一次均值漂移形成的矩形框作为最终的结果 if( i == criteria.maxCount) { iterRects.push_back(cur_rect); iterBhattaCoeff.push_back(BhattaCoeff); } return iterRects.size();//返回实际使用的迭代次数(漂移次数) }

下面请看运行效果:

基于核函数加权直方图的Mean Shift目标跟踪 (二维颜色直方图)相关推荐

  1. Mean shift目标跟踪算法

    文档下载链接https://download.csdn.net/download/OEMT_301/12089925 Mean shift作为一种跟踪算法经常被用到.它是一种无参数密度估计寻找局部极值 ...

  2. 基于元学习的红外弱小点状目标跟踪算法

    基于元学习的红外弱小点状目标跟踪算法 人工智能技术与咨询 昨天 本文来自<激光技术>,作者热孜亚·艾沙等 引言 红外点状目标的跟踪是红外搜索和跟踪(infrared search and ...

  3. Python基于pyzbar、opencv、pyqt5库,实现二维码识别 gui 应用程序开发

    二维码组成结构基本介绍 二维码识别背景介绍 视觉的方法可以用来估计位置和姿态.最容易想到的是在目标上布置多个容易识别的特征,这样使用opencv相机标定和.相机畸变矫正.轮廓提取.solvepnp来获 ...

  4. Matplotlib——直方图_hist()函数_histogram()函数_二维数据

    一个简单的直方图可以直观地展示数据的分布,包括数值分布的区间.密度和形状. 在实际的工作过程中,我们可能需要对数据进行数学建模和统计分析,这些数据处理技术往往基于数据符合的某些假设,而直方图是检查数据 ...

  5. python做直方图-python OpenCV学习笔记实现二维直方图

    本文介绍了python OpenCV学习笔记实现二维直方图,分享给大家,具体如下: 官方文档 – https://docs.opencv.org/3.4.0/dd/d0d/tutorial_py_2d ...

  6. 【机器学习】基于PCA/LDA的数据降维和可视化(二维+三维)

    基于PCA/LDA的数据降维和可视化 Introduction Project Intro File Intro Tools Intro Code&Dataset Link Process P ...

  7. 【opencv】直方图-3:二维直方图(一维灰度强度,二维色相饱和度,)

    4_10_3_直方图3:二维直方图 - OpenCV中文官方文档 学习查找和绘制2D直方图. 前情提要 一维直方图仅考虑一个特征,即像素的灰度强度值. 二维直方图要考虑两个特征. 通常,它用于查找颜色 ...

  8. 基于Opencv和STM32物理鼠标的目标跟踪器

    本文需要使用STM32最小系统板[3]实现一个带有串口的HID USB鼠标,实现STM32可以控制电脑鼠标的移动的功能函数,然后在上位机电脑中调用WIN32 API对整个电脑的桌面进行截图,再把获得的 ...

  9. Python画图(直方图、多张子图、二维图形、三维图形以及图中图)

    Python画图很方便,不管是平时的学习还是教学当中,都将经常用到,特别直观,其中主要用到两个常用的库,一个二维和三维的:matplotlib.pyplot,mpl_toolkits.mplot3d ...

最新文章

  1. Linux 内核环境搭建花絮
  2. 拥抱开放计算标准 重构数据中心格局
  3. python set并集update_Python中集合set()的使用及处理
  4. js 中断函数执行_js如何中断递归函数
  5. 微软Power BI 每月功能更新系列——3月Power BI 新功能学习
  6. E-R图练习(邮件客户端系统)
  7. WinRAR 4.01 简体中文版 [0530]
  8. 基于计算机视觉原理的自主足球机器人位置规划,基于计算机视觉原理的自主足球机器人位置规划...
  9. 艾敦制表神器实现EXCEL邮件合并
  10. 利用Android源码,轻松实现汉字转拼音功能
  11. moment 的使用 当月第一天
  12. 推荐一款STM32F030K6T6兼容替换灵动MM32F031K6T6
  13. IDEA做数据库操作时的一个莫名其妙的错误
  14. [郭德纲]挤兑死人不带脏字
  15. 关于计算机科学与技术学科和软件工程学科的区别
  16. JS--数据类型--渡一教育(视频笔记)
  17. nodejs+IIS+WebMatrix
  18. 戴尔灵越7000笔记本开机吱吱响,解决办法
  19. linux java桌面环境_Linux桌面环境玩转BT(转)
  20. WPF Visifire使用

热门文章

  1. 上交计算机考研科目,2020上海交通大学计算机考研大纲
  2. TeamTalk Netlib详解
  3. Java中类对象为空是什么意思?
  4. CGLIb 创建代理
  5. kali的网络设置及三种网络模式
  6. 目标检测的国内外研究现状
  7. Java设计模式入门
  8. 支付服务代码设计(策略模式,可扩展,接入方便)
  9. MySQL 获取所有库名、表名、字段名
  10. 魔改《合成大西瓜》——附试玩链接