从main()函数切入,分析整个TLD运行过程如下:(这里只是分析工作过程,全部注释的代码见博客的更新)1、分析程序运行的命令行参数;./run_tld-p../parameters.yml-s../datasets/06_car/car.mpg-b../datasets/06_car/init.txt–r2、读入初始化参数(程序中变量)的文件parameters.yml;3、通过文件或者用户鼠标框选的方式指定要跟踪的目标的BoundingBox;4、用上面得到的包含要跟踪目标的BoundingBox和第一帧图像去初始化TLD系统,tld.init(last_gray,box,bb_file);初始化包含的工作如下:4.1、buildGrid(frame1,box);检测器采用扫描窗口的策略:扫描窗口步长为宽高的10%,尺度缩放系数为1.2;此函数构建全部的扫描窗口grid,并计算每一个扫描窗口与输入的目标box的重叠度;重叠度定义为两个box的交集与它们的并集的比;4.2、为各种变量或者容器分配内存空间;4.3、getOverlappingBoxes(box,num_closest_init);此函数根据传入的box(目标边界框),在整帧图像中的全部扫描窗口中(由上面4.1得到)寻找与该box距离最小(即最相似,重叠度最大)的num_closest_init(10)个窗口,然后把这些窗口归入good_boxes容器。同时,把重叠度小于0.2的,归入bad_boxes容器;相当于对全部的扫描窗口进行筛选。并通过BBhull函数得到这些扫描窗口的最大边界。4.5、classifier.prepare(scales);准备分类器,scales容器里是所有扫描窗口的尺度,由上面的buildGrid()函数初始化;TLD的分类器有三部分:方差分类器模块、集合分类器模块和最近邻分类器模块;这三个分类器是级联的,每一个扫描窗口依次全部通过上面三个分类器,才被认为含有前景目标。这里prepare这个函数主要是初始化集合分类器模块;集合分类器(随机森林)基于n个基本分类器(共10棵树),每个分类器(树)都是基于一个pixelcomparisons(共13个像素比较集)的,也就是说每棵树有13个判断节点(组成一个pixelcomparisons),输入的图像片与每一个判断节点(相应像素点)进行比较,产生0或者1,然后将这13个0或者1连成一个13位的二进制码x(有2^13种可能),每一个x对应一个后验概率P(y|x)=#p/(#p+#n)(也有2^13种可能),#p和#n分别是正和负图像片的数目。那么整一个集合分类器(共10个基本分类器)就有10个后验概率了,将10个后验概率进行平均,如果大于阈值(一开始设经验值0.65,后面再训练优化)的话,就认为该图像片含有前景目标;后验概率P(y|x)=#p/(#p+#n)的产生方法:初始化时,每个后验概率都得初始化为0;运行时候以下面方式更新:将已知类别标签的样本(训练样本)通过n个分类器进行分类,如果分类结果错误,那么相应的#p和#n就会更新,这样P(y|x)也相应更新了。pixelcomparisons的产生方法:先用一个归一化的patch去离散化像素空间,产生所有可能的垂直和水平的pixelcomparisons,然后我们把这些pixelcomparisons随机分配给n个分类器,每个分类器得到完全不同的pixelcomparisons(特征集合),这样,所有分类器的特征组统一起来就可以覆盖整个patch了。特征是相对于一种尺度的矩形框而言的,TLD中第s种尺度的第i个特征features[s][i]=Feature(x1,y1,x2,y2);是两个随机分配的像素点坐标(就是由这两个像素点比较得到0或者1的)。每一种尺度的扫描窗口都含有totalFeatures=nstructs*structSize个特征;nstructs为树木(由一个特征组构建,每组特征代表图像块的不同视图表示)的个数;structSize为每棵树的特征个数,也即每棵树的判断节点个数;树上每一个特征都作为一个决策节点;prepare函数的工作就是先给每一个扫描窗口初始化了对应的pixelcomparisons(两个随机分配的像素点坐标);然后初始化后验概率为0;4.6、generatePositiveData(frame1,num_warps_init);此函数通过对第一帧图像的目标框box(用户指定的要跟踪的目标)进行仿射变换来合成训练初始分类器的正样本集。具体方法如下:先在距离初始的目标框最近的扫描窗口内选择10个boundingbox(已经由上面的getOverlappingBoxes函数得到,存于good_boxes里面了,还记得不?),然后在每个boundingbox的内部,进行±1%范围的偏移,±1%范围的尺度变化,±10%范围的平面内旋转,并且在每个像素上增加方差为5的高斯噪声(确切的大小是在指定的范围内随机选择的),那么每个box都进行20次这种几何变换,那么10个box将产生200个仿射变换的boundingbox,作为正样本。具体实现如下:getPattern(frame(best_box),pEx,mean,stdev);此函数将frame图像best_box区域的图像片归一化为均值为0的15*15大小的patch,存于pEx(用于最近邻分类器的正样本)正样本中(最近邻的box的Pattern),该正样本只有一个。generator(frame,pt,warped,bbhull.size(),rng);此函数属于PatchGenerator类的构造函数,用来对图像区域进行仿射变换,先RNG一个随机因子,再调用()运算符产生一个变换后的正样本。classifier.getFeatures(patch,grid[idx].sidx,fern);函数得到输入的patch的特征fern(13位的二进制代码);pX.push_back(make_pair(fern,1));//positivefernsfeatures,labels=1然后标记为正样本,存入pX(用于集合分类器的正样本)正样本库;以上的操作会循环num_warps*good_boxes.size()即20*10次,这样,pEx就有了一个正样本,而pX有了200个正样本了;4.7、meanStdDev(frame1(best_box),mean,stdev);统计best_box的均值和标准差,var=pow(stdev.val[0],2)*0.5;作为方差分类器的阈值。4.8、generateNegativeData(frame1);由于TLD仅跟踪一个目标,所以我们确定了目标框了,故除目标框外的其他图像都是负样本,无需仿射变换;具体实现如下:由于之前重叠度小于0.2的,都归入bad_boxes了,所以数量挺多,把方差大于var*0.5f的bad_boxes都加入负样本,同上面一样,需要classifier.getFeatures(patch,grid[idx].sidx,fern);和nX.push_back(make_pair(fern,0));得到对应的fern特征和标签的nX负样本(用于集合分类器的负样本);然后随机在上面的bad_boxes中取bad_patches(100个)个box,然后用getPattern函数将frame图像bad_box区域的图像片归一化到15*15大小的patch,存在nEx(用于最近邻分类器的负样本)负样本中。这样nEx和nX都有负样本了;(box的方差通过积分图像计算)4.9、然后将nEx的一半作为训练集nEx,另一半作为测试集nExT;同样,nX也拆分为训练集nX和测试集nXT;4.10、将负样本nX和正样本pX合并到ferns_data[]中,用于集合分类器的训练;4.11、将上面得到的一个正样本pEx和nEx合并到nn_data[]中,用于最近邻分类器的训练;4.12、用上面的样本训练集训练集合分类器(森林)和最近邻分类器:classifier.trainF(ferns_data,2);//bootstrap=2对每一个样本ferns_data[i],如果样本是正样本标签,先用measure_forest函数返回该样本所有树的所有特征值对应的后验概率累加值,该累加值如果小于正样本阈值(0.6*nstructs,这就表示平均值需要大于0.6(0.6*nstructs/nstructs),0.6是程序初始化时定的集合分类器的阈值,为经验值,后面会用测试集来评估修改,找到最优),也就是输入的是正样本,却被分类成负样本了,出现了分类错误,所以就把该样本添加到正样本库,同时用update函数更新后验概率。对于负样本,同样,如果出现负样本分类错误,就添加到负样本库。classifier.trainNN(nn_data);对每一个样本nn_data,如果标签是正样本,通过NNConf(nn_examples[i],isin,conf,dummy);计算输入图像片与在线模型之间的相关相似度conf,如果相关相似度小于0.65,则认为其不含有前景目标,也就是分类错误了;这时候就把它加到正样本库。然后就通过pEx.push_back(nn_examples[i]);将该样本添加到pEx正样本库中;同样,如果出现负样本分类错误,就添加到负样本库。4.13、用测试集在上面得到的集合分类器(森林)和最近邻分类器中分类,评价并修改得到最好的分类器阈值。classifier.evaluateTh(nXT,nExT);对集合分类器,对每一个测试集nXT,所有基本分类器的后验概率的平均值如果大于thr_fern(0.6),则认为含有前景目标,然后取最大的平均值(大于thr_fern)作为该集合分类器的新的阈值。对最近邻分类器,对每一个测试集nExT,最大相关相似度如果大于nn_fern(0.65),则认为含有前景目标,然后取最大的最大相关相似度(大于nn_fern)作为该最近邻分类器的新的阈值。5、进入一个循环:读入新的一帧,然后转换为灰度图像,然后再处理每一帧processFrame;6、processFrame(last_gray,current_gray,pts1,pts2,pbox,status,tl,bb_file);逐帧读入图片序列,进行算法处理。processFrame共包含四个模块(依次处理):跟踪模块、检测模块、综合模块和学习模块;6.1、跟踪模块:track(img1,img2,points1,points2);track函数完成前一帧img1的特征点points1到当前帧img2的特征点points2的跟踪预测;6.1.1、具体实现过程如下:(1)先在lastbox中均匀采样10*10=100个特征点(网格均匀撒点),存于points1:bbPoints(points1,lastbox);(2)利用金字塔LK光流法跟踪这些特征点,并预测当前帧的特征点(见下面的解释)、计算FBerror和匹配相似度sim,然后筛选出FB_error[i]=median(FB_error)和sim_error[i]median(sim_error)的特征点(舍弃跟踪结果不好的特征点),剩下的是不到50%的特征点:tracker.trackf2f(img1,img2,points,points2);(3)利用剩下的这不到一半的跟踪点输入来预测boundingbox在当前帧的位置和大小tbb:bbPredict(points,points2,lastbox,tbb);(4)跟踪失败检测:如果FBerror的中值大于10个像素(经验值),或者预测到的当前box的位置移出图像,则认为跟踪错误,此时不返回boundingbox:if(tracker.getFB()10||tbb.ximg2.cols||tbb.y