本文系原创,转载请注明。
有问题请留言或发邮箱:[email protected]
因为实验室项目工程的需要,最近在研究目标跟踪算法。这里提的Fast Compressive Tracking (快速压缩跟踪)算法是张开华教授在其之前的Compressive Tracking 算法(网站看这里)上做了一些简单的优化,本人测试的结果是FCT算法的处理速度在59帧/s左右(windows下),而之前的CT算法大概是29帧/s,修改后的速度还是不错的(这里的工程都是读图片帧序列,后面给出的工程资源有自带资源)。因为FCT的网站上没有给出代码的C++实现(只有MATLAB的代码,网站看这里),这里就贴出我按照其论文的意思给出C++的代码实现,这里的C++代码也是从其CT算法的c++代码上修改而来的,修改的地方我会在代码中说明。另外为了将代码能够移植到linux上,我对代码的初始运行的地方也做了修改,所以这里贴出的代码是可以运行在linux上的。代码中的注释参考了zouxy09大神的博客,最后为了方便大家运行,我会将FCT C++工程以及linux下运行的代码分别打包供大家下载。因为我也是初学者,能力有限,所以若文中有纰漏请读者指正,有问题请留言或者发我邮箱,thanks。
---------------------------------------------------------------------------------------------------
2015/1/24 补充:该版本C++代码没有实现多尺度的情况。我在看论文的时候没有仔细阅读论文(细看了CT,而没细看FCT),以为FCT论文中没有给出尺度变换的设计。后来有网友指出论文中有尺度变换,罪过罪过,很久之前的东西了,所以暂时没办法更新代码。
/************************************************************************
* 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: [email protected]
* Date: 2012/08/03
* History:
* Revised by Kaihua Zhang on 14/8/2012, 23/8/2012
* Email: [email protected]
* Homepage: http://www4.comp.polyu.edu.hk/~cskhzhang/
* Project Website: http://www4.comp.polyu.edu.hk/~cslzhang/CT/CT.htm
************************************************************************/
#pragma once
#include
#include
#include
using std::vector;
using namespace cv;
//---------------------------------------------------
class CompressiveTracker
{
public:
CompressiveTracker(void);
~CompressiveTracker(void);
private:
int featureMinNumRect;
int featureMaxNumRect;
int featureNum;//每个box的harr特征个数(也就是弱分类器个数)
vector> features;
vector> featuresWeight;
int rOuterPositive;//在离上一帧跟踪到的目标位置的距离小于rOuterPositive的范围内采集 正样本
vector samplePositiveBox;//采集的正样本box集
vector sampleNegativeBox;//采集的负样本box集
int rSearchWindow;//扫描窗口的大小,或者说检测box的大小
Mat imageIntegral; //图像的积分图
Mat samplePositiveFeatureValue;//采集的正样本的harr特征值 ???特征值是矩阵??
Mat sampleNegativeFeatureValue;//采集的负样本的harr特征值
//对每个样本z(m维向量),它的低维表示是v(n维向量,n远小于m)。假定v中的各元素是独立分布的。
//假定在分类器H(v)中的条件概率p(vi|y=1)和p(vi|y=0)属于高斯分布,并且可以用以下四个参数来描述:
//分别是描述正负样本的高斯分布的均值u和方差sigma
vector muPositive;
vector sigmaPositive;
vector muNegative;
vector sigmaNegative;
float learnRate;//学习速率,控制分类器参数更新的步长
vector 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& _sampleBox);
//这里sampleRect函数和CT算法稍微不同,增加了step参数用来表示不同的步长。
void sampleRect(Mat& _image, Rect& _objectBox, float _srw, vector& _sampleBox,int step);
void getFeatureValue(Mat& _imageIntegral, vector& _sampleBox, Mat& _sampleFeatureValue);
void classifierUpdate(Mat& _sampleFeatureValue, vector& _mu, vector& _sigma, float _learnRate);
void radioClassifier(vector& _muPos, vector& _sigmaPos, vector& _muNeg, vector& _sigmaNeg,
Mat& _sampleFeatureValue, float& _radioMax, int& _radioMaxIndex);
public:
void processFrame(Mat& _frame, Rect& _objectBox);
void init(Mat& _frame, Rect& _objectBox);
};
/************************************************************************
* File: RunTracker.cpp
* 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: [email protected]
* Date: 2012/08/03
* History:
* Revised by Kaihua Zhang on 14/8/2012, 23/8/2012
* Email: [email protected]
* Homepage: http://www4.comp.polyu.edu.hk/~cskhzhang/
* Project Website: http://www4.comp.polyu.edu.hk/~cslzhang/CT/CT.htm
************************************************************************/
#include
#include
#include
#include
#include
#include
#include
//#include
#include
#include "FastCompressiveTracker.h"
using namespace cv;
using namespace std;
void readConfig(char* configFileName, char* imgFilePath, Rect &box,int &num);
/* Description: read the tracking information from file "config.txt"
Arguments:
-configFileName: config file name
-ImgFilePath: Path of the storing image sequences
-box: [x y width height] intial tracking position
History: Created by Kaihua Zhang on 15/8/2012
*/
void readImageSequenceFiles(char* ImgFilePath,vector &imgNames,int &num);
/* Description: search the image names in the image sequences
Arguments:
-ImgFilePath: path of the image sequence
-imgNames: vector that stores image name
History: Created by Kaihua Zhang on 15/8/2012
*/
int main(int argc, char * argv[])
{
time_t start,stop;
start = time(NULL);//获取程序开始运行的时间
char imgFilePath[100];
char conf[100];
strcpy(conf,"./config.txt");
//char tmpDirPath[MAX_PATH+1];//MAX_PATH在windows下是260
char tmpDirPath[261];//this is a test for chengxin
Rect box; // [x y width height] tracking position
int num;
vector imgNames;
readConfig(conf,imgFilePath,box,num);//读取视频帧的配置信息
readImageSequenceFiles(imgFilePath,imgNames,num);//将每一帧的名称放入数组imgNames
// CT framework
CompressiveTracker ct;
Mat frame;
Mat grayImg;
sprintf(tmpDirPath, "%s/", imgFilePath);
imgNames[0].insert(0,tmpDirPath);
cout< &imgNames,int &num)
{
imgNames.clear();
/*
char tmpDirSpec[MAX_PATH+1];
sprintf (tmpDirSpec, "%s/*", imgFilePath);
WIN32_FIND_DATA f;
HANDLE h = FindFirstFile(tmpDirSpec , &f);
if(h != INVALID_HANDLE_VALUE)
{
FindNextFile(h, &f); //read ..
FindNextFile(h, &f); //read .
do
{
imgNames.push_back(f.cFileName);
} while(FindNextFile(h, &f));
}
FindClose(h);
*/
String sequencesName = "%05d.jpg";
for(int i=1;i<=num;i++)
{ char imgName[256];
sprintf(imgName,sequencesName.c_str(),i);
String name = imgName;
imgNames.push_back(name);
//cout<<"the name of this frame is "<
#include "FastCompressiveTracker.h"
#include
#include
using namespace cv;
using namespace std;
//------------------------------------------------
CompressiveTracker::CompressiveTracker(void)
{
featureMinNumRect = 2;
featureMaxNumRect = 4; // number of rectangle from 2 to 4
featureNum = 50; // number of all weaker classifiers, i.e,feature pool
rOuterPositive = 4; // radical scope of positive samples//scope是范围的意思
rSearchWindow = 25; // size of search window
muPositive = vector(featureNum, 0.0f);//50个
muNegative = vector(featureNum, 0.0f);//50个
sigmaPositive = vector(featureNum, 1.0f);//50个
sigmaNegative = vector(featureNum, 1.0f);//50个
learnRate = 0.85f; // Learning rate parameter
}
CompressiveTracker::~CompressiveTracker(void)
{
}
/*通过积分图来计算采集到的每一个样本的harr特征,这个特征通过与featuresWeight来相乘
就相当于投影到随机测量矩阵中了,也就是进行稀疏表达了。这里不明白的话,可以看下
论文中的图二,就比较直观了。
还有一点:实际上这里采用的不属于真正的harr特征,我博客中翻译有误。这里计算的是
在box中采样得到的不同矩形框的灰度加权求和(当权重是负数的时候就是灰度差)
当为了表述方便,我下面都用harr特征来描述。
每一个样本有50个harr特征,每一个harr特征是由2到3个随机选择的矩形框来构成的,
对这些矩形框的灰度加权求和作为这一个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.每一个样本有50个harr特征,每一个harr特征是由2到3个随机选择的矩形框来构成的,
*/
{ //_numFeature是一个样本box的harr特征个数,共50个。而上面说到,
//每一个harr特征是由2到3个随机选择的矩形框(vector()类型)来构成的。
features = vector>(_numFeature, vector());
//每一个反应特征的矩形框对应于一个权重,实际上就是随机测量矩阵中相应的元素,用它来与对应的特征
//相乘,表示以权重的程度来感知这个特征。换句话说,featuresWeight就是随机测量矩阵。
featuresWeight = vector>(_numFeature, vector());
int numRect;
Rect rectTemp;
float weightTemp;
for (int i=0; i<_numFeature; i++)//_numFeature是50
{ //numRect是 2或者 3
//那么下面的功能就是得到[2,4)范围的随机数,然后用cvFloor返回不大于参数的最大整数值,那要么是2,要么是3。
numRect = cvFloor(rng.uniform((double)featureMinNumRect, (double)featureMaxNumRect));//这两个值是2和4
for (int j=0; j& _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;
float inradsq = _rInner*_rInner;
float outradsq = _rOuter*_rOuter;
//我们是在上一帧跟踪的目标box的周围采集正样本和负样本的,而这个周围是通过以
//这个目标为中心的两个圆来表示,这两个圆的半径是_rInner和_rOuter。
//我们在离上一帧跟踪的目标box的小于_rInner距离的范围内采集正样本,
//在大于_rOuter距离的范围内采集负样本(论文中还有一个上界,但好像
//这里没有,其实好像也没什么必要噢)
int dist;
//这四个是为了防止采集的框超出图像范围的,对采集的box的x和y坐标做限制
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;
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);
//后两个条件是保证距离需要在_rInner和_rOuter的范围内
//那么rng.uniform(0.,1.) < prob 这个是干嘛的呢?
//连着上面看,如果_maxSampleNum大于那个最大个数,prob就大于1,这样,
//rng.uniform(0.,1.) < prob这个条件就总能满足,表示在这个范围产生的
//所以box我都要了(因为我本身想要更多的,但是你给不了我那么多,那么你能给的,我肯定全要了)。
//那如果你给的太多了,我不要那么多,也就是prob<1,那我就随机地跳几个走好了
if( rng.uniform(0.,1.)= outradsq ){
rec.x = c;
rec.y = r;
rec.width = _objectBox.width;
rec.height= _objectBox.height;
_sampleBox.push_back(rec);
i++;
}
}
_sampleBox.resize(i);
}
//这个sampleRect的重载函数是用来在上一帧跟踪的目标box的周围(距离小于_srw)采集若干box来待检测。
//与上面的那个不一样,上面那个是在这一帧已经检测出目标的基础上,采集正负样本来更新分类器的。
//上面那个属于论文中提到的算法的第四个步骤,这个是第一个步骤。然后过程差不多,没什么好说的了
//
void CompressiveTracker::sampleRect(Mat& _image, Rect& _objectBox, float _srw, vector& _sampleBox,int step)
/* Description: Compute the coordinate of samples when detecting the object.*/
{
int rowsz = _image.rows - _objectBox.height - 1;
int colsz = _image.cols - _objectBox.width - 1;
float inradsq = _srw*_srw;
int dist;
int minrow = max(0,(int)_objectBox.y-(int)_srw);
int maxrow = min((int)rowsz-1,(int)_objectBox.y+(int)_srw);
int mincol = max(0,(int)_objectBox.x-(int)_srw);
int maxcol = min((int)colsz-1,(int)_objectBox.x+(int)_srw);
int i = 0;
int r;
int c;
Rect rec(0,0,0,0);
_sampleBox.clear();//important
//step表示步长
for( r=minrow; r<=(int)maxrow; r=r+step )
for( c=mincol; c<=(int)maxcol; c=c+step ){
dist = (_objectBox.y-r)*(_objectBox.y-r) + (_objectBox.x-c)*(_objectBox.x-c);
if( dist < inradsq ){
rec.x = c;
rec.y = r;
rec.width = _objectBox.width;
rec.height= _objectBox.height;
_sampleBox.push_back(rec);
i++;
}
}
_sampleBox.resize(i);
}
// Compute the features of samples
//通过积分图来计算采集到的每一个样本的harr特征,这个特征通过与featuresWeight来相乘
//就相当于投影到随机测量矩阵中了,也就是进行稀疏表达了。这里不明白的话,可以看下
//论文中的图二,就比较直观了。所以这里得到的是:每个样本的稀疏表达后的harr特征。
//还有一点:实际上这里采用的不属于真正的harr特征,我博客中翻译有误。这里计算的是
//在box中采样得到的不同矩形框的灰度加权求和
void CompressiveTracker::getFeatureValue(Mat& _imageIntegral, vector& _sampleBox, Mat& _sampleFeatureValue)
{
int sampleBoxSize = _sampleBox.size();
_sampleFeatureValue.create(featureNum, sampleBoxSize, CV_32F);//featureNum是50,参数分别是行、列、类型
float tempValue;
int xMin;
int xMax;
int yMin;
int yMax;
for (int i=0; i(yMin, xMin) +
_imageIntegral.at(yMax, xMax) -
_imageIntegral.at(yMin, xMax) -
_imageIntegral.at(yMax, xMin));
}
_sampleFeatureValue.at(i,j) = tempValue;
}
}
}
// Update the mean and variance of the gaussian classifier
//论文中是通过用高斯分布去描述样本的每一个harr特征的概率分布的。高斯分布就可以通过期望和方差
//两个参数来表征。然后通过正负样本的每一个harr特征高斯概率分布的对数比值,来构建分类器决策
//该box属于目标还是背景。这里计算新采集到的正负样本的特征的期望和标准差,并用其来更新分类器
void CompressiveTracker::classifierUpdate(Mat& _sampleFeatureValue, vector& _mu, vector& _sigma, float _learnRate)
{//后面默认的参数个数是50个50个和0.85
Scalar muTemp;
Scalar sigmaTemp;
for (int i=0; i& _muPos, vector& _sigmaPos, vector& _muNeg, vector& _sigmaNeg,
Mat& _sampleFeatureValue, float& _radioMax, int& _radioMaxIndex)
{
float sumRadio;
_radioMax = -FLT_MAX;
_radioMaxIndex = 0;
float pPos;
float pNeg;
int sampleBoxNum = _sampleFeatureValue.cols;
for (int j=0; j(i,j)-_muPos[i])*(_sampleFeatureValue.at(i,j)-_muPos[i]) / -(2.0f*_sigmaPos[i]*_sigmaPos[i]+1e-30) ) / (_sigmaPos[i]+1e-30);
pNeg = exp( (_sampleFeatureValue.at(i,j)-_muNeg[i])*(_sampleFeatureValue.at(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;
}
}
}
//传入第一帧和要跟踪的目标box(由文件读入或用户鼠标框选),初始化处理
void CompressiveTracker::init(Mat& _frame, Rect& _objectBox)
{
// compute feature template
//计算box的harr特征模板,先存着
HaarFeature(_objectBox, featureNum);
//因为这是第一帧,目标box是由由文件读入或者用户鼠标框选的,是已知的,
//所以我们通过在这个目标box周围,采集正样本和负样本来初始化我们的分类器
// compute sample templates
sampleRect(_frame, _objectBox, rOuterPositive, 0, 1000000, samplePositiveBox);//rOuterPositive 默认是4
sampleRect(_frame, _objectBox, rSearchWindow*1.5, rOuterPositive+4.0, 100, sampleNegativeBox);//rSearchWindow是25
//计算积分图,用以快速的计算harr特征
integral(_frame, imageIntegral, CV_32F);
//通过上面的积分图,计算我们采样到的正负样本的box的harr特征
getFeatureValue(imageIntegral, samplePositiveBox, samplePositiveFeatureValue);
getFeatureValue(imageIntegral, sampleNegativeBox, sampleNegativeFeatureValue);
//通过上面的正负样本的特征来初始化分类器
classifierUpdate(samplePositiveFeatureValue, muPositive, sigmaPositive, learnRate);
classifierUpdate(sampleNegativeFeatureValue, muNegative, sigmaNegative, learnRate);
}
void CompressiveTracker::processFrame(Mat& _frame, Rect& _objectBox)
{
// predict
//第一次采样,半径为25,步长为4(跟CT算法不同地方,这里分了两次采样,将采样的数量减少)
sampleRect(_frame, _objectBox, rSearchWindow,detectBox,4);
integral(_frame, imageIntegral, CV_32F);
getFeatureValue(imageIntegral, detectBox, detectFeatureValue);
int radioMaxIndex;
float radioMax;
radioClassifier(muPositive, sigmaPositive, muNegative, sigmaNegative, detectFeatureValue, radioMax, radioMaxIndex);
_objectBox = detectBox[radioMaxIndex];
//第二次采样,半径为10,步长为1
sampleRect(_frame, _objectBox, 10,detectBox,1);
integral(_frame, imageIntegral, CV_32F);
getFeatureValue(imageIntegral, detectBox, detectFeatureValue);
radioClassifier(muPositive, sigmaPositive, muNegative, sigmaNegative, detectFeatureValue, radioMax, radioMaxIndex);
_objectBox = detectBox[radioMaxIndex];
// update
sampleRect(_frame, _objectBox, rOuterPositive, 0.0, 1000000, samplePositiveBox);
sampleRect(_frame, _objectBox, rSearchWindow*1.5, rOuterPositive+4.0, 100, sampleNegativeBox);
getFeatureValue(imageIntegral, samplePositiveBox, samplePositiveFeatureValue);
getFeatureValue(imageIntegral, sampleNegativeBox, sampleNegativeFeatureValue);
classifierUpdate(samplePositiveFeatureValue, muPositive, sigmaPositive, learnRate);
classifierUpdate(sampleNegativeFeatureValue, muNegative, sigmaNegative, learnRate);
}
linux下:点击打开链接 linux下通过make编译
windows下:点击打开链接 openCV2.4.9 vs2012的工程