TLD代码详尽注释

基于opencv3.1.0+vs2013

另外自己也是刚学习,如果有注释错误的地方,还请指出

utils.h

#include<opencv2/opencv.hpp>

using namespace std;
using namespace cv;

#pragma once

void drawBox(cv::Mat& image, CvRect  box, cv::Scalar color = cvScalarAll(255), int thick=1); 

void drawPoints(cv::Mat& image, std::vector<cv::Point2f> points,cv::Scalar color=cv::Scalar::all(255));

cv::Mat createMask(const cv::Mat& image, CvRect box);

float median(std::vector<float> v);

std::vector<int> index_shuffle(int begin,int end);

utils.cpp

#include "utils.h""
using namespace cv;
using namespace std;

/**************************************************************************
*函数名称:drawBox
*函数参数:输入图像、矩形框、框的颜色、框的线型
*函数功能:在指定帧中圈定目标
*输出参数:无
**************************************************************************/
void drawBox( Mat& image , CvRect box , Scalar color , int thick )
{
	rectangle( image , cvPoint( box.x , box.y ) , cvPoint( box.x + box.width , box.y + box.height ) , color , thick );
}



/******************************************************************************
*函数名称:drawPoints
*函数参数:输入图像、目标点集、点的颜色
*函数功能:在指定帧中圈定指定点
*调用函数:无
*输出参数:无
*******************************************************************************/
void drawPoints( Mat& image , vector<Point2f> points , Scalar color )
{
	for( vector<Point2f>::const_iterator i = points.begin() , ie = points.end(); i != ie; ++i )
	{
		Point center( cvRound( i->x ) , cvRound( i->y ) );
		circle( image , *i , 2 , color , 1 );
	}
}



/*************************************************************************
*函数名称:createMask
*函数参数:输入图像、目标框
*函数功能:创造在指定帧中感兴趣区域的掩模
*调用函数:无
*输出参数:掩模
*************************************************************************/
Mat createMask( const Mat& image , CvRect box )
{
	Mat mask = Mat::zeros( image.rows , image.cols , CV_8U );
	drawBox( mask , box , Scalar::all( 255 ) , CV_FILLED );
	return mask;
}


/***********************************************************************
*函数名称:median
*函数参数:浮点数组
*函数功能:找到浮点数组中的中位数
*调用函数:无
*输出参数:中位数(float)
************************************************************************/
float median( vector<float> v )
{
	// STL中的nth_element()方法找出一个数列中排名第n的那个数。  
	// 对于序列a[0:len-1]将第n大的数字,排在a[n],同时a[0:n-1]都小于a[n],a[n+1:]都大于a[n],  
	// 但a[n]左右的这两个序列不一定有序。  
	// 用在中值流跟踪算法中,寻找中值

	int n = floor( v.size() / 2 );
	nth_element( v.begin() , v.begin() + n , v.end() );
	return v[n];
}



/*************************************************************************
*函数名称:index_shuffle
*函数参数:起点、终点的位置   如0、1、2....
*函数功能:返回一个乱序的指定大小的整型数组
*输出参数:vector<int>型数组
*************************************************************************/
vector<int> index_shuffle( int begin , int end )
{
	vector<int> indexes( end - begin );
	for( int i = begin; i < end; i++ )
	{
		indexes[i] = i;
	}
	random_shuffle( indexes.begin() , indexes.end() );
	return indexes;
}


// 需要指定范围内的随机数,传统的方法是使用ANSI C的函数random(),然后格式化结果以便结果是落在  
// 指定的范围内。但是,使用这个方法至少有两个缺点。做格式化时,结果常常是扭曲的,且只支持整型数。  
// C++中提供了更好的解决方法,那就是STL中的random_shuffle()算法。产生指定范围内的随机元素集的最佳方法  
// 是创建一个顺序序列(也就是向量或者内置数组),在这个顺序序列中含有指定范围的所有值。  
// 例如,如果你需要产生100个0-99之间的数,那么就创建一个向量并用100个按升序排列的数填充向量.  
// 填充完向量之后,用random_shuffle()算法打乱元素排列顺序。  
// 默认的random_shuffle中, 被操作序列的index 与 rand() % N 两个位置的值交换,来达到乱序的目的。  
// index_shuffle()用于产生指定范围[begin:end]的随机数,返回随机数数组 

LKTracker.h

#include"utils.h""
#include <opencv2/opencv.hpp>

#ifndef LKTRACKER_H
#define LKTRACKER_H

using namespace std;
using namespace cv;

// 使用金字塔LK光流法跟踪,所以类的成员变量很多都是OpenCV中calcOpticalFlowPyrLK()函数的参数  
class LKTracker
{
private:

	int level;                               // 最大的金字塔层数 
	cv::Size window_size;	                 // 每个金字塔层的搜索窗口尺寸
	std::vector<uchar> status;               // 如果对应特征的光流被发现,数组中的每一个元素都被设置为 1, 否则设置为 0
	std::vector<uchar> FB_status;            // 用FB方法是否找到跟踪点的状态向量
	std::vector<float> similarity;           // 相似度
	std::vector<float> FB_error;             // Forward-Backward error方法,求FB_error的结果与原始位置的欧式距离
	std::vector<cv::Point2f> pointsFB;       // 后向轨迹跟踪参数

	float simmed;                            // 相似度中值
	float fbmed;                             // FB_error的中值

	// TermCriteria模板类,取代了之前的CvTermCriteria,这个类是作为迭代算法的终止条件的  
	// 该类变量需要3个参数,一个是类型,第二个参数为迭代的最大次数,最后一个是特定的阈值。  
	// 指定在每个金字塔层,为某点寻找光流的迭代过程的终止条件。
	cv::TermCriteria term_criteria;          // 迭代终止条件
	float lambda;                            // 某阈值??Lagrangian 乘子 

	void normCrossCorrelation( const cv::Mat& img1 , const cv::Mat& img2 , std::vector<cv::Point2f>& points1 , std::vector<cv::Point2f>& points2 );
	bool filterPts( std::vector<cv::Point2f>& points1 , std::vector<cv::Point2f>& points2 );
public:
	LKTracker(); 
	bool trackf2f( const cv::Mat& img1 , const cv::Mat& img2 ,std::vector<cv::Point2f> &points1 , std::vector<cv::Point2f> &points2 );
	float getFB()                            // 返回FB_error的中值
	{
		return fbmed;
	}
};

#endif

LKTracker.cpp

/*******************************************************************************
*类定义:LKTracker类
*类功能:金字塔LK光流法跟踪
*
*********************************************************************************/
#include "LKTracker.h""
using namespace cv;

//构造函数
LKTracker::LKTracker()
{
	// 该类变量需要3个参数,一个是类型,第二个参数为迭代的最大次数,最后一个是特定的阈值。
	term_criteria = TermCriteria( TermCriteria::COUNT + TermCriteria::EPS , 20 , 0.03 );    // 迭代终止条件
	window_size = Size( 4 , 4 );
	level = 5;
	lambda = 0.5;
}

/**********************************************************************************
*函数名称:track2f
*函数参数:前一帧图像、后一帧图像、输入2D点矢量、输出2D点矢量矩形框
*函数功能:在指定帧中圈定目标
*输出参数:无
************************************************************************************/
bool LKTracker::trackf2f( const Mat& img1 , const Mat& img2 , vector<Point2f> &points1 , vector<cv::Point2f> &points2 )
{

	//Forward-Backward tracking  前向轨迹跟踪
	calcOpticalFlowPyrLK( img1 , img2 , points1 , points2 , status , similarity , window_size , level , term_criteria , lambda , 0 );

	// 后向轨迹跟踪
	calcOpticalFlowPyrLK( img2 , img1 , points2 , pointsFB , FB_status , FB_error , window_size , level , term_criteria , lambda , 0 );

	// Compute the real FB-error
	// 原理很简单:从t时刻的图像的A点,跟踪到t+1时刻的图像B点;然后倒回来,从t+1时刻的图像的B点往回跟踪,  
	// 假如跟踪到t时刻的图像的C点,这样就产生了前向和后向两个轨迹,比较t时刻中 A点 和 C点 的距离,如果距离  
	// 小于一个阈值,那么就认为前向跟踪是正确的;这个距离就是FB_error 

	// Compute the real FB-error
	for( int i = 0; i < points1.size(); ++i )
	{
		FB_error[i] = norm( pointsFB[i] - points1[i] );        // norm为数组的范数
	}

	// Filter out points with FB_error[i] > median(FB_error) && points with sim_error[i] > median(sim_error)
	normCrossCorrelation( img1 , img2 , points1 , points2 );
	return filterPts( points1 , points2 );
}



// NCC 归一化交叉相关,FB error与NCC结合,使跟踪更稳定  交叉相关的图像匹配算法??  
// 交叉相关法的作用是进行云团移动的短时预测。选取连续两个时次的GMS-5卫星云图,将云图区域划分为32×32像素  
// 的图像子集,采用交叉相关法计算获取两幅云图的最佳匹配区域,根据前后云图匹配区域的位置和时间间隔,确  
// 定出每个图像子集的移动矢量(速度和方向),并对图像子集的移动矢量进行客观分析,其后,基于检验后的云  
// 图移动矢量集,利用后向轨迹方法对云图作短时外推预测。
/**********************************************************************************
*函数名称:LKTracker::normCrossCorrelation
*函数参数:前一帧图像、后一帧图像、输入2D点矢量、输出2D点矢量矩形框
*函数功能:计算每一个特征点的相似度
*输出参数:无
************************************************************************************/
void LKTracker::normCrossCorrelation( const Mat& img1 , const Mat& img2 , vector<Point2f>& points1 , vector<Point2f>& points2 )
{
	Mat rec0( 10 , 10 , CV_8U );
	Mat rec1( 10 , 10 , CV_8U );
	Mat res( 1 , 1 , CV_32F );

	for( int i = 0; i < points1.size(); i++ )
	{
		if( status[i] == 1 )          // 为1表示该特征点跟踪成功
		{
			// getRectSubPix 从原图像中提取提取一个感兴趣的矩形区域图像
			// InputArray image:输入图像
			// Size patchSize:获取矩形的大小
			// Point2f center:获取的矩形在原图像中的位置
			// OutputArray patch:表示输出的图像
			// int patchType = -1 :表示输出图像的深度
			// 利用NCC把跟踪预测的结果周围取10*10的小图片与原始位置周围10*10的小图片(使用函数getRectSubPix得到)

			getRectSubPix( img1 , Size( 10 , 10 ) , points1[i] , rec0 );
			getRectSubPix( img2 , Size( 10 , 10 ) , points2[i] , rec1 );

			// matchTemplate 模板匹配
			// 匹配前一帧和当前帧中提取的10x10象素矩形,得到匹配后的映射图像
			// CV_TM_CCOEFF_NORMED 归一化相关系数匹配法  
			// 参数分别为:欲搜索的图像。搜索模板。比较结果的映射图像。指定匹配方法
			matchTemplate( rec0 , rec1 , res , CV_TM_CCOEFF_NORMED );

			// 得到各个特征点的相似度大小
			similarity[i] = ( ( float * ) ( res.data ) )[0];

		}
		else
		{
			similarity[i] = 0.0;
		}
	}
	rec0.release();
	rec1.release();
	res.release();
}



/**********************************************************************************************
*函数名称:LKTracker::filterPts
*函数参数:输入2D点矢量、输出2D点矢量矩形框
*函数功能:筛选出 FB_error[i] <= median(FB_error) 和 sim_error[i] > median(sim_error) 的特征点
得到NCC和FB error结果的中值,分别去掉中值一半的跟踪结果不好的点
*输出参数:无
**********************************************************************************************/
bool LKTracker::filterPts( vector<Point2f>& points1 , vector<Point2f>& points2 )
{
	//Get Error Medians
	simmed = median( similarity );                     // 找到相似度的中值
	size_t i , k;
	for( i = k = 0; i < points2.size(); ++i )
	{
		if( !status[i] )                               // 对应特征点的光流没有被发现
		{
			continue;
		}			
		if( similarity[i] > simmed )                   // 剩下 similarity[i]> simmed 的特征点
		{
			points1[k] = points1[i];
			points2[k] = points2[i];
			FB_error[k] = FB_error[i];
			k++;
		}
	}
	if( k == 0 )                                       // 没有满足条件的点
	{
		return false;
	}
		
	points1.resize( k );
	points2.resize( k );
	FB_error.resize( k );

	fbmed = median( FB_error );                        // 找到FB_error的中值

	for( i = k = 0; i < points2.size(); ++i )
	{
		if( !status[i] )                               // 没有满足条件的点
		{
			continue;
		}			
		if( FB_error[i] <= fbmed )                     // 再对上一步剩下的特征点进一步筛选,剩下 FB_error[i] <= fbmed 的特征点 
		{
			points1[k] = points1[i];
			points2[k] = points2[i];
			k++;
		}
	}
	points1.resize( k );                               // points1重置大小
	points2.resize( k );                               // points2重置大小

	if( k > 0 )
	{
		return true;
	}
	else
	{
		return false;
	}
}


FerNNClassifier.h

#include <opencv2/opencv.hpp>
#include <stdio.h>

#ifndef FERNNCLASSIFIER_H
#define FERNNCLASSIFIER_H

using namespace cv;
using namespace std;

class FerNNClassifier
{
private:
	int acum;           // 最近邻分类器的训练次数
	int structSize;     // 每个分类器的节点数(树的深度)13
	int nstructs;       // 森林的树的个数,即分类器的个数 10
	float valid;        // 有效值 0.5(聚类的阈值标准?)
	float ncc_thesame;  // 最近邻相似度正样本的阈值 0.95,大于该值认为是最好的正样本(选一个)
	float thr_nn;       // 最近邻分类器的阈值 0.65
	float thr_fern;     // 集合分类器(森林)的10个后验概率阈值 0.6 大于该阈值认为含有前景目标
public:
	//Parameters
	float thr_nn_valid; // 检测器检测到的与跟踪器预测到的box的重叠度的阈值 0.7 

	void read( const cv::FileNode& file );
	void prepare( const std::vector<cv::Size>& scales );
	void getFeatures( const cv::Mat& image , const int& scale_idx , std::vector<int>& fern );
	void update( const std::vector<int>& fern , int C , int N );
	float measure_forest( std::vector<int> fern );
	void trainF( const std::vector<std::pair<std::vector<int> , int> >& ferns , int resample );
	void trainNN( const std::vector<cv::Mat>& nn_examples );
	void NNConf( const cv::Mat& example , std::vector<int>& isin , float& rsconf , float& csconf );
	void evaluateTh( const std::vector<std::pair<std::vector<int> , int> >& nXT , const std::vector<cv::Mat>& nExT );
	void show();
	
	// 得到树的个数10
	int getNumStructs()
	{
		return nstructs;
	}

	// 得到森林分类器的阈值
	float getFernTh()
	{
		return thr_fern;
	}

	// 得到最近邻分类器的阈值
	float getNNTh()
	{
		return thr_nn;
	}

	// 特征结构体
	struct Feature
	{
		uchar x1 , y1 , x2 , y2;
		Feature() : x1( 0 ) , y1( 0 ) , x2( 0 ) , y2( 0 )
		{
		}
		Feature( int _x1 , int _y1 , int _x2 , int _y2 ): x1( ( uchar ) _x1 ) , y1( ( uchar ) _y1 ) , x2( ( uchar ) _x2 ) , y2( ( uchar ) _y2 )
		{
		}
		bool operator ()( const cv::Mat& patch ) const
		{
			//返回的patch图像片在(y1,x1)和(y2, x2)点的像素比较值,返回0或者1
			return patch.at<uchar>( y1 , x1 ) > patch.at<uchar>( y2 , x2 );
		}
	};

	std::vector<std::vector<Feature> > features;     // 特征结构体数组(包含四个数值,重载一个()符号 )
	std::vector<std::vector<int> > nCounter;         // 负样本  10组,每组里含有2^13=8192个
	std::vector<std::vector<int> > pCounter;         // 正样本  10组,每组里含有2^13=8192个
	std::vector<std::vector<float> > posteriors;     // 森林的后验概率数组 10组,每组里含有2^13=8192个
	float thrN;                                      // 负阈值
	float thrP;                                      // 正阈值

	// 最近邻成员
	std::vector<cv::Mat> pEx;   // 最近邻正样本
	std::vector<cv::Mat> nEx;   // 最近邻负样本
};

#endif

FerNNClassifier.cpp

/**************************************************************************
*随机森林算法理解
*http://mp.weixin.qq.com/s?__biz=MzI5MDUyMDIxNA==&mid=2247483890&idx=1&sn=81912038d09c293759e9df4707bce6c5&chksm=ec1fec0bdb68651d81e00fad81c8501355ddb1fd86391f92b3e4e9a8e5706af14116f1297f2d&mpshare=1&scene=23&srcid=0103hQHL10gWqm9dBF5kAIEE#rd
**************************************************************************/
#include"FerNNClassifier.h"

using namespace cv;
using namespace std;


/*************************************************************************
*函数名称:FerNNClassifier::read
*函数参数:读取的参数文件名的引用
*函数功能:从参数文件中读取参数值
*调用函数:无
*输出参数:无
*************************************************************************/
void FerNNClassifier::read( const FileNode& file )
{
	// 下面这些参数通过程序开始运行时读入parameters.yml文件进行初始化
	valid        = ( float ) file["valid"];               // 0.5            
	ncc_thesame  = ( float ) file["ncc_thesame"];         // 0.95
	nstructs     = ( int )   file["num_trees"];           // 10
	structSize   = ( int )   file["num_features"];        // 13      
	thr_fern     = ( float ) file["thr_fern"];            // 0.6
	thr_nn       = ( float ) file["thr_nn"];              // 0.65
	thr_nn_valid = ( float ) file["thr_nn_valid"];        // 0.7
}


/*************************************************************************
*函数名称:FerNNClassifier::prepare
*函数参数:buildGrid函数中得到的scales容器
*函数功能:进行分类器的准备(Fern为主)
*          1、方差分类器:用于结合积分评分图以剔除bad_boxes中方差较小的负样本
*		   2、Fern分类器(随机森林分类器),通过后验概率判断是否包含前景
*		   3、最近邻NN分类器
*输出参数:无
*************************************************************************/
void FerNNClassifier::prepare( const vector<Size>& scales )
{
	acum = 0;

	//Initialize test locations for features
	int totalFeatures = nstructs*structSize;     // 所有的特征值的个数

	// 二维向量,包含全部尺度(scales)的扫描窗口,每个尺度包含totalFeatures个特征 
	features = vector<vector<Feature> >( scales.size() , vector<Feature>( totalFeatures ) );
	
	// opencv中自带的一个随机数发生器的类RNG 
	RNG& rng = theRNG();
	float x1f , x2f , y1f , y2f;
	int x1 , x2 , y1 , y2;

	// 集合分类器基于n个基本分类器,每个分类器都是基于一个pixel comparisons(像素比较集)的;  
	// pixel comparisons的产生方法:先用一个归一化的patch去离散化像素空间,产生所有可能的垂直和水平的pixel comparisons  
	// 然后我们把这些pixel comparisons随机分配给n个分类器,每个分类器得到完全不同的pixel comparisons(特征集合),  
	// 这样,所有分类器的特征组统一起来就可以覆盖整个patch了  

	// 用随机数去填充每一个尺度扫描窗口的特征 
	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;

			// 第s种尺度的第i个特征 ,两个随机分配的像素点坐标,Feature是一种由四个数组成的结构体
			features[s][i] = Feature( x1 , y1 , x2 , y2 );
		}

	}

	//Thresholds
	thrN = 0.5*nstructs;

	// 初始化后验概率为0,共10个
	// 后验概率指每一个分类器对传入的图像片进行像素对比,每一个像素对比得到0或者1,所有的特征13个comparison对比,  
	// 连成一个13位的二进制代码x,然后索引到一个记录了后验概率的数组P(y|x),y为0或者1(二分类),也就是出现x的  
	// 基础上,该图像片为y的概率是多少,对n个基本分类器的后验概率做平均,大于0.5则判定其含有目标 

	//Initialize Posteriors
	for( int i = 0; i < nstructs; i++ )
	{
		// 每一个每类器为一个后验概率的分布,这个分布有2^d个条目(entries),这里d是像素比较pixel comparisons  
		// 的个数,这里是structSize,即13个comparison,所以会产生2^13即8,192个可能的code,每一个code对应一个后验概率  
		// 后验概率P(y|x)= #p/(#p+#n) ,#p和#n分别是正和负图像片的数目,也就是下面的pCounter和nCounter  
		// 初始化时,每个后验概率都得初始化为0;运行时候以下面方式更新:已知类别标签的样本(训练样本)通过n个分类器  
		// 进行分类,如果分类结果错误,那么响应的#p和#n就会更新,这样P(y|x)也相应更新了

		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 ) );
	}
}

/*************************************************************************************************
*函数名称:FerNNClassifier::getFeatures
*函数参数:输入的patch图像、尺度变换的索引值、想要得到的13位二进制码num_warps * good_boxes.size()个
*函数功能:得到输入的patch的特征fern(13位的二进制码)
*输出参数:无
*************************************************************************************************/
void FerNNClassifier::getFeatures( const cv::Mat& image , const int& scale_idx , vector<int>& fern )
{
	int leaf;                                   // 叶子,为树的最终节点

	for( int t = 0; t < nstructs; t++ )         // 对于每一棵树,共10棵树
	{
		leaf = 0;
		for( int f = 0; f < structSize; f++ )   // 对于树上的每一个节点 13个节点
		{
			// struct Feature 特征结构体有一个运算符重载 bool operator ()(const cv::Mat& patch) const  
			// 返回的patch图像片在(y1,x1)和(y2, x2)点的像素比较值,返回0或者1  
			// 然后leaf就记录了这13位的二进制代码,作为特征

			leaf = ( leaf << 1 ) + features[scale_idx][t*structSize + f]( image );
		}
		fern[t] = leaf;                         // 记录每棵树最终的结果(13位二进制码)
	}
}

/********************************************************************
*函数名称:FerNNClassifier::measure_forest
*函数参数:fern数组
*函数功能:返回该样本所有树的对应于 所有特征值对应的后验概率的累加值
*输出参数:无
********************************************************************/
float FerNNClassifier::measure_forest( vector<int> fern )
{
	float votes = 0;
	for( int i = 0; i < nstructs; i++ )
	{
		// 后验概率posteriors[i][idx] = ((float)(pCounter[i][idx]))/(pCounter[i][idx] + nCounter[i][idx]);  
		votes += posteriors[i][fern[i]];                //每棵树的每个特征值对应的后验概率累加值
	}
	return votes;
}


/********************************************************************
*函数名称:FerNNClassifier::update
*函数参数:fern(一棵树对应的特征)数组,(C,N)=(1,1)或者(0,1)
*函数功能:更新正负样本数,同时更新后验概率
*输出参数:无
********************************************************************/
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,正样本数+1,如果C==0,负样本数+1
		( C == 1 ) ? pCounter[i][idx] += N : nCounter[i][idx] += N;
		if( pCounter[i][idx] == 0 )
		{
			posteriors[i][idx] = 0;
		}
		else
		{
			posteriors[i][idx] = ( ( float ) ( pCounter[i][idx] ) ) / ( pCounter[i][idx] + nCounter[i][idx] );
		}
	}
}


/********************************************************************
*函数名称:FerNNClassifier::trainF
*函数参数:样本组, resample = 2
*函数功能:训练集合分类器(n个基本分类器集合)
*输出参数:无
********************************************************************/
void FerNNClassifier::trainF( const vector<std::pair<vector<int> , int> >& ferns , int resample )
{
	thrP = thr_fern * nstructs;                               // 所有树的正样本阈值之和                                                              

	for( int i = 0; i < ferns.size(); i++ )
	{               
		if( ferns[i].second == 1 )                            // 为1表示正样本
		{         
			// measure_forest函数返回所有树的所有特征值对应的后验概率累加值  
			// 该累加值如果小于正样本阈值,也就是是输入的是正样本,却被分类成负样本了  
			// 出现分类错误,所以就把该样本添加到正样本库,同时更新后验概率
			// pair通过first和second访问两个成员,有operator = 和swap方法

			if( measure_forest( ferns[i].first ) <= thrP )    // 输入的是正样本,却被分类成负样本了  
				update( ferns[i].first , 1 , 1 );                
		}
		else                                                  // 为0表示负样本
		{                                            
			if( measure_forest( ferns[i].first ) >= thrN )    // 输入的是负样本,却被分类成正样本了
				update( ferns[i].first , 0 , 1 );              
		}
	}
}


/********************************************************************
*函数名称:FerNNClassifier::trainNN
*函数参数:通过森林分类器的样本(最近邻分类器的训练集)
*函数功能:训练最近邻分类器
*输出参数:无
********************************************************************/
void FerNNClassifier::trainNN( const vector<cv::Mat>& nn_examples )
{
	float conf , dummy;
	std::vector<int> y( nn_examples.size() , 0 );        // y数组元素初始化为0
	y[0] = 1;                                            // 传入trainNN这个函数的nn_examples样本集,只有一个pEx,在nn_examples[0] 
	std::vector<int> isin;                               // 计算NNConf所需参数
	for( int i = 0; i < nn_examples.size(); i++ )
	{          
		// 计算输入图像片与在线模型之间的相关相似度conf 
		NNConf( nn_examples[i] , isin , conf , dummy );   

		// 标签是正样本,如果相关相似度小于0.65 ,则认为其不含有前景目标,也就是分类错误了;这时候就把它加到正样本库
		if( y[i] == 1 && conf <= thr_nn )                // 即 i=0 的情况
		{                               
			if( isin[1]<0 )                              // 没有发现正样本相似度大于0.95的
			{                                          
				pEx = vector<Mat>( 1 , nn_examples[i] );                  
				continue;                                            
			}                                                        
			pEx.push_back( nn_examples[i] );             // 正样本入栈
		}                                                          
		if( y[i] == 0 && conf>0.5 )                      // 如果是负样本且相关相似度 >0.5                               
			nEx.push_back( nn_examples[i] );             // 负样本入栈                     

	}                                                                 
	acum++;                                              // 最近邻分类器的训练次数
	printf( "%d. Trained NN examples: %d positive %d negative\n" , acum , ( int ) pEx.size() , ( int ) nEx.size() );
}                                                                 

/********************************************************************
*函数名称:FerNNClassifier::NNConf
*函数参数:最近邻分类器的样本图像,
           isin  全初始化的-1
           第一个为是否发现传入图片与在线模型正样本近邻数据集pEx相似度超过0.95,发现置1
           第二个为在遍历在线模型时找到的第一个与输入图像patch相似度超过阈值ncc_thesame的box的索引
           第三个为是否发现传入图片与在线模型负样本近邻数据集nEx相似度超过0.95,发现置1
           rsconf   -Relative Similarity       相关相似度
           csconf   -Conservative Similarity   保守相似度
*函数功能:计算输入图像片与在线模型之间的相关相似度conf
*输出参数:无
*********************************************************************/
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 );        // (-1,-1,-1)
	if( pEx.empty() )                    // 正例样本为空
	{
		rsconf = 0;                      // 相关相似度 = 0
		csconf = 0;                      // 保守相似度 = 0
		return;
	}
	if( nEx.empty() )                    // 负样本为空
	{ 
		rsconf = 1;                      // 相关相似度 = 1  
		csconf = 1;                      // 保守相似度 = 1
		return;
	}
	Mat ncc( 1 , 1 , CV_32F );           // matchTemplate的参数

	float nccP , csmaxP , maxP = 0;
	float nccN , maxN = 0;

	bool anyP = false;                   // 是否存在最佳正例匹配图片
	bool anyN = false;                   // 是否存在最佳反例匹配图片

	int maxPidx;
	int validatedPart = ceil( pEx.size()*valid );   // ceil返回大于或者等于指定表达式的最小整数

	// 比较图像片patch到在线模型M的距离(相似度),计算正样本最近邻相似度,也就是将输入的图像片与  
	// 在线模型中所有的图像片进行匹配,找出最相似的那个图像片,也就是相似度的最大值 
	for( int i = 0; i < pEx.size(); i++ )
	{
		matchTemplate( pEx[i] , example , ncc , CV_TM_CCORR_NORMED );      // 计算匹配相似度
		
		nccP = ( ( ( float* ) ncc.data )[0] + 1 )*0.5;
		
		if( nccP > ncc_thesame )          // ncc_thesame: 0.95
		{
			anyP = true;                  // 存在最佳匹配图片
		}
			
		if( nccP > maxP )                 // 记录最大的相似度以及对应的图像片index索引值
		{
			maxP = nccP;                  // 记录正例最大的相似度
			maxPidx = i;                  // 记录对应的图像片index索引值
			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;                  // 记录反例最大的相似度
		}
			
	}

	// 更新相关相似度           //  更新相关相似度 =正样本最近邻相似度 / (正样本最近邻相似度 + 负样本最近邻相似度)
	float dN = 1 - maxN;        // 负样本不相似的最小程度
	float dP = 1 - maxP;        // 正样本不相似的最小程度

	// 正负样本不相似的最小情况下,负样本不相似所占比例定义为相关相似度,
	// 如果负样本不相似所占比重越大,那么该patch与负样本不相似的可能性越大,从而相关相似度越高
	rsconf = ( float ) dN / ( dN + dP );

	// 计算保守相似度
	// csmaxP <= maxP 所以csconf比rsconf小,更为“保守”
	dP = 1 - csmaxP;
	csconf = ( float ) dN / ( dN + dP );
}



/************************************************************************************
*函数名称:FerNNClassifier::evaluateTh
*函数参数:负样本数据测试集nXT(存放feature),负样本近邻数据测试集nExT(存放pattern)
*函数功能:将nXT和nExT作为测试集来校正阈值 thr_fern、thr_nn、thr_nn_valid
*输出参数:无
************************************************************************************/
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++ )
	{
		// 所有基本分类器的后验概率的平均值如果大于thr_fern,则认为含有前景目标  
		// measure_forest返回的是所有后验概率的累加和,nstructs 为树的个数

		fconf = ( float ) measure_forest( nXT[i].first ) / nstructs;
		if( fconf > thr_fern )
		{
			thr_fern = fconf;
		}
			
	}

	vector <int> isin;                                     // NNConf的参数准备
	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;
	}		
}


/********************************************************************
*函数名称:FerNNClassifier::show
*函数参数:无
*函数功能:把正样本库(在线模型)包含的所有正样本显示在窗口上
*输出参数:无
********************************************************************/
void FerNNClassifier::show()
{
	Mat examples( ( int ) pEx.size()*pEx[0].rows , pEx[0].cols , CV_8U );        // pEx为最近邻正样本
	double minval;
	Mat ex( pEx[0].rows , pEx[0].cols , pEx[0].type() );
	for( int i = 0; i < pEx.size(); i++ )
	{
		//minMaxLoc寻找矩阵(一维数组当作向量,用Mat定义)中最小值和最大值的位置.
		minMaxLoc( pEx[i] , &minval );           // 寻找pEx[i]的最小值存入minVal中
		pEx[i].copyTo( ex );
		ex = ex - minval;                        // 把像素亮度最小的像素重设为0,其他像素按此重设

		//Mat Mat::rowRange(int startrow, int endrow) const 为指定的行span创建一个新的矩阵头。
		//Range 结构包含着起始和终止的索引值
		Mat tmp = examples.rowRange( Range( i*pEx[i].rows , ( i + 1 )*pEx[i].rows ) );
		ex.convertTo( tmp , CV_8U );
	}
	imshow( "Examples" , examples );
}

TLD.h

#include "utils.h"
#include "LKTracker.h"
#include "FerNNClassifier.h"
#include "PatchGenerator.h"
#include <fstream>
#include <opencv2/opencv.hpp>

#ifndef TLD_H
#define TLD_H

struct BoundingBox:public cv::Rect                 // 公有继承自Rect,有两个成员重叠率和扫描窗口的尺度             
{
	BoundingBox() { }
	BoundingBox(cv::Rect r) : cv::Rect(r) { }
public:
	float overlap;                                 // 重叠率 两个窗口的交/两个窗口的并
	int sidx;                                      // 当前扫描窗口所在的尺度的序号
};

struct DetStruct                                   // 通过方差分类器检测模块的窗口结构体
{
	std::vector<int> bb;                           // 存放符合条件的框的序号
	std::vector<std::vector<int>> patt;            // ?
	std::vector<float> conf1;                      // 存放相关相似度(后验概率的累加值?)
	std::vector<float> conf2;                      // 存放保守相似度
	std::vector<std::vector<int>> isin;            // isin标志
	std::vector<cv::Mat> patch;                    // 选定框
};

struct TempStruct                                  // 暂存的结构体,只有patt()和conf(相关相似度)两个成员
{
	std::vector<std::vector<int>> patt;            // ?
	std::vector<float> conf;                       // 相关相似度 
};

struct OComparator        // 比较两者的重合度,有一个 vector<BoundingBox> 成员   
{
	OComparator(const std::vector<BoundingBox>& _grid) :grid(_grid) { }
	std::vector<BoundingBox> grid;
	bool operator()( int idx1 , int idx2 )
	{
		return grid[idx1].overlap > grid[idx2].overlap;
	}
};

struct CComparator
{//比较两者置信度
	CComparator( const std::vector<float>& _conf ) :conf( _conf )
	{
	}
	std::vector<float> conf;
	bool operator()( int idx1 , int idx2 )
	{
		return conf[idx1] > conf[idx2];
	}
};


class TLD
{
private:
	PatchGenerator generator;   // PatchGenerator类
	FerNNClassifier classifier; // 分类器类
	LKTracker tracker;          // 光流法跟踪类

	int bbox_step;              // bbox_step=7什么意思?没有用到
	int min_win;                // 最小矩形框应包含的像素个数 15 
	int patch_size;             // 归一化的框的大小 15*15

	int num_closest_init;       // 最初的最近的框的个数 10
	int num_warps_init;         // 最初的几何变换的种数 20
	int noise_init;             // 添加的高斯噪声 5
	float angle_init;           // 添加的角度变换 20
	float shift_init;           // 添加的平移变换 0.02
	float scale_init;           // 添加的尺度变换 0.02

  
	// update parameters for positive examples  
	int num_closest_update;
	int num_warps_update;
	int noise_update;
	float angle_update;
	float shift_update;
	float scale_update;

	// parameters for negative examples  
	float bad_overlap;          // 反例的重叠度阈值
	float bad_patches;          // 反例的个数 = num_patches = 100

	cv::Mat iisum;              // 积分图
	cv::Mat iisqsum;            // 平方积分图
	cv::Mat currentframe;       // 当前帧
	float var;                  // 方差

	// Training data  
	// std::pair主要的作用是将两个数据组合成一个数据,两个数据可以是同一类型或者不同类型。  
	// pair实质上是一个结构体,其主要的两个成员变量是first和second,这两个变量可以直接使用。  
	// 在这里用来表示样本,first成员为 features 特征点数组,second成员为 labels 样本类别标签  
	std::vector<std::pair<std::vector<int> , int>> pX;   // 正样本组(森林分类),<fern组(13位二进制数)10个表示10个后验概率, labels=1> 
	std::vector<std::pair<std::vector<int> , int>> nX;   // 负样本组(森林分类),<fern组(13位二进制数)10个表示10个后验概率, labels=0> 
	cv::Mat pEx;                                         // 正样本最近邻分类器的正样本(只有一个)
	std::vector<cv::Mat> nEx;                            // 最近邻分类器的负样本(100/2=50个)

	//Test data   
	std::vector<std::pair<std::vector<int> , int>> nXT;  // 负样本森林分类器测试数据集
	std::vector<cv::Mat> nExT;                           // 负样本最近邻分类器测试数据集

	//Last frame data  
	BoundingBox lastbox;                                 // 上一次的最好的框
	bool lastvalid;                                      // 上一次检测的有效性
	float lastconf;                                      // 上一次检测的置信度

	//Current frame data  
	//Tracker data  
	bool tracked;                                        // 是否跟踪到
	BoundingBox tbb;                                     // 利用剩下一半的跟踪点输入来预测bounding box在当前帧的位置和大小 tbb 
	bool tvalid;                                         // 跟踪有效性
	float tconf;                                         // 跟踪的相关相似度

	//Detector data  
	TempStruct tmp;                                      // 通过方差分类器的窗口,只有patt() vector<vector<int>>型 和 conf(相关相似度) vector<float> 型 两个成员
	DetStruct dt;                                        // 通过方差分类器和集合分类器的窗口
	std::vector<BoundingBox> dbb;                        // 通过三个分类器的bounding boxes
	std::vector<bool> dvalid;                            // 检测器检测到的bounding box的有效性
	std::vector<float> dconf;                            // 检测器检测到的bounding box的相关相似度
	bool detected;                                       // 通过方差分类器和森林分类器的扫描框个数是否大于0

	//Bounding Boxes
	
	std::vector<BoundingBox> gridoftracker;
	std::vector<int> gridscalnum;
	int numl = 0;
	int numr = 0;
	int numj = 0;

	std::vector<BoundingBox> grid;                       // 扫描窗矢量组                    
	std::vector<cv::Size> scales;                        // 扫描窗口的尺度数组
	std::vector<int> good_boxes;                         // 正样本组,indexes of bboxes with overlap > 0.6 存的是框的序号
	std::vector<int> bad_boxes;                          // 负样本组,indexes of bboxes with overlap < 0.2 存的是框的序号
	BoundingBox bbhull;                                  // hull of good_boxes 所有good_boxes的外接矩形
	BoundingBox best_box;                                // maximum overlapping bbox 重叠率最大的扫描窗口

public:
	TLD();
	TLD( const cv::FileNode& file );

	void read( const cv::FileNode& file );
	void init( const cv::Mat& frame1 , const cv::Rect &box , FILE* bb_file );
	void generatePositiveData( const cv::Mat& frame , int num_warps );
	void generateNegativeData( const cv::Mat& frame );
	void processFrame( const cv::Mat& img1 , const cv::Mat& img2 , std::vector<cv::Point2f>& points1 , std::vector<cv::Point2f>& points2 ,BoundingBox& bbnext , bool& lastboxfound , bool tl , FILE* bb_file );
	void track( const cv::Mat& img1 , const cv::Mat& img2 , std::vector<cv::Point2f>& points1 , std::vector<cv::Point2f>& points2 );
	void detect( const cv::Mat& frame );
	void clusterConf( const std::vector<BoundingBox>& dbb , const std::vector<float>& dconf , std::vector<BoundingBox>& cbb , std::vector<float>& cconf );
	void evaluate();
	void learn( const cv::Mat& img );

	void buildGrid( const cv::Mat& img , const cv::Rect& box );
	void changeGrid( BoundingBox& tobb );
	float bbOverlap( const BoundingBox& box1 , const BoundingBox& box2 );

	void getOverlappingBoxes( const cv::Rect& box1 , int num_closest );
	void getOverlappingBoxes( const cv::Rect& box1 , int num_cloest , cv::Mat frame );

	void getBBHull();
	void getPattern( const cv::Mat& img , cv::Mat& pattern , cv::Scalar& mean , cv::Scalar& stdev );
	void bbPoints( std::vector<cv::Point2f>& points , const BoundingBox& bb );
	void bbPredict( const std::vector<cv::Point2f>& points1 , const std::vector<cv::Point2f>& points2 ,const BoundingBox& bb1 , BoundingBox& bb2 );
	double getVar( const BoundingBox& box , const cv::Mat& sum , const cv::Mat& sqsum );
	bool bbComp( const BoundingBox& bb1 , const BoundingBox& bb2 );
	int clusterBB( const std::vector<BoundingBox>& dbb , std::vector<int>& indexes );
	int max( int a , int b );
	int min( int a , int b );
};

#endif


TLD.cpp

#include "TLD.h"
#include <stdio.h>

using namespace cv;
using namespace std;


TLD::TLD()
{

}

TLD::TLD( const FileNode& file )
{
	read( file );
}


/**********************************************************************
*函数名称:TLD::read
*函数参数:文件名
*函数功能:读取指定文件中的指定参数
*输出参数:无
**********************************************************************/
void TLD::read( const FileNode& file )
{
	///Bounding Box Parameters
	min_win = ( int ) file["min_win"];                       // 最小矩形框应包含的像素个数 15 
	
	// Genarator Parameters
	//initial parameters for positive examples
	patch_size       = ( int ) file["patch_size"];           // 归一化的框的大小 15*15
	num_closest_init = ( int ) file["num_closest_init"];     // 最初的最近的框的个数 10
	num_warps_init   = ( int ) file["num_warp_init"];        // 最初的几何变换的种数 20
	noise_init       = ( int ) file["noise_init"];           // 添加的高斯噪声 5
	angle_init       = ( float ) file["angle_init"];         // 添加的角度变换 20
	shift_init       = ( float ) file["shift_init"];         // 添加的平移变换 0.02
	scale_init       = ( float ) file["scale_init"];         // 添加的尺度变换 0.02

	//update parameters for positive examples
	num_closest_update = ( int ) file["num_closest_update"];  // 10
	num_warps_update   = ( int ) file["num_warps_update"];    // 10
	noise_update       = ( int ) file["noise_update"];        // 5
	angle_update	   = ( int ) file["angle_update"];		  // 10
	shift_update	   = ( int ) file["shift_update"];        // 0.02
	scale_update	   = ( int ) file["scale_update"];        // 0.02
	
	//parameters for negative examples
	bad_overlap = ( float ) file["overlap"];                  // 0.2
	bad_patches = ( int ) file["num_patches"];                // 100
	classifier.read( file );
}


/**********************************************************************
*函数名称:TLD::init
*函数参数:当前帧、感兴趣区域、写入的txt文件
*函数功能:完成准备工作
*输出参数:无
**********************************************************************/
void TLD::init( const Mat& frame1 , const Rect& box , FILE* bb_file )
{
	// Get Bounding Boxes
	buildGrid( frame1 , box );            // 此函数根据传入的box(目标边界框)在传入的图像frame1中构建全部的扫描窗口,并计算重叠度
	printf( "Created %d bounding boxes\n" , ( int ) grid.size() );
	
	// Preparation
	// allocation
	iisum.create( frame1.rows + 1 , frame1.cols + 1 , CV_32F );       // 创建积分图
	iisqsum.create( frame1.rows + 1 , frame1.cols + 1 , CV_64F );     // 创建平方积分图 

	/********************************************************************************
	size是当前vector容器真实占用的大小,也就是容器当前拥有多少个容器。
	capacity是指在发生realloc前能允许的最大元素数,即预分配的内存空间。
	当然,这两个属性分别对应两个方法:resize()和reserve()。
	使用resize(),容器内的对象内存空间是真正存在的。
	使用reserve()仅仅只是修改了capacity的值,容器内的对象并没有真实的内存空间(空间是"野"的)
	**********************************************************************************/

	// vector 的reserve增加了vector的capacity,但是它的size没有改变!而resize改变了vector  
	// 的capacity同时也增加了它的size!reserve是容器预留空间,但在空间内不真正创建元素对象,  
	// 所以在没有添加新的对象之前,不能引用容器内的元素。  
	// 不管是调用resize还是reserve,二者对容器原有的元素都没有影响。  
	// myVec.reserve( 100 );     // 新元素还没有构造, 此时不能用[]访问元素  
	// myVec.resize( 100 );      // 用元素的默认构造函数构造了100个新的元素,可以直接操作新元素

	dconf.reserve( 100 );        // 检测有效性组大小预设为100
	dbb.reserve( 100 );          // detect bounding box 大小预设为100
	bbox_step = 7;

	// tmp.conf.reserve(grid.size());
	tmp.conf = vector<float>( grid.size() );
	tmp.patt = vector<vector<int> >( grid.size() , vector<int>( 10 , 0 ) );
	
	// tmp.patt.reserve(grid.size());
	dt.bb.reserve( grid.size() );
	good_boxes.reserve( grid.size() );
	bad_boxes.reserve( grid.size() );
	
	// positive NN example 大小为15*15图像片 一个
	pEx.create( patch_size , patch_size , CV_64F );
	
	// Init Generator
	generator = PatchGenerator( 0 , 0 , noise_init , true , 1 - scale_init , 1 + scale_init , -angle_init*CV_PI / 180 , angle_init*CV_PI / 180 , -angle_init*CV_PI / 180 , angle_init*CV_PI / 180 );
	getOverlappingBoxes( box , num_closest_init );
	printf( "Found %d good boxes, %d bad boxes\n" , ( int ) good_boxes.size() , ( int ) bad_boxes.size() );
	printf( "Best Box: %d %d %d %d\n" , best_box.x , best_box.y , best_box.width , best_box.height );
	printf( "Bounding box hull: %d %d %d %d\n" , bbhull.x , bbhull.y , bbhull.width , bbhull.height );
	
	// Correct Bounding Box
	lastbox = best_box;              // 第一次,将best_box赋给lastbox
	lastconf = 1;                    // 第一次,将置信度置为1
	lastvalid = true;                // 第一次,自己选定的,一定可以检测到,所以上一次的有效性为真
	
	// Print  将上一次的最佳选定框的位置及置信度参数输入到文本文件
	fprintf( bb_file , "%d,%d,%d,%d,%f\n" , lastbox.x , lastbox.y , lastbox.br().x , lastbox.br().y , lastconf );
	
	// Prepare Classifier
	classifier.prepare( scales );                     // 准备分类器
	
	// Generate Data
	// Generate positive data
	generatePositiveData( frame1 , num_warps_init );  // 得到正样本,frame1为当前帧
	
	// Set variance threshold
	cv::Scalar stdev , mean;                                  // 颜色的均值与方差
	meanStdDev( frame1( best_box ) , mean , stdev );          // 计算best_box的均值与方差
	integral( frame1 , iisum , iisqsum );                     // 计算积分图和平方积分图

	var = pow( stdev.val[0] , 2 ) * 0.5; 
	cout << "variance: " << var << endl;
	
	// check variance
	double vr = getVar( best_box , iisum , iisqsum )*0.5;
	cout << "check variance: " << vr << endl;
	
	// Generate negative data
	generateNegativeData( frame1 );                  // 产生负样本
	
	// Split Negative Ferns into Training and Testing sets (they are already shuffled)
	int half = ( int ) nX.size() * 0.5f;
	nXT.assign( nX.begin() + half , nX.end() );      // 分配森林分类器负样本测试集
	nX.resize( half );
	
	// Split Negative NN Examples into Training and Testing sets
	half = ( int ) nEx.size() * 0.5f;
	nExT.assign( nEx.begin() + half , nEx.end() );   // 分配最近邻分类器负样本测试集
	nEx.resize( half );
	
	// Merge Negative Data with Positive Data and shuffle it
	std::vector<std::pair<std::vector<int> , int>> ferns_data( nX.size() + pX.size() );
	std::vector<int> idx = index_shuffle( 0 , ferns_data.size() );        // idx是一个从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++;
	}
	
	//Data already have been shuffled, just putting it in the same vector,数据已经被打乱,只需把他们放到一个数组中,一个正样本+50个负样本
	vector<cv::Mat> nn_data( nEx.size() + 1 );
	nn_data[0] = pEx;
	for( int i = 0; i < nEx.size(); i++ )
	{
		nn_data[i + 1] = nEx[i];
	}
	
	// Training 训练集合分类器和最近邻分类器 
	classifier.trainF( ferns_data , 2 ); 
	classifier.trainNN( nn_data );
	
	// Threshold Evaluation on testing sets
	classifier.evaluateTh( nXT , nExT );
}

/* Generate Positive data
* Inputs:
* - good_boxes (bbP)
* - best_box (bbP0)
* - frame (im0)
* Outputs:
* - Positive fern features (pX)
* - Positive NN examples (pEx)
*/
/***********************************************************************
*函数名称:TLD::generatePositiveData
*函数参数:输入当前帧、要进行仿射变换的次数
*函数功能:将good_boxes中的10个扫描窗口进行num_warps次变换,并得到正样本
*输出参数:无
************************************************************************/
void TLD::generatePositiveData( const cv::Mat& frame , int num_warps )
{
	cv::Scalar mean;            // 均值
	cv::Scalar stdev;           // 标准差

	//此函数将frame图像best_box区域的图像片归一化为均值为0的15*15大小的patch,存在pEx正样本中
	getPattern( frame( best_box ) , pEx , mean , stdev );

	cv::Mat img;
	cv::Mat warped;

	GaussianBlur( frame , img , cv::Size( 9 , 9 ) , 1.5 );   // 进行高斯平滑滤波

	warped = img( bbhull );                                  // 在img图像中截取bbhull区域(所有good_boxes的并集的外接矩形)赋给warped  
	cv::RNG& rng = theRNG();                                 // 产生一个随机数
	cv::Point2f pt( bbhull.x + ( bbhull.width - 1 )*0.5f , bbhull.y + ( bbhull.height - 1 )*0.5f ); // 获得bbhull窗口中心点坐标

	std::vector<int> fern( classifier.getNumStructs() );     // 建立10棵树(正例样本)的后验概率数组

	// pX为处理后的RectBox最大边界处理后的像素信息,pEx最近邻的RectBox的Pattern,bbP0为最近邻的RectBox。
	pX.clear();                                              // 正样本清空
	cv::Mat patch;
	if( pX.capacity() < num_warps * good_boxes.size() )		 // 对每一个good_boxes都要进行一次仿射变换?
	{
		pX.reserve( num_warps * good_boxes.size() );		 // 分配正样本空间 good_boxes是重叠率比较大的框
	}

	int idx;

	for( int i = 0; i < num_warps; i++ )
	{
		if( i > 0 )
		{
			// PatchGenerator类用来对图像区域进行仿射变换,先RNG一个随机因子,再调用()运算符产生一个变换后的正样本
			generator( frame , pt , warped , bbhull.size() , rng );   // 产生变换后的正样本
		}
		for( int b = 0; b < good_boxes.size(); b++ )
		{
			idx = good_boxes[b];                                      // good_boxes容器保存的是 grid 的索引
			patch = img( grid[idx] );                                 // 把img的 grid[idx] 区域(也就是bounding box重叠度高的)这一块图像片提取出来
			classifier.getFeatures( patch , grid[idx].sidx , fern );  // 得到输入的patch的特征fern(13位的二进制码)
			pX.push_back( make_pair( fern , 1 ) );                    // 正例 <fern(13位的二进制数), labels=1>        
		}
	}

	printf( "%d 个good_boxes生成了随机森林正样本%d个\n" , good_boxes.size() , ( int ) pX.size() );
}

/***************************************************************************************
*函数名称:TLD::getPattern
*函数参数:输入图像、输出图像、均值、方差
*函数功能:将输入图像变换为指定形式的图像(15*15,均值为0,32位浮点数)
*输出参数:无
**************************************************************************************/
void TLD::getPattern( const cv::Mat& img , cv::Mat& pattern , cv::Scalar& mean , cv::Scalar& stdev )
{
	resize( img , pattern , Size( patch_size , patch_size ) );   // 将图像放缩到patch_size*patch_size大小(15X15)
	meanStdDev( pattern , mean , stdev );                        // 计算均值和方差
	pattern.convertTo( pattern , CV_32F );
	pattern = pattern - mean.val[0];                             // 将每个元素的像素值减去均值使得到的pattern的均值为0
}


/* Inputs:
* - Image
* - bad_boxes (Boxes far from the bounding box)
* - variance (pEx variance)
* Outputs
* - Negative fern features (nX)
* - Negative NN examples (nEx)
*/
/**********************************************************************************
*函数名称:TLD::generateNegativeData
*函数参数:输入当前帧
*函数功能:生成集合分类器的负样本(及其特征值),生成最近邻分类器的负样本100个?
*输出参数:无
**********************************************************************************/
void TLD::generateNegativeData( const Mat& frame )
{
	
	random_shuffle( bad_boxes.begin() , bad_boxes.end() );        // 打乱bad_boxes的顺序
	int idx;                                                      // 负样本在grid中的索引值
	int a = 0;                                                    // 负样本数

	printf( "negative data generation started.\n" );
	
	std::vector<int> fern( classifier.getNumStructs() );          // 建立10棵树(负例样本)的后验概率数组
	nX.reserve( bad_boxes.size() );                               // 重设负样本的大小
	cv::Mat patch;

	for( int j = 0; j < bad_boxes.size(); j++ )                   // bad_boxes数量过多,选取前10%生产集合分类器的负样本
	{
		idx = bad_boxes[j];                                       // bad_boxes容器保存的是 grid 的索引
		if( getVar( grid[idx] , iisum , iisqsum ) < var * 0.5f )  // 只选择方差较大的放入负样本
		{
			continue;
		}
		patch = frame( grid[idx] );
		classifier.getFeatures( patch , grid[idx].sidx , fern );  // 得到负样本的fern值
		nX.push_back( make_pair( fern , 0 ) );                    // 类似于正样本的生成,得到负样本
		a++;                                                      // 负样本数+1
	}
	printf( "Negative examples generated: ferns: %d " , a );
	

	cv::Scalar dum1 , dum2;                                       // 均值、方差
	nEx = std::vector<cv::Mat>( bad_patches );                    // 在参数文件中 num_patches = 100 
	for( int i = 0; i < bad_patches; i++ )
	{
		idx = bad_boxes[i];
		patch = frame( grid[idx] );
		getPattern( patch , nEx[i] , dum1 , dum2 );
	}
	printf( "NN: %d\n" , ( int ) nEx.size() );
}


/*******************************************************************
*函数名称:TLD::getVal
*函数参数:最好匹配框、积分图、平方积分图
*函数功能:得到best_box1的颜色方差
*输出参数:best_box1的颜色方差
*******************************************************************/
double TLD::getVar( const BoundingBox& box , const Mat& sum , const Mat& sqsum )
{
	double brs = sum.at<int>( box.y + box.height , box.x + box.width );
	double bls = sum.at<int>( box.y + box.height , box.x );
	double trs = sum.at<int>( box.y , box.x + box.width );
	double tls = sum.at<int>( box.y , box.x );
	double brsq = sqsum.at<double>( box.y + box.height , box.x + box.width );
	double blsq = sqsum.at<double>( box.y + box.height , box.x );
	double trsq = sqsum.at<double>( box.y , box.x + box.width );
	double tlsq = sqsum.at<double>( box.y , box.x );
	double mean = ( brs + tls - trs - bls ) / ( ( double ) box.area() );
	double sqmean = ( brsq + tlsq - trsq - blsq ) / ( ( double ) box.area() );

	// 方差=E(X^2)-(EX)^2   EX表示均值
	return sqmean - mean*mean;
}


/******************************************************************************************************************************************
*函数名称:TLD::processFrame
*函数参数:上一帧灰度图、当前帧灰度图、光流法的特征点集1、光流法的特征点集2、下一个跟踪框、上一次是否找到、是否跟踪且学习、输出文件
*函数功能:跟踪、检测、综合模块,处理帧
*输出参数:无
******************************************************************************************************************************************/
void TLD::processFrame( const cv::Mat& img1 , const cv::Mat& img2 , vector<Point2f>& points1 , vector<Point2f>& points2 , BoundingBox& bbnext , bool& lastboxfound , bool tl , FILE* bb_file )
{
	std::vector<BoundingBox> cbb;                       // 聚类得到的各类代表扫描框
	std::vector<float> cconf;                           // 聚类的各类的相关相似度
	int confident_detections = 0;                       // 满足一定条件的聚类扫描框的个数
	int didx;                                           // detection index  检测索引
	if( lastboxfound && tl )                            // 如果上一次训练并学习?
	{
		track( img1 , img2 , points1 , points2 );       // 跟踪下一帧
	}
	else
	{
		tracked = false;                                // 将跟踪到置为false
	}
	
	//Detect  检测模块
	detect( img2 );
	
	//Integration  综合模块  
	//TLD只跟踪单目标,所以综合模块综合跟踪器跟踪到的单个目标和检测器检测到的多个目标,然后只输出保守相似度最大的一个目标
	if( tracked )
	{
		bbnext = tbb;
		lastconf = tconf;
		lastvalid = tvalid;
		printf( "Tracked\n" );
		if( detected )
		{       
			// 通过重叠度对检测器检测到的目标bounding box进行聚类,每个类其重叠度小于0.5
			clusterConf( dbb , dconf , cbb , cconf );                       
			printf( "Found %d clusters\n" , ( int ) cbb.size() );
			for( int i = 0; i < cbb.size(); i++ )
			{
				if( bbOverlap( tbb , cbb[i] ) < 0.5 && cconf[i] > tconf )
				{  
					//  Get index of a clusters that is far from tracker and are more confident than the tracker
					confident_detections++;          // 记录满足上述条件,也就是可信度比较高的目标box的个数
					didx = i;                        // 记录下检测的索引值
				}
			}

			// 如果只有一个满足上述条件的box,那么就用这个目标box来重新初始化跟踪器(也就是用检测器的结果去纠正跟踪器)
			if( confident_detections == 1 )
			{                              
				printf( "Found a better match..reinitializing tracking\n" );
				bbnext = cbb[didx];
				lastconf = cconf[didx];
				lastvalid = false;
			}
			else
			{
				printf( "%d confident cluster was found\n" , confident_detections );
				int cx = 0 , cy = 0 , cw = 0 , ch = 0;
				int close_detections = 0;
				for( int i = 0; i < dbb.size(); i++ )
				{
					if( bbOverlap( tbb , dbb[i] ) > 0.7 )
					{                     
						// 找到检测器检测到的box与跟踪器预测到的box距离很近(重叠度大于0.7)的box,对其坐标和大小进行累加
						cx += dbb[i].x;
						cy += dbb[i].y;
						cw += dbb[i].width;
						ch += dbb[i].height;
						close_detections++;              // 记录最近邻box的个数
						printf( "weighted detection: %d %d %d %d\n" , dbb[i].x , dbb[i].y , dbb[i].width , dbb[i].height );
					}
				}

				if( close_detections > 0 )
				{
					// 对与跟踪器预测到的box距离很近的box 和 跟踪器本身预测到的box 进行坐标与大小的平均作为最终的
					// 目标bounding box,但是跟踪器的权值较大

					bbnext.x = cvRound( ( float ) ( 10 * tbb.x + cx ) / ( float ) ( 10 + close_detections ) );   // weighted average trackers trajectory with the close detections
					bbnext.y = cvRound( ( float ) ( 10 * tbb.y + cy ) / ( float ) ( 10 + close_detections ) );
					bbnext.width = cvRound( ( float ) ( 10 * tbb.width + cw ) / ( float ) ( 10 + close_detections ) );
					bbnext.height = cvRound( ( float ) ( 10 * tbb.height + ch ) / ( float ) ( 10 + close_detections ) );
					printf( "Tracker bb: %d %d %d %d\n" , tbb.x , tbb.y , tbb.width , tbb.height );
					printf( "Average bb: %d %d %d %d\n" , bbnext.x , bbnext.y , bbnext.width , bbnext.height );
					printf( "Weighting %d close detection(s) with tracker..\n" , close_detections );
				}
				else
				{
					printf( "%d close detections were found\n" , close_detections );

				}
			}
		}
	}
	else
	{                                       //   If NOT tracking
		printf( "Not tracking..\n" );
		lastboxfound = false;
		lastvalid = false;

		// 如果跟踪器没有跟踪到目标,但是检测器检测到了一些可能的目标box,那么同样对其进行聚类,但只是简单的
		// 将聚类的cbb[0]作为新的跟踪目标box(不比较相似度了??还是里面已经排好序了??),重新初始化跟踪器

		if( detected )
		{                       
			clusterConf( dbb , dconf , cbb , cconf ); 
			printf( "Found %d clusters\n" , ( int ) cbb.size() );
			if( cconf.size() == 1 )
			{
				bbnext = cbb[0];
				lastconf = cconf[0];
				printf( "Confident detection..reinitializing tracker\n" );
				lastboxfound = true;
			}
		}
	}

	lastbox = bbnext;

	if( lastboxfound )
	{
		fprintf( bb_file , "%d,%d,%d,%d,%f\n" , lastbox.x , lastbox.y , lastbox.width , lastbox.height );
	}
	else
	{
		fprintf( bb_file , "NaN,NaN,NaN,NaN,NaN\n" );
	}

	// learn 学习模块
	if( lastvalid && tl )
	{
		learn( img2 );
	}
}


/*Inputs:
* -current frame(img2), last frame(img1), last Bbox(bbox_f[0]).
*Outputs:
*- Confidence(tconf), Predicted bounding box(tbb),Validity(tvalid), points2 (for display purposes only)
*/
/*******************************************************************
*函数名称:TLD::track
*函数参数:上一帧灰度图、当前帧灰度图、光流法的两个特征点集
*函数功能:得到best_box1的颜色方差
*输出参数:无(计算了 跟踪置信度、预测选定框位置、跟踪的有效性)
*******************************************************************/
void TLD::track( const Mat& img1 , const Mat& img2 , vector<Point2f>& points1 , vector<Point2f>& points2 )
{	
	// Generate points
	bbPoints( points1 , lastbox );       //网格均匀撒点(均匀采样),在lastbox中共产生最多10*10=100个特征点,存于points1
	if( points1.size() < 1 )
	{
		printf( "BB= %d %d %d %d, Points not generated\n" , lastbox.x , lastbox.y , lastbox.width , lastbox.height );
		tvalid = false;                 // 跟踪有效性置为false
		tracked = false;                // 跟踪到置为false
		return;
	}

	vector<Point2f> points = points1;

	// Frame-to-frame tracking with forward-backward error cheking
	// trackf2f函数完成:跟踪、计算FB error和匹配相似度sim,然后筛选出 FB_error[i] <= median(FB_error) 和   
	// sim_error[i] > median(sim_error) 的特征点(跟踪结果不好的特征点),剩下的是不到50%的特征点
	tracked = tracker.trackf2f( img1 , img2 , points , points2 );
	
	if( tracked )
	{
		// Bounding box prediction
		// 利用剩下的这不到一半的跟踪点输入来预测bounding box在当前帧的位置和大小 tbb 
		bbPredict( points , points2 , lastbox , tbb );


		// 跟踪失败检测:如果FB_error的中值大于10个像素(经验值),或者预测到的当前box的位置移出图像,则  
		// 认为跟踪错误,此时不返回bounding box;Rect::br()返回的是右下角的坐标  
		// getFB()返回的是FB_error的中值 
		if( tracker.getFB() > 10 || tbb.x > img2.cols || tbb.y > img2.rows || tbb.br().x < 1 || tbb.br().y < 1 )
		{
			tvalid = false; //too unstable prediction or bounding box out of image
			tracked = false;
			printf( "Too unstable predictions FB error=%f\n" , tracker.getFB() );
			return;
		}
		
		// Estimate Confidence and Validity
		cv::Mat pattern;
		cv::Scalar mean , stdev;
		BoundingBox bb;
		bb.x = max( tbb.x , 0 );
		bb.y = max( tbb.y , 0 );
		bb.width = min( min( img2.cols - tbb.x , tbb.width ) , min( tbb.width , tbb.br().x ) );
		bb.height = min( min( img2.rows - tbb.y , tbb.height ) , min( tbb.height , tbb.br().y ) );
		
		// 归一化img2(bb)对应的patch的size(放缩至patch_size = 15*15),存入pattern
		getPattern( img2( bb ) , pattern , mean , stdev );
		
		vector<int> isin;
		float dummy;

		// 计算图像片pattern到在线模型M的保守相似度 
		classifier.NNConf( pattern , isin , dummy , tconf ); 
		tvalid = lastvalid;

		// 保守相似度大于阈值,则评估跟踪有效
		if( tconf > classifier.thr_nn_valid )
		{
			tvalid = true;
		}
	}
	else
		printf( "No points tracked\n" );
}


/*******************************************************************
*函数名称:TLD::bbPoints
*函数参数:光流法特征点集1、上一帧中最佳匹配框
*函数功能:将特征点集1置为选定框中的一系列方阵形式排列的点
*输出参数:无
*******************************************************************/
void TLD::bbPoints( vector<cv::Point2f>& points , const BoundingBox& bb )
{
	int max_pts = 10;

	// 采样边界
	int margin_h = 0;
	int margin_v = 0;

	// 网格均匀撒点
	int stepx = ceil( ( bb.width - 2 * margin_h ) / max_pts );
	int stepy = ceil( ( bb.height - 2 * margin_v ) / max_pts );

	// 均匀撒点,共10*10=100个特征点
	for( int y = bb.y + margin_v; y < bb.y + bb.height - margin_v; y += stepy )
	{
		for( int x = bb.x + margin_h; x < bb.x + bb.width - margin_h; x += stepx )
		{
			points.push_back( Point2f( x , y ) );
		}
	}
}


/******************************************************************************************
*函数名称:TLD::bbPredict
*函数参数:选定框中的选定特征点,光流法的第二个特征点集,上一次跟踪到的最好框、预测框
*函数功能:用跟踪点的输入来预测bounding box在当前帧的大小和位置
*输出参数:无
******************************************************************************************/
void TLD::bbPredict( const vector<cv::Point2f>& points1 , const vector<cv::Point2f>& points2 ,const BoundingBox& bb1 , BoundingBox& bb2 )
{
	int npoints = ( int ) points1.size();            // 选定的特征点个数
	vector<float> xoff( npoints );                   // 位移
	vector<float> yoff( npoints );
	printf( "tracked points : %d\n" , npoints );

	for( int i = 0; i < npoints; i++ )               // 计算每一个点的位移量
	{
		xoff[i] = points2[i].x - points1[i].x;
		yoff[i] = points2[i].y - points1[i].y;
	}

	float dx = median( xoff );                       // 取位移量的中值
	float dy = median( yoff );
	float s;

	// 计算bounding box尺度scale的变化:通过计算当前特征点相互间的距离与先前(上一帧)特征点相互间的距离的  
	// 比值,以比值的中值作为尺度的变化因子 
	if( npoints > 1 )
	{
		vector<float> d;
		d.reserve( npoints*( npoints - 1 ) / 2 );
		for( int i = 0; i < npoints; i++ )
		{
			for( int j = i + 1; j < npoints; j++ )
			{
				// 计算当前特征点相互间的距离与先前(上一帧)特征点相互间的距离的比值(位移用绝对值)
				d.push_back( norm( points2[i] - points2[j] ) / norm( points1[i] - points1[j] ) );
			}
		}
		s = median( d );
	}
	else
	{
		s = 1.0;
	}
	float s1 = 0.5*( s - 1 )*bb1.width;
	float s2 = 0.5*( s - 1 )*bb1.height;
	printf( "s= %f s1= %f s2= %f \n" , s , s1 , s2 );

	// 得到当前bounding box的位置与大小信息  
	// 当前box的x坐标 = 前一帧box的x坐标 + 全部特征点位移的中值(可理解为box移动近似的位移)- 当前box宽的一半
	bb2.x = round( bb1.x + dx - s1 );
	bb2.y = round( bb1.y + dy - s2 );
	bb2.width = round( bb1.width*s );
	bb2.height = round( bb1.height*s );
	printf( "predicted bb: %d %d %d %d\n" , bb2.x , bb2.y , bb2.br().x , bb2.br().y );
}

void TLD::detect( const cv::Mat& frame )
{
	// cleaning
	dbb.clear();
	dconf.clear();
	dt.bb.clear();

	// GetTickCount返回从操作系统启动到现在所经过的时间
	double t = ( double ) getTickCount();
	cv::Mat img( frame.rows , frame.cols , CV_8U );
	integral( frame , iisum , iisqsum );                  // 计算frame的积分图、平方积分图
	GaussianBlur( frame , img , cv::Size( 9 , 9 ) , 1.5 );// 高斯降噪
	int numtrees = classifier.getNumStructs();            // 树的个数 10
	float fern_th = classifier.getFernTh();               // getFernTh()返回thr_fern; 集合分类器的分类阈值
	std::vector<int> ferns( 10 );                         // 10棵树
	float conf;                                           // 相似度
	int a = 0;                                            // 通过方差分类器的个数
	Mat patch;                                            // 选定框

	// 级联分类器模块一:方差检测模块,利用积分图计算每个待检测窗口的方差,方差大于var阈值(目标patch方差的50%)的,  
	// 则认为其含有前景目标
	for( int i = 0; i < grid.size(); i++ )
	{
		if( getVar( grid[i] , iisum , iisqsum ) >= var )
		{
			a++;                                           // 通过方差分类器的窗口个数

			// 级联分类器模块二:集合分类器检测模块 
			patch = img( grid[i] );
			classifier.getFeatures( patch , grid[i].sidx , ferns );  // 得到该patch特征(13位的二进制代码)
			conf = classifier.measure_forest( ferns );               // 计算该特征值对应的后验概率累加值
			tmp.conf[i] = conf;                                      // 通过方差分类器的窗口的后验概率之和 
			tmp.patt[i] = ferns;                                     // 通过方差分类器的窗口的10个特征值

			//如果集合分类器的后验概率的平均值大于阈值fern_th(由训练得到),就认为含有前景目标
			if( conf > numtrees*fern_th )
			{
				dt.bb.push_back( i );                                // 将通过以上两个检测模块的扫描窗口的序号记录在detect structure中
			}
		}
		else
			tmp.conf[i] = 0.0;
	}
	int detections = dt.bb.size();                                   // 通过方差分类器的个数
	printf( "%d Bounding boxes passed the variance filter\n" , a );
	printf( "%d Initial detection from Fern Classifier\n" , detections );

	//如果通过以上两个检测模块的扫描窗口数大于100个,则只取后验概率大的前100个
	if( detections > 100 )
	{
		// CComparator(tmp.conf)指定比较方式???
		nth_element( dt.bb.begin() , dt.bb.begin() + 100 , dt.bb.end() , CComparator( tmp.conf ) );
		dt.bb.resize( 100 );
		detections = 100;
	}
	
	if( detections == 0 )
	{
		detected = false;
		return;
	}
	printf( "Fern detector made %d detections " , detections );

	// 两次使用getTickCount(),然后再除以getTickFrequency(),计算出来的是以秒s为单位的时间(opencv 2.0 以前是ms
	t = ( double ) getTickCount() - t;
	printf( "in %gms\n" , t * 1000 / getTickFrequency() );          //打印以上代码运行使用的毫秒数

	//  Initialize detection structure 初始化检测结构体
	dt.patt = std::vector<std::vector<int> >( detections , std::vector<int>( 10 , 0 ) );        //  Corresponding codes of the Ensemble Classifier
	dt.conf1 = std::vector<float>( detections );                                                 //  Relative Similarity (for final nearest neighbour classifier)相似度(最后的最近邻分类器)
	dt.conf2 = std::vector<float>( detections );                                                 //  Conservative Similarity (for integration with tracker) 保守相似度
	dt.isin = std::vector<std::vector<int> >( detections , std::vector<int>( 3 , -1 ) );        //  Detected (isin=1) or rejected (isin=0) by nearest neighbour classifier
	dt.patch = std::vector<cv::Mat>( detections , cv::Mat( patch_size , patch_size , CV_32F ) ); //  Corresponding patches
	
	int idx;;
	Scalar mean , stdev;
	float nn_th = classifier.getNNTh();

	//级联分类器模块三:最近邻分类器检测模块
	for( int i = 0; i < detections; i++ )
	{                                        
		idx = dt.bb[i];                                                     
		patch = frame( grid[idx] );
		getPattern( patch , dt.patch[i] , mean , stdev );                
		
		// 计算图像片pattern到在线模型M的相关相似度和保守相似度
		classifier.NNConf( dt.patch[i] , dt.isin[i] , dt.conf1[i] , dt.conf2[i] );  
		dt.patt[i] = tmp.patt[idx];
		//printf("Testing feature %d, conf:%f isin:(%d|%d|%d)\n",i,dt.conf1[i],dt.isin[i][0],dt.isin[i][1],dt.isin[i][2]);
		// 相关相似度大于阈值,则认为含有前景目标 
		if( dt.conf1[i] > nn_th )
		{                                           
			dbb.push_back( grid[idx] );                                        
			dconf.push_back( dt.conf2[i] );                                     
		}
	}    
	
	//打印检测到的可能存在目标的扫描窗口数(可以通过三个级联检测器的
	if( dbb.size() > 0 )
	{
		printf( "Found %d NN matches\n" , ( int ) dbb.size() );
		detected = true;
	}
	else
	{
		printf( "No NN matches found.\n" );
		detected = false;
	}
}

void TLD::evaluate()
{
}


/***************************************************************************
*函数名称:TLD::learn
*函数参数:当前帧
*函数功能:学习模块
*输出参数:无
****************************************************************************/
void TLD::learn( const Mat& img )
{
	printf( "[Learning] " );
	
	// Check consistency
	BoundingBox bb;
	bb.x = max( lastbox.x , 0 );
	bb.y = max( lastbox.y , 0 );
	bb.width = min( min( img.cols - lastbox.x , lastbox.width ) , min( lastbox.width , lastbox.br().x ) );
	bb.height = min( min( img.rows - lastbox.y , lastbox.height ) , min( lastbox.height , lastbox.br().y ) );
	
	Scalar mean , stdev;
	Mat pattern;
	
	getPattern( img( bb ) , pattern , mean , stdev );        // 归一化img(bb)对应的patch的size(放缩至15*15),存入pattern
	
	vector<int> isin;
	float dummy , conf;
	classifier.NNConf( pattern , isin , conf , dummy );      // 计算输入图片(跟踪器的目标box)与在线模型之间的相关相似度conf
	
	if( conf < 0.5 )
	{
		printf( "Fast change..not training\n" );
		lastvalid = false;
		return;
	}
	if( pow( stdev.val[0] , 2 ) < var )
	{
		printf( "Low variance..not training\n" );
		lastvalid = false;
		return;
	}
	if( isin[2] == 1 )
	{
		printf( "Patch in negative data..not traing" );
		lastvalid = false;
		return;
	}


	// Data generation
	for( int i = 0; i < grid.size(); i++ )
	{
		grid[i].overlap = bbOverlap( lastbox , grid[i] );    // 计算所有扫描窗口与当前的目标box的重叠度
	}
	vector<pair<vector<int> , int> > fern_examples;          // 集合分类器的样本
	good_boxes.clear();
	bad_boxes.clear();

	// 此函数根据传入的lastbox,在整帧图像中的全部窗口中寻找与该lastbox距离最小(即最相似,  
	// 重叠度最大)的num_closest_update个窗口,然后把这些窗口归入good_boxes容器(只是把网格数组的索引存入)  
	// 同时,把重叠度小于0.2的,归入bad_boxes 容器
	getOverlappingBoxes( lastbox , num_closest_update );

	if( good_boxes.size()>0 )
	{
		// 用仿射变换产生正样本(类似于第一帧的方法,但只产生10*10=100个
		generatePositiveData( img , num_warps_update );
	}
	else
	{
		lastvalid = false;
		printf( "No good boxes... Not training" );
		return;
	}

	fern_examples.reserve( pX.size() + bad_boxes.size() );
	fern_examples.assign( pX.begin() , pX.end() );

	int idx;
	for( int i = 0; i < bad_boxes.size(); i++ )
	{
		idx = bad_boxes[i];
		if( tmp.conf[idx] >= 1 )                       // 加入负样本,相似度大于1(因为是后验概率累加值),则加入
		{
			fern_examples.push_back( make_pair( tmp.patt[idx] , 0 ) );
		}
	}
	vector<Mat> nn_examples;                          // 最近邻分类器的样本
	nn_examples.reserve( dt.bb.size() + 1 );
	nn_examples.push_back( pEx );
	for( int i = 0; i < dt.bb.size(); i++ )
	{
		idx = dt.bb[i];
		if( bbOverlap( lastbox , grid[idx] ) < bad_overlap )
			nn_examples.push_back( dt.patch[i] );
	}
	
	// Classifiers update
	classifier.trainF( fern_examples , 2 );
	classifier.trainNN( nn_examples );
	classifier.show();                                 // 把正样本库(在线模型)包含的所有正样本显示在窗口上
}


/**********************************************************************************
*函数名称:buildGrid
*函数参数:输入图像、矩形框
*函数功能:根据目标框在输入图像中构建全部扫描窗口并计算重叠度
*输出参数:无
**********************************************************************************/
void TLD::buildGrid( const cv::Mat& img , const cv::Rect& box )
{
	const float SHIFT = 0.1;               // 扫描窗口步长为宽高的 10% 

	// 尺度变化的参数
	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 };
	int width , height , min_bb_side;
	
	//Rect bbox;
	BoundingBox bbox;                  // 选定框
	cv::Size scale;                    // 尺度变换  
	int sc = 0;

	for (int s = 0; s < 21;s++)
	{
		// round()返回四舍五入的整数值
		width = round( box.width*SCALES[s] );
		height = round( box.height*SCALES[s] );
		min_bb_side = min( height , width );            //bounding box经过尺度变换之后最短的边 
	
		if( min_bb_side < min_win || width > img.cols || height > img.rows )
		{
			continue;
		}
			
		scale.width = width;
		scale.height = height;
		scales.push_back( scale );                     //把该窗口的尺度存入scales容器

		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;

				// 判断传入的bounding box(目标边界框)与 传入图像中的此时窗口的 重叠度,  
				// 以此来确定该图像窗口是否含有目标
				bbox.overlap = bbOverlap( bbox , BoundingBox( box ) );
				bbox.sidx = sc;                         //在尺度变换中筛选出满足矩形框大小条件的变换,其序号为sc
				grid.push_back( bbox );                 // grid里面存每一个扫描窗口(其x,y和高,宽,重叠率、尺度放缩索引值)
			}
		}
		sc++;
	}
}


/*********************************************************************************
*函数名称:TLD::bbOverlap
*函数参数:两个BoundingBox的引用
*函数功能:计算两个bounding box 的重叠度(两个box的交集 / 它们并集 )
*输出参数:重叠度(float)
*********************************************************************************/
float TLD::bbOverlap( const BoundingBox& box1 , const BoundingBox& box2 )
{
	// 先判断坐标,假如它们都没有重叠的地方,就直接返回0
	if( box1.x > box2.x + box2.width )
	{
		return 0.0;
	}
	if( box1.y > box2.y + box2.height )
	{
		return 0.0;
	}
	if( box1.x + box1.width < box2.x )
	{
		return 0.0;
	}
	if( box1.y + box1.height < box2.y )
	{
		return 0.0;
	}

	// 计算重叠区域的大小
	float colInt = min( box1.x + box1.width , box2.x + box2.width ) - max( box1.x , box2.x );
	float rowInt = min( box1.y + box1.height , box2.y + box2.height ) - max( box1.y , box2.y );

	float intersection = colInt * rowInt;
	float area1 = box1.width*box1.height;
	float area2 = box2.width*box2.height;

	return intersection / ( area1 + area2 - intersection );
}



/*************************************************************************
*函数名称:TLD::getOverlappingBoxes
*函数参数:跟踪窗口、good_box中要放入的扫描窗口的数量
*函数功能:1、找到与跟踪窗口重叠率最高的的扫描窗口best_box
           2、找到与跟踪窗口重叠率超过0.6的,放到good_boxes中,用于产生正样本
           3、找到与跟踪窗口重叠率小于0.2的,放到bad_boxes中,用于产生负样本
           4、调用getBBHull()函数,获取good_boxes中所有窗口的并集的外接矩形
*输出参数:无
**************************************************************************/
void TLD::getOverlappingBoxes( const cv::Rect& box1 , int num_closest )
{
	float max_overlap = 0;
	for( int i = 0; i < grid.size(); i++ )
	{
		if( grid[i].overlap > max_overlap )
		{
			max_overlap = grid[i].overlap;
			best_box = grid[i];
		}
		if( grid[i].overlap > 0.6 )
		{
			good_boxes.push_back( i );
		}
		else if( grid[i].overlap < bad_overlap )
		{
			bad_boxes.push_back( i );
		}
	}
	//Get the best num_closest (10) boxes and puts them in good_boxes
	if( good_boxes.size()>num_closest )
	{
		std::nth_element( good_boxes.begin() , good_boxes.begin() + num_closest , good_boxes.end() , OComparator( grid ) );
		good_boxes.resize( num_closest );
	}
	getBBHull();
}


/*************************************************************************
*函数名称:TLD::getBBHull()
*函数参数:跟踪窗口、good_box中要放入的扫描窗口的数量
*函数功能:找到所有点的外接矩形的位置和大小参数
*输出参数:无
**************************************************************************/
void TLD::getBBHull()
{
	int x1 = INT_MAX , x2 = 0;
	int y1 = INT_MAX , y2 = 0;
	int idx;
	for( int i = 0; i < good_boxes.size(); i++ )
	{
		idx = good_boxes[i];
		x1 = min( grid[idx].x , x1 );
		y1 = min( grid[idx].y , y1 );
		x2 = max( grid[idx].x + grid[idx].width , x2 );
		y2 = max( grid[idx].y + grid[idx].height , y2 );
	}
	bbhull.x = x1;
	bbhull.y = y1;
	bbhull.width = x2 - x1;
	bbhull.height = y2 - y1;
}

bool bbcomp( const BoundingBox& b1 , const BoundingBox& b2 )
{
	TLD t;
	if( t.bbOverlap( b1 , b2 ) < 0.5 )
		return false;
	else
		return true;
}
/*int TLD::clusterBB(const vector<BoundingBox>& dbb, vector<int>& indexes){
	//FIXME: Conditional jump or move depends on uninitialised value(s)
	const int c = dbb.size();
	//1. Build proximity matrix
	Mat D(c, c, CV_32F);
	float d;
	for (int i = 0; i<c; i++){
	for (int j = i + 1; j<c; j++){
	d = 1 - bbOverlap(dbb[i], dbb[j]);
	D.at<float>(i, j) = d;
	D.at<float>(j, i) = d;
	}
	}
	//2. Initialize disjoint clustering
	float L[c - 1]; //Level
	int nodes[c - 1][2];
	int belongs[c];
	int m = c;
	for (int i = 0; i<c; i++){
	belongs[i] = i;
	}
	for (int it = 0; it<c - 1; it++){
	//3. Find nearest neighbor
	float min_d = 1;
	int node_a, node_b;
	for (int i = 0; i<D.rows; i++){
	for (int j = i + 1; j<D.cols; j++){
	if (D.at<float>(i, j)<min_d && belongs[i] != belongs[j]){
	min_d = D.at<float>(i, j);
	node_a = i;
	node_b = j;
	}
	}
	}
	if (min_d>0.5){
	int max_idx = 0;
	bool visited;
	for (int j = 0; j<c; j++){
	visited = false;
	for (int i = 0; i<2 * c - 1; i++){
	if (belongs[j] == i){
	indexes[j] = max_idx;
	visited = true;
	}
	}
	if (visited)
	max_idx++;
	}
	return max_idx;
	}

	//4. Merge clusters and assign level
	L[m] = min_d;
	nodes[it][0] = belongs[node_a];
	nodes[it][1] = belongs[node_b];
	for (int k = 0; k<c; k++){
	if (belongs[k] == belongs[node_a] || belongs[k] == belongs[node_b])
	belongs[k] = m;
	}
	m++;
	}
	return 1;

	}*/


/**************************************************************************************************
*函数名称:TLD::clusterConf
*函数参数:检测器检测到的窗口、检测器检测到窗口的检测相关相似度、聚类的窗口、聚类窗口的相关相似度
*函数功能:对检测器检测到的目标bounding box进行聚类
*输出参数:无
**************************************************************************************************/
void TLD::clusterConf( const vector<BoundingBox>& dbb , const vector<float>& dconf , vector<BoundingBox>& cbb , vector<float>& cconf )
{
	// 聚类(Cluster)分析是由若干模式(Pattern)组成的,通常,模式是一个度量(Measurement)的向量,或者是多维空间中的  
	// 一个点。聚类分析以相似性为基础,在一个聚类中的模式之间比不在同一聚类中的模式之间具有更多的相似性。

	int numbb = dbb.size();
	vector<int> T;
	float space_thr = 0.5;
	int c = 1;                        // 记录 聚类的类个数
	switch( numbb )                   // 检测到的含有目标的bounding box个数 
	{
	case 1:                           // 如果只检测到一个,那么这个就是检测器检测到的目标
		cbb = vector<BoundingBox>( 1 , dbb[0] );
		cconf = vector<float>( 1 , dconf[0] );
		return;
		break;
	case 2:                           // 如果只检测到两个box,但他们的重叠度小于0.5
		T = vector<int>( 2 , 0 );
		if( 1 - bbOverlap( dbb[0] , dbb[1] ) > space_thr )
		{
			T[1] = 1;
			c = 2;                    // 重叠度小于0.5的box,属于不同的类
		}
		break;
	default:
		T = vector<int>( numbb , 0 );      // 检测到的box数目大于2个,则筛选出重叠度大于0.5的

		// stable_partition()重新排列元素,使得满足指定条件的元素排在不满足条件的元素前面。它维持着两组元素的顺序关系。  
		// STL partition就是把一个区间中的元素按照某个条件分成两类。返回第二类子集的起点  
		// bbcomp()函数判断两个box的重叠度小于0.5,返回false,否则返回true (分界点是重叠度:0.5)  
		// partition() 将dbb划分为两个子集,将满足两个box的重叠度小于0.5的元素移动到序列的前面,为一个子集,重叠度大于0.5的,  
		// 放在序列后面,为第二个子集,但两个子集的大小不知道,返回第二类子集的起点 

		c = partition( dbb , T , ( *bbcomp ) );     // 重叠度小于0.5的box,属于不同的类,所以c是不同的类别个数
		// c = clusterBB(dbb,T);
		break;
	}
	cconf = vector<float>( c );
	cbb = vector<BoundingBox>( c );
	printf( "Cluster indexes: " );
	BoundingBox bx;
	for( int i = 0; i < c; i++ )                    // 类别个数 
	{
		float cnf = 0;
		int N = 0 , mx = 0 , my = 0 , mw = 0 , mh = 0;
		for( int j = 0; j < T.size(); j++ )         // 检测到的bounding box个数
		{
			if( T[j] == i )                         // 将聚类为同一个类别的box的坐标和大小进行累加
			{
				printf( "%d " , i );
				cnf = cnf + dconf[j];
				mx = mx + dbb[j].x;
				my = my + dbb[j].y;
				mw = mw + dbb[j].width;
				mh = mh + dbb[j].height;
				N++;
			}
		}
		if( N > 0 )                                 // 然后求该类的box的坐标和大小的平均值,将平均值作为该类的box的代表
		{
			cconf[i] = cnf / N;
			bx.x = cvRound( mx / N );
			bx.y = cvRound( my / N );
			bx.width = cvRound( mw / N );
			bx.height = cvRound( mh / N );
			cbb[i] = bx;                            // 返回的是聚类,每一个类都有一个代表的bounding box
		}
	}
	printf( "\n" );
}

int TLD::max( int a , int b )
{
	if( a > b )
		return a;
	else
		return b;
}

int TLD::min( int a , int b )
{
	if( a > b )
		return b;
	else
		return a;
}

PatchGenerator.h

#include<opencv2\opencv.hpp>
#ifndef PATCHGENERATOR_H
#define PATCHGENERATOR_H

using namespace cv;

class PatchGenerator
{
public:
	bool randomBlur;
	double backgroundMin , backgroundMax;
	double noiseRange;
	double lambdaMin , lambdaMax;
	double thetaMin  , thetaMax;
	double phiMin    , phiMax;

	PatchGenerator();
	PatchGenerator(
		double _backgroundMin ,
		double _backgroundMax ,
		double _noiseRange ,
		bool _randomBlur = true ,
		double _lambdaMin = 0.6 ,
		double _lambdaMax = 1.5 ,
		double _thetaMin = -CV_PI ,
		double _thetaMax = CV_PI ,
		double _phiMin = -CV_PI ,
		double _phiMax = CV_PI );

	void operator()( const cv::Mat& image , Point2f pt , Mat& patch , Size patchSize , RNG& rng )const;
	void operator()( const cv::Mat& image , const Mat& transform , Mat& patch , Size patchSize , RNG& rng )const;
	void warpWholeImage( const cv::Mat&image , Mat& matT , Mat& buf , CV_OUT Mat& warped , int border , RNG& rng );
	void gerateRandomTransform( Point2f srcCenter , Point2f dstCenter , CV_OUT Mat& transform , RNG& rng , bool inverse = false )const;
	void setAffineParam( double lamda , double theta , double phi );
};
#endif

PatchGenerator.cpp

#include <opencv2/opencv.hpp>  
#include "PatchGenerator.h"


/*
The code below implements keypoint detector, fern-based point classifier and a planar object detector.

References:
1. Mustafa Özuysal, Michael Calonder, Vincent Lepetit, Pascal Fua,
"Fast KeyPoint Recognition Using Random Ferns,"
IEEE Transactions on Pattern Analysis and Machine Intelligence, 15 Jan. 2009.

2. Vincent Lepetit, Pascal Fua,
"Towards Recognizing Feature Points Using Classification Trees,"
Technical Report IC/2004/74, EPFL, 2004.
*/

const int progressBarSize = 50;

 Patch Generator //  

static const double DEFAULT_BACKGROUND_MIN = 0;
static const double DEFAULT_BACKGROUND_MAX = 256;
static const double DEFAULT_NOISE_RANGE = 5;
static const double DEFAULT_LAMBDA_MIN = 0.6;
static const double DEFAULT_LAMBDA_MAX = 1.5;
static const double DEFAULT_THETA_MIN = -CV_PI;
static const double DEFAULT_THETA_MAX = CV_PI;
static const double DEFAULT_PHI_MIN = -CV_PI;
static const double DEFAULT_PHI_MAX = CV_PI;

PatchGenerator::PatchGenerator()
	: backgroundMin( DEFAULT_BACKGROUND_MIN ) , backgroundMax( DEFAULT_BACKGROUND_MAX ) ,
	noiseRange( DEFAULT_NOISE_RANGE ) , randomBlur( true ) , lambdaMin( DEFAULT_LAMBDA_MIN ) ,
	lambdaMax( DEFAULT_LAMBDA_MAX ) , thetaMin( DEFAULT_THETA_MIN ) ,
	thetaMax( DEFAULT_THETA_MAX ) , phiMin( DEFAULT_PHI_MIN ) ,
	phiMax( DEFAULT_PHI_MAX )
{
}


PatchGenerator::PatchGenerator( double _backgroundMin , double _backgroundMax ,
	double _noiseRange , bool _randomBlur ,
	double _lambdaMin , double _lambdaMax ,
	double _thetaMin , double _thetaMax ,
	double _phiMin , double _phiMax )
	: backgroundMin( _backgroundMin ) , backgroundMax( _backgroundMax ) ,
	noiseRange( _noiseRange ) , randomBlur( _randomBlur ) ,
	lambdaMin( _lambdaMin ) , lambdaMax( _lambdaMax ) ,
	thetaMin( _thetaMin ) , thetaMax( _thetaMax ) ,
	phiMin( _phiMin ) , phiMax( _phiMax )
{
}


void PatchGenerator::gerateRandomTransform( Point2f srcCenter , Point2f dstCenter ,
	Mat& transform , RNG& rng , bool inverse ) const
{
	double lambda1 = rng.uniform( lambdaMin , lambdaMax );
	double lambda2 = rng.uniform( lambdaMin , lambdaMax );
	double theta = rng.uniform( thetaMin , thetaMax );
	double phi = rng.uniform( phiMin , phiMax );

	// Calculate random parameterized affine transformation A,  
	// A = T(patch center) * R(theta) * R(phi)' *  
	//     S(lambda1, lambda2) * R(phi) * T(-pt)  
	double st = sin( theta );
	double ct = cos( theta );
	double sp = sin( phi );
	double cp = cos( phi );
	double c2p = cp*cp;
	double s2p = sp*sp;

	double A = lambda1*c2p + lambda2*s2p;
	double B = ( lambda2 - lambda1 )*sp*cp;
	double C = lambda1*s2p + lambda2*c2p;

	double Ax_plus_By = A*srcCenter.x + B*srcCenter.y;
	double Bx_plus_Cy = B*srcCenter.x + C*srcCenter.y;

	transform.create( 2 , 3 , CV_64F );
	Mat_<double>& T = ( Mat_<double>& )transform;
	T( 0 , 0 ) = A*ct - B*st;
	T( 0 , 1 ) = B*ct - C*st;
	T( 0 , 2 ) = -ct*Ax_plus_By + st*Bx_plus_Cy + dstCenter.x;
	T( 1 , 0 ) = A*st + B*ct;
	T( 1 , 1 ) = B*st + C*ct;
	T( 1 , 2 ) = -st*Ax_plus_By - ct*Bx_plus_Cy + dstCenter.y;

	if( inverse )
		invertAffineTransform( T , T );
}


void PatchGenerator::operator ()( const Mat& image , Point2f pt , Mat& patch , Size patchSize , RNG& rng ) const
{
	double buffer[6];
	Mat_<double> T( 2 , 3 , buffer );

	gerateRandomTransform( pt , Point2f( ( patchSize.width - 1 )*0.5f , ( patchSize.height - 1 )*0.5f ) , T , rng );
	( *this )( image , T , patch , patchSize , rng );
}


void PatchGenerator::operator ()( const Mat& image , const Mat& T ,
	Mat& patch , Size patchSize , RNG& rng ) const
{
	patch.create( patchSize , image.type() );
	if( backgroundMin != backgroundMax )
	{
		rng.fill( patch , RNG::UNIFORM , Scalar::all( backgroundMin ) , Scalar::all( backgroundMax ) );
		warpAffine( image , patch , T , patchSize , INTER_LINEAR , BORDER_TRANSPARENT );
	}
	else
		warpAffine( image , patch , T , patchSize , INTER_LINEAR , BORDER_CONSTANT , Scalar::all( backgroundMin ) );

	int ksize = randomBlur ? ( unsigned ) rng % 9 - 5 : 0;
	if( ksize > 0 )
	{
		ksize = ksize * 2 + 1;
		GaussianBlur( patch , patch , Size( ksize , ksize ) , 0 , 0 );
	}

	if( noiseRange > 0 )
	{
		AutoBuffer<uchar> _noiseBuf( patchSize.width*patchSize.height*image.elemSize() );
		Mat noise( patchSize , image.type() , ( uchar* ) _noiseBuf );
		int delta = image.depth() == CV_8U ? 128 : image.depth() == CV_16U ? 32768 : 0;
		rng.fill( noise , RNG::NORMAL , Scalar::all( delta ) , Scalar::all( noiseRange ) );
		if( backgroundMin != backgroundMax )
			addWeighted( patch , 1 , noise , 1 , -delta , patch );
		else
		{
			for( int i = 0; i < patchSize.height; i++ )
			{
				uchar* prow = patch.ptr<uchar>( i );
				const uchar* nrow = noise.ptr<uchar>( i );
				for( int j = 0; j < patchSize.width; j++ )
					if( prow[j] != backgroundMin )
						prow[j] = saturate_cast< uchar >( prow[j] + nrow[j] - delta );
			}
		}
	}
}
                     
void PatchGenerator::warpWholeImage( const cv::Mat&image , Mat& matT , Mat& buf , CV_OUT Mat& warped , int border , RNG& rng )
{
	Mat_<double> T = matT;
	Rect roi( INT_MAX , INT_MAX , INT_MIN , INT_MIN );

	for( int k = 0; k < 4; k++ )
	{
		Point2f pt0 , pt1;
		pt0.x = ( float ) ( k == 0 || k == 3 ? 0 : image.cols );
		pt0.y = ( float ) ( k < 2 ? 0 : image.rows );
		pt1.x = ( float ) ( T( 0 , 0 )*pt0.x + T( 0 , 1 )*pt0.y + T( 0 , 2 ) );
		pt1.y = ( float ) ( T( 1 , 0 )*pt0.x + T( 1 , 1 )*pt0.y + T( 1 , 2 ) );

		roi.x = std::min( roi.x , cvFloor( pt1.x ) );
		roi.y = std::min( roi.y , cvFloor( pt1.y ) );
		roi.width = std::max( roi.width , cvCeil( pt1.x ) );
		roi.height = std::max( roi.height , cvCeil( pt1.y ) );
	}

	roi.width -= roi.x - 1;
	roi.height -= roi.y - 1;
	int dx = border - roi.x;
	int dy = border - roi.y;

	if( ( roi.width + border * 2 )*( roi.height + border * 2 ) > buf.cols )
		buf.create( 1 , ( roi.width + border * 2 )*( roi.height + border * 2 ) , image.type() );

	warped = Mat( roi.height + border * 2 , roi.width + border * 2 ,
		image.type() , buf.data );

	T( 0 , 2 ) += dx;
	T( 1 , 2 ) += dy;
	( *this )( image , T , warped , warped.size() , rng );

	if( T.data != matT.data )
		T.convertTo( matT , matT.type() );
}


// Params are assumed to be symmetrical: lambda w.r.t. 1, theta and phi w.r.t. 0  
void PatchGenerator::setAffineParam( double lambda , double theta , double phi )
{
	lambdaMin = 1. - lambda;
	lambdaMax = 1. + lambda;
	thetaMin = -theta;
	thetaMax = theta;
	phiMin = -phi;
	phiMax = phi;
}

run_tld.cpp

#include "utils.h"
#include "TLD.h"
#include <stdio.h>
#include <time.h>
#include <iostream>
#include <sstream>
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

//Global variables
Rect box;                    // 感兴趣区域
bool drawing_box = false;	 // 是否准备画矩形
bool gotBB = false;			 // 是否得到Bounding Box(感兴趣区域)
bool tl = true;				 // 是否跟踪且学习过
bool rep = false;            // 是否重复播放
bool fromfile = false;		 // 是否从文件中读取
string video;				 // 视频名称
clock_t T1 , T2;


/***************************************************************************
*函数名称:readBB
*函数参数:输入文件名
*函数功能:读取记录bounding box的文件,获得bounding box的四个参数
           左上角坐标x,y和宽、高;如在\datasets\06_car\init.txt中:
           记录了初始目标的bounding box,内容如下:142,125,232,164
*输出参数:无
***************************************************************************/
void readBB( char* file )
{
	ifstream bb_file( file );                 // 以输入方式打开文件
	string line;
	getline( bb_file , line );                // 从bb_file中读取一串输入流中的字符串
	// istringstream是C++里面的一种输入输出控制类,它可以创建一个对象,然后这个对象就可以绑定一行字符串,然后以空格为分隔符把该行分隔开来
	istringstream linestream( line );         // 创建istringstream对象并同时初始化,使其和字符串str绑定
	
	string x1 , y1 , x2 , y2;
	getline( linestream , x1 , ',' );
	getline( linestream , y1 , ',' );
	getline( linestream , x2 , ',' );
	getline( linestream , y2 , ',' );

	// atoi 功 能: 把字符串转换成整型数
	int x = atoi( x1.c_str() );               // c_str()返回一个指向正规C字符串的指针,内容与string串相同
	int y = atoi( y1.c_str() );
	int w = atoi( x2.c_str() ) - x;
	int h = atoi( y2.c_str() ) - y;
	box = Rect( x , y , w , h );              // 初始化感兴趣区域
}


/***********************************************************************************
*函数名称:mouseHandler
*函数参数:(鼠标)事件,鼠标位置(x,y),
*函数功能:得到目标区域的范围,用鼠标选中bounding box
*输出参数:无
***********************************************************************************/
void mouseHandler( int event , int x , int y , int flags , void *param )
{
	switch( event )
	{
	case CV_EVENT_MOUSEMOVE:              // 鼠标移动
		if( drawing_box )                 // 准备画矩形
		{
			box.width = x - box.x;
			box.height = y - box.y;
		}
		break;
	case CV_EVENT_LBUTTONDOWN:            // 鼠标左键按下
		drawing_box = true;               // 准备画矩形置为真
		box = Rect( x , y , 0 , 0 );      // 初始化矩形框的参数
		break;
	case CV_EVENT_LBUTTONUP:              // 鼠标左键抬起
		drawing_box = false;              // 准备画矩形置为假(已经画过了)
		if( box.width < 0 )
		{
			box.x += box.width;
			box.width *= -1;
		}
		if( box.height < 0 )
		{
			box.y += box.height;
			box.height *= -1;
		}
		gotBB = true;                      // 得到Bounding Box(感兴趣区域置为真)
		break;
	}
}


/************************************************************************
*函数名称:print_help
*函数参数:argv
*函数功能:打印出一些帮助信息
*输出参数:无
*************************************************************************/
void print_help( char** argv )
{
	printf( "use:\n     %s -p /path/parameters.yml\n" , argv[0] );
	printf( "-s    source video\n-b        bounding box file\n-tl  track and learn\n-r     repeat\n" );
}


/***********************************************************************
*函数名称:read_options
*函数参数:argc,argv,视频帧的引用,文件引用
*函数功能:按照不同的方式打开文件
*输出参数:无
************************************************************************/
void read_options( int argc , char** argv , VideoCapture& capture , FileStorage &fs )
{
	for( int i = 0; i < argc; i++ )
	{
		if( strcmp( argv[i] , "-b" ) == 0 )
		{
			if( argc > i )                        // 命令行参数个数没有到最后一个
			{
				readBB( argv[i + 1] );            // 得到bounding box参数
				gotBB = true;                     // 得到感兴趣区域
			}
			else
				print_help( argv );
		}
		if( strcmp( argv[i] , "-s" ) == 0 )       // argv[i]=="-s"
		{
			if( argc > i )                        // 如果命令行参数没有到最后一个
			{
				video = string( argv[i + 1] );    // 打开后面的一个命令行字符串(视频名称)
				capture.open( video );            // 打开视频
				fromfile = true;                  // 从文件打开置为真
			}
			else
				print_help( argv );

		}
		if( strcmp( argv[i] , "-p" ) == 0 )       // argv[i]=="-p"
		{
			if( argc > i )
			{
				fs.open( argv[i + 1] , FileStorage::READ );    // 打开parameter.yml文件读取初始化参数
			}
			else
				print_help( argv );
		}
		if( strcmp( argv[i] , "-no_tl" ) == 0 )    // argv[i]=="-no_tl"
		{
			tl = false;                            // 仅在第一帧训练(不跟踪,不学习)
		}

		if( strcmp( argv[i] , "-r" ) == 0 )        // argv[i]=="-r"
		{
			rep = true;                            // 重复播放(第一次学习,第二次检测)
		}
	}
}



/******************************************************************************************************************

程序的运行方式:
从摄像头运行:
run_tld 属性改成  -p ../parameters.yml

从文件读取运行:
run_tld 属性改成  -p ../parameters.yml -s ../datasets/06_car/car.mpg

从文件初始化bounding box参数:
run_tld 属性改成  -p ../parameters.yml -s ../datasets/06_car/car.mpg -b ../datasets/06_car/init.txt

仅用第一帧训练(无跟踪,无学习)
run_tld 属性改成  -p ../parameters.yml -s ../datasets/06_car/car.mpg -b ../datasets/06_car/init.txt -no_tl

测试最后的检测器效果(重复播放视频,第一次学习,第二次检测)
run_tld 属性改成  -p ../parameters.yml -s ../datasets/06_car/car.mpg -b ../datasets/06_car/init.txt -r

******************************************************************************************************************/
int main( int argc , char * argv [ ] )
{
	VideoCapture capture;
	capture.open( 0 );

	// OpenCV的C++接口中,用于保存图像的imwrite只能保存整数数据,且需作为图像格式。当需要保存浮  
	// 点数据或XML/YML文件时,OpenCV的C语言接口提供了cvSave函数,但这一函数在C++接口中已经被删除。  
	// 取而代之的是FileStorage类。

	FileStorage fs;
	//Read options
	read_options( argc , argv , capture , fs );

	//Init camera
	if( !capture.isOpened() )
	{
		cout << "capture device failed to open!" << endl;
		return 1;
	}

	//Register mouse callback to draw the bounding box
	cvNamedWindow( "TLD" , CV_WINDOW_AUTOSIZE );
	cvSetMouseCallback( "TLD" , mouseHandler , NULL );       // 用鼠标选中初始目标的bounding box

	//TLD framework
	TLD tld;
	//Read parameters file
	tld.read( fs.getFirstTopLevelNode() );            // 读取参数文件
	cv::Mat frame;                                    // 帧图像
	cv::Mat last_gray;                                // 上一次的灰度图
	cv::Mat first;                                    // 第一帧图像
	if( fromfile )                                    // 如果指定为从文件读取 
	{
		capture >> frame;                             // 读当前帧 
		cvtColor( frame , last_gray , CV_BGR2GRAY );  // 转化为灰度图存储到last_gray中
		frame.copyTo( first );                        // 拷贝作为第一帧
	}
	else                                              // 如果为读取摄像头,则设置获取的图像大小为320x240 
	{
		capture.set( CV_CAP_PROP_FRAME_WIDTH , 320 );
		capture.set( CV_CAP_PROP_FRAME_HEIGHT , 240 );
	}

	// Initialization
GETBOUNDINGBOX:                     // 获得Bounding box
	while( !gotBB )                 // 没有得到感兴趣区域
	{
		if( !fromfile )             // 没有从文件中读取
		{
			capture >> frame;       // 从视频中读取
		}
		else
		{
			first.copyTo( frame );  // 将第一帧深拷贝到first中
		}
		cvtColor( frame , last_gray , CV_RGB2GRAY );
		drawBox( frame , box );
		imshow( "TLD" , frame );
		if( cvWaitKey( 33 ) == 'q' )
			return 0;
	}
	if( min( box.width , box.height ) < ( int ) fs.getFirstTopLevelNode()["min_win"] )  // 如果选定框太小
	{
		cout << "Bounding box too small, try again." << endl;
		gotBB = false;
		goto GETBOUNDINGBOX;              //  跳到指定语句
	}

	//Remove callback
	cvSetMouseCallback( "TLD" , NULL , NULL );
	printf( "Initial Bounding Box = x:%d y:%d h:%d w:%d\n" , box.x , box.y , box.width , box.height );
	
	//Output file
	FILE  *bb_file = fopen( "bounding_boxes.txt" , "w" );
	
	//TLD initialization
	tld.init( last_gray , box , bb_file );

	///Run-time
	cv::Mat current_gray;                                  // 当前帧灰度图
	BoundingBox pbox;
	std::vector<cv::Point2f> pts1;                         // 光流法所需参数pts1
	std::vector<cv::Point2f> pts2;                         // 光流法所需参数pts2
	bool status = true;                                    // 是否跟踪成功的标志
	int frames = 1;                                        // 帧数
	int detections = 1;                                    // 检测到目标的帧数
REPEAT:
	T1 = clock();
	while( capture.read( frame ) )
	{
		//get frame
		cvtColor( frame , current_gray , CV_RGB2GRAY );    // 得到一帧

		//Process Frame		
		tld.processFrame( last_gray , current_gray , pts1 , pts2 , pbox , status , tl , bb_file );


		//Draw Points
		if( status )
		{
			drawPoints( frame , pts1 );
			drawPoints( frame , pts2 , Scalar( 0 , 255 , 0 ) );
			drawBox( frame , pbox );
			detections++;                              // 检测到的次数
		}

		//Display
		imshow( "TLD" , frame );
		
		//swap points and images
		swap( last_gray , current_gray );             // 交换图像帧
		pts1.clear();
		pts2.clear();
		frames++;                                     // 帧数 +1
		printf( "Detection rate: %d/%d\n" , detections , frames );
		if( cvWaitKey( 33 ) == 'q' )
			break;
	}
	T2 = clock();
	printf( "处理的平均速度为 %.4f 毫秒/帧" , static_cast< double >( T2 - T1 ) / CLOCKS_PER_SEC * 1000 / frames );
	if( rep )                                         // 仅重复一次
	{
		rep = false;
		tl = false;
		fclose( bb_file );
		
		bb_file = fopen( "final_detector.txt" , "w" );// 另外打开一个final_detector.txt文件记录最终检测器的参数

		//capture.set(CV_CAP_PROP_POS_AVI_RATIO,0);
		capture.release(); 
		capture.open( video );                        // 重新打开视频
		goto REPEAT;
	}
	fclose( bb_file );
	return 0;
}




  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 19
    评论
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值