作者算法的功能都是在tracker类中实现的,下面分析其头文件和cpp文件,头文件:
#ifndef TRACKER_H
#define TRACKER_H
#include "Rect.h"
#include
#include
#include
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 m_features;//存放特征指针
std::vector 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& samples, const FloatRect& centre, const std::vector& 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
#include
#include
#include
#include
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 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 rects = Sampler::PixelSamples(m_bb, m_config.searchRadius);
vector 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 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& samples, const FloatRect& centre, const vector& 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(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 rects = Sampler::RadialSamples(m_bb, 2*m_config.searchRadius, 5, 16);
//vector rects = Sampler::PixelSamples(m_bb, 2*m_config.searchRadius, true);
vector 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这个类更多的是负责采样工作,找到分数最高的样本,更新预测框;
}