370 lines
10 KiB
C++
370 lines
10 KiB
C++
|
// This file is part of OpenCV project.
|
||
|
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||
|
// of this distribution and at http://opencv.org/license.html.
|
||
|
|
||
|
/*
|
||
|
* The Evaluation Methodologies are partially based on:
|
||
|
* ====================================================================================================================
|
||
|
* [OTB] Y. Wu, J. Lim, and M.-H. Yang, "Online object tracking: A benchmark," in Computer Vision and Pattern Recognition (CVPR), 2013
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
enum BBTransformations
|
||
|
{
|
||
|
NoTransform = 0,
|
||
|
CenterShiftLeft = 1,
|
||
|
CenterShiftRight = 2,
|
||
|
CenterShiftUp = 3,
|
||
|
CenterShiftDown = 4,
|
||
|
CornerShiftTopLeft = 5,
|
||
|
CornerShiftTopRight = 6,
|
||
|
CornerShiftBottomLeft = 7,
|
||
|
CornerShiftBottomRight = 8,
|
||
|
Scale_0_8 = 9,
|
||
|
Scale_0_9 = 10,
|
||
|
Scale_1_1 = 11,
|
||
|
Scale_1_2 = 12
|
||
|
};
|
||
|
|
||
|
namespace {
|
||
|
|
||
|
std::vector<std::string> splitString(const std::string& s_, const std::string& delimiter)
|
||
|
{
|
||
|
std::string s = s_;
|
||
|
std::vector<string> token;
|
||
|
size_t pos = 0;
|
||
|
while ((pos = s.find(delimiter)) != std::string::npos)
|
||
|
{
|
||
|
token.push_back(s.substr(0, pos));
|
||
|
s.erase(0, pos + delimiter.length());
|
||
|
}
|
||
|
token.push_back(s);
|
||
|
return token;
|
||
|
}
|
||
|
|
||
|
float calcDistance(const Rect& a, const Rect& b)
|
||
|
{
|
||
|
Point2f p_a((float)(a.x + a.width / 2), (float)(a.y + a.height / 2));
|
||
|
Point2f p_b((float)(b.x + b.width / 2), (float)(b.y + b.height / 2));
|
||
|
Point2f diff = p_a - p_b;
|
||
|
return sqrt(diff.dot(diff));
|
||
|
}
|
||
|
|
||
|
float calcOverlap(const Rect& a, const Rect& b)
|
||
|
{
|
||
|
float rectIntersectionArea = (float)(a & b).area();
|
||
|
return rectIntersectionArea / (a.area() + b.area() - rectIntersectionArea);
|
||
|
}
|
||
|
|
||
|
} // namespace
|
||
|
|
||
|
template <typename Tracker, typename ROI_t = Rect2d>
|
||
|
class TrackerTest
|
||
|
{
|
||
|
public:
|
||
|
TrackerTest(const Ptr<Tracker>& tracker, const string& video, float distanceThreshold,
|
||
|
float overlapThreshold, int shift = NoTransform, int segmentIdx = 1, int numSegments = 10);
|
||
|
~TrackerTest() {}
|
||
|
void run();
|
||
|
|
||
|
protected:
|
||
|
void checkDataTest();
|
||
|
|
||
|
void distanceAndOverlapTest();
|
||
|
|
||
|
Ptr<Tracker> tracker;
|
||
|
string video;
|
||
|
std::vector<Rect> bbs;
|
||
|
int startFrame;
|
||
|
string suffix;
|
||
|
string prefix;
|
||
|
float overlapThreshold;
|
||
|
float distanceThreshold;
|
||
|
int segmentIdx;
|
||
|
int shift;
|
||
|
int numSegments;
|
||
|
|
||
|
int gtStartFrame;
|
||
|
int endFrame;
|
||
|
vector<int> validSequence;
|
||
|
|
||
|
private:
|
||
|
Rect applyShift(const Rect& bb);
|
||
|
};
|
||
|
|
||
|
template <typename Tracker, typename ROI_t>
|
||
|
TrackerTest<Tracker, ROI_t>::TrackerTest(const Ptr<Tracker>& _tracker, const string& _video, float _distanceThreshold,
|
||
|
float _overlapThreshold, int _shift, int _segmentIdx, int _numSegments)
|
||
|
: tracker(_tracker)
|
||
|
, video(_video)
|
||
|
, overlapThreshold(_overlapThreshold)
|
||
|
, distanceThreshold(_distanceThreshold)
|
||
|
, segmentIdx(_segmentIdx)
|
||
|
, shift(_shift)
|
||
|
, numSegments(_numSegments)
|
||
|
{
|
||
|
// nothing
|
||
|
}
|
||
|
|
||
|
template <typename Tracker, typename ROI_t>
|
||
|
Rect TrackerTest<Tracker, ROI_t>::applyShift(const Rect& bb_)
|
||
|
{
|
||
|
Rect bb = bb_;
|
||
|
Point center(bb.x + (bb.width / 2), bb.y + (bb.height / 2));
|
||
|
|
||
|
int xLimit = bb.x + bb.width - 1;
|
||
|
int yLimit = bb.y + bb.height - 1;
|
||
|
|
||
|
int h = 0;
|
||
|
int w = 0;
|
||
|
float ratio = 1.0;
|
||
|
|
||
|
switch (shift)
|
||
|
{
|
||
|
case CenterShiftLeft:
|
||
|
bb.x = bb.x - (int)ceil(0.1 * bb.width);
|
||
|
break;
|
||
|
case CenterShiftRight:
|
||
|
bb.x = bb.x + (int)ceil(0.1 * bb.width);
|
||
|
break;
|
||
|
case CenterShiftUp:
|
||
|
bb.y = bb.y - (int)ceil(0.1 * bb.height);
|
||
|
break;
|
||
|
case CenterShiftDown:
|
||
|
bb.y = bb.y + (int)ceil(0.1 * bb.height);
|
||
|
break;
|
||
|
case CornerShiftTopLeft:
|
||
|
bb.x = (int)cvRound(bb.x - 0.1 * bb.width);
|
||
|
bb.y = (int)cvRound(bb.y - 0.1 * bb.height);
|
||
|
|
||
|
bb.width = xLimit - bb.x + 1;
|
||
|
bb.height = yLimit - bb.y + 1;
|
||
|
break;
|
||
|
case CornerShiftTopRight:
|
||
|
xLimit = (int)cvRound(xLimit + 0.1 * bb.width);
|
||
|
|
||
|
bb.y = (int)cvRound(bb.y - 0.1 * bb.height);
|
||
|
bb.width = xLimit - bb.x + 1;
|
||
|
bb.height = yLimit - bb.y + 1;
|
||
|
break;
|
||
|
case CornerShiftBottomLeft:
|
||
|
bb.x = (int)cvRound(bb.x - 0.1 * bb.width);
|
||
|
yLimit = (int)cvRound(yLimit + 0.1 * bb.height);
|
||
|
|
||
|
bb.width = xLimit - bb.x + 1;
|
||
|
bb.height = yLimit - bb.y + 1;
|
||
|
break;
|
||
|
case CornerShiftBottomRight:
|
||
|
xLimit = (int)cvRound(xLimit + 0.1 * bb.width);
|
||
|
yLimit = (int)cvRound(yLimit + 0.1 * bb.height);
|
||
|
|
||
|
bb.width = xLimit - bb.x + 1;
|
||
|
bb.height = yLimit - bb.y + 1;
|
||
|
break;
|
||
|
case Scale_0_8:
|
||
|
ratio = 0.8f;
|
||
|
w = (int)(ratio * bb.width);
|
||
|
h = (int)(ratio * bb.height);
|
||
|
|
||
|
bb = Rect(center.x - (w / 2), center.y - (h / 2), w, h);
|
||
|
break;
|
||
|
case Scale_0_9:
|
||
|
ratio = 0.9f;
|
||
|
w = (int)(ratio * bb.width);
|
||
|
h = (int)(ratio * bb.height);
|
||
|
|
||
|
bb = Rect(center.x - (w / 2), center.y - (h / 2), w, h);
|
||
|
break;
|
||
|
case 11:
|
||
|
//scale 1.1
|
||
|
ratio = 1.1f;
|
||
|
w = (int)(ratio * bb.width);
|
||
|
h = (int)(ratio * bb.height);
|
||
|
|
||
|
bb = Rect(center.x - (w / 2), center.y - (h / 2), w, h);
|
||
|
break;
|
||
|
case 12:
|
||
|
//scale 1.2
|
||
|
ratio = 1.2f;
|
||
|
w = (int)(ratio * bb.width);
|
||
|
h = (int)(ratio * bb.height);
|
||
|
|
||
|
bb = Rect(center.x - (w / 2), center.y - (h / 2), w, h);
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return bb;
|
||
|
}
|
||
|
|
||
|
template <typename Tracker, typename ROI_t>
|
||
|
void TrackerTest<Tracker, ROI_t>::distanceAndOverlapTest()
|
||
|
{
|
||
|
bool initialized = false;
|
||
|
|
||
|
int fc = (startFrame - gtStartFrame);
|
||
|
|
||
|
bbs.at(fc) = applyShift(bbs.at(fc));
|
||
|
Rect currentBBi = bbs.at(fc);
|
||
|
ROI_t currentBB(currentBBi);
|
||
|
float sumDistance = 0;
|
||
|
float sumOverlap = 0;
|
||
|
|
||
|
string folder = cvtest::TS::ptr()->get_data_path() + "/" + TRACKING_DIR + "/" + video + "/" + FOLDER_IMG;
|
||
|
string videoPath = folder + "/" + video + ".webm";
|
||
|
|
||
|
VideoCapture c;
|
||
|
c.open(videoPath);
|
||
|
if (!c.isOpened())
|
||
|
throw SkipTestException("Can't open video file");
|
||
|
#if 0
|
||
|
c.set(CAP_PROP_POS_FRAMES, startFrame);
|
||
|
#else
|
||
|
if (startFrame)
|
||
|
std::cout << "startFrame = " << startFrame << std::endl;
|
||
|
for (int i = 0; i < startFrame; i++)
|
||
|
{
|
||
|
Mat dummy_frame;
|
||
|
c >> dummy_frame;
|
||
|
ASSERT_FALSE(dummy_frame.empty()) << i << ": " << videoPath;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
for (int frameCounter = startFrame; frameCounter < endFrame; frameCounter++)
|
||
|
{
|
||
|
Mat frame;
|
||
|
c >> frame;
|
||
|
|
||
|
ASSERT_FALSE(frame.empty()) << "frameCounter=" << frameCounter << " video=" << videoPath;
|
||
|
if (!initialized)
|
||
|
{
|
||
|
tracker->init(frame, currentBB);
|
||
|
std::cout << "frame size = " << frame.size() << std::endl;
|
||
|
initialized = true;
|
||
|
}
|
||
|
else if (initialized)
|
||
|
{
|
||
|
if (frameCounter >= (int)bbs.size())
|
||
|
break;
|
||
|
tracker->update(frame, currentBB);
|
||
|
}
|
||
|
float curDistance = calcDistance(currentBB, bbs.at(fc));
|
||
|
float curOverlap = calcOverlap(currentBB, bbs.at(fc));
|
||
|
|
||
|
#ifdef DEBUG_TEST
|
||
|
Mat result;
|
||
|
repeat(frame, 1, 2, result);
|
||
|
rectangle(result, currentBB, Scalar(0, 255, 0), 1);
|
||
|
Rect roi2(frame.cols, 0, frame.cols, frame.rows);
|
||
|
rectangle(result(roi2), bbs.at(fc), Scalar(0, 0, 255), 1);
|
||
|
imshow("result", result);
|
||
|
waitKey(1);
|
||
|
#endif
|
||
|
|
||
|
sumDistance += curDistance;
|
||
|
sumOverlap += curOverlap;
|
||
|
fc++;
|
||
|
}
|
||
|
|
||
|
float meanDistance = sumDistance / (endFrame - startFrame);
|
||
|
float meanOverlap = sumOverlap / (endFrame - startFrame);
|
||
|
|
||
|
EXPECT_LE(meanDistance, distanceThreshold);
|
||
|
EXPECT_GE(meanOverlap, overlapThreshold);
|
||
|
}
|
||
|
|
||
|
template <typename Tracker, typename ROI_t>
|
||
|
void TrackerTest<Tracker, ROI_t>::checkDataTest()
|
||
|
{
|
||
|
|
||
|
FileStorage fs;
|
||
|
fs.open(cvtest::TS::ptr()->get_data_path() + TRACKING_DIR + "/" + video + "/" + video + ".yml", FileStorage::READ);
|
||
|
fs["start"] >> startFrame;
|
||
|
fs["prefix"] >> prefix;
|
||
|
fs["suffix"] >> suffix;
|
||
|
fs.release();
|
||
|
|
||
|
string gtFile = cvtest::TS::ptr()->get_data_path() + TRACKING_DIR + "/" + video + "/gt.txt";
|
||
|
std::ifstream gt;
|
||
|
//open the ground truth
|
||
|
gt.open(gtFile.c_str());
|
||
|
ASSERT_TRUE(gt.is_open()) << gtFile;
|
||
|
string line;
|
||
|
int bbCounter = 0;
|
||
|
while (getline(gt, line))
|
||
|
{
|
||
|
bbCounter++;
|
||
|
}
|
||
|
gt.close();
|
||
|
|
||
|
int seqLength = bbCounter;
|
||
|
for (int i = startFrame; i < seqLength; i++)
|
||
|
{
|
||
|
validSequence.push_back(i);
|
||
|
}
|
||
|
|
||
|
//exclude from the images sequence, the frames where the target is occluded or out of view
|
||
|
string omitFile = cvtest::TS::ptr()->get_data_path() + TRACKING_DIR + "/" + video + "/" + FOLDER_OMIT_INIT + "/" + video + ".txt";
|
||
|
std::ifstream omit;
|
||
|
omit.open(omitFile.c_str());
|
||
|
if (omit.is_open())
|
||
|
{
|
||
|
string omitLine;
|
||
|
while (getline(omit, omitLine))
|
||
|
{
|
||
|
vector<string> tokens = splitString(omitLine, " ");
|
||
|
int s_start = atoi(tokens.at(0).c_str());
|
||
|
int s_end = atoi(tokens.at(1).c_str());
|
||
|
for (int k = s_start; k <= s_end; k++)
|
||
|
{
|
||
|
std::vector<int>::iterator position = std::find(validSequence.begin(), validSequence.end(), k);
|
||
|
if (position != validSequence.end())
|
||
|
validSequence.erase(position);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
omit.close();
|
||
|
gtStartFrame = startFrame;
|
||
|
//compute the start and the and for each segment
|
||
|
int numFrame = (int)(validSequence.size() / numSegments);
|
||
|
startFrame += (segmentIdx - 1) * numFrame;
|
||
|
endFrame = startFrame + numFrame;
|
||
|
|
||
|
std::ifstream gt2;
|
||
|
//open the ground truth
|
||
|
gt2.open(gtFile.c_str());
|
||
|
ASSERT_TRUE(gt2.is_open()) << gtFile;
|
||
|
string line2;
|
||
|
int bbCounter2 = 0;
|
||
|
while (getline(gt2, line2))
|
||
|
{
|
||
|
vector<string> tokens = splitString(line2, ",");
|
||
|
Rect bb(atoi(tokens.at(0).c_str()), atoi(tokens.at(1).c_str()), atoi(tokens.at(2).c_str()), atoi(tokens.at(3).c_str()));
|
||
|
ASSERT_EQ((size_t)4, tokens.size()) << "Incorrect ground truth file " << gtFile;
|
||
|
|
||
|
bbs.push_back(bb);
|
||
|
bbCounter2++;
|
||
|
}
|
||
|
gt2.close();
|
||
|
|
||
|
if (segmentIdx == numSegments)
|
||
|
endFrame = (int)bbs.size();
|
||
|
}
|
||
|
|
||
|
template <typename Tracker, typename ROI_t>
|
||
|
void TrackerTest<Tracker, ROI_t>::run()
|
||
|
{
|
||
|
srand(1); // FIXIT remove that, ensure that there is no "rand()" in implementation
|
||
|
|
||
|
ASSERT_TRUE(tracker);
|
||
|
|
||
|
checkDataTest();
|
||
|
|
||
|
//check for failure
|
||
|
if (::testing::Test::HasFatalFailure())
|
||
|
return;
|
||
|
|
||
|
distanceAndOverlapTest();
|
||
|
}
|