vendor: OpenCV 5.0.0 snapshot at 755e50675d97db9b7d449d8bd6b09888646f6c6e
This commit is contained in:
@@ -0,0 +1,357 @@
|
||||
#include "opencv2/core/utility.hpp"
|
||||
#include "opencv2/highgui.hpp"
|
||||
#include "opencv2/tracking.hpp"
|
||||
#include "opencv2/videoio.hpp"
|
||||
#include "opencv2/plot.hpp"
|
||||
#include "samples_utility.hpp"
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
using namespace cv;
|
||||
|
||||
// TODO: do normalization ala Kalal's assessment protocol for TLD
|
||||
|
||||
static const Scalar gtColor = Scalar(0, 255, 0);
|
||||
|
||||
static Scalar getNextColor()
|
||||
{
|
||||
const int num = 6;
|
||||
static Scalar colors[num] = {Scalar(160, 0, 0), Scalar(0, 0, 160), Scalar(0, 160, 160),
|
||||
Scalar(160, 160, 0), Scalar(160, 0, 160), Scalar(20, 50, 160)};
|
||||
static int id = 0;
|
||||
return colors[id < num ? id++ : num - 1];
|
||||
}
|
||||
|
||||
inline vector<Rect2d> readGT(const string &filename, const string &omitname)
|
||||
{
|
||||
vector<Rect2d> res;
|
||||
{
|
||||
ifstream input(filename.c_str());
|
||||
if (!input.is_open())
|
||||
CV_Error(Error::StsError, "Failed to open file");
|
||||
while (input)
|
||||
{
|
||||
Rect2d one;
|
||||
input >> one.x;
|
||||
input.ignore(numeric_limits<std::streamsize>::max(), ',');
|
||||
input >> one.y;
|
||||
input.ignore(numeric_limits<std::streamsize>::max(), ',');
|
||||
input >> one.width;
|
||||
input.ignore(numeric_limits<std::streamsize>::max(), ',');
|
||||
input >> one.height;
|
||||
input.ignore(numeric_limits<std::streamsize>::max(), '\n');
|
||||
if (input.good())
|
||||
res.push_back(one);
|
||||
}
|
||||
}
|
||||
if (!omitname.empty())
|
||||
{
|
||||
ifstream input(omitname.c_str());
|
||||
if (!input.is_open())
|
||||
CV_Error(Error::StsError, "Failed to open file");
|
||||
while (input)
|
||||
{
|
||||
unsigned int a = 0, b = 0;
|
||||
input >> a >> b;
|
||||
input.ignore(numeric_limits<std::streamsize>::max(), '\n');
|
||||
if (a > 0 && b > 0 && a < res.size() && b < res.size())
|
||||
{
|
||||
if (a > b)
|
||||
swap(a, b);
|
||||
for (vector<Rect2d>::iterator i = res.begin() + a; i != res.begin() + b; ++i)
|
||||
{
|
||||
*i = Rect2d();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
inline bool isGoodBox(const Rect2d &box) { return box.width > 0. && box.height > 0.; }
|
||||
const int LTRC_COUNT = 100;
|
||||
|
||||
struct AlgoWrap
|
||||
{
|
||||
AlgoWrap(const string &name_)
|
||||
: lastState(NotFound), name(name_), color(getNextColor()),
|
||||
numTotal(0), numResponse(0), numPresent(0), numCorrect_0(0), numCorrect_0_5(0),
|
||||
timeTotal(0), auc(LTRC_COUNT + 1, 0)
|
||||
{
|
||||
tracker = createTrackerByName(name);
|
||||
}
|
||||
|
||||
enum State
|
||||
{
|
||||
NotFound,
|
||||
Overlap_None,
|
||||
Overlap_0,
|
||||
Overlap_0_5,
|
||||
};
|
||||
|
||||
Ptr<Tracker> tracker;
|
||||
bool lastRes;
|
||||
Rect lastBox;
|
||||
State lastState;
|
||||
|
||||
// visual
|
||||
string name;
|
||||
Scalar color;
|
||||
|
||||
// results
|
||||
int numTotal; // frames passed to tracker
|
||||
int numResponse; // frames where tracker had response
|
||||
int numPresent; // frames where ground truth result present
|
||||
int numCorrect_0; // frames where overlap with GT > 0
|
||||
int numCorrect_0_5; // frames where overlap with GT > 0.5
|
||||
int64 timeTotal; // ticks
|
||||
vector<int> auc; // number of frames for each overlap percent
|
||||
|
||||
void eval(const Mat &frame, const Rect2d >Box, bool isVerbose)
|
||||
{
|
||||
// RUN
|
||||
lastBox = Rect();
|
||||
int64 frameTime = getTickCount();
|
||||
lastRes = tracker->update(frame, lastBox);
|
||||
frameTime = getTickCount() - frameTime;
|
||||
|
||||
// RESULTS
|
||||
double intersectArea = (gtBox & (Rect2d)lastBox).area();
|
||||
double unionArea = (gtBox | (Rect2d)lastBox).area();
|
||||
numTotal++;
|
||||
numResponse += (lastRes && isGoodBox(lastBox)) ? 1 : 0;
|
||||
numPresent += isGoodBox(gtBox) ? 1 : 0;
|
||||
double overlap = unionArea > 0. ? intersectArea / unionArea : 0.;
|
||||
numCorrect_0 += overlap > 0. ? 1 : 0;
|
||||
numCorrect_0_5 += overlap > 0.5 ? 1 : 0;
|
||||
auc[std::min(std::max((size_t)(overlap * LTRC_COUNT), (size_t)0), (size_t)LTRC_COUNT)]++;
|
||||
timeTotal += frameTime;
|
||||
|
||||
if (isVerbose)
|
||||
cout << name << " - " << overlap << endl;
|
||||
|
||||
if (isGoodBox(gtBox) != isGoodBox(lastBox)) lastState = NotFound;
|
||||
else if (overlap > 0.5) lastState = Overlap_0_5;
|
||||
else if (overlap > 0.0001) lastState = Overlap_0;
|
||||
else lastState = Overlap_None;
|
||||
}
|
||||
|
||||
void draw(Mat &image, const Point &textPoint) const
|
||||
{
|
||||
if (lastRes)
|
||||
rectangle(image, lastBox, color, 2, LINE_8);
|
||||
string suf;
|
||||
switch (lastState)
|
||||
{
|
||||
case AlgoWrap::NotFound: suf = " X"; break;
|
||||
case AlgoWrap::Overlap_None: suf = " ~"; break;
|
||||
case AlgoWrap::Overlap_0: suf = " +"; break;
|
||||
case AlgoWrap::Overlap_0_5: suf = " ++"; break;
|
||||
}
|
||||
putText(image, name + suf, textPoint, FONT_HERSHEY_PLAIN, 1, color, 1, LINE_AA);
|
||||
}
|
||||
|
||||
// calculates "lost track ratio" curve - row of values growing from 0 to 1
|
||||
// number of elements is LTRC_COUNT + 2
|
||||
Mat getLTRC() const
|
||||
{
|
||||
Mat t, res;
|
||||
Mat(auc).convertTo(t, CV_64F); // integral does not support CV_32S input
|
||||
integral(t.t(), res, CV_64F); // t is a column of values
|
||||
return res.row(1) / (double)numTotal;
|
||||
}
|
||||
|
||||
void plotLTRC(Mat &img) const
|
||||
{
|
||||
Ptr<plot::Plot2d> p_ = plot::Plot2d::create(getLTRC());
|
||||
p_->render(img);
|
||||
}
|
||||
|
||||
double calcAUC() const
|
||||
{
|
||||
return cv::sum(getLTRC())[0] / (double)LTRC_COUNT;
|
||||
}
|
||||
|
||||
void stat(ostream &out) const
|
||||
{
|
||||
out << name << endl;
|
||||
out << setw(20) << "Overlap > 0 " << setw(20) << (double)numCorrect_0 / numTotal * 100
|
||||
<< "%" << setw(20) << numCorrect_0 << endl;
|
||||
out << setw(20) << "Overlap > 0.5" << setw(20) << (double)numCorrect_0_5 / numTotal * 100
|
||||
<< "%" << setw(20) << numCorrect_0_5 << endl;
|
||||
|
||||
double p = (double)numCorrect_0_5 / numResponse;
|
||||
double r = (double)numCorrect_0_5 / numPresent;
|
||||
double f = 2 * p * r / (p + r);
|
||||
out << setw(20) << "Precision" << setw(20) << p * 100 << "%" << endl;
|
||||
out << setw(20) << "Recall " << setw(20) << r * 100 << "%" << endl;
|
||||
out << setw(20) << "f-measure" << setw(20) << f * 100 << "%" << endl;
|
||||
out << setw(20) << "AUC" << setw(20) << calcAUC() << endl;
|
||||
|
||||
double s = (timeTotal / getTickFrequency()) / numTotal;
|
||||
out << setw(20) << "Performance" << setw(20) << s * 1000 << " ms/frame" << setw(20) << 1 / s
|
||||
<< " fps" << endl;
|
||||
}
|
||||
};
|
||||
|
||||
inline ostream &operator<<(ostream &out, const AlgoWrap &w) { w.stat(out); return out; }
|
||||
|
||||
inline vector<AlgoWrap> initAlgorithms(const string &algList)
|
||||
{
|
||||
vector<AlgoWrap> res;
|
||||
istringstream input(algList);
|
||||
for (;;)
|
||||
{
|
||||
char one[30];
|
||||
input.getline(one, 30, ',');
|
||||
if (!input)
|
||||
break;
|
||||
cout << " " << one << " - ";
|
||||
AlgoWrap a(one);
|
||||
if (a.tracker)
|
||||
{
|
||||
res.push_back(a);
|
||||
cout << "OK";
|
||||
}
|
||||
else
|
||||
{
|
||||
cout << "FAILED";
|
||||
}
|
||||
cout << endl;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static const string &window = "Tracking API";
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const string keys =
|
||||
"{help h||show help}"
|
||||
"{video||video file to process}"
|
||||
"{gt||ground truth file (each line describes rectangle in format: '<x>,<y>,<w>,<h>')}"
|
||||
"{start|0|starting frame}"
|
||||
"{num|0|frame number (0 for all)}"
|
||||
"{omit||file with omit ranges (each line describes occluded frames: '<start> <end>')}"
|
||||
"{plot|false|plot LTR curves at the end}"
|
||||
"{v|false|print each frame info}"
|
||||
"{@algos||comma-separated algorithm names}";
|
||||
CommandLineParser p(argc, argv, keys);
|
||||
if (p.has("help"))
|
||||
{
|
||||
p.printMessage();
|
||||
return 0;
|
||||
}
|
||||
int startFrame = p.get<int>("start");
|
||||
int frameCount = p.get<int>("num");
|
||||
string videoFile = p.get<string>("video");
|
||||
string gtFile = p.get<string>("gt");
|
||||
string omitFile = p.get<string>("omit");
|
||||
string algList = p.get<string>("@algos");
|
||||
bool doPlot = p.get<bool>("plot");
|
||||
bool isVerbose = p.get<bool>("v");
|
||||
if (!p.check())
|
||||
{
|
||||
p.printErrors();
|
||||
return 0;
|
||||
}
|
||||
|
||||
cout << "Reading GT from " << gtFile << " ... ";
|
||||
vector<Rect2d> gt = readGT(gtFile, omitFile);
|
||||
if (gt.empty())
|
||||
CV_Error(Error::StsError, "Failed to read GT file");
|
||||
cout << gt.size() << " boxes" << endl;
|
||||
|
||||
cout << "Opening video " << videoFile << " ... ";
|
||||
VideoCapture cap;
|
||||
cap.open(videoFile);
|
||||
if (!cap.isOpened())
|
||||
CV_Error(Error::StsError, "Failed to open video file");
|
||||
cap.set(CAP_PROP_POS_FRAMES, startFrame);
|
||||
cout << "at frame " << startFrame << endl;
|
||||
|
||||
// INIT
|
||||
vector<AlgoWrap> algos = initAlgorithms(algList);
|
||||
Mat frame, image;
|
||||
cap >> frame;
|
||||
for (vector<AlgoWrap>::iterator i = algos.begin(); i != algos.end(); ++i)
|
||||
i->tracker->init(frame, gt[0]);
|
||||
|
||||
// DRAW
|
||||
{
|
||||
namedWindow(window, WINDOW_AUTOSIZE);
|
||||
frame.copyTo(image);
|
||||
rectangle(image, gt[0], gtColor, 2, LINE_8);
|
||||
imshow(window, image);
|
||||
}
|
||||
|
||||
bool paused = false;
|
||||
int frameId = 0;
|
||||
cout << "Hot keys:" << endl << " q - exit" << endl << " p - pause" << endl;
|
||||
for (;;)
|
||||
{
|
||||
if (!paused)
|
||||
{
|
||||
cap >> frame;
|
||||
if (frame.empty())
|
||||
{
|
||||
cout << "Done - video end" << endl;
|
||||
break;
|
||||
}
|
||||
frameId++;
|
||||
if (isVerbose)
|
||||
cout << endl << "Frame " << frameId << endl;
|
||||
// EVAL
|
||||
for (vector<AlgoWrap>::iterator i = algos.begin(); i != algos.end(); ++i)
|
||||
i->eval(frame, gt[frameId], isVerbose);
|
||||
// DRAW
|
||||
{
|
||||
Point textPoint(1, 16);
|
||||
frame.copyTo(image);
|
||||
rectangle(image, gt[frameId], gtColor, 2, LINE_8);
|
||||
putText(image, "GROUND TRUTH", textPoint, FONT_HERSHEY_PLAIN, 1, gtColor, 1, LINE_AA);
|
||||
for (vector<AlgoWrap>::iterator i = algos.begin(); i != algos.end(); ++i)
|
||||
{
|
||||
textPoint.y += 14;
|
||||
i->draw(image, textPoint);
|
||||
}
|
||||
imshow(window, image);
|
||||
}
|
||||
}
|
||||
|
||||
char c = (char)waitKey(1);
|
||||
if (c == 'q')
|
||||
{
|
||||
cout << "Done - manual exit" << endl;
|
||||
break;
|
||||
}
|
||||
else if (c == 'p')
|
||||
{
|
||||
paused = !paused;
|
||||
}
|
||||
if (frameCount && frameId >= frameCount)
|
||||
{
|
||||
cout << "Done - max frame count" << endl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// STAT
|
||||
for (vector<AlgoWrap>::iterator i = algos.begin(); i != algos.end(); ++i)
|
||||
cout << "==========" << endl << *i << endl;
|
||||
|
||||
if (doPlot)
|
||||
{
|
||||
Mat img(300, 300, CV_8UC3);
|
||||
for (vector<AlgoWrap>::iterator i = algos.begin(); i != algos.end(); ++i)
|
||||
{
|
||||
i->plotLTRC(img);
|
||||
imshow("LTR curve for " + i->name, img);
|
||||
}
|
||||
waitKey(0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user