TLD之检测篇(二)

TLD之检测篇(二)

TLD之扯淡篇(一)TLD之跟踪篇(三)TLD之学习篇(四)

扫描方式前面已经说过,具体参数【5.3】:scales step =1.2, horizontal step =10 percent of width, vertical step =10 percent of height, minimal bounding box size = 20 pixels. This setting produces around 50k bounding
boxes for a QVGA image (240×320), the exact number depends on the aspect ratio of the initial bounding box.

TLD分类器的三道关卡

如上图所示,TLD的检测算法共有三关:

第一关:方差

使用简单的阈值判断,阈值设定为初始选择目标方差的50%(存储在TLD::var),见【5.3.1】50
percent of variance of the patch that was selected  for tracking。

第二关:随机森林分类器

TLD特征

首先介绍一下TLD所使用的特征,2bit BP,很简单,就是任意两个点的大小关系,取值只有0和1。结合后面的分类器,我更倾向于将特征定义为0/1组成的串/向量。具体来   说,首先随机产生13对坐标,然后比较对应坐标像素值的大小,得到13个0/1,最后依次组成13位二进制数,也就可以看出一个整数。为了去噪,预先对图像进行了高斯滤波。不过图TLD特征中,只有10位。

TLD所使用的随机森林相当简单,一共有10棵树,每一棵树的分类特征就是上述的13位二进制串,与CART,ID3等决策树不同,它没有特征选择,它只是对特征分布进行了直方图统计,所以,每一个树可以看成一个贝叶斯分类器,而且直方图的区间数多到2^13。每一棵树分类的方法特么简单,正样本的概率为特征对应的区间正负样本数之比。最后对所有树的分类结果求和即为随机森林的最终分类结果,即正样本的概率。原文【5.3.2】说结果的平均值超过0.5就当做正样本,不过,实际代码中是超过0.6才算正样本,而结果超过0.5的负样本才作为随机森林重新训练的负样本,即hard
negative。

下面直接看源码实现

FerNNClassifier::prepare实现确定特征的比对位置,它在TLD::init函数中被调用,主要是这几个变量features[s][i]、thrN、 posteriors、pCounter、nCounter,命名非常直白,含义就不多说了。

void FerNNClassifier::prepare(const vector<Size>& scales){
  acum = 0;
  // 1. Initialize test locations for features
  //   随机产生需要坐标对(x1f,y1f,x2f,y2f,注意范围[0,1)),
  //   即确定由每一个特征是由哪些点对进行而得到,这些位置一旦确定就不会改变,
  //   由于我们要进行多尺度检测,所以同一个点在不同尺度scales,实际对应的坐标要乘以对应尺度的width和height。
  int totalFeatures = nstructs*structSize;//nstructs 10 structSize 13
  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; //产生[0,1)直接的浮点数
      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);
      }
  }
  // 2. Thresholds,负样本的阈值
  thrN = 0.5*nstructs;
  // 3. Initialize Posteriors,为统计直方图分配空间
  for (int i = 0; i<nstructs; i++) {
      posteriors.push_back(vector<float>(pow(2.0,structSize), 0));
      pCounter.push_back(vector<int>(pow(2.0,structSize), 0));
      nCounter.push_back(vector<int>(pow(2.0,structSize), 0));
  }
}

确定了由哪些点对得到特征后,就可以获取给定图像块(patch)的特征了,scale_idx是图像块的尺度索引,fern[t]就是所提取的第t个特征,前面说过,特征是一个13位二进制数,也就是一个整数。

void FerNNClassifier::getFeatures(const cv::Mat&image,constint& scale_idx, vector<int>& fern){
  int leaf;
  for (int t=0;t<nstructs;t++){
      leaf=0;
      for (int f=0; f<structSize; f++){
        //依次得到每一位
          leaf = (leaf << 1) + features[scale_idx][t*nstructs+f](image);
      }
      fern[t]=leaf;
  }
}

能够提取特征了,那么接下来就可以训练随机森林分类器,这部分稍微分的有点细,首先介绍训练函数FerNNClassifier::trainF(ferns,resample),ferns是训练集,即正/负样本的特征,不过呢,类别标号ferns[i].second==1即为正样本,resample是bootstrap的次数,其实函数实现时只有一轮,bootstrap是用容易分错的正样本和负样本更新分类器,measure_forest就是该分类器的分类函数,update是更新函数。

void FerNNClassifier::trainF(const vector<std::pair<vector<int>,int> >&ferns,intresample){
  thrP = thr_fern*nstructs; //0.6*10
      for (int i = 0; i <ferns.size(); i++){
          if(ferns[i].second==1){//正样本
              if(measure_forest(ferns[i].first)<=thrP)
                update(ferns[i].first,1,1);
          }else{//负样本
              if (measure_forest(ferns[i].first) >= thrN)
                update(ferns[i].first,0,1);
          }
      }
}

FerNNClassifier::measure_forest就是前面提到的,将10棵树的概率求和

float FerNNClassifier::measure_forest(vector<int>fern) {
  float votes = 0;
  for (int i = 0; i < nstructs; i++) {
      votes += posteriors[i][fern[i]];
  }
  return votes;
}

   update更新正负样本的直方图分布,注意:posteriors只算了正样本的概率

void FerNNClassifier::update(const vector<int>& fern,intC, int N) {
  int idx;
  for (int i = 0; i < nstructs; i++) {//10
      idx = fern[i];//13位的特征
     //C=1,正样本,C=0,负样本
      (C==1) ? pCounter[i][idx] +=N : nCounter[i][idx] +=N;
      if (pCounter[i][idx]==0) {//既然是正概率,如果正样本的数目为0,正样本的概率自然也为0
          posteriors[i][idx] = 0;
      } else {
          posteriors[i][idx] = ((float)(pCounter[i][idx]))/(pCounter[i][idx] + nCounter[i][idx]);
      }
  }
}

第三关:最近邻分类器

    最近邻分类器顾名思义咯,与随机森林一样,其中也涉及到特征、训练函数、分类函数。

   特征其实就是将图像块大小归一化(都变成patch_size×patch_size),零均值化【5.1】

void TLD::getPattern(const Mat& img, Mat& pattern,Scalar&mean,Scalar&stdev){
  resize(img,pattern,Size(patch_size,patch_size));
  meanStdDev(pattern,mean,stdev);
  pattern.convertTo(pattern,CV_32F);
  pattern = pattern-mean.val[0];
}

训练函数trainNN,我觉得最近邻分类器其实没有所谓的训练,因为只需要将容易分错的正/负样本加入正负样本集就可以了。其中,pEx是正样本集,nEx是负样本集。

void FerNNClassifier::trainNN(const vector<cv::Mat>& nn_examples){
  float conf,dummy;
  vector<int> y(nn_examples.size(),0);
  y[0]=1;//只有第一个是正样本,并不是原始的目标区域,而是best_box
  vector<int> isin;
  for (int i=0;i<nn_examples.size();i++){//  For each example
      NNConf(nn_examples[i],isin,conf,dummy);// Measure Relative similarity
      if (y[i]==1 && conf<=thr_nn){
          if (isin[1]<0){ //注意:如果pEx为空,NNConf直接返回 thr_nn=0,isin都为-1,
              pEx = vector<Mat>(1,nn_examples[i]);
              continue;
          }
          pEx.push_back(nn_examples[i]);//之前存在正样本,追加
      }
      if(y[i]==0 && conf>0.5)
        nEx.push_back(nn_examples[i]);
  }
  acum++;
  printf("%d. Trained NN examples: %d positive %d negative\n",acum,(int)pEx.size(),(int)nEx.size());
}

分类函数NNConf,计算的就是待分类样本example和NN分类器中所有正负样本的距离,距离是酱紫计算的(见OpenCV refermanual):

好吧,这个我也是第一次见,不过这个和相关系数特别像:

区别是没有减去均值,还记得我们前面提到图像块都进行了零均值化,因此距离就是计算相关系数……

不过,还要进行一些处理才方便作为距离测度, 相关系数的取值范围是[-1,1],加上1变成[0,2],再将范围缩小为[0,1]

    

相似性包含两种,Relative similarity和Conservative similarity,具体见【5.2】,不过这个版本采用了另一种计算方式,大家自己领会一下吧,我也说不上哪个好。

void FerNNClassifier::NNConf(const Mat& example, vector<int>& isin,float&rsconf,float&csconf){
  isin=vector<int>(3,-1);
  if (pEx.empty()){ //if isempty(tld.pex) % IF positive examples in the model are not defined THEN everything is negative
      rsconf = 0; //    conf1 = zeros(1,size(x,2));
      csconf=0;
      return;
  }
  if (nEx.empty()){ //if isempty(tld.nex) % IF negative examples in the model are not defined THEN everything is positive
      rsconf = 1;   //    conf1 = ones(1,size(x,2));
      csconf=1;
      return;
  }
  Mat ncc(1,1,CV_32F);
  float nccP,csmaxP,maxP=0;
  bool anyP=false;
  int maxPidx,validatedPart = ceil(pEx.size()*valid);//正样本的前 50%,用于计算Conservative similarit【5.2 5】
  float nccN, maxN=0;
  bool anyN=false;
  for (int i=0;i<pEx.size();i++){
      matchTemplate(pEx[i],example,ncc,CV_TM_CCORR_NORMED);// measure NCC to positive examples
     //相关系数的取值范围是[-1,1],加上1变成[0,2],再将范围缩小为[0,1]
      nccP=(((float*)ncc.data)[0]+1)*0.5;
      if (nccP>ncc_thesame)//0.95
        anyP=true;
      if(nccP > maxP){
          maxP=nccP;//Relative similarity
          maxPidx = i;
          if(i<validatedPart)
            csmaxP=maxP;//Conservative similari
      }
  }
  for (int i=0;i<nEx.size();i++){
      matchTemplate(nEx[i],example,ncc,CV_TM_CCORR_NORMED);//measure NCC to negative examples
      nccN=(((float*)ncc.data)[0]+1)*0.5;
      if (nccN>ncc_thesame)
        anyN=true;
      if(nccN > maxN)
        maxN=nccN;
  }
  //set isin
  if (anyP) isin[0]=1;  //if he query patch is highly correlated with any positive patch in the model then it is considered to be one of them
  isin[1]=maxPidx;      //get the index of the maximall correlated positive patch
  if (anyN) isin[2]=1;  //if  the query patch is highly correlated with any negative patch in the model then it is considered to be one of them
  //Measure Relative Similarity
  float dN=1-maxN;
  float dP=1-maxP;
  rsconf = (float)dN/(dN+dP); //与原文【5.2】有出入,不过也是可以理解的
  //Measure Conservative Similarity
  dP = 1 - csmaxP;
  csconf =(float)dN / (dN + dP);
}

TLD::detect函数

有了前面的铺垫,这段程序应该比较好懂了吧。

void TLD::detect(const cv::Mat&frame){
  //cleaning
  dbb.clear();
  dconf.clear();
  dt.bb.clear();//检测的结果,一个目标一个bounding box
  double t = (double)getTickCount();
  Mat img(frame.rows,frame.cols,CV_8U);
  integral(frame,iisum,iisqsum);//
  GaussianBlur(frame,img,Size(9,9),1.5);//
  int numtrees = classifier.getNumStructs();// nstructs: 10
  float fern_th = classifier.getFernTh();//thr_fern:0.6
  vector <int> ferns(10);
  float conf;
  int a=0;
  Mat patch;
  // 1. 方差->结果存在tmp ->随机森林-> dt.bb
  for (int i=0;i<grid.size();i++){//FIXME: BottleNeck
      if (getVar(grid[i],iisum,iisqsum)>=var){//第一关:方差
          a++;
        patch = img(grid[i]);
          classifier.getFeatures(patch,grid[i].sidx,ferns);//sidx:scale index
          conf = classifier.measure_forest(ferns);//第二关:随机森林
          tmp.conf[i]=conf; //只要能通过第一关就会保存到tmp
          tmp.patt[i]=ferns;
          if (conf>numtrees*fern_th){
              dt.bb.push_back(i); //第二关
          }
      }
      else
        tmp.conf[i]=0.0;//第一关都没过
  }
  int detections = dt.bb.size();
  printf("%d Bounding boxes passed the variance filter\n",a);
  printf("%d Initial detection from Fern Classifier\n",detections);
  if (detections>100){//第二关附加赛:100名以后的回家去
      nth_element(dt.bb.begin(),dt.bb.begin()+100,dt.bb.end(),CComparator(tmp.conf));
      dt.bb.resize(100);
      detections=100;
  }
  if (detections==0){
        detected=false;
        return;//啥都没看到……
      }
  printf("Fern detector made %d detections ",detections);
  t=(double)getTickCount()-t;
  printf("in %gms\n", t*1000/getTickFrequency());
                                                                      //  Initialize detection structure
  dt.patt = vector<vector<int> >(detections,vector<int>(10,0));       //  Corresponding codes of the Ensemble Classifier
  dt.conf1 = vector<float>(detections);                               //  Relative Similarity (for final nearest neighbour classifier)
  dt.conf2 =vector<float>(detections);                                //  Conservative Similarity (for integration with tracker)
  dt.isin = vector<vector<int> >(detections,vector<int>(3,-1));       //  Detected (isin=1) or rejected (isin=0) by nearest neighbour classifier
  dt.patch = vector<Mat>(detections,Mat(patch_size,patch_size,CV_32F));//  Corresponding patches,patch_size: 15
  int idx;
  Scalar mean, stdev;
  float nn_th = classifier.getNNTh();//thr_nn:0.65
  //3. 第三关:最近邻分类器,用Relative Similarity分类,但是却用 Conservative Similarity作为分数->dconf
  for (int i=0;i<detections;i++){                                        //  for every remaining detection
      idx=dt.bb[i];                                                      //  Get the detected bounding box index
     patch = frame(grid[idx]);
      getPattern(patch,dt.patch[i],mean,stdev);                //  Get pattern within bounding box
      classifier.NNConf(dt.patch[i],dt.isin[i],dt.conf1[i],dt.conf2[i]); //  Evaluate nearest neighbour classifier
      dt.patt[i]=tmp.patt[idx];//ferns
      if (dt.conf1[i]>nn_th){                                              //  idx = dt.conf1 > tld.model.thr_nn; % get all indexes that made it through the nearest neighbour
          dbb.push_back(grid[idx]);                                        //  BB    = dt.bb(:,idx); % bounding boxes
          dconf.push_back(dt.conf2[i]);                                     //  Conf  = dt.conf2(:,idx); % conservative confidences
      }
  }                                                                        //  end
  if (dbb.size()>0){
      printf("Found %d NN matches\n",(int)dbb.size());
      detected=true;
  }
  else{
      printf("No NN matches found.\n");
      detected=false;
  }
}
时间: 2024-12-20 19:51:16

TLD之检测篇(二)的相关文章

TLD之跟踪篇(三)

TLD之跟踪篇(三) TLD之扯淡篇(一).TLD之检测篇(二).TLD之学习篇(四) 目标跟踪的一般思想是跟踪目标中关键点.TLD也是跟踪点(但不是跟踪SIFT之类的关键点).点跟踪采用的是光流法,具体来说是Pyramidal Lucas-Kanade tracker,这个以后机会再介绍,推荐阅读<Learning OpenCV>第10章的Lucas-Kanade Method部分,这里只介绍OpenCV的实现函数,跳过原理和实现细节. 首先看跟踪点的函数,calcOpticalFlowPy

TLD之学习篇(四)

TLD之学习篇(四) TLD之扯淡篇(一).TLD之检测篇(二).TLD之跟踪篇(三) 这一部分是TLD算法的核心之处,有了前面两篇的铺垫,终于可以毫无顾忌的说说这一部分了.不过,还有一座大山,程序的初始化,这是程序运行的铺垫,内容很多--. 初始化 在run_tld.cpp中,一旦你选的要跟踪的目标box后便会调用初始化init: tld.init(last_gray,box,bb_file);//初始目标位置存储在box 内容太多,大伙看下面程序中标出的9点吧,其中3.5.8三点已经讲过啦,

OpenCV检测篇(二)——笑脸检测

前言 由于本文与上一篇OpenCV检测篇(一)--猫脸检测具有知识上的连贯性,所以建议没读过前一篇的先去阅读一下前一篇,前面讲过的内容这里会省略掉. 笑脸检测 其实也没什么可省略的,因为跟在opencv中,无论是人脸检测.人眼检测.猫脸检测.行人检测等等,套路都是一样的.正所谓: 自古深情留不住,总是套路得人心. 发挥主要作用的函数有且仅有一个:detectMultiScale().前一篇猫脸检测中已经提到过这个函数,这里就不再详细赘述. 这里只说一下笑脸检测的流程,显然也都是套路: 1.加载人

SQL Server调优系列玩转篇二(如何利用汇聚联合提示(Hint)引导语句运行)

原文:SQL Server调优系列玩转篇二(如何利用汇聚联合提示(Hint)引导语句运行) 前言 上一篇我们分析了查询Hint的用法,作为调优系列的最后一个玩转模块的第一篇.有兴趣的可以点击查看:SQL Server调优系列玩转篇(如何利用查询提示(Hint)引导语句运行) 本篇继续玩转模块的内容,同样,还是希望扎实掌握前面一系列的内容,才进入本模块的内容分析. 闲言少叙,进入本篇的内容. 技术准备 数据库版本为SQL Server2012,利用微软的以前的案例库(Northwind)进行分析,

学习OpenCV范例(二十四)—ViBe前景检测(二)

最近导师没给什么项目做,所以有那么一点点小时间,于是就研究起了前景检测,既然前景检测有很多种算法,那干脆就把这些模型都学起来吧,以后用到前景检测时至少还有那么几种方法可以选择,上次介绍的是GMM模型,其实GMM模型本身就是一个很不错的模型,现在也很多人在研究,并且做改进,主要是OpenCV有函数调用,用起来非常方便,当我们都在兴高采烈的讨论GMM各种好的时候,B哥不爽了,他说老子是搞前景检测的,怎么可能让你们这么嚣张,而且老子就不按照你那套路来,什么高斯模型,混合高斯模型,我统统不用,就来个简单

【转】java提高篇(二)-----理解java的三大特性之继承

[转]java提高篇(二)-----理解java的三大特性之继承 原文地址:http://www.cnblogs.com/chenssy/p/3354884.html 在<Think in java>中有这样一句话:复用代码是Java众多引人注目的功能之一.但要想成为极具革命性的语言,仅仅能够复制代码并对加以改变是不够的,它还必须能够做更多的事情.在这句话中最引人注目的是"复用代码",尽可能的复用代码使我们程序员一直在追求的,现在我来介绍一种复用代码的方式,也是java三大

JMS基础篇(二)

简介 异构集成是消息发挥作用的一个领域,大型公司内部可能会遇到很多的平台,Java,.net或者公司自己的平台等. 传送消息还应该支持异步机制,以提高系统整体的性能.异步传输一条消息意味着,发送者不必等到接收者接收或者处理消息,可以接着做后续的处理. 应用程序发送消息至另外一个应用程序,需要使用到消息中间件.消息中间件应提供容错,负载均衡,可伸缩的事务性等特性. JMS与JDBC类似,是一种与厂商无关的API.应用程序开发者可以使用同样的API来访问不同的系统. 可以认为JMS是一种标准,各消息

Qt学习总结-ui篇(二)

qccs定义圆角 border-radius:10px; 如果想给特定位置定义圆角,如: 左上角:border-left-top-radius:10px; 右下角色:border-right-bottom-rasius:10px; 半透明效果 只需要在css中使用rgba(100,100,100,40)这种形式来表示颜色即可. 为可执行文件添加图标 1.新建文件:finename.rc 文件名无所谓,只要后缀为rc就可以. 2.编辑新建的文件,输入以下内容: IDI_ICON1 ICON DIS

智能家居DIY-空气质量检测篇-获取温度和湿度篇

目录 智能家居DIY-空气质量检测篇-获取空气污染指数 前言 话说楼主终于升级当爸了,宝宝现在5个月了,宝宝出生的时候是冬天,正是魔都空气污染严重的时候,当时就想搞个自动开启空气净化器,由于种种原因一直没有时间搞,最近终于闲下来了这个事情终于提上议程了,现在是夏天,空气都还行,各种空气质量相关电子产品都打折,正是动手的好时机. 计划的主要功能有: 自动检测空气质量(pm2.5,Pm10) 自动检测温度,湿度,气压 定时上传空气质量数据到服务器,并通过手机App显示 当空气质量差的时候自动开启空气