TLD(Tracking-Learning-Detection)学习与源码理解之(跟踪器)

来源:互联网 发布:mac virtualbox usb 编辑:程序博客网 时间:2024/05/22 00:30

    目标跟踪的一般思想是跟踪目标中关键点。TLD也是跟踪点(但不是跟踪SIFT之类的关键点)。点跟踪采用的是光流法,具体来说是Pyramidal Lucas-Kanade tracker,这个以后机会再介绍,推荐阅读《Learning OpenCV》第10章的Lucas-Kanade Method部分,这里只介绍OpenCV的实现函数,跳过原理和实现细节。

    首先看跟踪点的函数,calcOpticalFlowPyrLK,它的作用是找到上一帧中的跟踪点在当前帧的位置。调用形式如下:

calcOpticalFlowPyrLK( img_last,img_currpoints_last points_curr, status, errs)

    参数意思应该很直白了吧,补充一下status为1,表示对应点找到了,为0就是没找到,errs自然是误差。注意:可以是单个点,也可以是点集,如果是点集,那么对应的status和errs就都是vector啦。

    下面说说怎么跟踪目标,TLD采用的是基于作者自己提出的Median-Flow tracker,此外增加了跟踪失败检测。

通过Forward-Backward error来筛选要跟踪的点

    前面提到TLD跟踪的不是关键点,它跟踪的是更简单的点:能稳定存在的点,那哪些点是稳定的呢?Median-Flow tracker的基本思想是,看反向跟踪后的残差,用所有点的残差中值作为稳定点的筛选条件。如上图中的黄色点就因为残差太大,被pass掉了,既然稳定点是可以筛选出来的,那么就不必煞费苦心的寻找那些关键点,可以直接将所有的点都作为初始跟踪点,好吧所,有的点毕竟还是太多了,于是作者是选取网格交叉点作为初始跟踪点(见下图框框中黄色的点点)。

 

Median Flow tracker 的流程图

    下面正式介绍作者的跟踪函数TLD::track调用形式如下:

track(img1,img2,points1,points2)

    img1是上一帧的图像,img2是当前帧的图像points1points2都是这个函数的输出函数,points1是将上一次跟踪到的目标区域lastbox划分成网格后,所得到的网格交点,即上图左边的黄色点,而points2points1中能稳定出现在当前帧出的点,即右图中的点

    下面结合上面的流程图,并补充TLD需要增加的环节,来介绍track。

TLD::track函数

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void TLD::track(const Mat& img1, const Mat& img2,vector<Point2f>& points1,vector<Point2f>& points2){  
  2.   //【5.4】  
  3.   // 1.Generate points  
  4.   bbPoints(points1,lastbox);  
  5.   if (points1.size()<1){//问题:何时会出现这种情况??  
  6.       printf("BB= %d %d %d %d, Points not generated\n",lastbox.x,lastbox.y,lastbox.width,lastbox.height);  
  7.       tvalid=false;  
  8.       tracked=false;  
  9.       return;  
  10.   }  
  11.   vector<Point2f> points = points1;  
  12.   //Frame-to-frame tracking with forward-backward error cheking  
  13.   // 2. 推断上一帧的points,在当前帧的位置,points->points2  
  14.   // 注意:只有通过筛选的point对 还保留在points,points2  
  15.   tracked = tracker.trackf2f(img1,img2,points,points2);  
  16.   if (tracked){//只要有一个点跟到了,就算跟到了……,是不是应该严格一点呢??  
  17.       // 3. Bounding box prediction  
  18.       bbPredict(points,points2,lastbox,tbb);//此时,lastbox,还是依据上一帧预测的目标在当前帧的位置  
  19.       // 4. Failure detection,检测 getFB()>10 || 完全出轨   
  20.       if (tracker.getFB()>10 || tbb.x>img2.cols ||  tbb.y>img2.rows || tbb.br().x < 1 || tbb.br().y <1){//br() bottom right坐标  
  21.           tvalid =false//too unstable prediction or bounding box out of image  
  22.           tracked = false;  
  23.           printf("Too unstable predictions FB error=%f\n",tracker.getFB());  
  24.           return;  
  25.       }  
  26.       // 5. Estimate Confidence and Validity  
  27.       Mat pattern;  
  28.       Scalar mean, stdev;  
  29.       BoundingBox bb;  
  30.       bb.x = max(tbb.x,0);  
  31.       bb.y = max(tbb.y,0);  
  32.       bb.width = min(min(img2.cols-tbb.x,tbb.width),min(tbb.width,tbb.br().x));//问题:我觉得后面的min没必要呀??  
  33.       bb.height = min(min(img2.rows-tbb.y,tbb.height),min(tbb.height,tbb.br().y));  
  34.       getPattern(img2(bb),pattern,mean,stdev);  
  35.       vector<int> isin;  
  36.       float dummy;  
  37.       classifier.NNConf(pattern,isin,dummy,tconf); //1.tconf是用Conservative Similarity  
  38.       tvalid = lastvalid;  
  39.       if (tconf>classifier.thr_nn_valid){//thr_nn_valid  
  40.           tvalid =true;//2.判定轨迹是否有效,从而决定是否要增加正样本,标志位tvalid【5.6.2 P-Expert】  
  41.       }  
  42.   }  
  43.   else  
  44.     printf("No points tracked\n");  
  45. }  

1.Initialize points to grid

    将bb切成10*10的网格,将网格交点存在points,函数为TLD::bbPoints

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //将bb切成10*10的网格,将网格交点存在points  
  2. void TLD::bbPoints(vector<cv::Point2f>& points,const BoundingBox& bb){  
  3.   int max_pts=10;  
  4.   int margin_h=0;//留白没有用到  
  5.   int margin_v=0;  
  6.   int stepx = ceil((bb.width-2*margin_h)/max_pts);//向上取整  
  7.   int stepy = ceil((bb.height-2*margin_v)/max_pts);  
  8.   for (int y=bb.y+margin_v;y<bb.y+bb.height-margin_v;y+=stepy){  
  9.       for (int x=bb.x+margin_h;x<bb.x+bb.width-margin_h;x+=stepx){  
  10.           points.push_back(Point2f(x,y));//最多有11*11=121个点  
  11.       }  
  12.   }  
  13. }  

2.Track points
3.Estimate tracking error
4.Filter out outliers

    这三步都在函数trackf2f 中,调用层次关系tld.processFrame->track->[tracked = tracker.trackf2f(img1,img2,points,points2)]

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //points1->points2,由于调用了filterPts,所以只有通过筛选的point对还保留在points1,points2  
  2. bool LKTracker::trackf2f(const Mat& img1, const Mat& img2,vector<Point2f> &points1, vector<cv::Point2f> &points2){  
  3.   //TODO!:implement c function cvCalcOpticalFlowPyrLK() or Faster tracking function  
  4.   //1. Track points,Forward-Backward tracking  
  5.   calcOpticalFlowPyrLK( img1,img2, points1, points2, status,similarity, window_size, level, term_criteria, lambda, 0);  
  6.   calcOpticalFlowPyrLK( img2,img1, points2, pointsFB, FB_status,FB_error, window_size, level, term_criteria, lambda, 0);  
  7.   //2. Estimate tracking error,Compute the real FB-error  
  8.   forint i= 0; i<points1.size(); ++i ){  
  9.         FB_error[i] = norm(pointsFB[i]-points1[i]);//残差为欧氏距离【ICPR 2】  
  10.   }  
  11.   //3.Filter out outliers  
  12.   //Filter out points with FB_error[i] > median(FB_error) && points with sim_error[i] > median(sim_error)  
  13.   normCrossCorrelation(img1,img2,points1,points2);  
  14.   return filterPts(points1,points2);  
  15. }  

    其中normCrossCorrelation(img1,img2,points1,points2)是对光流法跟踪的结果不放心,因此希望通过对比前后两点周围的小块的相似性,来进一步去掉不稳定的点。这次的相似性不是相关系数,而是normalized cross-correlation (NCC):

 

    这个比较复杂,建议看wiki的公式,其实还是前面提到的相关系数,只不过计算的时候需要自己减去均值。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void LKTracker::normCrossCorrelation(const Mat& img1,const Mat& img2, vector<Point2f>& points1, vector<Point2f>& points2) {  
  2.         Mat rec0(10,10,CV_8U);  
  3.         Mat rec1(10,10,CV_8U);  
  4.         Mat res(1,1,CV_32F);  
  5.         for (int i = 0; i < points1.size(); i++) {  
  6.                 if (status[i] == 1) {//跟踪到了  
  7.                         getRectSubPix( img1, Size(10,10), points1[i],rec0 );//以points1[i]为中心,提取10*10的小块  
  8.                         getRectSubPix( img2, Size(10,10), points2[i],rec1);  
  9.                         matchTemplate( rec0,rec1, res, CV_TM_CCOEFF_NORMED);//Cross Correlation   
  10.                         similarity[i] = ((float *)(res.data))[0];  
  11.   
  12.   
  13.                 } else {  
  14.                         similarity[i] = 0.0;  
  15.                 }  
  16.         }  
  17.         rec0.release();  
  18.         rec1.release();  
  19.         res.release();  
  20. }  

    该计算的都计算好了,终于可以筛选了,filterPts(points1,points2)
[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //Filter out points with FB_error[i] > median(FB_error) && points with sim_error[i] > median(sim_error)  
  2. bool LKTracker::filterPts(vector<Point2f>& points1,vector<Point2f>& points2){  
  3.   //Get Error Medians  
  4.   simmed = median(similarity);//NCC中值  
  5.   size_t i, k;  
  6.   for( i=k = 0; i<points2.size(); ++i ){//前向筛选,没跟踪到的不要  
  7.         if( !status[i])  
  8.           continue;  
  9.         if(similarity[i]> simmed){//normalized crosscorrelation (NCC)筛选,比对前后两点周围的小块  
  10.           points1[k] = points1[i];  
  11.           points2[k] = points2[i];  
  12.           FB_error[k] = FB_error[i];  
  13.           k++;  
  14.         }  
  15.     }  
  16.   if (k==0)  
  17.     return false;  
  18.   points1.resize(k);  
  19.   points2.resize(k);  
  20.   FB_error.resize(k);  
  21.   
  22.   
  23.   fbmed = median(FB_error);//残差中值  
  24.   for( i=k = 0; i<points2.size(); ++i ){//后向筛选,找到了,但是偏离太多  
  25.       if( !status[i])  
  26.         continue;  
  27.       if(FB_error[i] <= fbmed){  
  28.         points1[k] = points1[i];  
  29.         points2[k] = points2[i];  
  30.         k++;  
  31.       }  
  32.   }  
  33.   points1.resize(k);  
  34.   points2.resize(k);  
  35.   if (k>0)  
  36.     return true;  
  37.   else  
  38.     return false;  
  39. }  

5.Update bounding box

    bbPredict(points,points2,lastbox,tbb), pointspoints2是前面筛选完之后的点对,现在要依据points,points2来估计bb1的位移尺度变化,这两个信息都有了,自然可以决定lastbox在当前帧的位置tbb。

位移估计

   位移估计的方法是用所有点对x,y位移的中值作为位移的估计,如上图。尺度的估计的方法是用所有点对(同一帧)的伸缩比的中值作为尺度伸缩的估计,假设只有一堆点,尺度伸缩值的估计方式如下图:

尺度估计

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //依据points1,points2估计bb1的位移和尺度变化,这两个信息都有了,自然可以决定其范围bb2  
  2. void TLD::bbPredict(const vector<cv::Point2f>& points1,const vector<cv::Point2f>& points2,  
  3.                     const BoundingBox& bb1,BoundingBox& bb2)    {  
  4.   int npoints = (int)points1.size();  
  5.   vector<float> xoff(npoints);  
  6.   vector<float> yoff(npoints);  
  7.   printf("tracked points : %d\n",npoints);  
  8.   // 用位移的中值,作为目标位移的估计  
  9.   for (int i=0;i<npoints;i++){  
  10.       xoff[i]=points2[i].x-points1[i].x;  
  11.       yoff[i]=points2[i].y-points1[i].y;  
  12.   }  
  13.   float dx = median(xoff);//  
  14.   float dy = median(yoff);  
  15.   float s;  
  16.   // 用点对之间的距离的伸缩比例的中值,作为目标尺度变化的估计  
  17.   if (npoints>1){  
  18.       vector<float> d;  
  19.       d.reserve(npoints*(npoints-1)/2);  
  20.       for (int i=0;i<npoints;i++){  
  21.           for (int j=i+1;j<npoints;j++){ 
  22.               d.push_back(norm(points2[i]-points2[j])/norm(points1[i]-points1[j]));  
  23.           }  
  24.       }  
  25.       s = median(d);//  
  26.   }  
  27.   else {  
  28.       s = 1.0;  
  29.   }  
  30.   float s1 = 0.5*(s-1)*bb1.width;// top-left 坐标的偏移(s1,s2)  
  31.   float s2 = 0.5*(s-1)*bb1.height;  
  32.   printf("s= %f s1= %f s2= %f \n",s,s1,s2);  
  33.   bb2.x = round( bb1.x + dx -s1);  
  34.   bb2.y = round( bb1.y + dy -s2);  
  35.   bb2.width = round(bb1.width*s);  
  36.   bb2.height = round(bb1.height*s);  
  37.   printf("predicted bb: %d %d %d %d\n",bb2.x,bb2.y,bb2.br().x,bb2.br().y);  
  38. }  

6.Failure detection

    这一步很简单,原文是说A failure of the tracker is declared if  pixels,其中是残差的中值,残差即反向跟踪和原始跟踪点的距离。不过程序里面还要防止目标飞到图像外面去了。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. if (tracker.getFB()>10 || tbb.x>img2.cols || tbb.y>img2.rows || tbb.br().x < 1 || tbb.br().y <1){//br() bottom right坐标  
  2. tvalid =false//too unstable prediction or bounding box out of image  
  3. tracked = false;  
  4. printf("Too unstable predictions FB error=%f\n",tracker.getFB());  
  5. return;  
  6. }  

7.Estimate Confidence and Validity

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. Mat pattern;  
  2. Scalar mean, stdev;  
  3. BoundingBox bb;  
  4. bb.x = max(tbb.x,0);  
  5. bb.y = max(tbb.y,0);  
  6. bb.width = min(min(img2.cols-tbb.x,tbb.width),min(tbb.width,tbb.br().x));// bb.height = min(min(img2.rows-tbb.y,tbb.height),min(tbb.height,tbb.br().y));  
  7. getPattern(img2(bb),pattern,mean,stdev);  
  8. vector<int> isin;  
  9. float dummy;  
  10. classifier.NNConf(pattern,isin,dummy,tconf); //1.tconf是用Conservative Similarity  
  11. tvalid = lastvalid;   
  12. if (tconf>classifier.thr_nn_valid){//thr_nn_valid  
  13. tvalid =true;//2.判定轨迹是否有效,从而决定是否要增加正样本,标志位tvalid【5.6.2 P-Expert】  
  14. }  

    注释很清楚了,大家可以先忽略判定轨迹是否有效这一部分,只要知道它是用最近邻分类器的Conservative Similarity【5.2】作为跟踪目标的得分即可,后面要用这个分数和检测器进行比较。


0 0