ccb8979c610f00157487ac811116af2447f50962
[platform/upstream/opencv.git] / samples / cpp / detector_descriptor_evaluation.cpp
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 "opencv2/imgproc.hpp"
43 #include "opencv2/highgui.hpp"
44 #include "opencv2/features2d.hpp"
45 #include "opencv2/legacy.hpp"
46
47 #include <limits>
48 #include <cstdio>
49 #include <iostream>
50 #include <fstream>
51
52 using namespace std;
53 using namespace cv;
54
55
56 string data_path;
57 /****************************************************************************************\
58 *           Functions to evaluate affine covariant detectors and descriptors.            *
59 \****************************************************************************************/
60
61 static inline Point2f applyHomography( const Mat_<double>& H, const Point2f& pt )
62 {
63     double z = H(2,0)*pt.x + H(2,1)*pt.y + H(2,2);
64     if( z )
65     {
66         double w = 1./z;
67         return Point2f( (float)((H(0,0)*pt.x + H(0,1)*pt.y + H(0,2))*w),
68                                                 (float)((H(1,0)*pt.x + H(1,1)*pt.y + H(1,2))*w) );
69     }
70     return Point2f( numeric_limits<float>::max(), numeric_limits<float>::max() );
71 }
72
73 static inline void linearizeHomographyAt( const Mat_<double>& H, const Point2f& pt, Mat_<double>& A )
74 {
75     A.create(2,2);
76     double p1 = H(0,0)*pt.x + H(0,1)*pt.y + H(0,2),
77            p2 = H(1,0)*pt.x + H(1,1)*pt.y + H(1,2),
78            p3 = H(2,0)*pt.x + H(2,1)*pt.y + H(2,2),
79            p3_2 = p3*p3;
80     if( p3 )
81     {
82         A(0,0) = H(0,0)/p3 - p1*H(2,0)/p3_2; // fxdx
83         A(0,1) = H(0,1)/p3 - p1*H(2,1)/p3_2; // fxdy
84
85         A(1,0) = H(1,0)/p3 - p2*H(2,0)/p3_2; // fydx
86         A(1,1) = H(1,1)/p3 - p2*H(2,1)/p3_2; // fydx
87     }
88     else
89         A.setTo(Scalar::all(numeric_limits<double>::max()));
90 }
91
92 static void calcKeyPointProjections( const vector<KeyPoint>& src, const Mat_<double>& H, vector<KeyPoint>& dst )
93 {
94     if(  !src.empty() )
95     {
96         CV_Assert( !H.empty() && H.cols == 3 && H.rows == 3);
97         dst.resize(src.size());
98         vector<KeyPoint>::const_iterator srcIt = src.begin();
99         vector<KeyPoint>::iterator       dstIt = dst.begin();
100         for( ; srcIt != src.end(); ++srcIt, ++dstIt )
101         {
102             Point2f dstPt = applyHomography(H, srcIt->pt);
103
104             float srcSize2 = srcIt->size * srcIt->size;
105             Mat_<double> M(2, 2);
106             M(0,0) = M(1,1) = 1./srcSize2;
107             M(1,0) = M(0,1) = 0;
108             Mat_<double> invM; invert(M, invM);
109             Mat_<double> Aff; linearizeHomographyAt(H, srcIt->pt, Aff);
110             Mat_<double> dstM; invert(Aff*invM*Aff.t(), dstM);
111             Mat_<double> eval; eigen( dstM, eval );
112             CV_Assert( eval(0,0) && eval(1,0) );
113             float dstSize = (float)pow(1./(eval(0,0)*eval(1,0)), 0.25);
114
115             // TODO: check angle projection
116             float srcAngleRad = (float)(srcIt->angle*CV_PI/180);
117             Point2f vec1(cos(srcAngleRad), sin(srcAngleRad)), vec2;
118             vec2.x = (float)(Aff(0,0)*vec1.x + Aff(0,1)*vec1.y);
119             vec2.y = (float)(Aff(1,0)*vec1.x + Aff(0,1)*vec1.y);
120             float dstAngleGrad = fastAtan2(vec2.y, vec2.x);
121
122             *dstIt = KeyPoint( dstPt, dstSize, dstAngleGrad, srcIt->response, srcIt->octave, srcIt->class_id );
123         }
124     }
125 }
126
127 static void filterKeyPointsByImageSize( vector<KeyPoint>& keypoints, const Size& imgSize )
128 {
129     if( !keypoints.empty() )
130     {
131         vector<KeyPoint> filtered;
132         filtered.reserve(keypoints.size());
133         Rect r(0, 0, imgSize.width, imgSize.height);
134         vector<KeyPoint>::const_iterator it = keypoints.begin();
135         for( int i = 0; it != keypoints.end(); ++it, i++ )
136             if( r.contains(it->pt) )
137                 filtered.push_back(*it);
138         keypoints.assign(filtered.begin(), filtered.end());
139     }
140 }
141
142 /****************************************************************************************\
143 *                                  Detectors evaluation                                 *
144 \****************************************************************************************/
145 const int DATASETS_COUNT = 8;
146 const int TEST_CASE_COUNT = 5;
147
148 const string IMAGE_DATASETS_DIR = "detectors_descriptors_evaluation/images_datasets/";
149 const string DETECTORS_DIR = "detectors_descriptors_evaluation/detectors/";
150 const string DESCRIPTORS_DIR = "detectors_descriptors_evaluation/descriptors/";
151 const string KEYPOINTS_DIR = "detectors_descriptors_evaluation/keypoints_datasets/";
152
153 const string PARAMS_POSTFIX = "_params.xml";
154 const string RES_POSTFIX = "_res.xml";
155
156 const string REPEAT = "repeatability";
157 const string CORRESP_COUNT = "correspondence_count";
158
159 string DATASET_NAMES[DATASETS_COUNT] = { "bark", "bikes", "boat", "graf", "leuven", "trees", "ubc", "wall"};
160
161 string DEFAULT_PARAMS = "default";
162
163 string IS_ACTIVE_PARAMS = "isActiveParams";
164 string IS_SAVE_KEYPOINTS = "isSaveKeypoints";
165
166
167 class BaseQualityEvaluator
168 {
169 public:
170     BaseQualityEvaluator( const char* _algName, const char* _testName ) : algName(_algName), testName(_testName)
171     {
172         //TODO: change this
173         isWriteGraphicsData = true;
174     }
175
176     void run();
177
178     virtual ~BaseQualityEvaluator(){}
179
180 protected:
181     virtual string getRunParamsFilename() const = 0;
182     virtual string getResultsFilename() const = 0;
183     virtual string getPlotPath() const = 0;
184
185     virtual void calcQualityClear( int datasetIdx ) = 0;
186     virtual bool isCalcQualityEmpty( int datasetIdx ) const = 0;
187
188     void readAllDatasetsRunParams();
189     virtual void readDatasetRunParams( FileNode& fn, int datasetIdx ) = 0;
190     void writeAllDatasetsRunParams() const;
191     virtual void writeDatasetRunParams( FileStorage& fs, int datasetIdx ) const = 0;
192     void setDefaultAllDatasetsRunParams();
193     virtual void setDefaultDatasetRunParams( int datasetIdx ) = 0;
194     virtual void readDefaultRunParams( FileNode& /*fn*/ ) {}
195     virtual void writeDefaultRunParams( FileStorage& /*fs*/ ) const {}
196
197     bool readDataset( const string& datasetName, vector<Mat>& Hs, vector<Mat>& imgs );
198
199     virtual void readAlgorithm() {}
200     virtual void processRunParamsFile() {}
201     virtual void runDatasetTest( const vector<Mat>& /*imgs*/, const vector<Mat>& /*Hs*/, int /*di*/, int& /*progress*/ ) {}
202
203     virtual void processResults( int datasetIdx );
204     virtual void processResults();
205     virtual void writePlotData( int /*datasetIdx*/ ) const {}
206
207     string algName, testName;
208     bool isWriteParams, isWriteGraphicsData;
209 };
210
211 void BaseQualityEvaluator::readAllDatasetsRunParams()
212 {
213     string filename = getRunParamsFilename();
214     FileStorage fs( filename, FileStorage::READ );
215     if( !fs.isOpened() )
216     {
217         isWriteParams = true;
218         setDefaultAllDatasetsRunParams();
219         printf("All runParams are default.\n");
220     }
221     else
222     {
223         isWriteParams = false;
224         FileNode topfn = fs.getFirstTopLevelNode();
225
226         FileNode pfn = topfn[DEFAULT_PARAMS];
227         readDefaultRunParams(pfn);
228
229         for( int i = 0; i < DATASETS_COUNT; i++ )
230         {
231             FileNode fn = topfn[DATASET_NAMES[i]];
232             if( fn.empty() )
233             {
234                 printf( "%d-runParams is default.\n", i);
235                 setDefaultDatasetRunParams(i);
236             }
237             else
238                 readDatasetRunParams(fn, i);
239         }
240     }
241 }
242
243 void BaseQualityEvaluator::writeAllDatasetsRunParams() const
244 {
245     string filename = getRunParamsFilename();
246     FileStorage fs( filename, FileStorage::WRITE );
247     if( fs.isOpened() )
248     {
249         fs << "run_params" << "{"; // top file node
250         fs << DEFAULT_PARAMS << "{";
251         writeDefaultRunParams(fs);
252         fs << "}";
253         for( int i = 0; i < DATASETS_COUNT; i++ )
254         {
255             fs << DATASET_NAMES[i] << "{";
256             writeDatasetRunParams(fs, i);
257             fs << "}";
258         }
259         fs << "}";
260     }
261     else
262         printf( "File %s for writing run params can not be opened.\n", filename.c_str() );
263 }
264
265 void BaseQualityEvaluator::setDefaultAllDatasetsRunParams()
266 {
267     for( int i = 0; i < DATASETS_COUNT; i++ )
268         setDefaultDatasetRunParams(i);
269 }
270
271 bool BaseQualityEvaluator::readDataset( const string& datasetName, vector<Mat>& Hs, vector<Mat>& imgs )
272 {
273     Hs.resize( TEST_CASE_COUNT );
274     imgs.resize( TEST_CASE_COUNT+1 );
275     string dirname = data_path + IMAGE_DATASETS_DIR + datasetName + "/";
276     for( int i = 0; i < (int)Hs.size(); i++ )
277     {
278         stringstream filename; filename << "H1to" << i+2 << "p.xml";
279         FileStorage fs( dirname + filename.str(), FileStorage::READ );
280         if( !fs.isOpened() )
281         {
282             cout << "filename " << dirname + filename.str() << endl;
283             FileStorage fs2( dirname + filename.str(), FileStorage::READ );
284             return false;
285         }
286         fs.getFirstTopLevelNode() >> Hs[i];
287     }
288
289     for( int i = 0; i < (int)imgs.size(); i++ )
290     {
291         stringstream filename; filename << "img" << i+1 << ".png";
292         imgs[i] = imread( dirname + filename.str(), 0 );
293         if( imgs[i].empty() )
294         {
295             cout << "filename " << filename.str() << endl;
296             return false;
297         }
298     }
299     return true;
300 }
301
302 void BaseQualityEvaluator::processResults( int datasetIdx )
303 {
304     if( isWriteGraphicsData )
305         writePlotData( datasetIdx );
306 }
307
308 void BaseQualityEvaluator::processResults()
309 {
310     if( isWriteParams )
311         writeAllDatasetsRunParams();
312 }
313
314 void BaseQualityEvaluator::run()
315 {
316     readAlgorithm ();
317     processRunParamsFile ();
318
319     int notReadDatasets = 0;
320     int progress = 0;
321
322     FileStorage runParamsFS( getRunParamsFilename(), FileStorage::READ );
323     isWriteParams = (! runParamsFS.isOpened());
324     FileNode topfn = runParamsFS.getFirstTopLevelNode();
325     FileNode defaultParams = topfn[DEFAULT_PARAMS];
326     readDefaultRunParams (defaultParams);
327
328     cout << testName << endl;
329     for(int di = 0; di < DATASETS_COUNT; di++ )
330     {
331         cout << "Dataset " << di << " [" << DATASET_NAMES[di] << "] " << flush;
332         vector<Mat> imgs, Hs;
333         if( !readDataset( DATASET_NAMES[di], Hs, imgs ) )
334         {
335             calcQualityClear (di);
336             printf( "Images or homography matrices of dataset named %s can not be read\n",
337                         DATASET_NAMES[di].c_str());
338             notReadDatasets++;
339             continue;
340         }
341
342         FileNode fn = topfn[DATASET_NAMES[di]];
343         readDatasetRunParams(fn, di);
344
345         runDatasetTest (imgs, Hs, di, progress);
346         processResults( di );
347         cout << endl;
348     }
349     if( notReadDatasets == DATASETS_COUNT )
350     {
351         printf( "All datasets were not be read\n");
352         exit(-1);
353     }
354     else
355         processResults();
356     runParamsFS.release();
357 }
358
359
360
361 class DetectorQualityEvaluator : public BaseQualityEvaluator
362 {
363 public:
364     DetectorQualityEvaluator( const char* _detectorName, const char* _testName ) : BaseQualityEvaluator( _detectorName, _testName )
365     {
366         calcQuality.resize(DATASETS_COUNT);
367         isSaveKeypoints.resize(DATASETS_COUNT);
368         isActiveParams.resize(DATASETS_COUNT);
369
370         isSaveKeypointsDefault = false;
371         isActiveParamsDefault = false;
372     }
373
374 protected:
375     virtual string getRunParamsFilename() const;
376     virtual string getResultsFilename() const;
377     virtual string getPlotPath() const;
378
379     virtual void calcQualityClear( int datasetIdx );
380     virtual bool isCalcQualityEmpty( int datasetIdx ) const;
381
382     virtual void readDatasetRunParams( FileNode& fn, int datasetIdx );
383     virtual void writeDatasetRunParams( FileStorage& fs, int datasetIdx ) const;
384     virtual void setDefaultDatasetRunParams( int datasetIdx );
385     virtual void readDefaultRunParams( FileNode &fn );
386     virtual void writeDefaultRunParams( FileStorage &fs ) const;
387
388     virtual void writePlotData( int di ) const;
389
390     void openToWriteKeypointsFile( FileStorage& fs, int datasetIdx );
391
392     virtual void readAlgorithm();
393     virtual void processRunParamsFile() {}
394     virtual void runDatasetTest( const vector<Mat> &imgs, const vector<Mat> &Hs, int di, int &progress );
395
396     Ptr<FeatureDetector> specificDetector;
397     Ptr<FeatureDetector> defaultDetector;
398
399     struct Quality
400     {
401         float repeatability;
402         int correspondenceCount;
403     };
404     vector<vector<Quality> > calcQuality;
405
406     vector<bool> isSaveKeypoints;
407     vector<bool> isActiveParams;
408
409     bool isSaveKeypointsDefault;
410     bool isActiveParamsDefault;
411 };
412
413 string DetectorQualityEvaluator::getRunParamsFilename() const
414 {
415      return data_path + DETECTORS_DIR + algName + PARAMS_POSTFIX;
416 }
417
418 string DetectorQualityEvaluator::getResultsFilename() const
419 {
420     return data_path + DETECTORS_DIR + algName + RES_POSTFIX;
421 }
422
423 string DetectorQualityEvaluator::getPlotPath() const
424 {
425     return data_path + DETECTORS_DIR + "plots/";
426 }
427
428 void DetectorQualityEvaluator::calcQualityClear( int datasetIdx )
429 {
430     calcQuality[datasetIdx].clear();
431 }
432
433 bool DetectorQualityEvaluator::isCalcQualityEmpty( int datasetIdx ) const
434 {
435     return calcQuality[datasetIdx].empty();
436 }
437
438 void DetectorQualityEvaluator::readDefaultRunParams (FileNode &fn)
439 {
440     if (! fn.empty() )
441     {
442         isSaveKeypointsDefault = (int)fn[IS_SAVE_KEYPOINTS] != 0;
443         defaultDetector->read (fn);
444     }
445 }
446
447 void DetectorQualityEvaluator::writeDefaultRunParams (FileStorage &fs) const
448 {
449     fs << IS_SAVE_KEYPOINTS << isSaveKeypointsDefault;
450     defaultDetector->write (fs);
451 }
452
453 void DetectorQualityEvaluator::readDatasetRunParams( FileNode& fn, int datasetIdx )
454 {
455     isActiveParams[datasetIdx] = (int)fn[IS_ACTIVE_PARAMS] != 0;
456     if (isActiveParams[datasetIdx])
457     {
458         isSaveKeypoints[datasetIdx] = (int)fn[IS_SAVE_KEYPOINTS] != 0;
459         specificDetector->read (fn);
460     }
461     else
462     {
463         setDefaultDatasetRunParams(datasetIdx);
464     }
465 }
466
467 void DetectorQualityEvaluator::writeDatasetRunParams( FileStorage& fs, int datasetIdx ) const
468 {
469     fs << IS_ACTIVE_PARAMS << isActiveParams[datasetIdx];
470     fs << IS_SAVE_KEYPOINTS << isSaveKeypoints[datasetIdx];
471     defaultDetector->write (fs);
472 }
473
474 void DetectorQualityEvaluator::setDefaultDatasetRunParams( int datasetIdx )
475 {
476     isSaveKeypoints[datasetIdx] = isSaveKeypointsDefault;
477     isActiveParams[datasetIdx] = isActiveParamsDefault;
478 }
479
480 void DetectorQualityEvaluator::writePlotData(int di ) const
481 {
482     int imgXVals[] = { 2, 3, 4, 5, 6 }; // if scale, blur or light changes
483     int viewpointXVals[] = { 20, 30, 40, 50, 60 }; // if viewpoint changes
484     int jpegXVals[] = { 60, 80, 90, 95, 98 }; // if jpeg compression
485
486     int* xVals = 0;
487     if( !DATASET_NAMES[di].compare("ubc") )
488     {
489         xVals = jpegXVals;
490     }
491     else if( !DATASET_NAMES[di].compare("graf") || !DATASET_NAMES[di].compare("wall") )
492     {
493         xVals = viewpointXVals;
494     }
495     else
496         xVals = imgXVals;
497
498     stringstream rFilename, cFilename;
499     rFilename << getPlotPath() << algName << "_" << DATASET_NAMES[di]  << "_repeatability.csv";
500     cFilename << getPlotPath() << algName << "_" << DATASET_NAMES[di]  << "_correspondenceCount.csv";
501     ofstream rfile(rFilename.str().c_str()), cfile(cFilename.str().c_str());
502     for( int ci = 0; ci < TEST_CASE_COUNT; ci++ )
503     {
504         rfile << xVals[ci] << ", " << calcQuality[di][ci].repeatability << endl;
505         cfile << xVals[ci] << ", " << calcQuality[di][ci].correspondenceCount << endl;
506     }
507 }
508
509 void DetectorQualityEvaluator::openToWriteKeypointsFile( FileStorage& fs, int datasetIdx )
510 {
511     string filename = data_path + KEYPOINTS_DIR + algName + "_"+ DATASET_NAMES[datasetIdx] + ".xml.gz" ;
512
513     fs.open(filename, FileStorage::WRITE);
514     if( !fs.isOpened() )
515         printf( "keypoints can not be written in file %s because this file can not be opened\n", filename.c_str() );
516 }
517
518 inline void writeKeypoints( FileStorage& fs, const vector<KeyPoint>& keypoints, int imgIdx )
519 {
520     if( fs.isOpened() )
521     {
522         stringstream imgName; imgName << "img" << imgIdx;
523         write( fs, imgName.str(), keypoints );
524     }
525 }
526
527 inline void readKeypoints( FileStorage& fs, vector<KeyPoint>& keypoints, int imgIdx )
528 {
529     CV_Assert( fs.isOpened() );
530     stringstream imgName; imgName << "img" << imgIdx;
531     read( fs[imgName.str()], keypoints);
532 }
533
534 void DetectorQualityEvaluator::readAlgorithm ()
535 {
536     defaultDetector = FeatureDetector::create( algName );
537     specificDetector = FeatureDetector::create( algName );
538     if( !defaultDetector )
539     {
540         printf( "Algorithm can not be read\n" );
541         exit(-1);
542     }
543 }
544
545 static int update_progress( const string& /*name*/, int progress, int test_case_idx, int count, double dt )
546 {
547     int width = 60 /*- (int)name.length()*/;
548     if( count > 0 )
549     {
550         int t = cvRound( ((double)test_case_idx * width)/count );
551         if( t > progress )
552         {
553             cout << "." << flush;
554             progress = t;
555         }
556     }
557     else if( cvRound(dt) > progress )
558     {
559         cout << "." << flush;
560         progress = cvRound(dt);
561     }
562
563     return progress;
564 }
565
566 void DetectorQualityEvaluator::runDatasetTest (const vector<Mat> &imgs, const vector<Mat> &Hs, int di, int &progress)
567 {
568     Ptr<FeatureDetector> detector = isActiveParams[di] ? specificDetector : defaultDetector;
569     FileStorage keypontsFS;
570     if( isSaveKeypoints[di] )
571         openToWriteKeypointsFile( keypontsFS, di );
572
573     calcQuality[di].resize(TEST_CASE_COUNT);
574
575     vector<KeyPoint> keypoints1;
576     detector->detect( imgs[0], keypoints1 );
577     writeKeypoints( keypontsFS, keypoints1, 0);
578     int progressCount = DATASETS_COUNT*TEST_CASE_COUNT;
579
580     for( int ci = 0; ci < TEST_CASE_COUNT; ci++ )
581     {
582         progress = update_progress( testName, progress, di*TEST_CASE_COUNT + ci + 1, progressCount, 0 );
583         vector<KeyPoint> keypoints2;
584         float rep;
585         evaluateFeatureDetector( imgs[0], imgs[ci+1], Hs[ci], &keypoints1, &keypoints2,
586                                  rep, calcQuality[di][ci].correspondenceCount,
587                                  detector );
588         calcQuality[di][ci].repeatability = rep == -1 ? rep : 100.f*rep;
589         writeKeypoints( keypontsFS, keypoints2, ci+1);
590     }
591 }
592
593 // static void testLog( bool isBadAccuracy )
594 // {
595 //     if( isBadAccuracy )
596 //         printf(" bad accuracy\n");
597 //     else
598 //         printf("\n");
599 // }
600
601 /****************************************************************************************\
602 *                                  Descriptors evaluation                                 *
603 \****************************************************************************************/
604
605 const string RECALL = "recall";
606 const string PRECISION = "precision";
607
608 const string KEYPOINTS_FILENAME = "keypointsFilename";
609 const string PROJECT_KEYPOINTS_FROM_1IMAGE = "projectKeypointsFrom1Image";
610 const string MATCH_FILTER = "matchFilter";
611 const string RUN_PARAMS_IS_IDENTICAL = "runParamsIsIdentical";
612
613 const string ONE_WAY_TRAIN_DIR = "detectors_descriptors_evaluation/one_way_train_images/";
614 const string ONE_WAY_IMAGES_LIST = "one_way_train_images.txt";
615
616 class DescriptorQualityEvaluator : public BaseQualityEvaluator
617 {
618 public:
619     enum{ NO_MATCH_FILTER = 0 };
620     DescriptorQualityEvaluator( const char* _descriptorName, const char* _testName, const char* _matcherName = 0 ) :
621             BaseQualityEvaluator( _descriptorName, _testName )
622     {
623         calcQuality.resize(DATASETS_COUNT);
624         calcDatasetQuality.resize(DATASETS_COUNT);
625         commRunParams.resize(DATASETS_COUNT);
626
627         commRunParamsDefault.projectKeypointsFrom1Image = true;
628         commRunParamsDefault.matchFilter = NO_MATCH_FILTER;
629         commRunParamsDefault.isActiveParams = false;
630
631         if( _matcherName )
632             matcherName = _matcherName;
633     }
634
635 protected:
636     virtual string getRunParamsFilename() const;
637     virtual string getResultsFilename() const;
638     virtual string getPlotPath() const;
639
640     virtual void calcQualityClear( int datasetIdx );
641     virtual bool isCalcQualityEmpty( int datasetIdx ) const;
642
643     virtual void readDatasetRunParams( FileNode& fn, int datasetIdx ); //
644     virtual void writeDatasetRunParams( FileStorage& fs, int datasetIdx ) const;
645     virtual void setDefaultDatasetRunParams( int datasetIdx );
646     virtual void readDefaultRunParams( FileNode &fn );
647     virtual void writeDefaultRunParams( FileStorage &fs ) const;
648
649     virtual void readAlgorithm();
650     virtual void processRunParamsFile() {}
651     virtual void runDatasetTest( const vector<Mat> &imgs, const vector<Mat> &Hs, int di, int &progress );
652
653     virtual void writePlotData( int di ) const;
654     void calculatePlotData( vector<vector<DMatch> > &allMatches, vector<vector<uchar> > &allCorrectMatchesMask, int di );
655
656     struct Quality
657     {
658         float recall;
659         float precision;
660     };
661     vector<vector<Quality> > calcQuality;
662     vector<vector<Quality> > calcDatasetQuality;
663
664     struct CommonRunParams
665     {
666         string keypontsFilename;
667         bool projectKeypointsFrom1Image;
668         int matchFilter; // not used now
669         bool isActiveParams;
670     };
671     vector<CommonRunParams> commRunParams;
672
673     Ptr<GenericDescriptorMatch> specificDescMatcher;
674     Ptr<GenericDescriptorMatch> defaultDescMatcher;
675
676     CommonRunParams commRunParamsDefault;
677     string matcherName;
678 };
679
680 string DescriptorQualityEvaluator::getRunParamsFilename() const
681 {
682     return data_path + DESCRIPTORS_DIR + algName + PARAMS_POSTFIX;
683 }
684
685 string DescriptorQualityEvaluator::getResultsFilename() const
686 {
687     return data_path + DESCRIPTORS_DIR + algName + RES_POSTFIX;
688 }
689
690 string DescriptorQualityEvaluator::getPlotPath() const
691 {
692     return data_path + DESCRIPTORS_DIR + "plots/";
693 }
694
695 void DescriptorQualityEvaluator::calcQualityClear( int datasetIdx )
696 {
697     calcQuality[datasetIdx].clear();
698 }
699
700 bool DescriptorQualityEvaluator::isCalcQualityEmpty( int datasetIdx ) const
701 {
702     return calcQuality[datasetIdx].empty();
703 }
704
705 void DescriptorQualityEvaluator::readDefaultRunParams (FileNode &fn)
706 {
707     if (! fn.empty() )
708     {
709         commRunParamsDefault.projectKeypointsFrom1Image = (int)fn[PROJECT_KEYPOINTS_FROM_1IMAGE] != 0;
710         commRunParamsDefault.matchFilter = (int)fn[MATCH_FILTER];
711         defaultDescMatcher->read (fn);
712     }
713 }
714
715 void DescriptorQualityEvaluator::writeDefaultRunParams (FileStorage &fs) const
716 {
717     fs << PROJECT_KEYPOINTS_FROM_1IMAGE << commRunParamsDefault.projectKeypointsFrom1Image;
718     fs << MATCH_FILTER << commRunParamsDefault.matchFilter;
719     defaultDescMatcher->write (fs);
720 }
721
722 void DescriptorQualityEvaluator::readDatasetRunParams( FileNode& fn, int datasetIdx )
723 {
724     commRunParams[datasetIdx].isActiveParams = (int)fn[IS_ACTIVE_PARAMS] != 0;
725     if (commRunParams[datasetIdx].isActiveParams)
726     {
727         commRunParams[datasetIdx].keypontsFilename = (string)fn[KEYPOINTS_FILENAME];
728         commRunParams[datasetIdx].projectKeypointsFrom1Image = (int)fn[PROJECT_KEYPOINTS_FROM_1IMAGE] != 0;
729         commRunParams[datasetIdx].matchFilter = (int)fn[MATCH_FILTER];
730         specificDescMatcher->read (fn);
731     }
732     else
733     {
734         setDefaultDatasetRunParams(datasetIdx);
735     }
736 }
737
738 void DescriptorQualityEvaluator::writeDatasetRunParams( FileStorage& fs, int datasetIdx ) const
739 {
740     fs << IS_ACTIVE_PARAMS << commRunParams[datasetIdx].isActiveParams;
741     fs << KEYPOINTS_FILENAME << commRunParams[datasetIdx].keypontsFilename;
742     fs << PROJECT_KEYPOINTS_FROM_1IMAGE << commRunParams[datasetIdx].projectKeypointsFrom1Image;
743     fs << MATCH_FILTER << commRunParams[datasetIdx].matchFilter;
744
745     defaultDescMatcher->write (fs);
746 }
747
748 void DescriptorQualityEvaluator::setDefaultDatasetRunParams( int datasetIdx )
749 {
750     commRunParams[datasetIdx] = commRunParamsDefault;
751     commRunParams[datasetIdx].keypontsFilename = "SURF_" + DATASET_NAMES[datasetIdx] + ".xml.gz";
752 }
753
754 void DescriptorQualityEvaluator::writePlotData( int di ) const
755 {
756     stringstream filename;
757     filename << getPlotPath() << algName << "_" << DATASET_NAMES[di] << ".csv";
758     FILE *file = fopen (filename.str().c_str(), "w");
759     size_t size = calcDatasetQuality[di].size();
760     for (size_t i=0;i<size;i++)
761     {
762         fprintf( file, "%f, %f\n", 1 - calcDatasetQuality[di][i].precision, calcDatasetQuality[di][i].recall);
763     }
764     fclose( file );
765 }
766
767 void DescriptorQualityEvaluator::readAlgorithm( )
768 {
769     defaultDescMatcher = GenericDescriptorMatcher::create( algName );
770     specificDescMatcher = GenericDescriptorMatcher::create( algName );
771
772     if( !defaultDescMatcher )
773     {
774         Ptr<DescriptorExtractor> extractor = DescriptorExtractor::create( algName );
775         Ptr<DescriptorMatcher> matcher = DescriptorMatcher::create( matcherName );
776         defaultDescMatcher = makePtr<VectorDescriptorMatch>( extractor, matcher );
777         specificDescMatcher = makePtr<VectorDescriptorMatch>( extractor, matcher );
778
779         if( !extractor || !matcher )
780         {
781             printf("Algorithm can not be read\n");
782             exit(-1);
783         }
784     }
785 }
786
787 void DescriptorQualityEvaluator::calculatePlotData( vector<vector<DMatch> > &allMatches, vector<vector<uchar> > &allCorrectMatchesMask, int di )
788 {
789     vector<Point2f> recallPrecisionCurve;
790     computeRecallPrecisionCurve( allMatches, allCorrectMatchesMask, recallPrecisionCurve );
791
792     calcDatasetQuality[di].clear();
793     const float resultPrecision = 0.5;
794     bool isResultCalculated = false;
795     const double eps = 1e-2;
796
797     Quality initQuality;
798     initQuality.recall = 0;
799     initQuality.precision = 0;
800     calcDatasetQuality[di].push_back( initQuality );
801
802     for( size_t i=0;i<recallPrecisionCurve.size();i++ )
803     {
804         Quality quality;
805         quality.recall = recallPrecisionCurve[i].y;
806         quality.precision = 1 - recallPrecisionCurve[i].x;
807         Quality back = calcDatasetQuality[di].back();
808
809         if( fabs( quality.recall - back.recall ) < eps && fabs( quality.precision - back.precision ) < eps )
810             continue;
811
812         calcDatasetQuality[di].push_back( quality );
813
814         if( !isResultCalculated && quality.precision < resultPrecision )
815         {
816             for(int ci=0;ci<TEST_CASE_COUNT;ci++)
817             {
818                 calcQuality[di][ci].recall = quality.recall;
819                 calcQuality[di][ci].precision = quality.precision;
820             }
821             isResultCalculated = true;
822         }
823     }
824 }
825
826 void DescriptorQualityEvaluator::runDatasetTest (const vector<Mat> &imgs, const vector<Mat> &Hs, int di, int &progress)
827 {
828     FileStorage keypontsFS( data_path + KEYPOINTS_DIR + commRunParams[di].keypontsFilename, FileStorage::READ );
829     if( !keypontsFS.isOpened())
830     {
831        calcQuality[di].clear();
832        printf( "keypoints from file %s can not be read\n", commRunParams[di].keypontsFilename.c_str() );
833        return;
834     }
835
836     Ptr<GenericDescriptorMatcher> descMatch = commRunParams[di].isActiveParams ? specificDescMatcher : defaultDescMatcher;
837     calcQuality[di].resize(TEST_CASE_COUNT);
838
839     vector<KeyPoint> keypoints1;
840     readKeypoints( keypontsFS, keypoints1, 0);
841
842     int progressCount = DATASETS_COUNT*TEST_CASE_COUNT;
843
844     vector<vector<DMatch> > allMatches1to2;
845     vector<vector<uchar> > allCorrectMatchesMask;
846     for( int ci = 0; ci < TEST_CASE_COUNT; ci++ )
847     {
848         progress = update_progress( testName, progress, di*TEST_CASE_COUNT + ci + 1, progressCount, 0 );
849
850         vector<KeyPoint> keypoints2;
851         if( commRunParams[di].projectKeypointsFrom1Image )
852         {
853             // TODO need to test function calcKeyPointProjections
854             calcKeyPointProjections( keypoints1, Hs[ci], keypoints2 );
855             filterKeyPointsByImageSize( keypoints2,  imgs[ci+1].size() );
856         }
857         else
858             readKeypoints( keypontsFS, keypoints2, ci+1 );
859         // TODO if( commRunParams[di].matchFilter )
860
861         vector<vector<DMatch> > matches1to2;
862         vector<vector<uchar> > correctMatchesMask;
863         vector<Point2f> recallPrecisionCurve; // not used because we need recallPrecisionCurve for
864                                               // all images in dataset
865         evaluateGenericDescriptorMatcher( imgs[0], imgs[ci+1], Hs[ci], keypoints1, keypoints2,
866                                           &matches1to2, &correctMatchesMask, recallPrecisionCurve,
867                                           descMatch );
868         allMatches1to2.insert( allMatches1to2.end(), matches1to2.begin(), matches1to2.end() );
869         allCorrectMatchesMask.insert( allCorrectMatchesMask.end(), correctMatchesMask.begin(), correctMatchesMask.end() );
870     }
871
872     calculatePlotData( allMatches1to2, allCorrectMatchesMask, di );
873 }
874
875 //--------------------------------- Calonder descriptor test --------------------------------------------
876 class CalonderDescriptorQualityEvaluator : public DescriptorQualityEvaluator
877 {
878 public:
879     CalonderDescriptorQualityEvaluator() :
880             DescriptorQualityEvaluator( "Calonder", "quality-descriptor-calonder") {}
881     virtual void readAlgorithm( )
882     {
883         string classifierFile = data_path + "/features2d/calonder_classifier.rtc";
884         Ptr<DescriptorExtractor> extractor = makePtr<CalonderDescriptorExtractor<float> >( classifierFile );
885         defaultDescMatcher = makePtr<VectorDescriptorMatch>(
886             extractor,
887             makePtr<BFMatcher>(extractor->defaultNorm()));
888         specificDescMatcher = defaultDescMatcher;
889     }
890 };
891
892 //--------------------------------- One Way descriptor test --------------------------------------------
893 class OneWayDescriptorQualityTest : public DescriptorQualityEvaluator
894 {
895 public:
896     OneWayDescriptorQualityTest() :
897         DescriptorQualityEvaluator("ONEWAY", "quality-descriptor-one-way")
898     {
899     }
900 protected:
901     virtual void processRunParamsFile ();
902     virtual void writeDatasetRunParams( FileStorage& fs, int datasetIdx ) const;
903 };
904
905 void OneWayDescriptorQualityTest::processRunParamsFile ()
906 {
907     string filename = getRunParamsFilename();
908     FileStorage fs = FileStorage (filename, FileStorage::READ);
909     FileNode fn = fs.getFirstTopLevelNode();
910     fn = fn[DEFAULT_PARAMS];
911
912     string pcaFilename = data_path + (string)fn["pcaFilename"];
913     string trainPath = data_path + (string)fn["trainPath"];
914     string trainImagesList = (string)fn["trainImagesList"];
915     int patch_width = fn["patchWidth"];
916     int patch_height = fn["patchHeight"];
917     Size patchSize = cvSize (patch_width, patch_height);
918     int poseCount = fn["poseCount"];
919
920     if (trainImagesList.length () == 0 )
921         return;
922
923     fs.release ();
924
925     readAllDatasetsRunParams();
926
927     Ptr<OneWayDescriptorBase> base(
928         new OneWayDescriptorBase(patchSize, poseCount, pcaFilename,
929                                  trainPath, trainImagesList));
930
931     Ptr<OneWayDescriptorMatch> match = makePtr<OneWayDescriptorMatch>();
932     match->initialize( OneWayDescriptorMatch::Params (), base );
933     defaultDescMatcher = match;
934     writeAllDatasetsRunParams();
935 }
936
937 void OneWayDescriptorQualityTest::writeDatasetRunParams( FileStorage& fs, int datasetIdx ) const
938 {
939     fs << IS_ACTIVE_PARAMS << commRunParams[datasetIdx].isActiveParams;
940     fs << KEYPOINTS_FILENAME << commRunParams[datasetIdx].keypontsFilename;
941     fs << PROJECT_KEYPOINTS_FROM_1IMAGE << commRunParams[datasetIdx].projectKeypointsFrom1Image;
942     fs << MATCH_FILTER << commRunParams[datasetIdx].matchFilter;
943 }
944
945 int main( int argc, char** argv )
946 {
947     if( argc != 2 )
948     {
949         cout << "Format: " << argv[0] << " testdata path (path to testdata/cv)" << endl;
950         return -1;
951     }
952
953     data_path = argv[1];
954 #ifdef WIN32
955     if( *data_path.rbegin() != '\\' )
956         data_path = data_path + "\\";
957 #else
958     if( *data_path.rbegin() != '/' )
959         data_path = data_path + "/";
960 #endif
961
962     Ptr<BaseQualityEvaluator> evals[] =
963     {
964         makePtr<DetectorQualityEvaluator>( "FAST", "quality-detector-fast" ),
965         makePtr<DetectorQualityEvaluator>( "GFTT", "quality-detector-gftt" ),
966         makePtr<DetectorQualityEvaluator>( "HARRIS", "quality-detector-harris" ),
967         makePtr<DetectorQualityEvaluator>( "MSER", "quality-detector-mser" ),
968         makePtr<DetectorQualityEvaluator>( "STAR", "quality-detector-star" ),
969         makePtr<DetectorQualityEvaluator>( "SIFT", "quality-detector-sift" ),
970         makePtr<DetectorQualityEvaluator>( "SURF", "quality-detector-surf" ),
971
972         makePtr<DescriptorQualityEvaluator>( "SIFT", "quality-descriptor-sift", "BruteForce" ),
973         makePtr<DescriptorQualityEvaluator>( "SURF", "quality-descriptor-surf", "BruteForce" ),
974         makePtr<DescriptorQualityEvaluator>( "FERN", "quality-descriptor-fern"),
975         makePtr<CalonderDescriptorQualityEvaluator>()
976     };
977
978     for( size_t i = 0; i < sizeof(evals)/sizeof(evals[0]); i++ )
979     {
980         evals[i]->run();
981         cout << endl;
982     }
983 }