struck 利用结构化SVM来实现视觉跟踪,在深度学习流行起来之前,struck是视觉跟踪领域效果最好的方法。深度学习流行之后,利用泛化的卷积特征能够得到很好的效果。struck的优点在于,它可以使用任意的特征来实现跟踪,因此它可以利用卷积神经网络提取的特征,然后结合结构化SVM来实现视觉跟踪,这样的效果说不定更好。
struck的源码是C++实现的,作者写的很好,思路清晰,代码结构清晰,而且与论文中的相符,没有那么多小trick,结果比较可靠。
下面从它的主函数开始,分析这份源码是如何实现的:
main.cpp
/*
* Struck: Structured Output Tracking with Kernels
*
* Code to accompany the paper:
* Struck: Structured Output Tracking with Kernels
* Sam Hare, Amir Saffari, Philip H. S. Torr
* International Conference on Computer Vision (ICCV), 2011
*
* Copyright (C) 2011 Sam Hare, Oxford Brookes University, Oxford, UK
*
* This file is part of Struck.
*
* Struck is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Struck is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Struck. If not, see .
*
*/
#include "Tracker.h"
#include "Config.h"
#include
#include
#include
#include
using namespace std;
using namespace cv;
static const int kLiveBoxWidth = 80;
static const int kLiveBoxHeight = 80;
void rectangle(Mat& rMat, const FloatRect& rRect, const Scalar& rColour)
{
IntRect r(rRect);
rectangle(rMat, Point(r.XMin(), r.YMin()), Point(r.XMax(), r.YMax()), rColour);
}
int main(int argc, char* argv[])
{
//这几句话没啥作用,我给注释掉
#ifndef WIN32
string programName = argv[0];
programName = programName.substr(programName.find_first_of('/'));
cout << "programName: " << programName << endl;
#endif
// read config file
string configPath = "../docs/config.txt";
Config conf(configPath);//作者定义的类Config 读取了所有的配置信息,并且cout输出
cout << conf << endl;
if (conf.features.size() == 0)
{
cout << "error: no features specified in config" << endl;
return EXIT_FAILURE;
}
if (argc > 1)
{
conf.sequenceName = argv[1];
}
ofstream outFile;//定义一个输出文件流,输出结果
if (conf.resultsPath != "")
{
#ifdef WIN32
string resultsPath = conf.resultsPath + "/" + conf.sequenceName + "_result.txt";
#else
string resultsPath = conf.resultsPath + "/" + conf.sequenceName + "_" + programName + "Result.txt";
#endif
outFile.open(resultsPath, ios::out);
if (!outFile)
{
cout << "error: could not open results file: " << conf.resultsPath << endl;
return EXIT_FAILURE;
}
}
// if no sequence specified then use the camera
bool useCamera = (conf.sequenceName == "");//根据在config.txt中是否给出视频名称,判断是否使用摄像头
VideoCapture cap;
int startFrame = -1;
int endFrame = -1;
FloatRect initBB;//这是一个模板类,
string imgFormat;
float scaleW = 1.f;
float scaleH = 1.f;
if (useCamera)//使用摄像头
{
if (!cap.open(0))
{
cout << "error: could not start camera capture" << endl;
return EXIT_FAILURE;
}
startFrame = 0;
endFrame = INT_MAX;
Mat tmp;
cap >> tmp;//读入一帧视频
scaleW = (float)conf.frameWidth/tmp.cols;//config中宽/读入视频的宽,比率
scaleH = (float)conf.frameHeight/tmp.rows;
/*该函数,创造了一个矩形,左上角在(120,80),80*80的矩形*/
initBB = IntRect(conf.frameWidth/2-kLiveBoxWidth/2, conf.frameHeight/2-kLiveBoxHeight/2, kLiveBoxWidth, kLiveBoxHeight);
cout << "press 'i' to initialise tracker" << endl;
}
else//使用视频
{
// parse frames file
string framesFilePath = conf.sequenceBasePath+"/"+conf.sequenceName+"/"+"frames.txt";
ifstream framesFile(framesFilePath.c_str(), ios::in);
if (!framesFile)
{
cout << "error: could not open sequence frames file: " << framesFilePath << endl;
return EXIT_FAILURE;
}
string framesLine;
getline(framesFile, framesLine);
printf("%s", framesLine.c_str());
sscanf(framesLine.c_str(), "%d,%d", &startFrame, &endFrame);
if (framesFile.fail() || startFrame == -1 || endFrame == -1)
{
cout << "error: could not parse sequence frames file" << endl;
return EXIT_FAILURE;
}
imgFormat = conf.sequenceBasePath+"/"+conf.sequenceName+"/img/%04d.jpg";//qyy changed
// read first frame to get size
char imgPath[256];
sprintf(imgPath, imgFormat.c_str(), startFrame);
Mat tmp = cv::imread(imgPath, 0);
scaleW = (float)conf.frameWidth/tmp.cols;
scaleH = (float)conf.frameHeight/tmp.rows;
// read init box from ground truth file
string gtFilePath = conf.sequenceBasePath+"/"+conf.sequenceName+"/"+"groundtruth_rect.txt";//qyy changed
ifstream gtFile(gtFilePath.c_str(), ios::in);
if (!gtFile)
{
cout << "error: could not open sequence gt file: " << gtFilePath << endl;
return EXIT_FAILURE;
}
string gtLine;
getline(gtFile, gtLine);
float xmin = -1.f;
float ymin = -1.f;
float width = -1.f;
float height = -1.f;
sscanf(gtLine.c_str(), "%f,%f,%f,%f", &xmin, &ymin, &width, &height);
if (gtFile.fail() || xmin < 0.f || ymin < 0.f || width < 0.f || height < 0.f)
{
cout << "error: could not parse sequence gt file" << endl;
return EXIT_FAILURE;
}
initBB = FloatRect(xmin*scaleW, ymin*scaleH, width*scaleW, height*scaleH);
}
Tracker tracker(conf);//使用conf类,初始化Tracker类
if (!conf.quietMode)//quietMode模式下,不显示结果,只运算
{
namedWindow("result");
}
Mat result(conf.frameHeight, conf.frameWidth, CV_8UC3);
bool paused = false;
bool doInitialise = false;
srand(conf.seed);
for (int frameInd = startFrame; frameInd <= endFrame; ++frameInd)
{
cout << "frame num is: " << frameInd << endl;//qyy
Mat frame;
if (useCamera)
{
Mat frameOrig;
cap >> frameOrig;
resize(frameOrig, frame, Size(conf.frameWidth, conf.frameHeight));
//imshow("result",frame);//qyy
//waitKey(0);//qyy
flip(frame, frame, 1);//作者把视频左右对称翻转了,不知道为什么这么做?
//imshow("result", frame);//qyy
//waitKey(0);//qyy
frame.copyTo(result);
if (doInitialise)
{
if (tracker.IsInitialised())
{
tracker.Reset();
}
else
{
tracker.Initialise(frame, initBB);
}
doInitialise = false;
}
else if (!tracker.IsInitialised())
{
rectangle(result, initBB, CV_RGB(255, 255, 255));//没有初始化,就在result上画白色框框
}
}
else
{
char imgPath[256];
sprintf(imgPath, imgFormat.c_str(), frameInd);
Mat frameOrig = cv::imread(imgPath, 0);//第二个参数flag指定读取的颜色类型,=0表示读取为灰度图像
cout << "frameOrig.channels: " << frameOrig.channels() << endl;//qyy
if (frameOrig.empty())
{
cout << "error: could not read frame: " << imgPath << endl;
return EXIT_FAILURE;
}
resize(frameOrig, frame, Size(conf.frameWidth, conf.frameHeight));
cvtColor(frame, result, CV_GRAY2RGB);//作者读进来的时候是灰度图像,为了显示转换成3通道都是灰度图
if (frameInd == startFrame)//如果是第一帧,初始化
{
tracker.Initialise(frame, initBB);
}
}
if (tracker.IsInitialised())//如果初始化了,就开始跟踪
{
tracker.Track(frame);//跟踪程序,把tracker当做一个类来对待,很清晰明了啊,赞一个;算法都在这里面实现
if (!conf.quietMode && conf.debugMode)
{
tracker.Debug();//debug模式下,可以开启很多额外的窗口显示
}
rectangle(result, tracker.GetBB(), CV_RGB(0, 255, 0));//使用绿色框,画出跟踪的效果
if (outFile)//这里是得到的矩形框,存储到txt文本中
{
const FloatRect& bb = tracker.GetBB();
outFile << bb.XMin() / scaleW << "," << bb.YMin() / scaleH << "," << bb.Width() / scaleW << "," << bb.Height() / scaleH << flush << endl;
cout << "cout to file: " << bb.XMin() / scaleW << "," << bb.YMin() / scaleH << "," << bb.Width() / scaleW << "," << bb.Height() / scaleH << endl;
}
}
if (!conf.quietMode)//如果使用的是摄像头,作者提供了几个按键来选择是否初始化,我用的是OTB数据集,就不管这个了
{
imshow("result", result);
int key = waitKey(paused ? 0 : 1);
if (key != -1)
{
if (key == 27 || key == 113) // esc q
{
break;
}
else if (key == 112) // p
{
paused = !paused;
}
else if (key == 105 && useCamera)//i
{
doInitialise = true;
cout << "initialised !" << endl;//qyy
}
}
if (conf.debugMode && frameInd == endFrame)
{
cout << "\n\nend of sequence, press any key to exit" << endl;
//waitKey();
}
}
}
if (outFile.is_open())
{
outFile.close();
}
return EXIT_SUCCESS;
}
所以,后面我主要关注tracker这个类做了什么,我们看到在main.cpp中调用了tracker.Initialize Debug Track这几个成员函数,所以这几个函数是作者算法实现的关键。