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