Real-time Compressive Tracking

Real-Time Compressive Tracking, Kaihua Zhang, LeiZhang,and Ming-Hsuan Yang

这篇论文由香港理工大学张开华发表在2012年的ECCV之上,论文附有数据对比及代码。

论文主页及源码下载:http://www4.comp.polyu.edu.hk/~cslzhang/CT/CT.htm

跟踪效果:http://v.youku.com/v_show/id_XNDMzODcxNjcy.html

根据作者的描述,这是一种简单高效地基于压缩感知的跟踪算法。首先利用符合压缩感知RIP条件的随机测量矩阵多尺度图像特征进行降维,然后在降维后的特征上采用简单的朴素贝叶斯分类器进行分类。该跟踪算法非常简单,但是实验结果鲁棒性强,速度大概能到达40帧/秒。

 

该压缩跟踪算法的流程如下:

输入:第t帧图像

Step 1 :在t-1帧跟踪到的目标位置It-1的周围(也就是满足Dγ={z|||l(z)−lt−1||<γ,与It-1距离小于γ)采样n个图像片,然后对这些图像片进行Harr特征提取(降维),得到每个图像片的特征向量v。

Step 2:使用公式(4)中分类器H(v)对这些v进行分类,找到最大分类分数的图像片作为当前帧跟踪到的目标,位置为It

Step 3:采样两个样本集:Dα= {z|||l(z) − lt||< α}和 Dζ ,β= {z|ζ < ||l(z)−lt||<β}其中,α< ζ < β;

Step 4:提取上述两个样本集的特征,通过公式(6)来更新分类器参数。

输出:跟踪到的目标位置It和更新后的分类器参数。

 

有关这篇文章的详细讲解可以参见博文:

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

这里主要讲解该算法的C++代码实现流程。它包含头文件CompressiveTracker.h和源文件CompressiveTracker.cpp。

头文件包含CompressiveTracker类定义。这些变量包括:

 

  /************************************************************************ 
 * File: CompressiveTracker.h 
  * Brief: C++ demo for paper: Kaihua Zhang, Lei Zhang, Ming-Hsuan Yang,"Real-Time Compressive Tracking," ECCV 2012. 
 * Version: 1.0 
  * Author: Yang Xian 
 * Email: yang_xian521@163.com 
  * Date: 2012/08/03 
 * History: 
  * Revised by Kaihua Zhang on 14/8/2012 
* Email: zhkhua@gmail.com 
 * Homepage: http://www4.comp.polyu.edu.hk/~cskhzhang/ 
************************************************************************/  
#pragma once  
 #include <opencv2/opencv.hpp>  
#include <vector>  
   
using std::vector;  
 using namespace cv;  
//---------------------------------------------------  
 class CompressiveTracker  
{  
 public:  
    CompressiveTracker(void);  //构造函数
     ~CompressiveTracker(void);  //析构函数
  
 private:  
    int featureMinNumRect;  //
     int featureMaxNumRect;  //
     int featureNum;  //每个box的类harr特征个数(即弱分类器个数),文中取为50. 每一个样本有50个类harr特征,每一个类harr特征是由2到3个随机选择的矩形框来构成的,对这些矩形框的灰度加权求和作为这一个类harr特征的特征值。
     vector<vector<Rect>> features;  // box的特征(向量,大小为featureNum,每个元素代表矩形框)
    vector<vector<float>> featuresWeight;  //随机测量矩阵R的非零元素(矩阵大小为featureNum x s,s即为文中公式(2)中的s)
     int rOuterPositive; //在目标位置附近采集正样本的范围  
    vector<Rect> samplePositiveBox;  // 采集的正样本集(向量,每个元素代表矩形框)  
     vector<Rect> sampleNegativeBox;  //采集的负样本集(向量,每个元素代表矩形框) 
    int rSearchWindow;   //扫描窗口的大小
     Mat imageIntegral;   //图像的积分图(矩阵) 
    Mat samplePositiveFeatureValue;  // 采集的正样本的类harr特征的值(矩阵) 
     Mat sampleNegativeFeatureValue;  //采集的负样本的类harr特征的值(矩阵)
     vector<float> muPositive;       //向量(大小为featureNum),描述正样本的高斯分布均值
    vector<float> sigmaPositive;  // 向量(大小为featureNum),描述正样本的高斯分布方差
     vector<float> muNegative;      //向量(大小为featureNum),描述负样本的高斯分布均值
    vector<float> sigmaNegative;  //向量(大小为featureNum),描述负样本的高斯分布方差
     float learnRate;   //学习速率,控制分类器参数更新的步长  
    vector<Rect> detectBox;  // 向量(每个元素代表矩形框),待检测的box  
     Mat detectFeatureValue;  //
    RNG rng;  // 随机数  
   
private:  
     void HaarFeature(Rect& _objectBox, int _numFeature);  
    void sampleRect(Mat& _image, Rect& _objectBox, float _rInner, float _rOuter, int _maxSampleNum, vector<Rect>& _sampleBox);  
     void sampleRect(Mat& _image, Rect& _objectBox, float _srw, vector<Rect>& _sampleBox);  
    void getFeatureValue(Mat& _imageIntegral, vector<Rect>& _sampleBox, Mat& _sampleFeatureValue);  
     void classifierUpdate(Mat& _sampleFeatureValue, vector<float>& _mu, vector<float>& _sigma, float _learnRate);  
    void radioClassifier(vector<float>& _muPos, vector<float>& _sigmaPos, vector<float>& _muNeg, vector<float>& _sigmaNeg,  
                         Mat& _sampleFeatureValue, float& _radioMax, int& _radioMaxIndex);  
public:  
     void processFrame(Mat& _frame, Rect& _objectBox);  
    void init(Mat& _frame, Rect& _objectBox);  
 }; 

 

源文件CompressiveTracker.cpp描述函数体。这些函数包括:

HaarFeature——计算Harr特征

sampleRect——在上一帧跟踪的目标box的周围采集若干正样本和负样本,来初始化或者更新分类器(step 4)

sampleRect重载——在上一帧跟踪的目标box的周围采集若干box来确定下一帧的目标box(step 1)

getFeatureValue——通过积分图来计算采集到的每一个样本的harr特征

classifierUpdate——计算新采集到的正负样本的特征的期望和标准差,并用其来更新分类器(公式(6))

radioClassifier——计算分类分数(公式(4))

processFrame——传入上一帧跟踪到的box,来处理新的一帧

init——传入第一帧和要跟踪的目标box(由文件读入或者用户鼠标框选),来初始化分类器

其中前6个是私有函数后2个是公有函数。

先来看初始化函数init

  void CompressiveTracker::init(Mat& _frame, Rect& _objectBox)  
  /* Description: initialize randommeasurement matrix, sample feature value, classifier
    Arguments: 
     -_frame:        processing frame 
     -_objectBox:    recent object position  
  */
 {  
      // compute feature template  
     //计算初始目标box的类harr特征(其中包含随机测量矩阵R的非零元素值,该矩阵只在此计算一次,并且存储在featuresWeight中,用于后续帧的计算)  
     HaarFeature(_objectBox, featureNum);  
  
     // compute sample templates  
    //在初始目标box周围,采集正样本和负样本来初始化我们的分类器  
    sampleRect(_frame, _objectBox, rOuterPositive, 0, 1000000, samplePositiveBox);  
     sampleRect(_frame, _objectBox, rSearchWindow*1.5, rOuterPositive+4.0, 100, sampleNegativeBox);  
  
     //用integral函数(opencv库函数)计算积分图,用于快速计算类harr特征  
    integral(_frame, imageIntegral, CV_32F);  
   
    //通过上面的积分图,计算我们采样到的正负样本的类harr特征  
     getFeatureValue(imageIntegral, samplePositiveBox, samplePositiveFeatureValue);  
    getFeatureValue(imageIntegral, sampleNegativeBox, sampleNegativeFeatureValue);  
       
    //通过上面的正负样本的特征来初始化分类器  
     classifierUpdate(samplePositiveFeatureValue, muPositive, sigmaPositive, learnRate);  
    classifierUpdate(sampleNegativeFeatureValue, muNegative, sigmaNegative, learnRate);  
 }  

首先来看函数HarrFeature,它是通过积分图来计算采集到的样本的类harr特征。

  void CompressiveTracker::HaarFeature(Rect& _objectBox, int _numFeature)  
 /*Description: compute Haar features 
    Arguments: 
   -_objectBox: [x y width height] object rectangle 
    -_numFeature: total number of features. The default is 50. 
 */  
  {  
     // _numFeature是一个样本box的类harr特征的个数,这里取为50个  
      //每一个harr特征由2到3个随机选择的矩形框(vector<Rect>())构成  
    features = vector<vector<Rect>>(_numFeature, vector<Rect>());  
     // featuresWeight就是随机测量矩阵中的非零元素值,行指标表示特征,列指标表示矩形框(s)。 
     featuresWeight = vector<vector<float>>(_numFeature, vector<float>());  
      
     // numRect是每个特征对应的矩形框的个数,也即文中公式(2)中的s   
     int numRect;  
    Rect rectTemp;  
     float weightTemp;  
        
     for (int i=0; i<_numFeature; i++)  
    {  
         // featureMinNumRect = 2, featureMinNumRect = 4
          //生成[2,4]内的随机数,并向下取整  
        numRect = cvFloor(rng.uniform((double)featureMinNumRect, (double)featureMaxNumRect));  
           
        //int c = 1;  
         for (int j=0; j<numRect; j++)  
        {  
             // rectemp用于保存得到的矩形框(位置、长宽)
             rectTemp.x = cvFloor(rng.uniform(0.0, (double)(_objectBox.width - 3)));  
            rectTemp.y = cvFloor(rng.uniform(0.0, (double)(_objectBox.height - 3)));  
            rectTemp.width = cvCeil(rng.uniform(0.0, (double)(_objectBox.width - rectTemp.x - 2)));  
             rectTemp.height = cvCeil(rng.uniform(0.0, (double)(_objectBox.height - rectTemp.y - 2)));  
            //这里的矩形框是相对于目标box的相对位置  
             features[i].push_back(rectTemp);  
              
             //weightTemp = (float)pow(-1.0, c);  
            //pow(-1.0, c)也就是-1的c次方,而c随机地取0或者1,也就是说weightTemp是随机的正或者负。  
             //随机测量矩阵中,矩阵元素有三种,sqrt(s)、-sqrt(s)和零。为正和为负的概率是相等的,  
            //这就是为什么是[2,4)均匀采样的原因,就是取0或者1概率一样。  
             //但是这里为什么是sqrt(s)分之一呢?还有什么时候是0呢?论文中是0的概率不是挺大的吗?  
            //没有0元素,哪来的稀疏表达和压缩呢?不懂,恳请指导!(当然稀疏表达的另一个好处  
             // weightTemp  
            weightTemp = (float)pow(-1.0, cvFloor(rng.uniform(0.0, 2.0))) / sqrt(float(numRect)); 
               
            //保存每一个矩形框对应的权重  
             featuresWeight[i].push_back(weightTemp);  
             
         }  
    }  
 }

然后是采集样本用于分类器参数的更新和目标位置的预测,这分别由函数sampleRect及其重载函数来实现

  void CompressiveTracker::sampleRect(Mat& _image, Rect& _objectBox, float _rInner, float _rOuter, int _maxSampleNum, vector<Rect>& _sampleBox)  
  /* Description: compute the coordinate of positive and negative sample image templates 
     Arguments: 
     -_image:        processing frame 
     -_objectBox:    recent object position  
     -_rInner:       inner sampling radius 
     -_rOuter:       Outer sampling radius 
     -_maxSampleNum: maximal number of sampled images 
     -_sampleBox:    Storing the rectangle coordinates of the sampled images. 
 */  
 {  
     int rowsz = _image.rows - _objectBox.height - 1;  
     int colsz = _image.cols - _objectBox.width - 1;  
     //在上一帧跟踪的目标box的周围采集正样本和负样本
     // _rInner对应文中Algorithm 1中的alpha,_rOuter对应文中的zeta。  
     //在小于_rInner距离的区域内采集正样本 
     //在大于_rOuter距离的区域内采集负样本
     float inradsq = _rInner*_rInner;  
     float outradsq = _rOuter*_rOuter;  
       
     int dist;  
   
     //限制采集的矩形框的坐标,一是保证其在由_rInner和_rOuter确定的范围内,二是防止其超出整帧图像的范围  
     int minrow = max(0,(int)_objectBox.y-(int)_rInner);  
     int maxrow = min((int)rowsz-1,(int)_objectBox.y+(int)_rInner);  
     int mincol = max(0,(int)_objectBox.x-(int)_rInner);  
     int maxcol = min((int)colsz-1,(int)_objectBox.x+(int)_rInner);  
           
     int i = 0;  
      // 分子_maxSampleNum是我们需要采集的矩形框的最大个数  
      //分母是可以采集的矩形框的最大个数
      //prob用于对所有采集的矩形框进行取舍判断
     float prob = ((float)(_maxSampleNum))/(maxrow-minrow+1)/(maxcol-mincol+1);  
   
     int r;  
     int c;  
       
     _sampleBox.clear();// important  
     Rect rec(0,0,0,0);  
   
     for( r=minrow; r<=(int)maxrow; r++ )  
         for( c=mincol; c<=(int)maxcol; c++ ){  
             //计算生成的矩形框到目标矩形框的距离  
             dist = (_objectBox.y-r)*(_objectBox.y-r) + (_objectBox.x-c)*(_objectBox.x-c);  
              // 第一个条件是保证矩形框个数不超过由_maxSampleNum决定的上限
              //如果超过,则随机选择其中的若干个
             //后两个条件是保证距离需要在_rInner和_rOuter的范围内 
             if( rng.uniform(0.,1.) < prob && dist < inradsq && dist >= outradsq ){  
   
                 rec.x = c;  
                 rec.y = r;  
                 rec.width = _objectBox.width;  //目标矩形框的大小始终没变化  
                 rec.height= _objectBox.height;  
                   
                 _sampleBox.push_back(rec);                
                   
                 i++;  
             }  
         }  
       
         _sampleBox.resize(i);  
           
 }

它的重载函数的函数体基本相同,只是参数略有不同,因为该重载函数是用来预测目标的位置,即算法步骤中的step 1,所以只需用到参数rSearchWindow,即这里的_srw。

void CompressiveTracker::sampleRect(Mat& _image, Rect& _objectBox, float _srw, vector<Rect>& _sampleBox) 

再来看看函数getFeatureValue是怎么得到每个特征对应的值的

  void CompressiveTracker::getFeatureValue(Mat& _imageIntegral, vector<Rect>& _sampleBox, Mat& _sampleFeatureValue)  
  {  
      //sampleBox存储采集的样本矩形框的位置
      int sampleBoxSize = _sampleBox.size();  
     // sampleFeatureValue存储样本矩形框对应的特征的值,行指标为特征序号,列指标为样本序号
      _sampleFeatureValue.create(featureNum, sampleBoxSize, CV_32F);  
      float tempValue;  
      int xMin;  
      int xMax;  
     int yMin;  
     int yMax;  
   
     for (int i=0; i<featureNum; i++)  
     {  
         for (int j=0; j<sampleBoxSize; j++)  
         {  
             tempValue = 0.0f;  
             for (size_t k=0; k<features[i].size(); k++)  
             {  
                 // features中的矩形框是相对于目标box的相对位置的,  
                 //所以需要加上box的坐标才是其在整幅图像中的坐标  
                 xMin = _sampleBox[j].x + features[i][k].x;  
                 xMax = _sampleBox[j].x + features[i][k].x + features[i][k].width;  
                 yMin = _sampleBox[j].y + features[i][k].y;  
                 yMax = _sampleBox[j].y + features[i][k].y + features[i][k].height;  
                 //通过积分图来快速计算一个矩形框的所有像素灰度之和
                 // tempValue就是经过随机测量矩阵加权后的灰度和 
                 //每一个类harr特征是由2到3个矩形框来构成的,对这些矩形框的灰度加权求和  
                 //作为这一个类harr特征的值  
                 tempValue += featuresWeight[i][k] *   
                     (_imageIntegral.at<float>(yMin, xMin) +  
                     _imageIntegral.at<float>(yMax, xMax) -  
                     _imageIntegral.at<float>(yMin, xMax) -  
                     _imageIntegral.at<float>(yMax, xMin));  
             }  
             _sampleFeatureValue.at<float>(i,j) = tempValue;  
         }  
     }  
 }

接下来是分类器的更新,由函数classifierUpdate实现

  void CompressiveTracker::classifierUpdate(Mat& _sampleFeatureValue, vector<float>& _mu, vector<float>& _sigma, float _learnRate)  
 {  
      Scalar muTemp;  
     Scalar sigmaTemp;  
        
     for (int i=0; i<featureNum; i++)  
      {  
         //对每个类harr特征,计算其期望和标准差  
          meanStdDev(_sampleFeatureValue.row(i), muTemp, sigmaTemp);  
         
         //文中的公式(6)  
        _sigma[i] = (float)sqrt( _learnRate*_sigma[i]*_sigma[i] + (1.0f-_learnRate)*sigmaTemp.val[0]*sigmaTemp.val[0]   
+ _learnRate*(1.0f-_learnRate)*(_mu[i]-muTemp.val[0])*(_mu[i]-muTemp.val[0]));  // equation 6 in paper  
  
         _mu[i] = _mu[i]*_learnRate + (1.0f-_learnRate)*muTemp.val[0];   // equation 6 in paper  
    }  
 }

计算文中的H(v)即分类器分数,对各个矩形框的分类器分数进行计算,选择分数最大的矩形框作为目标矩形框,由函数radioClassifier实现(作者ratio把写成了radio,呵呵)

  void CompressiveTracker::radioClassifier(vector<float>& _muPos, vector<float>& _sigmaPos, vector<float>& _muNeg, vector<float>& _sigmaNeg,  
                                           Mat& _sampleFeatureValue, float& _radioMax, int& _radioMaxIndex)  
  {  
      float sumRadio;  
      // FLT_MAX是最大的浮点数的宏定义
      _radioMax = -FLT_MAX;   
      _radioMaxIndex = 0;  
      float pPos;  
      float pNeg;  
     int sampleBoxNum = _sampleFeatureValue.cols;  //采样得到的矩形框的个数
   
     for (int j=0; j<sampleBoxNum; j++)   
     {  
         sumRadio = 0.0f;  
         for (int i=0; i<featureNum; i++)  
         {    
             pPos = exp( (_sampleFeatureValue.at<float>(i,j)-_muPos[i])*(_sampleFeatureValue.at<float>(i,j)-_muPos[i]) / -(2.0f*_sigmaPos[i]*_sigmaPos[i]+1e-30) ) / (_sigmaPos[i]+1e-30);  
             pNeg = exp( (_sampleFeatureValue.at<float>(i,j)-_muNeg[i])*(_sampleFeatureValue.at<float>(i,j)-_muNeg[i]) / -(2.0f*_sigmaNeg[i]*_sigmaNeg[i]+1e-30) ) / (_sigmaNeg[i]+1e-30);  
               
             sumRadio += log(pPos+1e-30) - log(pNeg+1e-30);  // equation 4  
         }  
         if (_radioMax < sumRadio) 
         {  
             _radioMax = sumRadio;  
             _radioMaxIndex = j;  
         }  
     }  
 }

最后是processFrame函数,它用于连续处理图像帧

  void CompressiveTracker::processFrame(Mat& _frame, Rect& _objectBox)  
  {  
      // predict  
      //在上一帧跟踪到的box周围,采集需要检测的box框  
      sampleRect(_frame, _objectBox, rSearchWindow, detectBox);  
      //计算这一帧的积分图  
      integral(_frame, imageIntegral, CV_32F);  
      //用积分图来计算上面采集到的每个box的类harr特征  
      getFeatureValue(imageIntegral, detectBox, detectFeatureValue);  
     int radioMaxIndex;  
     float radioMax;  
     //对上面的每个box进行匹配分类  
     radioClassifier(muPositive, sigmaPositive, muNegative, sigmaNegative, detectFeatureValue, radioMax, radioMaxIndex);  
     //分数最高的那个box即为下一帧的目标box 
     _objectBox = detectBox[radioMaxIndex];  
   
     // update  
     //在新跟踪到的这个目标box的周围,采集正样本和负样本来更新分类器  
     sampleRect(_frame, _objectBox, rOuterPositive, 0.0, 1000000, samplePositiveBox);  
     sampleRect(_frame, _objectBox, rSearchWindow*1.5, rOuterPositive+4.0, 100, sampleNegativeBox);        
     //通过上面的积分图,计算我们采样到的正负样本box的类harr特征  
     getFeatureValue(imageIntegral, samplePositiveBox, samplePositiveFeatureValue);  
     getFeatureValue(imageIntegral, sampleNegativeBox, sampleNegativeFeatureValue);  
       
     //通过上面的正负样本的特征来更新我们的分类器  
     classifierUpdate(samplePositiveFeatureValue, muPositive, sigmaPositive, learnRate);  
     classifierUpdate(sampleNegativeFeatureValue, muNegative, sigmaNegative, learnRate);  
 }

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值