本序列文章的目的是总结一下这段时间所学到的,主要分为以下几部分,本章是第五部分。
1 算法概述
2 runtld.cpp源码解析
3 tld.cpp源码解析
4 LKTracker(重点)
5 FerNNClassifier.cpp源码解析(重点)
6 tld_utils.cpp源码解析
方差分类器
代码中是通过积分图来计算一个图的灰度值的方差的。
fern分类器
a先讲解一下一个图的fern特征。
这个fern分类器分为10个小的分类器(10棵树),
每棵树有13个节点,一个图被这13个节点处理后会变为13位的二进制码(比如0011001100010)。
10棵树处理后,就会产生10个13位的二进制码。就是该图的fern特征。
b上面提高一个图会被一棵树的13个节点处理,那么这个特征抽取过程是怎么样的呢?下面讲解。
A分类器的初始化
一棵树的初始化工作,根据一个patch的大小,随机均匀产生点对的集合,就是13个点对。所谓点对,就是两个点,比如(1,1)和(1,5),表示两个像素在图像的位置,这些点以后用来定位像素。
10棵树的初始化就是重复上面的操作了。
B计算一个patch(指定大小的图)的fern特征
先看看一个patch如何被一棵树处理。
一棵树其实就是13个点对的集合,每个点对处理一次,比如这个点对(2,4)和(5,6),把这两个点在patch的像素找出来比较大小,返回1或者0。13个点对就得到13个1或者0了。这样一个patch就被一棵树处理为一个13位的二进制码了。
一个patch的fern特征就是被10棵树处理后的10个13位二进制码了。
c训练过程
A先验概率:每个fern特征在每棵树下都对应有一个先验概率。
比如计算fern特征在第i棵树的先验概率:
令idx=fern[i],则
posteriors[i][idx]= ((float)(pCounter[i][idx]))/(pCounter[i][idx]+nCounter[i][idx]);
其中,
pCounter[i][idx]对于第i棵树,含有idx特征的正样本的数量
nCounter[i][idx]对于第i棵树,含有idx特征的负样本的数量
B训练的过程,就是根据新的正负样本,不断更新pCounter和nCounter,posteriors的值。
如果每一个新进来的样本,要通过之前的先验概率的和对它进行权重的判断,要跟某个阈值比较,符合条件才用新来的样本来更新posteriors。
d分类
其实就是对fern特征进行权值衡量,判断大于还是小于某个阈值。
Nnc分类器
所有的patch要处理为15*15的大小。
训练:每进来一个新的样本patch,先将这个patch将之前保存的所有正的负的patch做比较,算出一个相似度的值,如果这个相似度跟某个阈值比较符合条件,就将这个新样本添加到库中。
那么这个相似度是怎么算的呢?
先说说一个patch和另一个patch的相似度怎么算。
代码中用的是库函数,算法是CV_TM_CCORR_NORMED(网上翻译为归一化相关匹配法)。
http://www.cnblogs.com/xrwang/archive/2010/02/05/MatchTemplate.html
我看论文提到的是
Similaritybetween two patches pi , pj is defined as
S(pi, pj ) = 0.5(NCC(pi , pj ) + 1),
whereNCC is a Normalized Correlation Coefficient.
NormalizedCorrelation Coefficient网上翻译为归一化相关系数
这里先不管究竟哪种算法好。
总之,一个pacth跟库中所有正样本比较,算出最大相似度,也跟所有负样本算出最大相似度。
两者结合算出相对相似度和保守相似读。(具体细节看代码)
/* * FerNNClassifier.cpp * * Created on: Jun 14, 2011 * Author: alantrrs */ #include <FerNNClassifier.h> using namespace cv; using namespace std; void FerNNClassifier::read(const FileNode& file){ ///Classifier Parameters valid = (float)file["valid"]; ncc_thesame = (float)file["ncc_thesame"]; nstructs = (int)file["num_trees"];//树木的数量,为10 structSize = (int)file["num_features"];//每个树的节点数量,为13 thr_fern = (float)file["thr_fern"]; thr_nn = (float)file["thr_nn"]; thr_nn_valid = (float)file["thr_nn_valid"]; } /** * 随机产生一个patch的颜色(点位置)比较集 * 先验概率初始化 */ void FerNNClassifier::prepare(const vector<Size>& scales){ acum = 0; //Initialize test locations for features int totalFeatures = nstructs*structSize; 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);//所谓像素比较,就是指两个点的位置。随机产生的,产生后不改变 } } //Thresholds thrN = 0.5*nstructs; //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)); } } /** * 按照预先算好的像素比较集,从image得到具体像素来计算像素比较,作为特征输出。一个图,一个缩放尺度,对应10棵树的特征 * image:图像矩阵,存放像素信息 * scale_idx:缩放尺寸 * fern:算出特征值放这里 */ void FerNNClassifier::getFeatures(const cv::Mat& image,const int& 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);//feature结构体存储的是两个点的位置 //返回的patch图像片在(y1,x1)和(y2, x2)点的像素比较值,返回0或者1 //然后leaf就记录了这13位的二进制代码,作为特征 } fern[t]=leaf;//fern是存储10个13位二进制码的数组,表示一个patch的特征 } } /** * 算出某些(13个)特征在所有(13棵)树的先验概率和 */ float FerNNClassifier::measure_forest(vector<int> fern) { float votes = 0; for (int i = 0; i < nstructs; i++) { votes += posteriors[i][fern[i]];//一个棵对一个特征有一定的权值,就是10个概率相加 } return votes; } /* * 如果正样本或者负样本的数量变化了,重现计算这个特征的先验概率 * pCounter[i][idx]对于第i棵树,含有idx特征的正样本的数量 * nCounter[i][idx]对于第i棵树,含有idx特征的负样本的数量 */ void FerNNClassifier::update(const vector<int>& fern, int C, int N) { int idx; for (int i = 0; i < nstructs; i++) { idx = fern[i]; (C==1) ? pCounter[i][idx] += N : nCounter[i][idx] += N; // pCounter[i][idx] 在第i棵树上,含有特征idx的正样本的数量 if (pCounter[i][idx]==0) { posteriors[i][idx] = 0; } else { posteriors[i][idx] = ((float)(pCounter[i][idx]))/(pCounter[i][idx] + nCounter[i][idx]); } } } /* * 输入已经一些打好标签(标志着是正还是负样本)的特征值,来更新先验概率(训练的本质?) */ void FerNNClassifier::trainF(const vector<std::pair<vector<int>,int> >& ferns,int resample){ // Conf = function(2,X,Y,Margin,Bootstrap,Idx) // 0 1 2 3 4 5 // double *X = mxGetPr(prhs[1]); -> ferns[i].first // int numX = mxGetN(prhs[1]); -> ferns.size() // double *Y = mxGetPr(prhs[2]); ->ferns[i].second // double thrP = *mxGetPr(prhs[3]) * nTREES; ->threshold*nstructs // int bootstrap = (int) *mxGetPr(prhs[4]); ->resample thrP = thr_fern*nstructs; // int step = numX / 10; //for (int j = 0; j < resample; j++) { // for (int j = 0; j < bootstrap; j++) { for (int i = 0; i < ferns.size(); i++){ // for (int i = 0; i < step; i++) { // for (int k = 0; k < 10; k++) { // int I = k*step + i;//box index // double *x = X+nTREES*I; //tree index 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); } } //} } void FerNNClassifier::trainNN(const vector<cv::Mat>& nn_examples){ float conf,dummy; vector<int> y(nn_examples.size(),0); y[0]=1;//只有一个等于1啊,什么意思呢 ?只有第一个正样本? 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 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)]; } // end acum++; printf("%d. Trained NN examples: %d positive %d negative\n",acum,(int)pEx.size(),(int)nEx.size()); } // end /** * 这是nn分类起的核心,分类的根据就是根据相似度的值来分的 * rsconf 相似度 * csconf 保守相似度 */ void FerNNClassifier::NNConf(const Mat& example, vector<int>& isin,float& rsconf,float& csconf){ /*Inputs: * -NN Patch * Outputs: * -Relative Similarity (rsconf), Conservative Similarity (csconf), In pos. set|Id pos set|In neg. set (isin) */ 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); 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 归一化相关匹配法 nccP=(((float*)ncc.data)[0]+1)*0.5; if (nccP>ncc_thesame) anyP=true; if(nccP > maxP){ maxP=nccP; maxPidx = i; if(i<validatedPart) csmaxP=maxP; } } 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); //Measure Conservative Similarity dP = 1 - csmaxP; csconf =(float)dN / (dN + dP); } /** * 根据新的负样本,重新计算fern特征的阈值 和 nn分类器的阈值 * 阈值要比所有的负样本的自信度要大 */ void FerNNClassifier::evaluateTh(const vector<pair<vector<int>,int> >& nXT,const vector<cv::Mat>& nExT){ float fconf; for (int i=0;i<nXT.size();i++){ fconf = (float) measure_forest(nXT[i].first)/nstructs; if (fconf>thr_fern) thr_fern=fconf; } vector <int> isin; float conf,dummy; for (int i=0;i<nExT.size();i++){ NNConf(nExT[i],isin,conf,dummy); if (conf>thr_nn) thr_nn=conf; } if (thr_nn>thr_nn_valid) thr_nn_valid = thr_nn; } void FerNNClassifier::show(){ Mat examples((int)pEx.size()*pEx[0].rows,pEx[0].cols,CV_8U); double minval; Mat ex(pEx[0].rows,pEx[0].cols,pEx[0].type()); for (int i=0;i<pEx.size();i++){ minMaxLoc(pEx[i],&minval); pEx[i].copyTo(ex); ex = ex-minval; Mat tmp = examples.rowRange(Range(i*pEx[i].rows,(i+1)*pEx[i].rows)); ex.convertTo(tmp,CV_8U); } imshow("Examples",examples); }
注:
原作者是用matlab实现的,我分析的源码是其他大神用c++和opencv实现的,源码可以从
https://github.com/arthurv/OpenTLD或者https://github.com/alantrrs/OpenTLD下载
本序列参考了zouxy09同学的序列文章,在此表示感谢
http://blog.csdn.net/zouxy09/article/details/7893011