本序列文章的目的是总结一下这段时间所学到的,主要分为以下几部分,本章是第五部分。
1 算法概述
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