基于MeanShift的目标跟踪算法及实现


这次将介绍基于MeanShift的目标跟踪算法,首先谈谈简介,然后给出算法实现流程,最后实现了一个单目标跟踪的MeanShift算法【matlab/c两个版本】

      csdn贴公式比较烦,原谅我直接截图了…

 

一、简介

     首先扯扯无参密度估计理论,无参密度估计也叫做非参数估计,属于数理统计的一个分支,和参数密度估计共同构成了概率密度估计方法。参数密度估计方法要求特征空间服从一个已知的概率密度函数,在实际的应用中这个条件很难达到。而无参数密度估计方法对先验知识要求最少,完全依靠训练数据进行估计,并且可以用于任意形状的密度估计。所以依靠无参密度估计方法,即不事先规定概率密度函数的结构形式,在某一连续点处的密度函数值可由该点邻域中的若干样本点估计得出。常用的无参密度估计方法有:直方图法、最近邻域法和核密度估计法。

     MeanShift算法正是属于核密度估计法,它不需要任何先验知识而完全依靠特征空间中样本点的计算其密度函数值。对于一组采样数据,直方图法通常把数据的值域分成若干相等的区间,数据按区间分成若干组,每组数据的个数与总参数个数的比率就是每个单元的概率值;核密度估计法的原理相似于直方图法,只是多了一个用于平滑数据的核函数。采用核函数估计法,在采样充分的情况下,能够渐进地收敛于任意的密度函数,即可以对服从任何分布的数据进行密度估计。

     然后谈谈MeanShift的基本思想及物理含义:

基于MeanShift的目标跟踪算法及实现_第1张图片

基于MeanShift的目标跟踪算法及实现_第2张图片

基于MeanShift的目标跟踪算法及实现_第3张图片

    此外,从公式1中可以看到,只要是落入Sh的采样点,无论其离中心x的远近,对最终的Mh(x)计算的贡献是一样的。然而在现实跟踪过程中,当跟踪目标出现遮挡等影响时,由于外层的像素值容易受遮挡或背景的影响,所以目标模型中心附近的像素比靠外的像素更可靠。因此,对于所有采样点,每个样本点的重要性应该是不同的,离中心点越远,其权值应该越小。故引入核函数和权重系数来提高跟踪算法的鲁棒性并增加搜索跟踪能力。

      接下来,谈谈核函数:

基于MeanShift的目标跟踪算法及实现_第4张图片

基于MeanShift的目标跟踪算法及实现_第5张图片

    核函数也叫窗口函数,在核估计中起到平滑的作用。常用的核函数有:Uniform,Epannechnikov,Gaussian等。本文算法只用到了Epannechnikov,它数序定义如下:

基于MeanShift的目标跟踪算法及实现_第6张图片

二、基于MeanShift的目标跟踪算法

     基于均值漂移的目标跟踪算法通过分别计算目标区域和候选区域内像素的特征值概率得到关于目标模型和候选模型的描述,然后利用相似函数度量初始帧目标模型和当前帧的候选模版的相似性,选择使相似函数最大的候选模型并得到关于目标模型的Meanshift向量,这个向量正是目标由初始位置向正确位置移动的向量。由于均值漂移算法的快速收敛性,通过不断迭代计算Meanshift向量,算法最终将收敛到目标的真实位置,达到跟踪的目的。

     下面通过图示直观的说明MeanShift跟踪算法的基本原理。如下图所示:目标跟踪开始于数据点xi0(空心圆点xi0,xi1,…,xiN表示的是中心点,上标表示的是的迭代次数,周围的黑色圆点表示不断移动中的窗口样本点,虚线圆圈代表的是密度估计窗口的大小)。箭头表示样本点相对于核函数中心点的漂移向量,平均的漂移向量会指向样本点最密集的方向,也就是梯度方向。因为 Meanshift 算法是收敛的,因此在当前帧中通过反复迭代搜索特征空间中样本点最密集的区域,搜索点沿着样本点密度增加的方向“漂移”到局部密度极大点点xiN,也就是被认为的目标位置,从而达到跟踪的目的,MeanShift 跟踪过程结束。

基于MeanShift的目标跟踪算法及实现_第7张图片

 

 

基于MeanShift的目标跟踪算法及实现_第8张图片

基于MeanShift的目标跟踪算法及实现_第9张图片

基于MeanShift的目标跟踪算法及实现_第10张图片

基于MeanShift的目标跟踪算法及实现_第11张图片

基于MeanShift的目标跟踪算法及实现_第12张图片

运动目标的实现过程【具体算法】:

基于MeanShift的目标跟踪算法及实现_第13张图片

 

三、代码实现

说明:

1.       RGB颜色空间刨分,采用16*16*16的直方图

2.       目标模型和候选模型的概率密度计算公式参照上文

3.       opencv版本运行:按P停止,截取目标,再按P,进行单目标跟踪

4.       Matlab版本,将视频改为图片序列,第一帧停止,手工标定目标,双击目标区域,进行单目标跟踪。

 

matlab版本:

 

[plain] view plain copy print ?
  1. function [] = select()  
  2. close all;  
  3. clear all;  
  4. %%%%%%%%%%%%%%%%%%根据一幅目标全可见的图像圈定跟踪目标%%%%%%%%%%%%%%%%%%%%%%%  
  5. I=imread('result72.jpg');  
  6. figure(1);  
  7. imshow(I);  
  8.   
  9.   
  10. [temp,rect]=imcrop(I);  
  11. [a,b,c]=size(temp);         %a:row,b:col  
  12.   
  13.   
  14. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%计算目标图像的权值矩阵%%%%%%%%%%%%%%%%%%%%%%%  
  15. y(1)=a/2;  
  16. y(2)=b/2;  
  17. tic_x=rect(1)+rect(3)/2;  
  18. tic_y=rect(2)+rect(4)/2;  
  19. m_wei=zeros(a,b);%权值矩阵  
  20. h=y(1)^2+y(2)^2 ;%带宽  
  21.   
  22.   
  23. for i=1:a  
  24.     for j=1:b  
  25.         dist=(i-y(1))^2+(j-y(2))^2;  
  26.         m_wei(i,j)=1-dist/h; %epanechnikov profile  
  27.     end  
  28. end  
  29. C=1/sum(sum(m_wei));%归一化系数  
  30.   
  31.   
  32. %计算目标权值直方图qu  
  33. %hist1=C*wei_hist(temp,m_wei,a,b);%target model  
  34. hist1=zeros(1,4096);  
  35. for i=1:a  
  36.     for j=1:b  
  37.         %rgb颜色空间量化为16*16*16 bins  
  38.         q_r=fix(double(temp(i,j,1))/16);  %fix为趋近0取整函数  
  39.         q_g=fix(double(temp(i,j,2))/16);  
  40.         q_b=fix(double(temp(i,j,3))/16);  
  41.         q_temp=q_r*256+q_g*16+q_b;            %设置每个像素点红色、绿色、蓝色分量所占比重  
  42.         hist1(q_temp+1)= hist1(q_temp+1)+m_wei(i,j);    %计算直方图统计中每个像素点占的权重  
  43.     end  
  44. end  
  45. hist1=hist1*C;  
  46. rect(3)=ceil(rect(3));  
  47. rect(4)=ceil(rect(4));  
  48.   
  49.   
  50.   
  51.   
  52. %%%%%%%%%%%%%%%%%%%%%%%%%读取序列图像  
  53. myfile=dir('D:\matlab7\work\mean shift\image\*.jpg');  
  54. lengthfile=length(myfile);  
  55.   
  56.   
  57. for l=1:lengthfile  
  58.     Im=imread(myfile(l).name);  
  59.     num=0;  
  60.     Y=[2,2];  
  61.       
  62.       
  63.     %%%%%%%mean shift迭代  
  64.     while((Y(1)^2+Y(2)^2>0.5)&num<20)   %迭代条件  
  65.         num=num+1;  
  66.         temp1=imcrop(Im,rect);  
  67.         %计算侯选区域直方图  
  68.         %hist2=C*wei_hist(temp1,m_wei,a,b);%target candidates pu  
  69.         hist2=zeros(1,4096);  
  70.         for i=1:a  
  71.             for j=1:b  
  72.                 q_r=fix(double(temp1(i,j,1))/16);  
  73.                 q_g=fix(double(temp1(i,j,2))/16);  
  74.                 q_b=fix(double(temp1(i,j,3))/16);  
  75.                 q_temp1(i,j)=q_r*256+q_g*16+q_b;  
  76.                 hist2(q_temp1(i,j)+1)= hist2(q_temp1(i,j)+1)+m_wei(i,j);  
  77.             end  
  78.         end  
  79.         hist2=hist2*C;  
  80.         figure(2);  
  81.         subplot(1,2,1);  
  82.         plot(hist2);  
  83.         hold on;  
  84.           
  85.         w=zeros(1,4096);  
  86.         for i=1:4096  
  87.             if(hist2(i)~=0) %不等于  
  88.                 w(i)=sqrt(hist1(i)/hist2(i));  
  89.             else  
  90.                 w(i)=0;  
  91.             end  
  92.         end  
  93.           
  94.           
  95.           
  96.         %变量初始化  
  97.         sum_w=0;  
  98.         xw=[0,0];  
  99.         for i=1:a;  
  100.             for j=1:b  
  101.                 sum_w=sum_w+w(uint32(q_temp1(i,j))+1);  
  102.                 xw=xw+w(uint32(q_temp1(i,j))+1)*[i-y(1)-0.5,j-y(2)-0.5];  
  103.             end  
  104.         end  
  105.         Y=xw/sum_w;  
  106.         %中心点位置更新  
  107.         rect(1)=rect(1)+Y(2);  
  108.         rect(2)=rect(2)+Y(1);  
  109.     end  
  110.       
  111.       
  112.     %%%跟踪轨迹矩阵%%%  
  113.     tic_x=[tic_x;rect(1)+rect(3)/2];  
  114.     tic_y=[tic_y;rect(2)+rect(4)/2];  
  115.       
  116.     v1=rect(1);  
  117.     v2=rect(2);  
  118.     v3=rect(3);  
  119.     v4=rect(4);  
  120.     %%%显示跟踪结果%%%  
  121.     subplot(1,2,2);  
  122.     imshow(uint8(Im));  
  123.     title('目标跟踪结果及其运动轨迹');  
  124.     hold on;  
  125.     plot([v1,v1+v3],[v2,v2],[v1,v1],[v2,v2+v4],[v1,v1+v3],[v2+v4,v2+v4],[v1+v3,v1+v3],[v2,v2+v4],'LineWidth',2,'Color','r');  
  126.     plot(tic_x,tic_y,'LineWidth',2,'Color','b');  
  127.       
  128.       
  129. end  


 运行结果:

基于MeanShift的目标跟踪算法及实现_第14张图片

 

 

 

opencv版本:

[cpp] view plain copy print ?
  1. #include "stdafx.h"  
  2. #include "cv.h"  
  3. #include "highgui.h"  
  4. #define  u_char unsigned char  
  5. #define  DIST 0.5  
  6. #define  NUM 20  
  7.   
  8. //全局变量  
  9. bool pause = false;  
  10. bool is_tracking = false;  
  11. CvRect drawing_box;  
  12. IplImage *current;  
  13. double *hist1, *hist2;  
  14. double *m_wei;                                                                  //权值矩阵  
  15. double C = 0.0;                                                                //归一化系数  
  16.   
  17. void init_target(double *hist1, double *m_wei, IplImage *current)  
  18. {  
  19.     IplImage *pic_hist = 0;  
  20.     int t_h, t_w, t_x, t_y;  
  21.     double h, dist;  
  22.     int i, j;  
  23.     int q_r, q_g, q_b, q_temp;  
  24.       
  25.     t_h = drawing_box.height;  
  26.     t_w = drawing_box.width;  
  27.     t_x = drawing_box.x;  
  28.     t_y = drawing_box.y;  
  29.   
  30.     h = pow(((double)t_w)/2,2) + pow(((double)t_h)/2,2);            //带宽  
  31.     pic_hist = cvCreateImage(cvSize(300,200),IPL_DEPTH_8U,3);     //生成直方图图像  
  32.   
  33.     //初始化权值矩阵和目标直方图  
  34.     for (i = 0;i < t_w*t_h;i++)  
  35.     {  
  36.         m_wei[i] = 0.0;  
  37.     }  
  38.   
  39.     for (i=0;i<4096;i++)  
  40.     {  
  41.         hist1[i] = 0.0;  
  42.     }  
  43.   
  44.     for (i = 0;i < t_h; i++)  
  45.     {  
  46.         for (j = 0;j < t_w; j++)  
  47.         {  
  48.             dist = pow(i - (double)t_h/2,2) + pow(j - (double)t_w/2,2);  
  49.             m_wei[i * t_w + j] = 1 - dist / h;   
  50.             //printf("%f\n",m_wei[i * t_w + j]);  
  51.             C += m_wei[i * t_w + j] ;  
  52.         }  
  53.     }  
  54.   
  55.     //计算目标权值直方  
  56.     for (i = t_y;i < t_y + t_h; i++)  
  57.     {  
  58.         for (j = t_x;j < t_x + t_w; j++)  
  59.         {  
  60.             //rgb颜色空间量化为16*16*16 bins  
  61.             q_r = ((u_char)current->imageData[i * current->widthStep + j * 3 + 2]) / 16;  
  62.             q_g = ((u_char)current->imageData[i * current->widthStep + j * 3 + 1]) / 16;  
  63.             q_b = ((u_char)current->imageData[i * current->widthStep + j * 3 + 0]) / 16;  
  64.             q_temp = q_r * 256 + q_g * 16 + q_b;  
  65.             hist1[q_temp] =  hist1[q_temp] +  m_wei[(i - t_y) * t_w + (j - t_x)] ;  
  66.         }  
  67.     }  
  68.   
  69.     //归一化直方图  
  70.     for (i=0;i<4096;i++)  
  71.     {  
  72.         hist1[i] = hist1[i] / C;  
  73.         //printf("%f\n",hist1[i]);  
  74.     }  
  75.   
  76.     //生成目标直方图  
  77.     double temp_max=0.0;  
  78.   
  79.     for (i = 0;i < 4096;i++)         //求直方图最大值,为了归一化  
  80.     {  
  81.         //printf("%f\n",val_hist[i]);  
  82.         if (temp_max < hist1[i])  
  83.         {  
  84.             temp_max = hist1[i];  
  85.         }  
  86.     }  
  87.     //画直方图  
  88.     CvPoint p1,p2;  
  89.     double bin_width=(double)pic_hist->width/4096;  
  90.     double bin_unith=(double)pic_hist->height/temp_max;  
  91.   
  92.     for (i = 0;i < 4096; i++)  
  93.     {  
  94.         p1.x = i * bin_width;  
  95.         p1.y = pic_hist->height;  
  96.         p2.x = (i + 1)*bin_width;  
  97.         p2.y = pic_hist->height - hist1[i] * bin_unith;  
  98.         //printf("%d,%d,%d,%d\n",p1.x,p1.y,p2.x,p2.y);  
  99.         cvRectangle(pic_hist,p1,p2,cvScalar(0,255,0),-1,8,0);  
  100.     }  
  101.     cvSaveImage("hist1.jpg",pic_hist);  
  102.     cvReleaseImage(&pic_hist);  
  103. }  
  104.   
  105. void MeanShift_Tracking(IplImage *current)  
  106. {  
  107.     int num = 0, i = 0, j = 0;  
  108.     int t_w = 0, t_h = 0, t_x = 0, t_y = 0;  
  109.     double *w = 0, *hist2 = 0;  
  110.     double sum_w = 0, x1 = 0, x2 = 0,y1 = 2.0, y2 = 2.0;  
  111.     int q_r, q_g, q_b;  
  112.     int *q_temp;  
  113.     IplImage *pic_hist = 0;  
  114.   
  115.     t_w = drawing_box.width;  
  116.     t_h = drawing_box.height;  
  117.       
  118.     pic_hist = cvCreateImage(cvSize(300,200),IPL_DEPTH_8U,3);     //生成直方图图像  
  119.     hist2 = (double *)malloc(sizeof(double)*4096);  
  120.     w = (double *)malloc(sizeof(double)*4096);  
  121.     q_temp = (int *)malloc(sizeof(int)*t_w*t_h);  
  122.   
  123.     while ((pow(y2,2) + pow(y1,2) > 0.5)&& (num < NUM))  
  124.     {  
  125.         num++;  
  126.         t_x = drawing_box.x;  
  127.         t_y = drawing_box.y;  
  128.         memset(q_temp,0,sizeof(int)*t_w*t_h);  
  129.         for (i = 0;i<4096;i++)  
  130.         {  
  131.             w[i] = 0.0;  
  132.             hist2[i] = 0.0;  
  133.         }  
  134.   
  135.         for (i = t_y;i < t_h + t_y;i++)  
  136.         {  
  137.             for (j = t_x;j < t_w + t_x;j++)  
  138.             {  
  139.                 //rgb颜色空间量化为16*16*16 bins  
  140.                 q_r = ((u_char)current->imageData[i * current->widthStep + j * 3 + 2]) / 16;  
  141.                 q_g = ((u_char)current->imageData[i * current->widthStep + j * 3 + 1]) / 16;  
  142.                 q_b = ((u_char)current->imageData[i * current->widthStep + j * 3 + 0]) / 16;  
  143.                 q_temp[(i - t_y) *t_w + j - t_x] = q_r * 256 + q_g * 16 + q_b;  
  144.                 hist2[q_temp[(i - t_y) *t_w + j - t_x]] =  hist2[q_temp[(i - t_y) *t_w + j - t_x]] +  m_wei[(i - t_y) * t_w + j - t_x] ;  
  145.             }  
  146.         }  
  147.   
  148.         //归一化直方图  
  149.         for (i=0;i<4096;i++)  
  150.         {  
  151.             hist2[i] = hist2[i] / C;  
  152.             //printf("%f\n",hist2[i]);  
  153.         }  
  154.         //生成目标直方图  
  155.         double temp_max=0.0;  
  156.   
  157.         for (i=0;i<4096;i++)         //求直方图最大值,为了归一化  
  158.         {  
  159.             if (temp_max < hist2[i])  
  160.             {  
  161.                 temp_max = hist2[i];  
  162.             }  
  163.         }  
  164.         //画直方图  
  165.         CvPoint p1,p2;  
  166.         double bin_width=(double)pic_hist->width/(4368);  
  167.         double bin_unith=(double)pic_hist->height/temp_max;  
  168.   
  169.         for (i = 0;i < 4096; i++)  
  170.         {  
  171.             p1.x = i * bin_width;  
  172.             p1.y = pic_hist->height;  
  173.             p2.x = (i + 1)*bin_width;  
  174.             p2.y = pic_hist->height - hist2[i] * bin_unith;  
  175.             cvRectangle(pic_hist,p1,p2,cvScalar(0,255,0),-1,8,0);  
  176.         }  
  177.         cvSaveImage("hist2.jpg",pic_hist);  
  178.       
  179.         for (i = 0;i < 4096;i++)  
  180.         {  
  181.             if (hist2[i] != 0)  
  182.             {  
  183.                 w[i] = sqrt(hist1[i]/hist2[i]);  
  184.             }else  
  185.             {  
  186.                 w[i] = 0;  
  187.             }  
  188.         }  
  189.               
  190.         sum_w = 0.0;  
  191.         x1 = 0.0;  
  192.         x2 = 0.0;  
  193.   
  194.         for (i = 0;i < t_h; i++)  
  195.         {  
  196.             for (j = 0;j < t_w; j++)  
  197.             {  
  198.                 //printf("%d\n",q_temp[i * t_w + j]);  
  199.                 sum_w = sum_w + w[q_temp[i * t_w + j]];  
  200.                 x1 = x1 + w[q_temp[i * t_w + j]] * (i - t_h/2);  
  201.                 x2 = x2 + w[q_temp[i * t_w + j]] * (j - t_w/2);  
  202.             }  
  203.         }  
  204.         y1 = x1 / sum_w;  
  205.         y2 = x2 / sum_w;  
  206.           
  207.         //中心点位置更新  
  208.         drawing_box.x += y2;  
  209.         drawing_box.y += y1;  
  210.   
  211.         //printf("%d,%d\n",drawing_box.x,drawing_box.y);  
  212.     }  
  213.     free(hist2);  
  214.     free(w);  
  215.     free(q_temp);  
  216.     //显示跟踪结果  
  217.     cvRectangle(current,cvPoint(drawing_box.x,drawing_box.y),cvPoint(drawing_box.x+drawing_box.width,drawing_box.y+drawing_box.height),CV_RGB(255,0,0),2);  
  218.     cvShowImage("Meanshift",current);  
  219.     //cvSaveImage("result.jpg",current);  
  220.     cvReleaseImage(&pic_hist);  
  221. }  
  222.   
  223. void onMouse( int event, int x, int y, int flags, void *param )  
  224. {  
  225.     if (pause)  
  226.     {  
  227.         switch(event)  
  228.         {  
  229.         case CV_EVENT_LBUTTONDOWN:   
  230.             //the left up point of the rect  
  231.             drawing_box.x=x;  
  232.             drawing_box.y=y;  
  233.             break;  
  234.         case CV_EVENT_LBUTTONUP:  
  235.             //finish drawing the rect (use color green for finish)  
  236.             drawing_box.width=x-drawing_box.x;  
  237.             drawing_box.height=y-drawing_box.y;  
  238.             cvRectangle(current,cvPoint(drawing_box.x,drawing_box.y),cvPoint(drawing_box.x+drawing_box.width,drawing_box.y+drawing_box.height),CV_RGB(255,0,0),2);  
  239.             cvShowImage("Meanshift",current);  
  240.               
  241.             //目标初始化  
  242.             hist1 = (double *)malloc(sizeof(double)*16*16*16);  
  243.             m_wei =  (double *)malloc(sizeof(double)*drawing_box.height*drawing_box.width);  
  244.             init_target(hist1, m_wei, current);  
  245.             is_tracking = true;  
  246.             break;  
  247.         }  
  248.         return;  
  249.     }  
  250. }  
  251.   
  252.   
  253.   
  254. int _tmain(int argc, _TCHAR* argv[])  
  255. {  
  256.     CvCapture *capture=cvCreateFileCapture("test.avi");  
  257.     current = cvQueryFrame(capture);  
  258.     char res[20];  
  259.     int nframe = 0;  
  260.   
  261.     while (1)  
  262.     {     
  263.     /*  sprintf(res,"result%d.jpg",nframe); 
  264.         cvSaveImage(res,current); 
  265.         nframe++;*/  
  266.         if(is_tracking)  
  267.         {  
  268.             MeanShift_Tracking(current);  
  269.         }  
  270.   
  271.         int c=cvWaitKey(1);  
  272.         //暂停  
  273.         if(c == 'p')   
  274.         {  
  275.             pause = true;  
  276.             cvSetMouseCallback( "Meanshift", onMouse, 0 );  
  277.         }  
  278.         while(pause){  
  279.             if(cvWaitKey(0) == 'p')  
  280.                 pause = false;  
  281.         }  
  282.         cvShowImage("Meanshift",current);  
  283.         current = cvQueryFrame(capture); //抓取一帧  
  284.     }  
  285.   
  286.     cvNamedWindow("Meanshift",1);  
  287.     cvReleaseCapture(&capture);  
  288.     cvDestroyWindow("Meanshift");  
  289.     return 0;  
  290. }  

运行结果:

基于MeanShift的目标跟踪算法及实现_第15张图片 基于MeanShift的目标跟踪算法及实现_第16张图片

初始目标直方图:

基于MeanShift的目标跟踪算法及实现_第17张图片

候选目标直方图:

基于MeanShift的目标跟踪算法及实现_第18张图片

把OpenCV上的光流法,meanshift和camshift算法等看完学习之后,突然间想去查查文献,看看现在关于目标跟踪的一些主流算法,下面就简单的总结下。
运动目标跟踪就是在一段视频序列图像中的每幅图像中实时的找到所感兴趣的运动目标。在目标跟踪的研究上,大致上可以看出有两种研究思路:
a.不依赖于先验知识,直接从图像序列中检测到运动目标,并进行目标识别,最终跟踪感兴趣的运动目标;
b.依赖于目标的先验知识,首先为运动目标建模,然后在图像序列中实时找到相匹配的运动目标。

围绕这样的两种思路,学者们提出了很多算法,但是在算法的鲁棒性、准确性和普适性还不是特别的理想。运动目标跟踪的难点主要在于:
a.复杂背景干扰:在对目标进行跟踪时,往往会受到许多不同的背景的干扰。比如纹理复杂的背景可能会使基于边缘的跟踪器失效;或者是背景中与目标颜色相似的物体可能使基于颜色的跟踪器偏离真正的目标。
b.目标外观的变化:视频目标跟踪通常是在非控制条件下进行的,许多因素都会导致目标外观的变化,比如:光照变化,目标的形变等,因此要想对目标进行长时间的跟踪,就需要合理的处理这些变化,同时能够针对这些变化提出有效的观测似然函数,这个难度就很大。
c.遮挡问题:被跟踪的目标在运动过程中可能会被其他物体部分遮挡或全部遮挡,如何解决遮挡问题,这部分的研究一直是视频跟踪领域的研究的热点之一。
d.计算的有效性:视频跟踪是以帧速率进行的,这必然涉及到对大量的图像数据进行处理,要想进行实时处理,也要面临很大的数据处理问题。

对于不依赖先验知识的目标跟踪来讲,运动检测是实现跟踪的第一步。运动检测即为从序列图像中将变化区域从背景图像中提取出来。运动目标检测根据目标和摄像机之间的关系,可以分为静态背景下运动检测和动态背景下运动检测。下面简单的罗列下,如果感兴趣的话,可以查阅相关文献对照。

静态背景
1.背景差分法
2.帧间差分法
3.光流法

动态背景跟踪
1.基于区域的跟踪算法
2.基于特征的跟踪算法
3.基于轮廓的跟踪算法

上面就是大致的写了下算法分类的标准,其实这部分的算法有很多,都有特定的适用条件,我还在看相关的文献。


目标跟踪Camshift算法(Opencv实现)

#include "opencv2/video/tracking.hpp"

#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
#include <ctype.h>


using namespace cv;
using namespace std;

Mat image;
bool backprojMode = false; //表示知否进入反向投影,true进入反向投影
bool selectObject = false;
int trackObject = 0; //跟踪目标数目
bool showHist = true;//是否显示直方图
Point origin;//鼠标第一次单击时的位置
Rect selection;//鼠标选择的矩形框
int vmin = 10, vmax = 256, smin = 30;
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;
break;
case CV_EVENT_LBUTTONUP://左键松开,完成选择跟踪物体
selectObject = false;
if( selection.width > 0 && selection.height > 0 )
trackObject = -1;
break;
}
}

static void ShowHelpText()
{

cout << "\n\n\t操作说明: \n"
"\t\t用鼠标框选对象来初始化跟踪\n"
"\t\tESC - 退出程序\n"
"\t\tc - 停止追踪\n"
"\t\tb - 开/关-投影视图\n"
"\t\th - 显示/隐藏-对象直方图\n"
"\t\tp - 暂停视频\n";
}

const char* keys =
{
"{1|  | 0 | camera number}"
};

int main( int argc, const char** argv )
{
ShowHelpText();
VideoCapture cap;
Rect trackWindow;
int hsize = 16;
float hranges[] = {0,180};
const float* phranges = hranges;

cap.open(0);

if( !cap.isOpened() )
{
cout << "不能初始化摄像头\n";
}

namedWindow( "Histogram", 0 );
namedWindow( "CamShift Demo", 0 );
setMouseCallback( "CamShift Demo", onMouse, 0 );
createTrackbar( "Vmin", "CamShift Demo", &vmin, 256, 0 );
createTrackbar( "Vmax", "CamShift Demo", &vmax, 256, 0 );
createTrackbar( "Smin", "CamShift Demo", &smin, 256, 0 );

Mat frame, hsv, hue, mask, hist, histimg = Mat::zeros(200, 320, CV_8UC3), backproj;
bool paused = false;


for(;;)
{
if( !paused )
{
cap >> frame;
if( frame.empty() )
break;
}


frame.copyTo(image);


if( !paused )
{
cvtColor(image, hsv, COLOR_BGR2HSV);


if( trackObject )
{
int _vmin = vmin, _vmax = vmax;

                               /*
inRange()检查元素的取值范围是否在两个矩阵的像素取值之间,这里利用好了hsv的3个通道,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};
hue.create(hsv.size(), hsv.depth());
mixChannels(&hsv, 1, &hue, 1, ch, 1);


if( trackObject < 0 )
{
Mat roi(hue, selection), maskroi(mask, selection);
calcHist(&roi, 1, 0, maskroi, hist, 1, &hsize, &phranges);//计算直方图
normalize(hist, hist, 0, 255, CV_MINMAX);


trackWindow = selection;
trackObject = 1;


histimg = Scalar::all(0);
int binW = histimg.cols / hsize;
Mat buf(1, hsize, CV_8UC3);
for( int i = 0; i < hsize; i++ )
buf.at<Vec3b>(i) = Vec3b(saturate_cast<uchar>(i*180./hsize), 255, 255);
cvtColor(buf, buf, CV_HSV2BGR);

                                       //绘制直方图
for( int i = 0; i < hsize; i++ )
{
int val = saturate_cast<int>(hist.at<float>(i)*histimg.rows/255);
rectangle( histimg, Point(i*binW,histimg.rows),
Point((i+1)*binW,histimg.rows - val),
Scalar(buf.at<Vec3b>(i)), -1, 8 );
}
}


calcBackProject(&hue, 1, 0, hist, backproj, &phranges);//计算反向投影
backproj &= mask;
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);
}


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

程序流程:

1、打开摄像头

2、显示帮助信息:

3、鼠标响应:当鼠标左键按下时,保存此时的坐标origion= Pont(x,y),并且

   selectObject=1;执行if里面的语句,selection保存鼠标选择区域,当鼠

   标松开时,selectObject=0,trackObject=-1。

4、如果没有按下暂停键”c”,图像由BGR转化为HSV,inRange()将HSV中的

   (0-180,30-256,10-256)保存在mask中,分离H通道并保存在hue中。

5、如果鼠标选择区域松开后,根据鼠标的选择区域计算直方图并且归一化到

   0-255,将selection区域保存在trackWindow中,且track Object=1。

6、绘制目标区域的方向投影的直方图:创建buf(1*16的3通道矩阵,并赋值

   为(0-168,255,255),以这个渐变的颜色作为直方图的色彩显示。)

7、计算反向投影,,就是首先计算图像中的某一特征,然后根据特征寻找图像中

   存在的特征。

8、目标跟踪:CamShift函数返回一个有方向角度的矩阵。该函数的实现首先是

   利用meanshift算法计算出要跟踪的中心,然后调整初始窗口的大小位置和方

   向角度。在camshift内部调用了meanshift算法计算目标的重心。

9、如果跟踪的矩形框的面积小于1,重新定义跟踪窗口,否则保持不变

10、转化为BGR,并绘制出椭圆形状,如果继续跟踪,转到步奏8。

 

函数解释:

1、inRange(hsv,Scalar(0,smin,MIN(_vmin,-vmax)),Scalar(180,256,MAX(_vmin,-vm

   ax)),mask):检查元素的取值范围是否在另两个矩阵的元素取值之间,返回ma

   sk验证矩阵。

2mixChannels(&hsv,1,&hue,1.ch,1):抽取 hsv图像中的通道0

   &hsv:一系列输入图像的数组, 被拷贝的通道的来源

      1输入数组中图像的数目

      &hue:一系列目的图像的数组, 储存拷贝的通道

      1:目的数组中图像的数目

      ch[] = {0,0}:通道索引对的数组,指示如何将输入图像的某一通道拷贝到目的

   图像的某一通道。在这里,&hsv图像的Hue(0) 通道被拷贝到&hue图像(单

   通道)的0 通道。

       1:通道索引对德数目

3、roi(hue,selection):设置hue中的感兴趣区域,如果设置了感兴趣区域,那么在使

OpenCV函数的时候,只会对ROI区域操作,其它区域忽略。

4、maskroi(mask,selection):掩码

5、calcHist(&roi,1,0,maskroi,hist,1,&hist,&phranges):计算直方图

&roi:输入图像

1:第一个参数中存放了多少张图像

0:需要统计的通道索引

maskroi:输入图像的mask,可选的操作掩码,这里的非零掩码元素用于标记出统计直方图的数组元素数据

hist:,输出的目标直方图

1:需要计算的直方图的维度

&hist:存放每个维度的直方图尺寸的数组

&phranges:每一维数取值范围。

6、normalize(hist,hist,0,255,CV_MINMAX):归一化

7、Rectangle(histimg,Point(i*binW,histimg.rows),Point((i+1)*binW),histimg.rows-val),S

calar(buf.at<Vec3b>(i),-1,8);

C++: void rectangle(Mat& img,Point pt1, Pointpt2, const Scalar&color, intthickness=1,intlineType=8, intshift=0)

C++: void rectangle(Mat& img,Rect rec, const Scalar&color, intthickness=1, intlineType=8,intshift=0 )

img – 画矩形的对象 

pt1 – 矩形的一个顶点,左上角的.

pt2 – 另一个顶点,右下角的. 

rec – 确定矩形的另一种方式,给左上角坐标和长宽 

color – 指定矩形的颜色或亮度(灰度图像)

thickness – 矩形边框的粗细. 负值(like CV_FILLED表示要画一个填充的矩形 

lineType – 边框线型. 

8 (or 0) - 8-connected line8邻接)连接线。4 - 4-connected line(4邻接)连接线。CV_AA - antialiased 线条。

8、calcBackProject(&hue,1,0,hist,backProj,&phranges)

Hue:输入图像

1:输入图像的个数

0:需要统计的通道索引

Hist:输入的直方图

backProj:目标反向投影阵列,必须为单通道

Phranges:每一个维度数组的边界

9、CamShift(backProj,trackWindow,TerCriteria(CV_TERMCRIT_EPS | 

CV_TERMCRIT_ITER,10,1))

backProj:输入图像直方图的反向投影图

trackWindow:跟踪目标的初始位置矩形框

TerCriteria(CV_TERMCRIT_EPS | CV_TERMCRIT_ITER,10,1):算法结束条件


TLD算法:作者展示算法的效果,http://www.56.com/u83/v_NTk3Mzc1NTI.html。下面这个csdn博客里有人做的相关总结,感觉挺好的,收藏了!下面有个Compressive Tracking的网址,提供的代码很少,但实时性很好,matlab代码下下来就能用。

----------------------------------------------------------------------------------------------------------------------------------------------

以下博文转自:http://blog.csdn.net/windtalkersm/article/details/8018980

TLD是一种算法的简称,原作者把它叫做Tracking-Learning-Detection。搞视觉的人看到这个名字都会吓一跳,很ambitious的计划。是09年的工作,不算太久,不过也不太新。网上关于这个的资源其实很多,很大程度和作者开放源代码有关。


学习过程中碰到的第一个问题就是资源太多---当然是相对这个领域而言,一般能找到一个忠实再现算法的源码就已经很好了。所以把找到的list一下,虽然有点浪费时间,希望可以对其他人有所帮助。具体的细节就不多说了,有很多很棒的分析也列在下面,比如zouxy09写的源码注释,实在不能再详细了。如果硬要找茬,那就是大段的文字让人头晕,也没怎么排版。


=== 2015-06-29  更新 ===

补充3个博客资料和一个框架图


6. OPENTLD学习总结

http://blog.csdn.net/tiandawenwu/article/details/17462705

写的很不错。侧重点在learning 和随机森林的构建上。可惜读的人少的可怜,多数人都只想着怎么把程序跑起来,然后呢。。。


7. openTLD 源码解读

http://blog.csdn.net/muzi198783/article/details/7392255

写的相当简单。懂的人可以借鉴


8. Tracking-Learning-Detection TLD解析

http://blog.csdn.net/wood_water/article/details/9017681

又是一系列文章 。这个TLD都被嚼烂了,还是去玩Deep Learning吧。。。作者的博客也不错,后生可畏。。。


9.  2011_09_26_TLD框架图

http://blog.sina.cn/dpool/blog/s/blog_8ff949cf0100yfog.html?cre=blogpagew&mod=f&loc=3&r=ar&rfunc=9
真的够了。。。。


源代码资源:

1. 原作者 Zdenek Kalal

作者主页: http://info.ee.surrey.ac.uk/Personal/Z.Kalal/

源代码页: https://github.com/zk00006/OpenTLD

编程语言:Matlab + C


2. Alan Torres版

源代码页:https://github.com/alantrrs/OpenTLD

实现语言:C++


3. arthurv版

源代码页:https://github.com/arthurv/OpenTLD

实现语言:C++

注:和上面的没有发现任何区别


4. jmfs版

源代码页:https://github.com/jmfs/OpenTLD

实现语言:C++

注:和上面两个没有区别,只不过加入了VS2010工程文件,理论上可以直接在Windows下编译通过。不过opencv检测不到作者的webcam(!!!),所以他用了另一个VideoInput类来handle摄像头输入。

 

This is an adaptation of arthurv's fork of OpenTLD (https://github.com/arthurv/OpenTLD) 
to be immeadiately runnable in Visual Studio 2010.


 

5. Georg Nebehay版 (终于有个不一样的了。。。。)

源代码页:http://gnebehay.github.com/OpenTLD/

注1:这个的好处是提供可执行文件下载(Ubuntu 10.04和Windows)。BUT, as you would expect,基本上到了你的机器上都跑不了。还是自己老老实实build吧。

注2:这个版本需要安装Qt。不过好像作者关掉了Qt的选项(相关代码还在),所以可以编译,但无法显示结果

注3:CSDN下载上有个“openTLD Qt 版“,就是这个版本。不过加了VS的工程文件---在我的机器上还是不能PnP, don't bother

http://download.csdn.net/download/muzi198783/4111915


6. Paul Nader版(又一个Qt 版!)

QOpenTLD: http://qopentld.sourceforge.net/

源代码页: http://sourceforge.net/projects/qopentld/

注1:需要OpenCV和Qt。 原系统要求Qt 4.3.7OpenCV 2.2。

注2:Windows和Linux下都提供了编译工程或makefile。估计也是唯一一个移植到Android平台下的TLD!


7. Ben Pryke版(又一个student project!)

源代码页:https://github.com/Ninjakannon/BPTLD

注:依然是Matlab+C/C++的混合实现。亮点是有很详细的Documentation(8页),介绍了算法的理解和实现细节。可以帮助理解原算法


博客资源(中文):

1.  庖丁解牛TLD (yang_xian521)

http://blog.csdn.net/yang_xian521/article/details/6952870

注1:从文章看作者是基于原作者的matlab版分析的。从函数名看上面的2/3/4应该是matlab--->C++的"直译",函数名都没变。这样最好,可以和下面的对照着看,同时学matlab和C++


2.  TLD(Tracking-Learning-Detection)学习与源码理解 (zouxy09)

http://blog.csdn.net/zouxy09/article/details/7893011

注1: 用的是<<arthurv版>>,前面说过,不能再详细了!

注2: 下面三个是从这篇copy的


3. 《再谈PN学习》:

http://blog.csdn.net/carson2005/article/details/7647519

4. 《比微软kinect更强的视频跟踪算法--TLD跟踪算法介绍》

http://blog.csdn.net/carson2005/article/details/7647500

5. 《TLD视觉跟踪技术解析》

http://www.asmag.com.cn/number/n-50168.shtml


想说的话:

1. 分享:前段时间把 TLD::init(...)看完了,本想一鼓作气,其他的事太多只好放下。不过我对detection和tracking比较熟,init中已经把learning作了一遍,看懂了剩下的就容易了。现在重新捡起,无意中发现了zouxy09的注释,省了太多力气,半天就看完了。很多细节不用自己去抠--- 我们常抱怨这资源那文档太少,羡慕老外能力强,动作快,和他们愿意分享关系太大。经常看到一些不错的文章收藏起来,过几天去看居然删了!


可以理解可能是开公司要保密,但如果害怕别人知道了自己的思路就做不下去,那还是不要在这个领域做了。算法只是思想,谁也垄断不了。算法也一定是不断更新的, 抱着一个算法不放也存活不了几年。原作者也基于这个技术开公司了,也没见他们基于这个限制别人使用。SIFT,SURF都patent了也没听说赚了大钱,kinect告诉你算法也实现不了。要保密的是实现细节


2. 比较: 终于看完了实现,总的感觉这个算法还是更象工程应用(engineering)而不是理论突破(也不能要求太多了是不是)。感觉这么结合后并不一定会比单个的跟踪(tracking)或检测(detection-by-classification)模块作的更好,毕竟还是没有解决外观(appearence)和尺度(scale)变化这两个根本难题。 不过这种框架反而应该在实际中非常实用,因为----------可调的参数太多了!

TLD相信很多人都试过了,实时性很多人都在抱怨,而且拿到自己的视频上总要调些参数效果才好。


比较起来更喜欢今年ECCV上Kaihua Zhang的Compressive Tracking:理论高深的吓人(开玩笑),源码简单的吓人。而且是目前为止我试过的off-the-shelf的tracker中跟踪效果最好的,不用调任何参数,绝对实时----代码那么少,想不实时都难吧(顺便说一句作者的blog就在上面提到过)。这才是做研究的方法,有个强大的理论做支撑,实现可以很简单却不会影响效果。所以如果搞数学的人愿意做应用,很多人都会下岗

http://www4.comp.polyu.edu.hk/~cslzhang/CT/CT.htm


另一个PWP(Pixel-Wise Posteriors),publish时间和TLD差不多,性能看上去也很美,不过作者说要开源,一直没有兑现。是个遗憾。个人觉得level set对部分遮挡效果应该很好,做到实时也不是难事

http://www.robots.ox.ac.uk/~cbibby/research_pwp.shtml


3. 总结:TLD其实是一个非常合适的入门和进阶算法:

a. 有理论,有高质量的paper(BMVC, CVPR, ICPR, 最后PAMI)

b. 有源代码!Matlab, C++, Windows, Linux, .....你还想要啥?

c. 有不同大牛小牛分享的详细的介绍和详细的代码注释(几乎每一行都解释到了)!

4. 牵涉面广,涉及到detection, tracking, classifcation,传统的视觉技术就是这么硬梆梆的划分的三大类。研究完了对每一部分多少能有点心得。



你可能感兴趣的:(基于MeanShift的目标跟踪算法及实现)