struck(结构化SVM用于视觉跟踪)--源代码详解--tracker.cpp

作者算法的功能都是在tracker类中实现的,下面分析其头文件和cpp文件,头文件:

#ifndef TRACKER_H
#define TRACKER_H

#include "Rect.h"

#include <vector>
#include <Eigen/Core>
#include <opencv/cv.h>

class Config;
class Features;
class Kernel;
class LaRank;
class ImageRep;

class Tracker
{
public:
	Tracker(const Config& conf);
	~Tracker();
	
	void Initialise(const cv::Mat& frame, FloatRect bb);//初始化
	void Reset();//复位
	void Track(const cv::Mat& frame);//跟踪
	void Debug();//用于显示跟踪的效果框图,便于调试
	
	inline const FloatRect& GetBB() const { return m_bb; }
	inline bool IsInitialised() const { return m_initialised; }
	
private:
	const Config& m_config;//这个指向常量的引用,用来绑定构造器传入的conf
	bool m_initialised;//标志这个track是否初始化了
	std::vector<Features*> m_features;//存放特征指针
	std::vector<Kernel*> m_kernels;
	LaRank* m_pLearner;
	FloatRect m_bb;//一个矩形框
	cv::Mat m_debugImage;//调试时用于显示的图片
	bool m_needsIntegralImage;//是否需要积分图,为了方便计算haar特征,当使用haar特征的时候,需要先计算积分图
	bool m_needsIntegralHist;//是否需要积分颜色直方图
	
	void UpdateLearner(const ImageRep& image);//更新学习器,也就是更新结构化SVM
	void UpdateDebugImage(const std::vector<FloatRect>& samples, const FloatRect& centre, const std::vector<double>& scores);//更新用于调试的显示图片
};

#endif


源文件:

#include "Tracker.h"
#include "Config.h"
#include "ImageRep.h"
#include "Sampler.h"
#include "Sample.h"
#include "GraphUtils.h"

#include "HaarFeatures.h"
#include "RawFeatures.h"
#include "HistogramFeatures.h"
#include "MultiFeatures.h"

#include "Kernels.h"

#include "LaRank.h"

#include <opencv/cv.h>
#include <opencv/highgui.h>

#include <Eigen/Core>

#include <vector>
#include <algorithm>

using namespace cv;
using namespace std;
using namespace Eigen;

Tracker::Tracker(const Config& conf) :
	m_config(conf),
	m_initialised(false),
	m_pLearner(0),
	m_debugImage(2*conf.searchRadius+1, 2*conf.searchRadius+1, CV_32FC1),//用于显示的debug图片初始化为32位单通道,长宽根据搜索半径来
	m_needsIntegralImage(false)
{
	Reset();//reset函数帮助我们创建了一个新的learner对象,还有相应的feature对象和kernel对象
}

Tracker::~Tracker()
{
	delete m_pLearner;
	for (int i = 0; i < (int)m_features.size(); ++i)
	{
		delete m_features[i];
		delete m_kernels[i];
	}
}

void Tracker::Reset()
{
	//reset的功能很简单,主要是两个方面
	//1 是把成员变量初始化,如果成员指针有指向动态内存则清除掉,总之变量恢复到构造器刚刚创建对象的时候
	//2 根据config的配置,将某些成员变量设置为true,创建相应的特征对象和核对象,创建learner对象
	
	m_initialised = false;
	m_debugImage.setTo(0);
	if (m_pLearner) delete m_pLearner;
	for (int i = 0; i < (int)m_features.size(); ++i)
	{
		delete m_features[i];
		delete m_kernels[i];
	}
	m_features.clear();
	m_kernels.clear();
	
	m_needsIntegralImage = false;
	m_needsIntegralHist = false;
	
	int numFeatures = m_config.features.size();//我关注的是haar特征,没有用多特征,所以这个size就是1
	vector<int> featureCounts;
	for (int i = 0; i < numFeatures; ++i)
	{
		switch (m_config.features[i].feature)
		{
		case Config::kFeatureTypeHaar:
			m_features.push_back(new HaarFeatures(m_config));
			m_needsIntegralImage = true;
			break;			
		case Config::kFeatureTypeRaw:
			m_features.push_back(new RawFeatures(m_config));
			break;
		case Config::kFeatureTypeHistogram:
			m_features.push_back(new HistogramFeatures(m_config));
			m_needsIntegralHist = true;
			break;
		}
		featureCounts.push_back(m_features.back()->GetCount());
		
		switch (m_config.features[i].kernel)
		{
		case Config::kKernelTypeLinear:
			m_kernels.push_back(new LinearKernel());
			break;
		case Config::kKernelTypeGaussian:
			m_kernels.push_back(new GaussianKernel(m_config.features[i].params[0]));
			break;
		case Config::kKernelTypeIntersection:
			m_kernels.push_back(new IntersectionKernel());
			break;
		case Config::kKernelTypeChi2:
			m_kernels.push_back(new Chi2Kernel());
			break;
		}
	}
	
	if (numFeatures > 1)
	{
		MultiFeatures* f = new MultiFeatures(m_features);
		m_features.push_back(f);
		
		MultiKernel* k = new MultiKernel(m_kernels, featureCounts);
		m_kernels.push_back(k);		
	}
	
	//根据config,以及使用的特征对象,核函数对象,创建一个新的学习器对象
	//这个learner就是我们的结构化SVM
	m_pLearner = new LaRank(m_config, *m_features.back(), *m_kernels.back());
}
	
//视觉跟踪中,第一帧会给出gt值用来初始化学习器
void Tracker::Initialise(const cv::Mat& frame, FloatRect bb)
{
	m_bb = IntRect(bb);//给学习器需要的矩形框赋值
	ImageRep image(frame, m_needsIntegralImage, m_needsIntegralHist);//创建学习器需要的图像对象ImageRep
	for (int i = 0; i < 1; ++i)//这个for循环只进行一次,要不要无所谓
	{
		UpdateLearner(image);//用这个图像对象,对学习器进行更新,也就是第一次更新学习器
	}
	m_initialised = true;//标志位表示学习器已经完成初始化了
}

void Tracker::Track(const cv::Mat& frame)
{
	assert(m_initialised);
	//计算积分图,若选择haar特征,m_needsIntegralImage=true,m_needsIntegralHist=false
	//ImageRep对象是方便learner对象在取特征的时候使用
	ImageRep image(frame, m_needsIntegralImage, m_needsIntegralHist);
	//在上一帧矩形框的searchRadius范围内,采样n个矩形框,存在vector中
	vector<FloatRect> rects = Sampler::PixelSamples(m_bb, m_config.searchRadius);
	
	vector<FloatRect> keptRects;//存储筛选后的框
	keptRects.reserve(rects.size());
	for (int i = 0; i < (int)rects.size(); ++i)
	{
		if (!rects[i].IsInside(image.GetRect())) continue;//超出图像范围的框,被舍弃掉
		keptRects.push_back(rects[i]);
	}
	
	//矩形框产生好了,就可以利用框和ImageRep对象产生正负样本,然后送给学习器评估了
	MultiSample sample(image, keptRects);
	
	vector<double> scores;
	//这一句耗时,计算量较大,learner对象对样本进行评估,评估的分数存储在向量scores中
	//所以这一句的实现,是作者结构化SVM算法正向计算的核心思想
	m_pLearner->Eval(sample, scores);
	
	double bestScore = -DBL_MAX;
	int bestInd = -1;
	for (int i = 0; i < (int)keptRects.size(); ++i)
	{		
		if (scores[i] > bestScore)
		{
			bestScore = scores[i];//找到分数最高的样本,记录分数和编号
			bestInd = i;
		}
	}
	
	UpdateDebugImage(keptRects, m_bb, scores);//在上一帧的m_bb周围,根据score的大小,画不同颜色的点
	
	if (bestInd != -1)
	{
		m_bb = keptRects[bestInd];//把这一帧得到的最新的
		UpdateLearner(image);//这一句耗时,作者更新结构化SVM的算法都在这个函数中了
#if VERBOSE		
		cout << "track score: " << bestScore << endl;
#endif
	}
	
	//看完track这个函数之后,会发现后续要关注的重点就是m_pLearner->Eval()函数和UpdateLearner()函数
	
}

void Tracker::UpdateDebugImage(const vector<FloatRect>& samples, const FloatRect& centre, const vector<double>& scores)
{
	double mn = VectorXd::Map(&scores[0], scores.size()).minCoeff();//使用Eigen的Map静态方法,创建了一个临时对象
	double mx = VectorXd::Map(&scores[0], scores.size()).maxCoeff();//使用该临时对象的成员函数,得到了最大值和最小值
	m_debugImage.setTo(0);//把图片像素点全部设置为0
	for (int i = 0; i < (int)samples.size(); ++i)
	{
		int x = (int)(samples[i].XMin() - centre.XMin());//qyy,计算左上角之间的距离,为什么不计算矩形中心点的距离?
		int y = (int)(samples[i].YMin() - centre.YMin());
		
		//作者在一个圆形区域内根据得到的score赋值,score最高的是1,score最低的是0
		//m_debugImage这个图片的格式是32FC1,所以等于1表示为白色,=0表示为黑色,所以我们就可以方便的看到
		//在整个搜索半径内,越白的地方的置信度越高
		m_debugImage.at<float>(m_config.searchRadius+y, m_config.searchRadius+x) = (float)((scores[i]-mn)/(mx-mn));
	}
}

void Tracker::Debug()
{
	imshow("tracker", m_debugImage);//显示m_debugImage图片
	m_pLearner->Debug();//调用学习器的debug函数
}

void Tracker::UpdateLearner(const ImageRep& image)
{
	// note these return the centre sample at index 0
	vector<FloatRect> rects = Sampler::RadialSamples(m_bb, 2*m_config.searchRadius, 5, 16);
	//vector<FloatRect> rects = Sampler::PixelSamples(m_bb, 2*m_config.searchRadius, true);
	
	vector<FloatRect> keptRects;
	keptRects.push_back(rects[0]); // the true sample,第0个rect是真值
	for (int i = 1; i < (int)rects.size(); ++i)
	{
		if (!rects[i].IsInside(image.GetRect())) continue;//超出图像范围的框,被舍弃掉
		keptRects.push_back(rects[i]);
	}
		
#if VERBOSE		
	cout << keptRects.size() << " samples" << endl;
#endif
		
	MultiSample sample(image, keptRects);
	m_pLearner->Update(sample, 0);//所以还是调用学习器对象的更新函数
	
	//看到这里tracker类就看完了,可以发现,作者算法实现的核心就是在 LaRank* m_pLearner这样一个学习器对象里面了
	//tracker这个类更多的是负责采样工作,找到分数最高的样本,更新预测框;
}





















猜你喜欢

转载自blog.csdn.net/sloanqin/article/details/54986540