1 // This file is part of OpenCV project.
2 // It is subject to the license terms in the LICENSE file found in the top-level directory
3 // of this distribution and at http://opencv.org/license.html.
4 
5 /*
6  * The Evaluation Methodologies are partially based on:
7  * ====================================================================================================================
8  *  [OTB] Y. Wu, J. Lim, and M.-H. Yang, "Online object tracking: A benchmark," in Computer Vision and Pattern Recognition (CVPR), 2013
9  *
10  */
11 
12 enum BBTransformations
13 {
14     NoTransform = 0,
15     CenterShiftLeft = 1,
16     CenterShiftRight = 2,
17     CenterShiftUp = 3,
18     CenterShiftDown = 4,
19     CornerShiftTopLeft = 5,
20     CornerShiftTopRight = 6,
21     CornerShiftBottomLeft = 7,
22     CornerShiftBottomRight = 8,
23     Scale_0_8 = 9,
24     Scale_0_9 = 10,
25     Scale_1_1 = 11,
26     Scale_1_2 = 12
27 };
28 
29 namespace {
30 
splitString(const std::string & s_,const std::string & delimiter)31 std::vector<std::string> splitString(const std::string& s_, const std::string& delimiter)
32 {
33     std::string s = s_;
34     std::vector<string> token;
35     size_t pos = 0;
36     while ((pos = s.find(delimiter)) != std::string::npos)
37     {
38         token.push_back(s.substr(0, pos));
39         s.erase(0, pos + delimiter.length());
40     }
41     token.push_back(s);
42     return token;
43 }
44 
calcDistance(const Rect & a,const Rect & b)45 float calcDistance(const Rect& a, const Rect& b)
46 {
47     Point2f p_a((float)(a.x + a.width / 2), (float)(a.y + a.height / 2));
48     Point2f p_b((float)(b.x + b.width / 2), (float)(b.y + b.height / 2));
49     return sqrt(pow(p_a.x - p_b.x, 2) + pow(p_a.y - p_b.y, 2));
50 }
51 
calcOverlap(const Rect & a,const Rect & b)52 float calcOverlap(const Rect& a, const Rect& b)
53 {
54     float rectIntersectionArea = (float)(a & b).area();
55     return rectIntersectionArea / (a.area() + b.area() - rectIntersectionArea);
56 }
57 
58 }  // namespace
59 
60 template <typename Tracker, typename ROI_t = Rect2d>
61 class TrackerTest
62 {
63 public:
64     TrackerTest(const Ptr<Tracker>& tracker, const string& video, float distanceThreshold,
65             float overlapThreshold, int shift = NoTransform, int segmentIdx = 1, int numSegments = 10);
~TrackerTest()66     ~TrackerTest() {}
67     void run();
68 
69 protected:
70     void checkDataTest();
71 
72     void distanceAndOverlapTest();
73 
74     Ptr<Tracker> tracker;
75     string video;
76     std::vector<Rect> bbs;
77     int startFrame;
78     string suffix;
79     string prefix;
80     float overlapThreshold;
81     float distanceThreshold;
82     int segmentIdx;
83     int shift;
84     int numSegments;
85 
86     int gtStartFrame;
87     int endFrame;
88     vector<int> validSequence;
89 
90 private:
91     Rect applyShift(const Rect& bb);
92 };
93 
94 template <typename Tracker, typename ROI_t>
TrackerTest(const Ptr<Tracker> & _tracker,const string & _video,float _distanceThreshold,float _overlapThreshold,int _shift,int _segmentIdx,int _numSegments)95 TrackerTest<Tracker, ROI_t>::TrackerTest(const Ptr<Tracker>& _tracker, const string& _video, float _distanceThreshold,
96         float _overlapThreshold, int _shift, int _segmentIdx, int _numSegments)
97     : tracker(_tracker)
98     , video(_video)
99     , overlapThreshold(_overlapThreshold)
100     , distanceThreshold(_distanceThreshold)
101     , segmentIdx(_segmentIdx)
102     , shift(_shift)
103     , numSegments(_numSegments)
104 {
105     // nothing
106 }
107 
108 template <typename Tracker, typename ROI_t>
applyShift(const Rect & bb_)109 Rect TrackerTest<Tracker, ROI_t>::applyShift(const Rect& bb_)
110 {
111     Rect bb = bb_;
112     Point center(bb.x + (bb.width / 2), bb.y + (bb.height / 2));
113 
114     int xLimit = bb.x + bb.width - 1;
115     int yLimit = bb.y + bb.height - 1;
116 
117     int h = 0;
118     int w = 0;
119     float ratio = 1.0;
120 
121     switch (shift)
122     {
123     case CenterShiftLeft:
124         bb.x = bb.x - (int)ceil(0.1 * bb.width);
125         break;
126     case CenterShiftRight:
127         bb.x = bb.x + (int)ceil(0.1 * bb.width);
128         break;
129     case CenterShiftUp:
130         bb.y = bb.y - (int)ceil(0.1 * bb.height);
131         break;
132     case CenterShiftDown:
133         bb.y = bb.y + (int)ceil(0.1 * bb.height);
134         break;
135     case CornerShiftTopLeft:
136         bb.x = (int)cvRound(bb.x - 0.1 * bb.width);
137         bb.y = (int)cvRound(bb.y - 0.1 * bb.height);
138 
139         bb.width = xLimit - bb.x + 1;
140         bb.height = yLimit - bb.y + 1;
141         break;
142     case CornerShiftTopRight:
143         xLimit = (int)cvRound(xLimit + 0.1 * bb.width);
144 
145         bb.y = (int)cvRound(bb.y - 0.1 * bb.height);
146         bb.width = xLimit - bb.x + 1;
147         bb.height = yLimit - bb.y + 1;
148         break;
149     case CornerShiftBottomLeft:
150         bb.x = (int)cvRound(bb.x - 0.1 * bb.width);
151         yLimit = (int)cvRound(yLimit + 0.1 * bb.height);
152 
153         bb.width = xLimit - bb.x + 1;
154         bb.height = yLimit - bb.y + 1;
155         break;
156     case CornerShiftBottomRight:
157         xLimit = (int)cvRound(xLimit + 0.1 * bb.width);
158         yLimit = (int)cvRound(yLimit + 0.1 * bb.height);
159 
160         bb.width = xLimit - bb.x + 1;
161         bb.height = yLimit - bb.y + 1;
162         break;
163     case Scale_0_8:
164         ratio = 0.8f;
165         w = (int)(ratio * bb.width);
166         h = (int)(ratio * bb.height);
167 
168         bb = Rect(center.x - (w / 2), center.y - (h / 2), w, h);
169         break;
170     case Scale_0_9:
171         ratio = 0.9f;
172         w = (int)(ratio * bb.width);
173         h = (int)(ratio * bb.height);
174 
175         bb = Rect(center.x - (w / 2), center.y - (h / 2), w, h);
176         break;
177     case 11:
178         //scale 1.1
179         ratio = 1.1f;
180         w = (int)(ratio * bb.width);
181         h = (int)(ratio * bb.height);
182 
183         bb = Rect(center.x - (w / 2), center.y - (h / 2), w, h);
184         break;
185     case 12:
186         //scale 1.2
187         ratio = 1.2f;
188         w = (int)(ratio * bb.width);
189         h = (int)(ratio * bb.height);
190 
191         bb = Rect(center.x - (w / 2), center.y - (h / 2), w, h);
192         break;
193     default:
194         break;
195     }
196 
197     return bb;
198 }
199 
200 template <typename Tracker, typename ROI_t>
distanceAndOverlapTest()201 void TrackerTest<Tracker, ROI_t>::distanceAndOverlapTest()
202 {
203     bool initialized = false;
204 
205     int fc = (startFrame - gtStartFrame);
206 
207     bbs.at(fc) = applyShift(bbs.at(fc));
208     Rect currentBBi = bbs.at(fc);
209     ROI_t currentBB(currentBBi);
210     float sumDistance = 0;
211     float sumOverlap = 0;
212 
213     string folder = cvtest::TS::ptr()->get_data_path() + "/" + TRACKING_DIR + "/" + video + "/" + FOLDER_IMG;
214     string videoPath = folder + "/" + video + ".webm";
215 
216     VideoCapture c;
217     c.open(videoPath);
218     if (!c.isOpened())
219         throw SkipTestException("Can't open video file");
220 #if 0
221     c.set(CAP_PROP_POS_FRAMES, startFrame);
222 #else
223     if (startFrame)
224         std::cout << "startFrame = " << startFrame << std::endl;
225     for (int i = 0; i < startFrame; i++)
226     {
227         Mat dummy_frame;
228         c >> dummy_frame;
229         ASSERT_FALSE(dummy_frame.empty()) << i << ": " << videoPath;
230     }
231 #endif
232 
233     for (int frameCounter = startFrame; frameCounter < endFrame; frameCounter++)
234     {
235         Mat frame;
236         c >> frame;
237 
238         ASSERT_FALSE(frame.empty()) << "frameCounter=" << frameCounter << " video=" << videoPath;
239         if (!initialized)
240         {
241             tracker->init(frame, currentBB);
242             std::cout << "frame size = " << frame.size() << std::endl;
243             initialized = true;
244         }
245         else if (initialized)
246         {
247             if (frameCounter >= (int)bbs.size())
248                 break;
249             tracker->update(frame, currentBB);
250         }
251         float curDistance = calcDistance(currentBB, bbs.at(fc));
252         float curOverlap = calcOverlap(currentBB, bbs.at(fc));
253 
254 #ifdef DEBUG_TEST
255         Mat result;
256         repeat(frame, 1, 2, result);
257         rectangle(result, currentBB, Scalar(0, 255, 0), 1);
258         Rect roi2(frame.cols, 0, frame.cols, frame.rows);
259         rectangle(result(roi2), bbs.at(fc), Scalar(0, 0, 255), 1);
260         imshow("result", result);
261         waitKey(1);
262 #endif
263 
264         sumDistance += curDistance;
265         sumOverlap += curOverlap;
266         fc++;
267     }
268 
269     float meanDistance = sumDistance / (endFrame - startFrame);
270     float meanOverlap = sumOverlap / (endFrame - startFrame);
271 
272     EXPECT_LE(meanDistance, distanceThreshold);
273     EXPECT_GE(meanOverlap, overlapThreshold);
274 }
275 
276 template <typename Tracker, typename ROI_t>
checkDataTest()277 void TrackerTest<Tracker, ROI_t>::checkDataTest()
278 {
279 
280     FileStorage fs;
281     fs.open(cvtest::TS::ptr()->get_data_path() + TRACKING_DIR + "/" + video + "/" + video + ".yml", FileStorage::READ);
282     fs["start"] >> startFrame;
283     fs["prefix"] >> prefix;
284     fs["suffix"] >> suffix;
285     fs.release();
286 
287     string gtFile = cvtest::TS::ptr()->get_data_path() + TRACKING_DIR + "/" + video + "/gt.txt";
288     std::ifstream gt;
289     //open the ground truth
290     gt.open(gtFile.c_str());
291     ASSERT_TRUE(gt.is_open()) << gtFile;
292     string line;
293     int bbCounter = 0;
294     while (getline(gt, line))
295     {
296         bbCounter++;
297     }
298     gt.close();
299 
300     int seqLength = bbCounter;
301     for (int i = startFrame; i < seqLength; i++)
302     {
303         validSequence.push_back(i);
304     }
305 
306     //exclude from the images sequence, the frames where the target is occluded or out of view
307     string omitFile = cvtest::TS::ptr()->get_data_path() + TRACKING_DIR + "/" + video + "/" + FOLDER_OMIT_INIT + "/" + video + ".txt";
308     std::ifstream omit;
309     omit.open(omitFile.c_str());
310     if (omit.is_open())
311     {
312         string omitLine;
313         while (getline(omit, omitLine))
314         {
315             vector<string> tokens = splitString(omitLine, " ");
316             int s_start = atoi(tokens.at(0).c_str());
317             int s_end = atoi(tokens.at(1).c_str());
318             for (int k = s_start; k <= s_end; k++)
319             {
320                 std::vector<int>::iterator position = std::find(validSequence.begin(), validSequence.end(), k);
321                 if (position != validSequence.end())
322                     validSequence.erase(position);
323             }
324         }
325     }
326     omit.close();
327     gtStartFrame = startFrame;
328     //compute the start and the and for each segment
329     int numFrame = (int)(validSequence.size() / numSegments);
330     startFrame += (segmentIdx - 1) * numFrame;
331     endFrame = startFrame + numFrame;
332 
333     std::ifstream gt2;
334     //open the ground truth
335     gt2.open(gtFile.c_str());
336     ASSERT_TRUE(gt2.is_open()) << gtFile;
337     string line2;
338     int bbCounter2 = 0;
339     while (getline(gt2, line2))
340     {
341         vector<string> tokens = splitString(line2, ",");
342         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()));
343         ASSERT_EQ((size_t)4, tokens.size()) << "Incorrect ground truth file " << gtFile;
344 
345         bbs.push_back(bb);
346         bbCounter2++;
347     }
348     gt2.close();
349 
350     if (segmentIdx == numSegments)
351         endFrame = (int)bbs.size();
352 }
353 
354 template <typename Tracker, typename ROI_t>
run()355 void TrackerTest<Tracker, ROI_t>::run()
356 {
357     srand(1);  // FIXIT remove that, ensure that there is no "rand()" in implementation
358 
359     ASSERT_TRUE(tracker);
360 
361     checkDataTest();
362 
363     //check for failure
364     if (::testing::Test::HasFatalFailure())
365         return;
366 
367     distanceAndOverlapTest();
368 }
369