首页 > 代码库 > TLD之学习篇(四)

TLD之学习篇(四)

TLD之学习篇(四)

TLD之扯淡篇(一)、TLD之检测篇(二)TLD之跟踪篇(三)

    这一部分是TLD算法的核心之处,有了前面两篇的铺垫,终于可以毫无顾忌的说说这一部分了。不过,还有一座大山,程序的初始化,这是程序运行的铺垫,内容很多……。

初始化

    在run_tld.cpp中,一旦你选的要跟踪的目标box后便会调用初始化init:

tld.init(last_gray,box,bb_file);//初始目标位置存储在box

内容太多,大伙看下面程序中标出的9点吧,其中3、5、8三点已经讲过啦,所调用的函数就不再展开,具体见我共享的注释,后面再补充介绍几点:

void TLD::init(const Mat& frame1,constRect&box,FILE*bb_file){
  //bb_file =fopen("bounding_boxes.txt","w");
  //Get Bounding Boxes
  // 1.预先计算好所有位置,所有尺度的bb,并计算每一个bb与初始跟踪区域的面积交/并
  // 问题:好多呀,有必要吗?一劳永逸的事情,倒也耽误不了多少工夫
  buildGrid(frame1,box);
  printf("Created %dbounding boxes\n",(int)grid.size());
  //Preparation
  //allocation
  iisum.create(frame1.rows+1,frame1.cols+1,CV_32F);
  iisqsum.create(frame1.rows+1,frame1.cols+1,CV_64F);
  dconf.reserve(100);
  dbb.reserve(100);
  bbox_step =7;
  //tmp.conf.reserve(grid.size());
  tmp.conf = vector<float>(grid.size());
  tmp.patt = vector<vector<int>>(grid.size(),vector<int>(10,0));
  //tmp.patt.reserve(grid.size());
  dt.bb.reserve(grid.size());//预留空间
  good_boxes.reserve(grid.size());
  bad_boxes.reserve(grid.size());
  pEx.create(patch_size,patch_size,CV_64F);
  //Init Generator
  generator = PatchGenerator(0,0,noise_init,true,1-scale_init,1+scale_init,-angle_init*CV_PI/180,angle_init*CV_PI/180,-angle_init*CV_PI/180,angle_init*CV_PI/180);
  // 2. 依据1中计算的overlap,选出10(num_closest_init)个good box,1个best_box,其余的都作为bad box(N个)
  //【5.6.1】select10 bounding boxes on the  scanning gridthat are closest to the initial bounding box.
  getOverlappingBoxes(box,num_closest_init);
  printf("Found %d goodboxes, %d bad boxes\n",(int)good_boxes.size(),(int)bad_boxes.size());
  printf("Best Box: %d%d %d %d\n",best_box.x,best_box.y,best_box.width,best_box.height);
  printf("Bounding boxhull: %d %d %d %d\n",bbhull.x,bbhull.y,bbhull.width,bbhull.height);
  //Correct BoundingBox
  lastbox=best_box;//注意是best_box
  lastconf=1;
  lastvalid=true;
fprintf(bb_file,"%d,%d,%d,%d,%f\n",lastbox.x,lastbox.y,lastbox.br().x,lastbox.br().y,lastconf);
  //Prepare Classifier
  //3. 确定随机森林分类器特征的计算方式,随机产生了 130对点,比较其相互的大小关系(二值化)->作为10个列向量(向量化)
  classifier.prepare(scales);
  ///Generate Data
  // Generate positivedata【5.6.1】
  // generate 20warped versions by geometric transformations(shift _1%,scale change_1%,in-plane rotation 10)
  // and add them withGaussian noise(_ ¼ 5) on pixels.The result is 200 synthetic positive patches.
  // 4. 得到最近邻分类器的正样本pEx,提取的是best_box的patch;随机森林分类器的正样本由 goodbox 变形繁衍(1->20)得到
  generatePositiveData(frame1,num_warps_init);
  // 5. Set variancethreshold
  Scalar stdev,mean;
  meanStdDev(frame1(best_box),mean,stdev);//注意是best_box,而不是我们自己框定的box
  integral(frame1,iisum,iisqsum);
  var = pow(stdev.val[0],2)*0.5; //【5.3.1】50percent of variance of the patch that was selected  for tracking
  cout << "variance:" << var << endl;
  //check variance
  double vr=  getVar(best_box,iisum,iisqsum)*0.5;
  cout << "checkvariance: " << vr << endl;
  // 6. Generatenegative data,得到随机森林的负样本集:nX(特征fern,N多),最近邻的负样本集:nEx(图像块patch,100个)
  generateNegativeData(frame1);
  // 7. 构造训练集和测试集(负样本五五分)
   //Split NegativeFerns into Training and Testing sets (they are already shuffled)
  int half =(int)nX.size()*0.5f;//保留后一半作为测试
 nXT.assign(nX.begin()+half,nX.end());
  nX.resize(half);
  //Split Negative NNExamples into Training and Testing sets
  half = (int)nEx.size()*0.5f;
 nExT.assign(nEx.begin()+half,nEx.end());
  nEx.resize(half);
  //Merge NegativeData with Positive Data and shuffle it
  // [nX +pX]-->ferns_data
  vector<pair<vector<int>,int>> ferns_data(nX.size()+pX.size());
  vector<int> idx= index_shuffle(0,ferns_data.size());//再一次打乱+-样本的顺序
  int a=0;
  for (inti=0;i<pX.size();i++){//pX是在 generatePositiveData中产生 10*20
      ferns_data[idx[a]] = pX[i];
      a++;
  }
  for (inti=0;i<nX.size();i++){
      ferns_data[idx[a]] = nX[i];
      a++;
  }
  //Data already havebeen shuffled, just putting it in the same vector
  //[pEx(1个) nEx(N多)]->nn_data
  vector<cv::Mat>nn_data(nEx.size()+1);
  nn_data[0] = pEx;
  for (inti=0;i<nEx.size();i++){
      nn_data[i+1]= nEx[i];
  }
  // 8.Training,决策森林和最近邻
  classifier.trainF(ferns_data,2); //bootstrap= 2
  classifier.trainNN(nn_data);
  // 9.ThresholdEvaluation on testing sets,检查是否要提高阈值thr_fern,thr_nn,thr_nn_valid
  classifier.evaluateTh(nXT,nExT);//仅此一次调用,后面都不会提高了
}

1. buildGrid

    前面说过TLD是多尺度滑动窗口检测,所以这就把所有窗口能滑到的bb(包括坐标空间,尺度空间)都计算好啦,存在grid里面。

7. 构造训练集和测试集

 

训练集

测试集(都只有负样本)

NN分类器

[pEx(1个) nEx(N/2)]-->nn_data

nExT (N/2)

随机森林

[nX(N/2) + pX(200)]-->ferns_data

nXT(N/2)

4.generatePositiveData【5.6.1】

    这个函数很有争议,很多人认为程序错了,其实不然。各位看客可以先自己试着找找,看看能否找到错误。

void TLD::generatePositiveData(const Mat& frame, intnum_warps){
  Scalar mean;
  Scalar stdev;
  getPattern(frame(best_box),pEx,mean,stdev);//pEx
  //Get Fern featureson warped patches
  Mat img;
  Mat warped;
  GaussianBlur(frame,img,Size(9,9),1.5);
  warped = img(bbhull);
  RNG&rng = theRNG();
  Point2fpt(bbhull.x+(bbhull.width-1)*0.5f,bbhull.y+(bbhull.height-1)*0.5f);//水平,垂直中心,即旋转的中心
  vector<int>fern(classifier.getNumStructs());
  pX.clear();//
  Mat patch;
  if(pX.capacity()<num_warps*good_boxes.size())
    pX.reserve(num_warps*good_boxes.size());
  int idx;
  for (inti=0;i<num_warps;i++){//每一个good_boxes都生num_warps个pX
     if(i>0)
       generator(frame,pt,warped,bbhull.size(),rng);//仿射变换    for (intb=0;b<good_boxes.size();b++){
         idx=good_boxes[b];
       patch = img(grid[idx]);
        classifier.getFeatures(patch,grid[idx].sidx,fern);
         pX.push_back(make_pair(fern,1));
     }
  }
  printf("Positiveexamples generated: ferns:%d NN:1\n",(int)pX.size());
}
     揭晓答案,有些人认为

generator(frame,pt,warped,bbhull.size(),rng);

    这一步错了,因为仿射变换的结果是放在warped里面,而后来还是从img提取patch(patch = img(grid[idx])),所以他们觉得应该修改成:

generator(img,pt,img,frame.size(),rng);

    开始我也认为错了,三人成虎呀。事实真的如此吗?其实,我们忽略了一个细节,注意到  warped = img(bbhull); 也就是说warped是img bbhull区域的浅拷贝,它俩是共享存储空间的,于是warped一变,img也就会变。

本着为科学真理献身的精神,我用ImageWatch这个工具给自己做了个验证实验……,为了写个笔记,我也是蛮拼的……

 技术分享

  技术分享

 技术分享

    这是generator执行之后的结果,可以看到frame没变,是原始帧的图像,很明显,warped沿顺时针反向旋转了,接下来,我们惊奇地发现,img的bbhull部分也旋转了,有图有真相,啥也不用说了吧。

    我没有找到这个函数的API说明,所以各位不妨通过上面三张图,再猜猜generator(frame,pt,warped,bbhull.size(),rng)的参数含义和具体实现,frame肯定是输入图像,pt是旋转中心,warped自然是输出结果,而bbhull.size()是最终要保留的区域大小,rng自然是仿射变换的参数。我觉得算法可能是先将整个framept为中心,进行仿射变换,将结果暂存,假设是放在temp中,然后再从temp提取出bbhull区域。哈哈,这样做自然是做了很多无用功。

    现在再回头看看generator(img,pt,img,frame.size(),rng)这种改法,其实并不可取,原来每次都是使用frame,做一点点变化得到正样本,而改成这样后,每次就都是在上一次的基础上继续变化,所以20次变化后,img和原始frame可能就相去甚远,比如,很不巧,每次都顺时针转9°,20次变化之后,我就完全倒立了……。那么这个样本有必要加到训练集吗,No!

程序流程

    当我们框定要跟踪的目标后,会调用回调函数,mouseHandler,然后就是前面介绍的tld.init(last_gray,box,bb_file),之后的工作就很枯燥了,循环处理每一帧图像 tld.processFrame(last_gray,current_gray,pts1,pts2,pbox,status,tl,bb_file);

 技术分享

    processFrame函数里面就是T、L、D三大主角要上演的剧本啦,没想到前的主角介绍,场景搭建工作这么费事……,哎,写博客真是一项苦力活。下面这段程序,各位看客仔细看看,最好能理解每一步是在做什么。

void TLD::processFrame(const cv::Mat&img1,const cv::Mat&img2,vector<Point2f>&points1,vector<Point2f>&points2,BoundingBox&bbnext,bool&lastboxfound, booltl, FILE*bb_file){
  vector<BoundingBox>cbb;//聚类之后的bounding box
  vector<float>cconf;
  intconfident_detections=0;//小D的结果聚类之后,分数比小T高的数目
  int didx; //detectionindex
  /// 1.Track
  if(lastboxfound&&tl){//前一帧目标出现过,我们才跟踪,否则只能检测了
      track(img1,img2,points1,points2);
  }
  else{
      tracked = false;
  }
  /// 2.Detect
  detect(img2);
  /// 3.Integration
  if(tracked){
      bbnext=tbb;//  小T, bbnext这是你下一次要跟踪的目标
      lastconf=tconf;
      lastvalid=tvalid;
      printf("Tracked\n");
    //--------- 4. Detetor Vs Trackr-------------
      if(detected)
    { //   if Detected
         clusterConf(dbb,dconf,cbb,cconf);//检测的结果太多,所以要进行非极大值抑制,这里是用cluster的方法,时间消耗应该不少吧??
          printf("Found %dclusters\n",(int)cbb.size());
        //Get index of a clusters that is farfrom tracker and are more confident than the tracker
          for (inti=0;i<cbb.size();i++){
              if(bbOverlap(tbb,cbb[i])<0.5 && cconf[i]>tconf){//小D小T分歧很大,而且小D更有把握(都是用Conservativesimilarity对比)
                 confident_detections++;//看看小D是不是眼花了
                  didx=i; //detectionindex
              }
          }
        /*----------------小T向小D学习------------------*/
          if(confident_detections==1){//小D没有眼花,而且看得比小T更清楚
              printf("Found a bettermatch..reinitializing tracking\n");//if there is ONEsuch a cluster, re-initialize the tracker
              bbnext=cbb[didx];//重新初始化要跟踪的目标
              lastconf=cconf[didx];//
              lastvalid=false; // 小T,你这一帧表现不好,所以这次小D就不跟你学习了
   }
          else {
          printf("%d confidentcluster was found\n",confident_detections);
              intcx=0,cy=0,cw=0,ch=0;
              intclose_detections=0;
              for (int i=0;i<dbb.size();i++){
                  if(bbOverlap(tbb,dbb[i])>0.7){ // Get mean of close detections
                      cx +=dbb[i].x;
                      cy +=dbb[i].y;
                      cw +=dbb[i].width;
                      ch +=dbb[i].height;
                     close_detections++;
                      printf("weighteddetection: %d %d %d %d\n",dbb[i].x,dbb[i].y,dbb[i].width,dbb[i].height);
                  }
              }
              if(close_detections>0){
                  bbnext.x =cvRound((float)(10*tbb.x+cx)/(float)(10+close_detections));// weighted averagetrackers trajectory with the close detections
                  bbnext.y =cvRound((float)(10*tbb.y+cy)/(float)(10+close_detections));
                  bbnext.width =cvRound((float)(10*tbb.width+cw)/(float)(10+close_detections));
                  bbnext.height=  cvRound((float)(10*tbb.height+ch)/(float)(10+close_detections));
                  printf("Trackerbb: %d %d %d %d\n",tbb.x,tbb.y,tbb.width,tbb.height);
                  printf("Average bb: %d%d %d %d\n",bbnext.x,bbnext.y,bbnext.width,bbnext.height);
                  printf("Weighting%d close detection(s) with tracker..\n",close_detections);
              }
              else{
                printf("%dclose detections were found\n",close_detections);
              }
          }
      }
  }
  else{                                      //   If NOT tracking
      printf("Nottracking..\n");
      lastboxfound = false;
      lastvalid = false;
      if(detected){                          //  and detector is defined
         clusterConf(dbb,dconf,cbb,cconf);  //  cluster detections
          printf("Found%d clusters\n",(int)cbb.size());
          if(cconf.size()==1){//大于1呢?,眼花了呗
              bbnext=cbb[0];//注意使用cbb来初始化
              lastconf=cconf[0];
              printf("Confidentdetection..reinitializing tracker\n");
              lastboxfound = true;
          }
      }
  }
  lastbox=bbnext; //
  if (lastboxfound)
    fprintf(bb_file,"%d,%d,%d,%d,%f\n",lastbox.x,lastbox.y,lastbox.br().x,lastbox.br().y,lastconf);
  else
    fprintf(bb_file,"NaN,NaN,NaN,NaN,NaN\n");
  if(lastvalid && tl)//tvalid
    //------------- 5. learn ---------------
    learn(img2);
}

    首先我觉得大家自己应该试着想一下,检测结果的可能出现的各种情况,以及该怎么处理。

    下面是我列出来的情况,以及作者是怎么处理的,算是对作者写这一部分代码时现场的还原吧,哈哈,有没有感觉像侦探在还原犯罪现场,so,不保证完全正确。

    (1)英雄所见略同,二者检测的目标区域很相似

条件,bbOverlap(tbb,cbb[i])>=0.5对所有cbb都满足,就没必要你争我抢的了,最终结果应该是综合小D和小T,可是作者是以小T为主,即小T参考小D的检测结果,表示不太公平,不过貌似也没有什么综合的好法子,能够平等对待小D,再说小T能跟踪到的目标,就已经表明这个目标具有帧与帧直接的连续性,是相对比较靠谱的。这种情况下分数一般还是会有差距的,有差距就说明存在学习的空间呀,所以相互学习一下也是极好的。

    1)如果小T的检测分数更高,那么还是让小D向小T学习下吧,虽说小D的检测结果已经比较好了,这样做的必要性不是很大,但是我们要精益求精嘛。

    2)如果小D的检测分数更高,作者并没有让小T学习小D,相反,是让小D学习小T,这个地方貌似有点不近情理。小T不用学习倒是可以理解,毕竟跟踪器需要学习的就是目标的新位置,而它的位置已经比较接近了,所以学习就显得没那么必要了。但是让小D学习小T,表示很郁闷,如果说小T的分数更低呢?你这不是误导小D吗?哈哈,这个就要到学习部分就知道了,剧透一下,小D学不学习小T还得过一关。我觉得,作者让小D学习小T的初衷,应该还是希望小D能够精益求精,但是对待小T可就没这么严格了。

    (2)小D眼瞎了……好吧,这个词的不太恰当,总之,小D没有检测到目标,也就是说,所有grid里面的bb都没通过小D的三道屏障。最后的结果自然是小T说了算,小D还得向小T虚心学习,这种情况可能是待跟踪的目标的外形发生了改变,比如遮挡。

    (3)小D眼花了

    这次小D检测到了目标,不过可疑的目标很多,但是TLD 1.0是单目标跟踪算法,如果出现多个可疑目标的话,那么检测结果自然是有问题的,判断方法:看if (confident_detections>1)是否成立了。

前面说过,虽然小D眼花了,但长兄为父,所以,最终结果还是得参考小T,即小T参考小D的检测结果,当然,小D也要向小T虚心学习

    (4)小D的结果比小T差

    小D的分数都没小T高,cconf[i]<=tconf,那么confident_detections=0,处理方式同上。

    (5)小D结果比小T好,而且双方的分歧很大

    判断方法:if(confident_detections==1),这种情况下,小T要向小D学习,霸气的小D自然是不需要向小T学习最终的结果由小D说了算

    其实,还有很多种情况的,比如,小T没跟踪到(可依据小D的结果,可再细分),小T和小D都没跟踪到。

    总之,这些情况下要做的事情主要是这三件:

  • 综合小D小T的检测结果
  • 小T向小D学习
  • 小D向小T学习

小T参考小D的检测结果

    注意,我用的是参考,所以有可能不理会小D,何时参考呢?,依据原则是bbOverlap(tbb,dbb[i])>0.7是否成立。

    1)小D的结果和小T比较接近:bbOverlap(tbb,dbb[i])>0.7

    2)小D错的离谱,还自以为是:bbOverlap(tbb,dbb[i])<=0.7

    具体参考方式:对bbOverlap(tbb, dbb[i])>0.7的dbb[i]加权求重心,宽和高,tbb的权重为10,而小D的权重为1,权重如此悬殊,是因为小T每次只能得到一个bb,而小D每次能得到很多个。

for (int i = 0; i<dbb.size(); i++){
   if(bbOverlap(tbb, dbb[i])>0.7){ // Getmean of close detections
      cx += dbb[i].x;
      cy += dbb[i].y;
      cw += dbb[i].width;
      ch += dbb[i].height;
      close_detections++;
      printf("weighteddetection: %d %d %d %d\n", dbb[i].x, dbb[i].y, dbb[i].width, dbb[i].height);
   }
}
if (close_detections>0){
   bbnext.x = cvRound((float)(10 *tbb.x + cx) / (float)(10 + close_detections));// weighted average trackers trajectory with theclose detections
   bbnext.y = cvRound((float)(10 *tbb.y + cy) / (float)(10 + close_detections));
   bbnext.width = cvRound((float)(10 *tbb.width + cw) / (float)(10 + close_detections));
   bbnext.height = cvRound((float)(10 *tbb.height + ch) / (float)(10 + close_detections));
   printf("Tracker bb: %d%d %d %d\n", tbb.x, tbb.y, tbb.width, tbb.height);
   printf("Average bb: %d%d %d %d\n", bbnext.x, bbnext.y, bbnext.width, bbnext.height);
   printf("Weighting %dclose detection(s) with tracker..\n", close_detections);
}

小T向小D学习

    我觉得看这一段的注释已经够直白了,不细说了,重点是bbnext=cbb[didx];//重新初始化要跟踪的目标

if (confident_detections==1){ //D没有眼花,而且看得比小T更清楚

           printf("Found a bettermatch..reinitializing tracking\n");//if there is ONEsuch a cluster, re-initialize the tracker

              bbnext=cbb[didx];//重新初始化要跟踪的目标

              lastconf=cconf[didx];//

              lastvalid=false; // T,你这一帧表现不好,所以这次小D就不跟你学习了

}

D向小T学习

   这一部分内容稍微多点,首先要明白小D之所以要学习,原因有两方面:

    (1)更新检测器

    因为目标的外形可能会不断变化,那么小D需要及时将这些变化的样本加入训练集重新训练。问题来了,目标外形变化之后,小D自然是很难检测到的,那谁来告我们哪些是要加入的新样本呢?好吧,看标题就知道,自然是小T,问题又来了,小T是能利用目标在帧与帧之间的连续性,跟踪到小D所不能检测到的目标,可是小T的结果不一定靠谱呀,如果小T错了,岂不导致小D也跟着错……。所以,我们还得甄别小T的结果是否靠谱,这一步很困难【5.6.2】。

 技术分享

    作者的方法看起来有点玄乎,其实只是最近邻分类器来判断,不过这个最近邻分类器的样本点加了时间限制,按照正样本加入的时间顺序,只保留前50%的正样本。图中,黑色点是负样本,红色点是正样本,都是特征空间!不是坐标空间!,×是轨迹的起点,然后就可以依据前50%的正样本点,得出最近邻的分界线,也就是图中黑色的圈圈。当跟踪的目标其在特征空间属于这个core区域就认为是有效的轨迹。这一部分实现在TLD::track的最后一步

 if(tconf>classifier.thr_nn_valid){//thr_nn_valid
          tvalid =true;//2.判定轨迹是否有效,从而决定是否要增加正样本,标志位tvalid【5.6.2P-Expert】
  }

   既然轨迹是有效的,那么就认为这个样本是真正的正样本,于是和初始化类似,取周围10个最接近的bb,然后仿射变换由1->10,最后能得到100个正样本。这一部分和下面部分的代码实现都在TLD::learn中,后面一并给出。

    这一部分是为检测器样本输送比较靠谱的正样本,也就是原文说的P-expert。

P-expert exploitsthe temporal structure in the video and assumes that the object moves along atrajectory. The P-expert remembers the location of the object in the previousframe and estimates the object location in current frame using a frame-to-frametracker. If the detector labeled the current location as negative (i.e., madefalse negative error), the P-expert generates a positive example.

    (2)校正检测器

    如果检测器的结果不如跟踪器,那就说明检测器有可能错了,有错自然要改。首先我们要清楚,何时有错,由于是单目标跟踪,于是只能有一个正确的目标,当跟踪器的结果靠谱的时候,那么目标周围的窗口就都是负样本(当然还是得保持一点距离,于是原文认为与目标面积的交/并<0.2的窗口都是负样本),直觉上,我们觉得这样肯定是有风险的,到底有没有风险呢?参见原文4.2 节。

    这里再详细介绍一下负样本。我们的目的是要用新的负样本训练分类器,但是,我们都知道用hard negative训练更为有效,于是我们还要对负样本进行筛选,对于随机森林分类器而言,只有能通过第一关(方差)的负样本才有必要加入它的训练集(其实只是统计直方图)。对于最近邻分类器而言,只有连续通过前两关的负样本才有资格作为最近邻的负样本,不过,对于那些还能被检测器误认为是正样本的检测窗口来说,将它们作为负样本,效果肯定更好,而且最近邻分类器的分类时间直接和样本数目成正比例关系,所以也负担不起太多负样本呀,于是作者就这么干了【5.6.3 N-Expert】。

N-expert exploitsthe spatial structure in the video and assumes that the object can appear at asingle location only.The N-expert analyzes all responses of the detector in thecurrent frame and the response produced by the tracker and selects the one thatis the most confident. Patches that are not overlapping with the maximallyconfident patch are labeled as negative. The maximally confident patchreinitializes the location of the tracker.

 技术分享

P-N Learning:Bootstrapping Binary Classifiers by Structural Constraints CVPR 2010

    上图中红色bb即依据track是否有效而添加的hard negative。

技术分享


   上图就是一个校正检测器的例子,带*号的是N-Expert认为的目标区域,黄色bb是P-expert所认为的目标区域,t+2时,P-expert错了,而N-Expert及时发现了这一错误,那么下一次训练的时候就可以校正了。这张图,我认为和作者的P-expert ,N-Expert是可以达到这个目的,但是他的代码实现并不能达到这个目的。因为作者的P-expert,N-Expert都是建立在跟踪轨迹有效的基础上,那么目标区域只有一个,而图中是有两个,所以必定也要给N-Expert赋予能找到目标的能力,而代码实现部分,他只有找负样本的能力。So,这一部分我很迷惑,希望得到高人指点。

   最后,再看一个一维的图示。

技术分享

   注意看上面的图,红色点应该是检测器的输出,结合跟踪的轨迹之后,与轨迹不符合的红色点被标成了黑色,即负样本,对应检测器的校正,而蓝色点是新增的正样本,即对应检测器的更新。当然,这是比较理想的情况,跟踪器一直比较稳定,而检测器不是很稳定。

TLD::learn函数

   对照前面的内容,这段程序应该说得比较清楚了吧.

void TLD::learn(const Mat& img){// current_gray
  printf("[Learning]");
  ///Check consistency
  BoundingBox bb;
  bb.x = max(lastbox.x,0); //lastbox
  bb.y = max(lastbox.y,0);
  bb.width = min(min(img.cols-lastbox.x,lastbox.width),min(lastbox.width,lastbox.br().x));
  bb.height = min(min(img.rows-lastbox.y,lastbox.height),min(lastbox.height,lastbox.br().y));
  Scalar mean,stdev;
  Matpattern;
  getPattern(img(bb),pattern,mean,stdev);//pattern:resizedZero-Mean patch,为什么要弄成0均值呢?是算相关系数
  vector<int>isin;
  float dummy,conf;
  // 1. 再粗略地检测一遍lastbox,因为结果是加权的,如果偏差很大,岂不是误导检测器
 classifier.NNConf(pattern,isin,conf,dummy);
  if(conf<0.5) {//Relative Similarity,注意:nn_thr >= 0.65,所以阈值降低了,因为我们要迎接新人
      printf("Fastchange..not training\n");//形变是缓慢的,你如此不同,应该不是同类
      lastvalid =false;
      return;
  }
  if(pow(stdev.val[0],2)<var){
      printf("Lowvariance..not training\n");
      lastvalid=false;
      return;
  }
  if(isin[2]==1){//是否是负样本
      printf("Patchin negative data..not traing");
      lastvalid=false;
      return;
  }
/// Data generation
  for (inti=0;i<grid.size();i++){//为getOverlappingBoxes函数预先计算grid[i].overlap
      grid[i].overlap =bbOverlap(lastbox,grid[i]);
  }
  vector<pair<vector<int>,int>> fern_examples;
  good_boxes.clear();
  bad_boxes.clear();
  // 2. 用lastbox,重新计算good,bad,bestbb还有 bbhull
 getOverlappingBoxes(lastbox,num_closest_update);//num_closest_update: 10
  if(good_boxes.size()>0)
  // 3. 更新这一帧的 pX pEx,【5.6.2P-Expert】
    generatePositiveData(img,num_warps_update);//注意:是用best_box,而不是lastbox
  else{
    lastvalid = false;
    printf("No goodboxes..Not training");
    return;
  }
 fern_examples.reserve(pX.size()+bad_boxes.size());
 fern_examples.assign(pX.begin(),pX.end());
  int idx;
  // 4. 从bad_boxes挑选hardnegative作为新增的随机森林训练负样本集【5.6.3N-Expert】
  for (inti=0;i<bad_boxes.size();i++){
      idx=bad_boxes[i];
      if(tmp.conf[idx]>=1){//回忆一下 grid->方差->结果存在tmp,conf是随机森林的分数
         fern_examples.push_back(make_pair(tmp.patt[idx],0));
      }
  }
  // 5. 从dt.bb中挑选hardnegative作为新增的最近邻分类器的负样本集【5.6.3N-Expert】
  vector<Mat> nn_examples;
 nn_examples.reserve(dt.bb.size()+1);
  nn_examples.push_back(pEx);//唯一一个正样本
  for (inti=0;i<dt.bb.size();i++){
      idx = dt.bb[i];
      if(bbOverlap(lastbox,grid[idx]) < bad_overlap)
       nn_examples.push_back(dt.patch[i]);
  }
  /// 6. Classifiersupdate
 classifier.trainF(fern_examples,2);
  classifier.trainNN(nn_examples);
  //问题:fern_examples和nn_examples都是新的数据,完全没有用到之前的pX,pEx,nx,nEx???
  //原来,随机森林分类器只要保存直方图统计就可以了,所以不需要存储正负样本集
  //而最近邻分类器,并没有clearpEx,nEx,而是不断地追加正负样本
  classifier.show();
}

上升到P-NLearning

    Ref:P-N Learning Bootstrapping Binary Classifiers by StructuralConstraints CVPR 10

 技术分享

技术分享

技术分享

    这一部分以后再写,准备回家……

TLD之学习篇(四)