TLD(Tracking-Learning-Detection)是英国萨里大学的一个捷克籍博士生Zdenek Kalal在其攻读博士学位期间提出的一种新的单目标长时间(long term tracking)跟踪算法。该算法与传统跟踪算法的显著区别在于将传统的跟踪算法和传统的检测算法相结合来解决被跟踪目标在被跟踪过程中发生的形变、部分遮挡等问题。同时,通过一种改进的在线学习机制不断更新跟踪模块的“显著特征点”和检测模块的目标模型及相关参数,从而使得跟踪效果更加稳定、鲁棒、可靠。
源码下载:
https://github.com/arthurv/OpenTLD
TLD的整个流程
1、先初始化工作。
(1) 以已经给出的已知目标去训练样本库(所谓的样本库就是存有许多目标特征的记忆库)
在训练样本之前先对图像进行网格化处理;
void TLD::buildGrid( cv::Mat& img, const cv::Rect& box){
const float SHIFT = 0.1;
const float SCALES[] = {0.16151,0.19381,0.23257,0.27908,0.33490,0.40188,0.48225,
0.57870,0.69444,0.83333,1,1.20000,1.44000,1.72800,
2.07360,2.48832,2.98598,3.58318,4.29982,5.15978,6.19174};
//cv::Mat Mat_show(img);
// Mat_show.copyTo;
int width, height, min_bb_side;
//Rect bbox;
BoundingBox bbox;
Size scale;
int sc=0;
for (int s=0;s<21;s++){
width = round(box.width*SCALES[s]); //输入的box的宽、高乘一个scale //参数Rect
height = round(box.height*SCALES[s]);
min_bb_side = min(height,width); //求出box后的宽、高最小值
if (min_bb_side < min_win || width > img.cols || height > img.rows)//min_win=???????
continue;
scale.width = width;
scale.height = height;
scales.push_back(scale); //保存21个不同scale的的Size的宽高
//按照SHIFT*min_bb_side(宽高的最小值)的步长历遍整个相框,求出不过的scale的Rect;
for (int y=1;y<img.rows-height;y+=round(SHIFT*min_bb_side)){
for (int x=1;x<img.cols-width;x+=round(SHIFT*min_bb_side)){
bbox.x = x;
bbox.y = y;
bbox.width = width;
bbox.height = height;
//rectangle(img,Point(bbox.x,bbox.y),Point( bbox.x+bbox.width,bbox.y+bbox.height),Scalar(255,255,255));///
// imshow("img",img);//Mat frame;
// circle(img,Point(),2,Scalar())
// drawBox( img, CvRect box, Scalar color, int thick)
bbox.overlap = bbOverlap(bbox,BoundingBox(box));
bbox.sidx = sc;
grid.push_back(bbox);
}
}
sc++;
}
}
整个函数的作用是将图片经过一定的变化和比例,获得不同比例下的网格,每一个比例都有一个整图的网格,并且对所有的网格进行编 码,这个编码(grid.push_back(bbox));由于图片的大小没有改变而是固定的;并获得与已知目标的的(交集与并集相除的结果存于)overlap中。
(2)获得好box,坏的box,最好的box,(所谓的好坏就是网格的box与目标的box的overlap(交集除并集))
如果是大于0.6,则认为是好的box。
如果是小于0.2,则认为是坏的box。
当然,最大的值是最好的box。
(好的box 的当”决策蕨“做正的样本pX,坏的box“决策蕨x则认为是负样本nX,最好的的box用于“”最邻近“的正样本pEx)
接着是把正负的样本整合到一起(用到generateNegativeData函数和generatePositiveData函数):
(2.1) 在进行把好的box、坏的box、最好的box被处理为样本之前还要经过一些准备
1、(classifier.prepare(scales);)、这个准备只要是为了决策蕨算法的特征提取
(这里只要参考博客:http://johnhany.net/2014/05/tld-the-theory/#imageclose-568)
集成分类器(Ensemble Classifier)。实际上是一个随机蕨分类器(Random Ferns Classifier),类似于随机森林(Random Forest),区别在于随机森林的树中每层节点判断准则不同,而随机蕨的“蕨”中每层只有一种判断准则。
如上图所示,把左面的树每层节点改成相同的判断条件,就变成了右面的蕨。所以蕨也不再是树状结构,而是线性结构。随机蕨分类器根据样本的特征值判断其分类。从图像元中任意选取两点A和B,比较这两点的亮度值,若A的亮度大于B,则特征值为1,否则为0。每选取一对新位置,就是一个新的特征值。蕨的每个节点就是对一对像素点进行比较。
比如取5对点,红色为A,蓝色为B,样本图像经过含有5个节点的蕨,每个节点的结果按顺序排列起来,得到长度为5的二进制序列01011,转化成十进制数字11。这个11就是该样本经过这个蕨得到的结果。
同一类的很多个样本经过同一个蕨,得到了该类结果的分布直方图。高度代表类的先验概率p(F|C),F代表蕨的结果(如果蕨有s个节点,则共有1+2^s种结果)。
不同类的样本经过同一个蕨,得到不同的先验概率分布。
(classifier.prepare(scales)这个函数就是说:
每一个仿射图片(仿射图片的位置大小为bbHull(等同所有好的box的并集所围成的大矩形))有nstructs颗蕨,每颗蕨有structSize个特征;所以一共int totalFeatures = nstructs*structSize种特征要提取,但是由于每一个box都有21种比例,而且每个的特征点的位置是不一样的,尽量涵盖整个图片,所以有
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);
}
}
(classifier.prepare(scales)的函数只会运行一次,所以它的每种比例的的特征点都是固定不变的,
(2.2)样本特征的提取
2..21决策蕨的特征提取
FerNNClassifier::getFeatures(const cv::Mat& image,const int& scale_idx, vector<int>& fern)
获得box碎片的的nstructs颗蕨特征,并存在vector<int>中,过程等同上面的博客蕨的过程和
正样本:
详见函数(generatePositiveDataData)
for (int i=0;i<num_warps;i++){
if (i>0)
generator(frame,pt,warped,bbhull.size(),rng);
// cv::imshow ("warped",warped);
// cv::imshow ("frame",img);
for (int b=0;b<good_boxes.size();b++){
idx=good_boxes[b];
patch = img(grid[idx]);
// cv::imshow ("p",patch);
classifier.getFeatures(patch,grid[idx].sidx,fern);
pX.push_back(make_pair(fern,1));
负样本:详见函数(generateNegativeData)
for (int j=0;j<bad_boxes.size();j++){
idx = bad_boxes[j];
if (getVar(grid[idx],iisum,iisqsum)<var*0.5f)
continue;
patch = frame(grid[idx]);
classifier.getFeatures(patch,grid[idx].sidx,fern);
nX.push_back(make_pair(fern,0));
a++;
}
2.2 最近邻分类器算法:
最近邻分类器(Nearest Neighbor Classifier)。计算新样本的相对相似度,如大于0.6,则认为是正样本。相似度规定如下:
图像元pi和pj的相似度,公式里的N是规范化的相关系数,所以S的取值范围就在[0,1]之间,
负最近邻相似度,
相对相似度,取值范围在[0,1]之间,值越大代表相似度越高,
所以,检测器是追踪器的监督者,因为检测器要改正追踪器的错误;而追踪器是训练检测器时的监督者,因为要用追踪器的结果对检测器的分类结果进行监督。用另一段程序对训练过程进行监督,而不是由人来监督,这也是称P-N学习为“半监督”机器学习的原因。
TLD的工作流程如下图所示。首先,检测器由一系列包围框产生样本,经过级联分类器产生正样本,放入样本集;然后使用追踪器估计出物体的新位置,P专家根据这个位置又产生正样本,N专家从这些正样本里选出一个最可信的,同时把其他正样本标记为负;最后用正样本更新检测器的分类器参数,并确定下一帧物体包围框的位置。
把蕨的算法正负样本的整合为(ferns_data):(详见函数TLD_init)
vector<pair<vector<int>,int> > ferns_data(nX.size()+pX.size());
vector<int> idx = index_shuffle(0,ferns_data.size());
int a=0;
for (int i=0;i<pX.size();i++){
ferns_data[idx[a]] = pX[i];
a++;
}
for (int i=0;i<nX.size();i++){
ferns_data[idx[a]] = nX[i];
a++;
更新的过程:详见trainFF函数:
- void FerNNClassifier::NNConf(const Mat& example, vector<int>& isin,float& rsconf,float& csconf){
- isin=vector<int>(3,-1); //vector<T> v3(n, i); v3包含n个值为i的元素。 三个元素都是-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); //ceil返回大于或者等于指定表达式的最小整数
- float nccN, maxN=0;
- bool anyN=false;
- //比较图像片p到在线模型M的距离(相似度),计算正样本最近邻相似度,也就是将输入的图像片与
- //在线模型中所有的图像片进行匹配,找出最相似的那个图像片,也就是相似度的最大值
- 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) //ncc_thesame: 0.95
- anyP=true;
- if(nccP > maxP){
- maxP=nccP; //记录最大的相似度以及对应的图像片index索引值
- 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 he query patch is highly correlated with any positive patch in the model then it is considered to be one of them
- if (anyP) isin[0]=1;
- isin[1]=maxPidx; //get the index of the maximall correlated positive patch
- //if the query patch is highly correlated with any negative patch in the model then it is considered to be one of them
- if (anyN) isin[2]=1;
- //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);
- }