1 /*M///////////////////////////////////////////////////////////////////////////////////////
2 //
3 // IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
4 //
5 // By downloading, copying, installing or using the software you agree to this license.
6 // If you do not agree to this license, do not download, install,
7 // copy or use the software.
8 //
9 //
10 // Intel License Agreement
11 // For Open Source Computer Vision Library
12 //
13 // Copyright (C) 2000, Intel Corporation, all rights reserved.
14 // Third party copyrights are property of their respective owners.
15 //
16 // Redistribution and use in source and binary forms, with or without modification,
17 // are permitted provided that the following conditions are met:
18 //
19 // * Redistribution's of source code must retain the above copyright notice,
20 // this list of conditions and the following disclaimer.
21 //
22 // * Redistribution's in binary form must reproduce the above copyright notice,
23 // this list of conditions and the following disclaimer in the documentation
24 // and/or other materials provided with the distribution.
25 //
26 // * The name of Intel Corporation may not be used to endorse or promote products
27 // derived from this software without specific prior written permission.
28 //
29 // This software is provided by the copyright holders and contributors "as is" and
30 // any express or implied warranties, including, but not limited to, the implied
31 // warranties of merchantability and fitness for a particular purpose are disclaimed.
32 // In no event shall the Intel Corporation or contributors be liable for any direct,
33 // indirect, incidental, special, exemplary, or consequential damages
34 // (including, but not limited to, procurement of substitute goods or services;
35 // loss of use, data, or profits; or business interruption) however caused
36 // and on any theory of liability, whether in contract, strict liability,
37 // or tort (including negligence or otherwise) arising in any way out of
38 // the use of this software, even if advised of the possibility of such damage.
39 //
40 //M*/
41
42 #include "test_precomp.hpp"
43 #include "opencv2/highgui.hpp"
44
45 using namespace std;
46 using namespace cv;
47
48 const string FEATURES2D_DIR = "features2d";
49 const string IMAGE_FILENAME = "tsukuba.png";
50
51 /****************************************************************************************\
52 * Algorithmic tests for descriptor matchers *
53 \****************************************************************************************/
54 class CV_DescriptorMatcherTest : public cvtest::BaseTest
55 {
56 public:
CV_DescriptorMatcherTest(const string & _name,const Ptr<DescriptorMatcher> & _dmatcher,float _badPart)57 CV_DescriptorMatcherTest( const string& _name, const Ptr<DescriptorMatcher>& _dmatcher, float _badPart ) :
58 badPart(_badPart), name(_name), dmatcher(_dmatcher)
59 {}
60 protected:
61 static const int dim = 500;
62 static const int queryDescCount = 300; // must be even number because we split train data in some cases in two
63 static const int countFactor = 4; // do not change it
64 const float badPart;
65
66 virtual void run( int );
67 void generateData( Mat& query, Mat& train );
68
69 void emptyDataTest();
70 void matchTest( const Mat& query, const Mat& train );
71 void knnMatchTest( const Mat& query, const Mat& train );
72 void radiusMatchTest( const Mat& query, const Mat& train );
73
74 string name;
75 Ptr<DescriptorMatcher> dmatcher;
76
77 private:
operator =(const CV_DescriptorMatcherTest &)78 CV_DescriptorMatcherTest& operator=(const CV_DescriptorMatcherTest&) { return *this; }
79 };
80
emptyDataTest()81 void CV_DescriptorMatcherTest::emptyDataTest()
82 {
83 assert( !dmatcher.empty() );
84 Mat queryDescriptors, trainDescriptors, mask;
85 vector<Mat> trainDescriptorCollection, masks;
86 vector<DMatch> matches;
87 vector<vector<DMatch> > vmatches;
88
89 try
90 {
91 dmatcher->match( queryDescriptors, trainDescriptors, matches, mask );
92 }
93 catch(...)
94 {
95 ts->printf( cvtest::TS::LOG, "match() on empty descriptors must not generate exception (1).\n" );
96 ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT );
97 }
98
99 try
100 {
101 dmatcher->knnMatch( queryDescriptors, trainDescriptors, vmatches, 2, mask );
102 }
103 catch(...)
104 {
105 ts->printf( cvtest::TS::LOG, "knnMatch() on empty descriptors must not generate exception (1).\n" );
106 ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT );
107 }
108
109 try
110 {
111 dmatcher->radiusMatch( queryDescriptors, trainDescriptors, vmatches, 10.f, mask );
112 }
113 catch(...)
114 {
115 ts->printf( cvtest::TS::LOG, "radiusMatch() on empty descriptors must not generate exception (1).\n" );
116 ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT );
117 }
118
119 try
120 {
121 dmatcher->add( trainDescriptorCollection );
122 }
123 catch(...)
124 {
125 ts->printf( cvtest::TS::LOG, "add() on empty descriptors must not generate exception.\n" );
126 ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT );
127 }
128
129 try
130 {
131 dmatcher->match( queryDescriptors, matches, masks );
132 }
133 catch(...)
134 {
135 ts->printf( cvtest::TS::LOG, "match() on empty descriptors must not generate exception (2).\n" );
136 ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT );
137 }
138
139 try
140 {
141 dmatcher->knnMatch( queryDescriptors, vmatches, 2, masks );
142 }
143 catch(...)
144 {
145 ts->printf( cvtest::TS::LOG, "knnMatch() on empty descriptors must not generate exception (2).\n" );
146 ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT );
147 }
148
149 try
150 {
151 dmatcher->radiusMatch( queryDescriptors, vmatches, 10.f, masks );
152 }
153 catch(...)
154 {
155 ts->printf( cvtest::TS::LOG, "radiusMatch() on empty descriptors must not generate exception (2).\n" );
156 ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT );
157 }
158
159 }
160
generateData(Mat & query,Mat & train)161 void CV_DescriptorMatcherTest::generateData( Mat& query, Mat& train )
162 {
163 RNG& rng = theRNG();
164
165 // Generate query descriptors randomly.
166 // Descriptor vector elements are integer values.
167 Mat buf( queryDescCount, dim, CV_32SC1 );
168 rng.fill( buf, RNG::UNIFORM, Scalar::all(0), Scalar(3) );
169 buf.convertTo( query, CV_32FC1 );
170
171 // Generate train decriptors as follows:
172 // copy each query descriptor to train set countFactor times
173 // and perturb some one element of the copied descriptors in
174 // in ascending order. General boundaries of the perturbation
175 // are (0.f, 1.f).
176 train.create( query.rows*countFactor, query.cols, CV_32FC1 );
177 float step = 1.f / countFactor;
178 for( int qIdx = 0; qIdx < query.rows; qIdx++ )
179 {
180 Mat queryDescriptor = query.row(qIdx);
181 for( int c = 0; c < countFactor; c++ )
182 {
183 int tIdx = qIdx * countFactor + c;
184 Mat trainDescriptor = train.row(tIdx);
185 queryDescriptor.copyTo( trainDescriptor );
186 int elem = rng(dim);
187 float diff = rng.uniform( step*c, step*(c+1) );
188 trainDescriptor.at<float>(0, elem) += diff;
189 }
190 }
191 }
192
matchTest(const Mat & query,const Mat & train)193 void CV_DescriptorMatcherTest::matchTest( const Mat& query, const Mat& train )
194 {
195 dmatcher->clear();
196
197 // test const version of match()
198 {
199 vector<DMatch> matches;
200 dmatcher->match( query, train, matches );
201
202 if( (int)matches.size() != queryDescCount )
203 {
204 ts->printf(cvtest::TS::LOG, "Incorrect matches count while test match() function (1).\n");
205 ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT );
206 }
207 else
208 {
209 int badCount = 0;
210 for( size_t i = 0; i < matches.size(); i++ )
211 {
212 DMatch& match = matches[i];
213 if( (match.queryIdx != (int)i) || (match.trainIdx != (int)i*countFactor) || (match.imgIdx != 0) )
214 badCount++;
215 }
216 if( (float)badCount > (float)queryDescCount*badPart )
217 {
218 ts->printf( cvtest::TS::LOG, "%f - too large bad matches part while test match() function (1).\n",
219 (float)badCount/(float)queryDescCount );
220 ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT );
221 }
222 }
223 }
224
225 // test const version of match() for the same query and test descriptors
226 {
227 vector<DMatch> matches;
228 dmatcher->match( query, query, matches );
229
230 if( (int)matches.size() != query.rows )
231 {
232 ts->printf(cvtest::TS::LOG, "Incorrect matches count while test match() function for the same query and test descriptors (1).\n");
233 ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT );
234 }
235 else
236 {
237 for( size_t i = 0; i < matches.size(); i++ )
238 {
239 DMatch& match = matches[i];
240 //std::cout << match.distance << std::endl;
241
242 if( match.queryIdx != (int)i || match.trainIdx != (int)i || std::abs(match.distance) > FLT_EPSILON )
243 {
244 ts->printf( cvtest::TS::LOG, "Bad match (i=%d, queryIdx=%d, trainIdx=%d, distance=%f) while test match() function for the same query and test descriptors (1).\n",
245 i, match.queryIdx, match.trainIdx, match.distance );
246 ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT );
247 }
248 }
249 }
250 }
251
252 // test version of match() with add()
253 {
254 vector<DMatch> matches;
255 // make add() twice to test such case
256 dmatcher->add( vector<Mat>(1,train.rowRange(0, train.rows/2)) );
257 dmatcher->add( vector<Mat>(1,train.rowRange(train.rows/2, train.rows)) );
258 // prepare masks (make first nearest match illegal)
259 vector<Mat> masks(2);
260 for(int mi = 0; mi < 2; mi++ )
261 {
262 masks[mi] = Mat(query.rows, train.rows/2, CV_8UC1, Scalar::all(1));
263 for( int di = 0; di < queryDescCount/2; di++ )
264 masks[mi].col(di*countFactor).setTo(Scalar::all(0));
265 }
266
267 dmatcher->match( query, matches, masks );
268
269 if( (int)matches.size() != queryDescCount )
270 {
271 ts->printf(cvtest::TS::LOG, "Incorrect matches count while test match() function (2).\n");
272 ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT );
273 }
274 else
275 {
276 int badCount = 0;
277 for( size_t i = 0; i < matches.size(); i++ )
278 {
279 DMatch& match = matches[i];
280 int shift = dmatcher->isMaskSupported() ? 1 : 0;
281 {
282 if( i < queryDescCount/2 )
283 {
284 if( (match.queryIdx != (int)i) || (match.trainIdx != (int)i*countFactor + shift) || (match.imgIdx != 0) )
285 badCount++;
286 }
287 else
288 {
289 if( (match.queryIdx != (int)i) || (match.trainIdx != ((int)i-queryDescCount/2)*countFactor + shift) || (match.imgIdx != 1) )
290 badCount++;
291 }
292 }
293 }
294 if( (float)badCount > (float)queryDescCount*badPart )
295 {
296 ts->printf( cvtest::TS::LOG, "%f - too large bad matches part while test match() function (2).\n",
297 (float)badCount/(float)queryDescCount );
298 ts->set_failed_test_info( cvtest::TS::FAIL_BAD_ACCURACY );
299 }
300 }
301 }
302 }
303
knnMatchTest(const Mat & query,const Mat & train)304 void CV_DescriptorMatcherTest::knnMatchTest( const Mat& query, const Mat& train )
305 {
306 dmatcher->clear();
307
308 // test const version of knnMatch()
309 {
310 const int knn = 3;
311
312 vector<vector<DMatch> > matches;
313 dmatcher->knnMatch( query, train, matches, knn );
314
315 if( (int)matches.size() != queryDescCount )
316 {
317 ts->printf(cvtest::TS::LOG, "Incorrect matches count while test knnMatch() function (1).\n");
318 ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT );
319 }
320 else
321 {
322 int badCount = 0;
323 for( size_t i = 0; i < matches.size(); i++ )
324 {
325 if( (int)matches[i].size() != knn )
326 badCount++;
327 else
328 {
329 int localBadCount = 0;
330 for( int k = 0; k < knn; k++ )
331 {
332 DMatch& match = matches[i][k];
333 if( (match.queryIdx != (int)i) || (match.trainIdx != (int)i*countFactor+k) || (match.imgIdx != 0) )
334 localBadCount++;
335 }
336 badCount += localBadCount > 0 ? 1 : 0;
337 }
338 }
339 if( (float)badCount > (float)queryDescCount*badPart )
340 {
341 ts->printf( cvtest::TS::LOG, "%f - too large bad matches part while test knnMatch() function (1).\n",
342 (float)badCount/(float)queryDescCount );
343 ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT );
344 }
345 }
346 }
347
348 // test version of knnMatch() with add()
349 {
350 const int knn = 2;
351 vector<vector<DMatch> > matches;
352 // make add() twice to test such case
353 dmatcher->add( vector<Mat>(1,train.rowRange(0, train.rows/2)) );
354 dmatcher->add( vector<Mat>(1,train.rowRange(train.rows/2, train.rows)) );
355 // prepare masks (make first nearest match illegal)
356 vector<Mat> masks(2);
357 for(int mi = 0; mi < 2; mi++ )
358 {
359 masks[mi] = Mat(query.rows, train.rows/2, CV_8UC1, Scalar::all(1));
360 for( int di = 0; di < queryDescCount/2; di++ )
361 masks[mi].col(di*countFactor).setTo(Scalar::all(0));
362 }
363
364 dmatcher->knnMatch( query, matches, knn, masks );
365
366 if( (int)matches.size() != queryDescCount )
367 {
368 ts->printf(cvtest::TS::LOG, "Incorrect matches count while test knnMatch() function (2).\n");
369 ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT );
370 }
371 else
372 {
373 int badCount = 0;
374 int shift = dmatcher->isMaskSupported() ? 1 : 0;
375 for( size_t i = 0; i < matches.size(); i++ )
376 {
377 if( (int)matches[i].size() != knn )
378 badCount++;
379 else
380 {
381 int localBadCount = 0;
382 for( int k = 0; k < knn; k++ )
383 {
384 DMatch& match = matches[i][k];
385 {
386 if( i < queryDescCount/2 )
387 {
388 if( (match.queryIdx != (int)i) || (match.trainIdx != (int)i*countFactor + k + shift) ||
389 (match.imgIdx != 0) )
390 localBadCount++;
391 }
392 else
393 {
394 if( (match.queryIdx != (int)i) || (match.trainIdx != ((int)i-queryDescCount/2)*countFactor + k + shift) ||
395 (match.imgIdx != 1) )
396 localBadCount++;
397 }
398 }
399 }
400 badCount += localBadCount > 0 ? 1 : 0;
401 }
402 }
403 if( (float)badCount > (float)queryDescCount*badPart )
404 {
405 ts->printf( cvtest::TS::LOG, "%f - too large bad matches part while test knnMatch() function (2).\n",
406 (float)badCount/(float)queryDescCount );
407 ts->set_failed_test_info( cvtest::TS::FAIL_BAD_ACCURACY );
408 }
409 }
410 }
411 }
412
radiusMatchTest(const Mat & query,const Mat & train)413 void CV_DescriptorMatcherTest::radiusMatchTest( const Mat& query, const Mat& train )
414 {
415 dmatcher->clear();
416 // test const version of match()
417 {
418 const float radius = 1.f/countFactor;
419 vector<vector<DMatch> > matches;
420 dmatcher->radiusMatch( query, train, matches, radius );
421
422 if( (int)matches.size() != queryDescCount )
423 {
424 ts->printf(cvtest::TS::LOG, "Incorrect matches count while test radiusMatch() function (1).\n");
425 ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT );
426 }
427 else
428 {
429 int badCount = 0;
430 for( size_t i = 0; i < matches.size(); i++ )
431 {
432 if( (int)matches[i].size() != 1 )
433 badCount++;
434 else
435 {
436 DMatch& match = matches[i][0];
437 if( (match.queryIdx != (int)i) || (match.trainIdx != (int)i*countFactor) || (match.imgIdx != 0) )
438 badCount++;
439 }
440 }
441 if( (float)badCount > (float)queryDescCount*badPart )
442 {
443 ts->printf( cvtest::TS::LOG, "%f - too large bad matches part while test radiusMatch() function (1).\n",
444 (float)badCount/(float)queryDescCount );
445 ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT );
446 }
447 }
448 }
449
450 // test version of match() with add()
451 {
452 int n = 3;
453 const float radius = 1.f/countFactor * n;
454 vector<vector<DMatch> > matches;
455 // make add() twice to test such case
456 dmatcher->add( vector<Mat>(1,train.rowRange(0, train.rows/2)) );
457 dmatcher->add( vector<Mat>(1,train.rowRange(train.rows/2, train.rows)) );
458 // prepare masks (make first nearest match illegal)
459 vector<Mat> masks(2);
460 for(int mi = 0; mi < 2; mi++ )
461 {
462 masks[mi] = Mat(query.rows, train.rows/2, CV_8UC1, Scalar::all(1));
463 for( int di = 0; di < queryDescCount/2; di++ )
464 masks[mi].col(di*countFactor).setTo(Scalar::all(0));
465 }
466
467 dmatcher->radiusMatch( query, matches, radius, masks );
468
469 //int curRes = cvtest::TS::OK;
470 if( (int)matches.size() != queryDescCount )
471 {
472 ts->printf(cvtest::TS::LOG, "Incorrect matches count while test radiusMatch() function (1).\n");
473 ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT );
474 }
475
476 int badCount = 0;
477 int shift = dmatcher->isMaskSupported() ? 1 : 0;
478 int needMatchCount = dmatcher->isMaskSupported() ? n-1 : n;
479 for( size_t i = 0; i < matches.size(); i++ )
480 {
481 if( (int)matches[i].size() != needMatchCount )
482 badCount++;
483 else
484 {
485 int localBadCount = 0;
486 for( int k = 0; k < needMatchCount; k++ )
487 {
488 DMatch& match = matches[i][k];
489 {
490 if( i < queryDescCount/2 )
491 {
492 if( (match.queryIdx != (int)i) || (match.trainIdx != (int)i*countFactor + k + shift) ||
493 (match.imgIdx != 0) )
494 localBadCount++;
495 }
496 else
497 {
498 if( (match.queryIdx != (int)i) || (match.trainIdx != ((int)i-queryDescCount/2)*countFactor + k + shift) ||
499 (match.imgIdx != 1) )
500 localBadCount++;
501 }
502 }
503 }
504 badCount += localBadCount > 0 ? 1 : 0;
505 }
506 }
507 if( (float)badCount > (float)queryDescCount*badPart )
508 {
509 //curRes = cvtest::TS::FAIL_INVALID_OUTPUT;
510 ts->printf( cvtest::TS::LOG, "%f - too large bad matches part while test radiusMatch() function (2).\n",
511 (float)badCount/(float)queryDescCount );
512 ts->set_failed_test_info( cvtest::TS::FAIL_BAD_ACCURACY );
513 }
514 }
515 }
516
run(int)517 void CV_DescriptorMatcherTest::run( int )
518 {
519 Mat query, train;
520 generateData( query, train );
521
522 matchTest( query, train );
523
524 knnMatchTest( query, train );
525
526 radiusMatchTest( query, train );
527 }
528
529 /****************************************************************************************\
530 * Tests registrations *
531 \****************************************************************************************/
532
TEST(Features2d_DescriptorMatcher_BruteForce,regression)533 TEST( Features2d_DescriptorMatcher_BruteForce, regression )
534 {
535 CV_DescriptorMatcherTest test( "descriptor-matcher-brute-force",
536 DescriptorMatcher::create("BruteForce"), 0.01f );
537 test.safe_run();
538 }
539
TEST(Features2d_DescriptorMatcher_FlannBased,regression)540 TEST( Features2d_DescriptorMatcher_FlannBased, regression )
541 {
542 CV_DescriptorMatcherTest test( "descriptor-matcher-flann-based",
543 DescriptorMatcher::create("FlannBased"), 0.04f );
544 test.safe_run();
545 }
546
TEST(Features2d_DMatch,read_write)547 TEST( Features2d_DMatch, read_write )
548 {
549 FileStorage fs(".xml", FileStorage::WRITE + FileStorage::MEMORY);
550 vector<DMatch> matches;
551 matches.push_back(DMatch(1,2,3,4.5f));
552 fs << "Match" << matches;
553 String str = fs.releaseAndGetString();
554 ASSERT_NE( strstr(str.c_str(), "4.5"), (char*)0 );
555 }
556