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