TLD的源码整理总结工作————准备篇

   TLD(Tracking-Learning-Detection)是英国萨里大学的一个捷克籍博士生Zdenek Kalal在其攻读博士学位期间提出的一种新的单目标长时间(long term tracking)跟踪算法。该算法与传统跟踪算法的显著区别在于将传统的跟踪算法和传统的检测算法相结合来解决被跟踪目标在被跟踪过程中发生的形变、部分遮挡等问题。同时,通过一种改进的在线学习机制不断更新跟踪模块的“显著特征点”和检测模块的目标模型及相关参数,从而使得跟踪效果更加稳定、鲁棒、可靠。

       源码下载:

https://github.com/arthurv/OpenTLD


TLD的整个流程

     1、先初始化工作。

       (1) 以已经给出的已知目标去训练样本库(所谓的样本库就是存有许多目标特征的记忆库)

               在训练样本之前先对图像进行网格化处理;

               

            void TLD::buildGrid(  cv::Mat& img, const cv::Rect& box){

  const float SHIFT = 0.1;

  const float SCALES[] = {0.16151,0.19381,0.23257,0.27908,0.33490,0.40188,0.48225,

                          0.57870,0.69444,0.83333,1,1.20000,1.44000,1.72800,

                          2.07360,2.48832,2.98598,3.58318,4.29982,5.15978,6.19174};

  //cv::Mat Mat_show(img);

 // Mat_show.copyTo;

  int width, height, min_bb_side;

  //Rect bbox;

  BoundingBox bbox;

  Size scale;

  int sc=0;

  for (int s=0;s<21;s++){

    width = round(box.width*SCALES[s]);            //输入的box的宽、高乘一个scale                    //参数Rect

    height = round(box.height*SCALES[s]);

    min_bb_side = min(height,width);               //求出box后的宽、高最小值

    if (min_bb_side < min_win || width > img.cols || height > img.rows)//min_win=???????

      continue;

    scale.width = width;

    scale.height = height;

    scales.push_back(scale);   //保存21个不同scale的的Size的宽高

 //按照SHIFT*min_bb_side(宽高的最小值)的步长历遍整个相框,求出不过的scale的Rect;

    for (int y=1;y<img.rows-height;y+=round(SHIFT*min_bb_side)){

      for (int x=1;x<img.cols-width;x+=round(SHIFT*min_bb_side)){

        bbox.x = x;

        bbox.y = y;

        bbox.width = width;

        bbox.height = height;

  //rectangle(img,Point(bbox.x,bbox.y),Point( bbox.x+bbox.width,bbox.y+bbox.height),Scalar(255,255,255));///

  // imshow("img",img);//Mat frame;


  // circle(img,Point(),2,Scalar())

  // drawBox( img, CvRect box, Scalar color, int thick)

        bbox.overlap = bbOverlap(bbox,BoundingBox(box));

        bbox.sidx = sc;

        grid.push_back(bbox);

      }

    }

    sc++;

  }  

}

  整个函数的作用是将图片经过一定的变化和比例,获得不同比例下的网格,每一个比例都有一个整图的网格,并且对所有的网格进行编             码,这个编码(grid.push_back(bbox));由于图片的大小没有改变而是固定的;并获得与已知目标的的(交集与并集相除的结果存于)overlap中。

     (2)获得好box,坏的box,最好的box,(所谓的好坏就是网格的box与目标的box的overlap(交集除并集))

                   如果是大于0.6,则认为是好的box。

                    如果是小于0.2,则认为是坏的box。

                    当然,最大的值是最好的box。

                 (好的box 的当”决策蕨“做正的样本pX,坏的box“决策蕨x则认为是负样本nX,最好的的box用于“”最邻近“的正样本pEx) 

                 接着是把正负的样本整合到一起(用到generateNegativeData函数和generatePositiveData函数):

        (2.1)  在进行把好的box、坏的box、最好的box被处理为样本之前还要经过一些准备

                     1、(classifier.prepare(scales);)、这个准备只要是为了决策蕨算法的特征提取

  (这里只要参考博客:http://johnhany.net/2014/05/tld-the-theory/#imageclose-568

  集成分类器(Ensemble Classifier)。实际上是一个随机蕨分类器(Random Ferns Classifier),类似于随机森林(Random Forest),区别在于随机森林的树中每层节点判断准则不同,而随机蕨的“蕨”中每层只有一种判断准则。


image-574

        如上图所示,把左面的树每层节点改成相同的判断条件,就变成了右面的蕨。所以蕨也不再是树状结构,而是线性结构。随机蕨分类器根据样本的特征值判断其分类。从图像元中任意选取两点A和B,比较这两点的亮度值,若A的亮度大于B,则特征值为1,否则为0。每选取一对新位置,就是一个新的特征值。蕨的每个节点就是对一对像素点进行比较。

        比如取5对点,红色为A,蓝色为B,样本图像经过含有5个节点的蕨,每个节点的结果按顺序排列起来,得到长度为5的二进制序列01011,转化成十进制数字11。这个11就是该样本经过这个蕨得到的结果。


image-575

        同一类的很多个样本经过同一个蕨,得到了该类结果的分布直方图。高度代表类的先验概率p(F|C),F代表蕨的结果(如果蕨有s个节点,则共有1+2^s种结果)。


image-576

        不同类的样本经过同一个蕨,得到不同的先验概率分布。


image-577
        以上过程可以视为对分类器的训练。当有新的未标签样本加入时,假设它经过这个蕨的结果为00011(即3),然后从已知的分布中寻找后验概率最大的一个。由于样本集固定时,右下角公式的分母是相同的,所以只要找在F=3时高度最大的那一类,就是新样本的分类。

image-578
        只用一个蕨进行分类会有较大的偶然性。另取5个新的特征值就可以构成新的蕨。用很多个蕨对同一样本分类,投票数最大的类就作为新样本的分类,这样在很大程度上提高了分类器的准确度。

(classifier.prepare(scales)这个函数就是说:

 每一个仿射图片(仿射图片的位置大小为bbHull(等同所有好的box的并集所围成的大矩形))有nstructs颗蕨,每颗蕨有structSize个特征;所以一共int totalFeatures = nstructs*structSize种特征要提取,但是由于每一个box都有21种比例,而且每个的特征点的位置是不一样的,尽量涵盖整个图片,所以有

features = vector<vector<Feature> >(scales.size(),vector<Feature> (totalFeatures));

  RNG& rng = theRNG();

  float x1f,x2f,y1f,y2f;

  int x1, x2, y1, y2;

  for (int i=0;i<totalFeatures;i++){

      x1f = (float)rng;

      y1f = (float)rng;

      x2f = (float)rng;

      y2f = (float)rng;

      for (int s=0;s<scales.size();s++){

          x1 = x1f * scales[s].width;

          y1 = y1f * scales[s].height;

          x2 = x2f * scales[s].width;

          y2 = y2f * scales[s].height;

          features[s][i] = Feature(x1, y1, x2, y2);

      }

  }

(classifier.prepare(scales)的函数只会运行一次,所以它的每种比例的的特征点都是固定不变的,

(2.2)样本特征的提取

           2..21决策蕨的特征提取

          FerNNClassifier::getFeatures(const cv::Mat& image,const int& scale_idx, vector<int>& fern)

          获得box碎片的的nstructs颗蕨特征,并存在vector<int>中,过程等同上面的博客蕨的过程和

正样本:

    详见函数(generatePositiveDataData)

     for (int i=0;i<num_warps;i++){

     if (i>0)

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

  //   cv::imshow ("warped",warped);

 //    cv::imshow ("frame",img);

       for (int b=0;b<good_boxes.size();b++){

         idx=good_boxes[b];

   patch = img(grid[idx]);

 // cv::imshow ("p",patch);

         classifier.getFeatures(patch,grid[idx].sidx,fern);

         pX.push_back(make_pair(fern,1));


负样本:详见函数(generateNegativeData)

 for (int j=0;j<bad_boxes.size();j++){

      idx = bad_boxes[j];

          if (getVar(grid[idx],iisum,iisqsum)<var*0.5f)

            continue;

      patch =  frame(grid[idx]);

   classifier.getFeatures(patch,grid[idx].sidx,fern);

      nX.push_back(make_pair(fern,0));

      a++;

  }



2.2  最近邻分类器算法:

  最近邻分类器(Nearest Neighbor Classifier)。计算新样本的相对相似度,如大于0.6,则认为是正样本。相似度规定如下:

        图像元pi和pj的相似度,公式里的N是规范化的相关系数,所以S的取值范围就在[0,1]之间,


image-579
        正最近邻相似度,

image-580

        负最近邻相似度,


image-581

        相对相似度,取值范围在[0,1]之间,值越大代表相似度越高,


image-582

        所以,检测器是追踪器的监督者,因为检测器要改正追踪器的错误;而追踪器是训练检测器时的监督者,因为要用追踪器的结果对检测器的分类结果进行监督。用另一段程序对训练过程进行监督,而不是由人来监督,这也是称P-N学习为“半监督”机器学习的原因。

        TLD的工作流程如下图所示。首先,检测器由一系列包围框产生样本,经过级联分类器产生正样本,放入样本集;然后使用追踪器估计出物体的新位置,P专家根据这个位置又产生正样本,N专家从这些正样本里选出一个最可信的,同时把其他正样本标记为负;最后用正样本更新检测器的分类器参数,并确定下一帧物体包围框的位置。

TLD-workflow
最近邻算法的样本的提取:
        最近邻算法的正样本的提取pEx:
       详见:gerneratePositiveData函数:
       getPattern(frame(best_box),pEx,mean,stdev);
void TLD::getPattern(const Mat& img, Mat& pattern,Scalar& mean,Scalar& stdev){
  //Output: resized Zero-Mean patch
  resize(img,pattern,Size(patch_size,patch_size));
  meanStdDev(pattern,mean,stdev);
  pattern.convertTo(pattern,CV_32F);
  pattern = pattern-mean.val[0];
}
这个函数的作用是resize图片的大小,变成统一大小的图片;然后求图片的均值,标准差等;
最后把一通道图片转换为32位浮点型,并减去其均值,获得近邻的正样本pEx;
近邻的负样本:同样的方法(详见generateNegativeData函数
     nEx=vector<Mat>(bad_patches);
  for (int i=0;i<bad_patches;i++){
      idx=bad_boxes[i];
   patch = frame(grid[idx]);
      getPattern(patch,nEx[i],dum1,dum2);
   std::cout<<"bad_patches>>>>>>>>>>>>"<<bad_patches<<std::endl;
  }

  

把蕨的算法正负样本的整合为(ferns_data):(详见函数TLD_init)

 vector<pair<vector<int>,int> > ferns_data(nX.size()+pX.size());

  vector<int> idx = index_shuffle(0,ferns_data.size());

  int a=0;

  for (int i=0;i<pX.size();i++){

      ferns_data[idx[a]] = pX[i];

      a++;

  }

  for (int i=0;i<nX.size();i++){

      ferns_data[idx[a]] = nX[i];

      a++;

 
   接下来是 近算法的样本的整合在一起:
   vector<cv::Mat> nn_data(nEx.size()+1);
  nn_data[0] = pEx;
  for (int i=0;i<nEx.size();i++){
      nn_data[i+1]= nEx[i];
  }
 

两种的算法的不同之处是,近邻算法的正样本只有一个,其他的都是负样本了

3、现在有了样本,就要进行训练样本,更新样本库
    3.1、 决策蕨的算法classifier.trainF(ferns_data,2)
            训练的过程是:
             设定阀值:th_P=thr_fern*nstructs
              由于样本经过整合,则正样本的第二值ferns[i].second==1,负样本的的第二值是ferns[i].second==0
 FerNNClassifier::measure_forest(vector<int> fern)函数获得 nstructs颗蕨所对应的特征的后验概率的和;
 (posteriors[i][fern[i]]对应在的是是第几颗蕨的fern[i]特征的后验概率)
  float FerNNClassifier::measure_forest(vector<int> fern) {
  float votes = 0;
  for (int i = 0; i < nstructs; i++) {
      votes += posteriors[i][fern[i]];
  }

      
     后验概率的初始化工作FerNNClassifier::prepare(const vector<Size>& scales)函数中,如下
 定义:
std::vector< std::vector<int> > nCounter; //negative counter
  std::vector< std::vector<int> > pCounter; //positive counter
  std::vector< std::vector<float> > posteriors; //Ferns posteriors

从posteriors的定义可以知道,posteriors是vector<vector >容器,等同一个二维的数组,
下面的for循环是, 利用 posteriors.push_back(vector<float>(pow(2.0,structSize), 0))一次给每一行的数组赋值为0,
而且每一行的大小为(pow(2.0,structSize),则是有(pow(2.0,structSize)个"0",因为有structSize种特征点,每个特征点不是0就是1,有两种结果,所有一共有2的structSize次方种(pow(2.0,structSize),则。
详见:prepare函数:
   for (int i = 0; i<nstructs; i++) {    
      posteriors.push_back(vector<float>(pow(2.0,structSize), 0));
   std::cout<<"pow(2.0,structSize)****************************"<<pow(2.0,structSize)<<std::endl;
      pCounter.push_back(vector<int>(pow(2.0,structSize), 0));
      nCounter.push_back(vector<int>(pow(2.0,structSize), 0));
  }

   更新的过程:详见trainFF函数:
 如果正样本的后验概率小阀值,则更新其每颗蕨所所对应的特征的后验概率,
                  if(ferns[i].second==1){                           //       if (Y[I] == 1) {
              if(measure_forest(ferns[i].first)<=thrP)      //         if (measure_forest(x) <= thrP)
                update(ferns[i].first,1,1);                 //             update(x,1,1);
          }else{                                            //        }else{
              if (measure_forest(ferns[i].first) >= thrN)   //         if (measure_forest(x) >= thrN)
                update(ferns[i].first,0,1);                 //             update(x,0,1);
          }
 如果正样本的后验概率小于阀值,说明样本库需要学习,增加P专家(  pCounter),目标是确定的,二后验概率小阀值,说明了目标的有了新的特征或者被当成负样本,所以需要学习,
如果是负样本的后验概率的大于阀值,说明的了之前样本库的后验概率所保存的正样本特征已经变成负特征(实在不知道怎么解释),所以需要增加N专家;

最邻近算法:trainNN:
 详见:FerNNClassifier::trainNN(const vector<cv::Mat>& nn_examples)函数

  for (int i=0;i<nn_examples.size();i++){                          //  For each e--xample
      NNConf(nn_examples[i],isin,conf,dummy);                      //  Measure Relative similarity
      if (y[i]==1 && conf<=thr_nn){                                //    if y(i) == 1 && conf1 <= tld.model.thr_nn % 0.65
          if (isin[1]<0){                                          //      if isnan(isin(2))
              pEx = vector<Mat>(1,nn_examples[i]);                 //        tld.pex = x(:,i);
              continue;                                            //        continue;
          }                                                        //      end
          //pEx.insert(pEx.begin()+isin[1],nn_examples[i]);        //      tld.pex = [tld.pex(:,1:isin(2)) x(:,i) tld.pex(:,isin(2)+1:end)]; % add to model
          pEx.push_back(nn_examples[i]);
      }                                                            //    end
      if(y[i]==0 && conf>0.5)                                      //  if y(i) == 0 && conf1 > 0.5
        nEx.push_back(nn_examples[i]);                             //    tld.nex = [tld.nex x(:,i)];
  
这里涉及函数NNConf;
  1. void FerNNClassifier::NNConf(const Mat& example, vector<int>& isin,float& rsconf,float& csconf){  
  2.   isin=vector<int>(3,-1);  //vector<T> v3(n, i); v3包含n个值为i的元素。 三个元素都是-1  
  3.   if (pEx.empty()){ //if isempty(tld.pex) % IF positive examples in the model are not defined THEN everything is negative  
  4.       rsconf = 0; //    conf1 = zeros(1,size(x,2));  
  5.       csconf=0;  
  6.       return;  
  7.   }  
  8.   if (nEx.empty()){ //if isempty(tld.nex) % IF negative examples in the model are not defined THEN everything is positive  
  9.       rsconf = 1;   //    conf1 = ones(1,size(x,2));  
  10.       csconf=1;  
  11.       return;  
  12.   }  
  13.   Mat ncc(1,1,CV_32F);  
  14.   float nccP, csmaxP, maxP=0;  
  15.   bool anyP=false;  
  16.   int maxPidx, validatedPart = ceil(pEx.size()*valid);  //ceil返回大于或者等于指定表达式的最小整数  
  17.   float nccN, maxN=0;  
  18.   bool anyN=false;  
  19.   //比较图像片p到在线模型M的距离(相似度),计算正样本最近邻相似度,也就是将输入的图像片与  
  20.   //在线模型中所有的图像片进行匹配,找出最相似的那个图像片,也就是相似度的最大值  
  21.   for (int i=0;i<pEx.size();i++){  
  22.       matchTemplate(pEx[i], example, ncc, CV_TM_CCORR_NORMED);      // measure NCC to positive examples  
  23.       nccP=(((float*)ncc.data)[0]+1)*0.5;  //计算匹配相似度  
  24.       if (nccP>ncc_thesame)  //ncc_thesame: 0.95  
  25.         anyP=true;  
  26.       if(nccP > maxP){  
  27.           maxP=nccP;    //记录最大的相似度以及对应的图像片index索引值  
  28.           maxPidx = i;  
  29.           if(i<validatedPart)  
  30.             csmaxP=maxP;  
  31.       }  
  32.   }  
  33.   //计算负样本最近邻相似度  
  34.   for (int i=0;i<nEx.size();i++){  
  35.       matchTemplate(nEx[i],example,ncc,CV_TM_CCORR_NORMED);     //measure NCC to negative examples  
  36.       nccN=(((float*)ncc.data)[0]+1)*0.5;  
  37.       if (nccN>ncc_thesame)  
  38.         anyN=true;  
  39.       if(nccN > maxN)  
  40.         maxN=nccN;  
  41.   }  
  42.   //set isin  
  43.   //if he query patch is highly correlated with any positive patch in the model then it is considered to be one of them  
  44.   if (anyP) isin[0]=1;    
  45.   isin[1]=maxPidx;      //get the index of the maximall correlated positive patch  
  46.   //if  the query patch is highly correlated with any negative patch in the model then it is considered to be one of them  
  47.   if (anyN) isin[2]=1;   
  48.     
  49.   //Measure Relative Similarity  
  50.   //相关相似度 = 正样本最近邻相似度 / (正样本最近邻相似度 + 负样本最近邻相似度)  
  51.   float dN=1-maxN;  
  52.   float dP=1-maxP;  
  53.   rsconf = (float)dN/(dN+dP);  
  54.     
  55.   //Measure Conservative Similarity  
  56.   dP = 1 - csmaxP;  
  57.   csconf =(float)dN / (dN + dP);  
  58. }  
就是所有train NN的正样本库的(Mat 类型)与样本进行了匹配,找到最大的匹配值
 float dN=1-maxN;
  float dP=1-maxP;
  rsconf = (float)dN/(dN+dP);
  //Measure Conservative Similarity
  dP = 1 - csmaxP;
  csconf =(float)dN / (dN + dP);

如果rsconf = (float)dN/(dN+dP)<=thr_nn;//标签是正样本,如果相关相似度小于0.65 ,则认为其不含有前景目标,也就是分类错误了;这时候就把它加到正样本库  
如果 if(y[i]==0 && conf>0.5) //道理和上面的决策蕨的算法有点类似
        nEx.push_back(nn_examples[i]);     
现在都训练和初始化所有的样本,接下来是检测和跟踪,学习     

初学者,个人自己理解有误,所以有不足的地方,敬请谅解,希望能得到大神的指点;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值