diff --git a/tests/cv/src/adetectordescriptor_evaluation.cpp b/tests/cv/src/adetectordescriptor_evaluation.cpp new file mode 100644 index 000000000..257840d10 --- /dev/null +++ b/tests/cv/src/adetectordescriptor_evaluation.cpp @@ -0,0 +1,1812 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// Intel License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2000, Intel Corporation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of Intel Corporation may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +//M*/ + +#include "cvtest.h" +#include +#include + +using namespace std; +using namespace cv; + +#define AFFINE_COVARIANT_VERSION + +inline Point2f applyHomography( const Mat_& H, const Point2f& pt ) +{ + double z = H(2,0)*pt.x + H(2,1)*pt.y + H(2,2); + if( z ) + { + double w = 1./z; + return Point2f( (H(0,0)*pt.x + H(0,1)*pt.y + H(0,2))*w, (H(1,0)*pt.x + H(1,1)*pt.y + H(1,2))*w ); + } + return Point2f( numeric_limits::max(), numeric_limits::max() ); +} + +inline void linearizeHomographyAt( const Mat_& H, const Point2f& pt, Mat_& A ) +{ + A.create(2,2); + double p1 = H(0,0)*pt.x + H(0,1)*pt.y + H(0,2), + p2 = H(1,0)*pt.x + H(1,1)*pt.y + H(1,2), + p3 = H(2,0)*pt.x + H(2,1)*pt.y + H(2,2), + p3_2 = p3*p3; + if( p3 ) + { + A(0,0) = H(0,0)/p3 - p1*H(2,0)/p3_2; // fxdx + A(0,1) = H(0,1)/p3 - p1*H(2,1)/p3_2; // fxdy + + A(1,0) = H(1,0)/p3 - p2*H(2,0)/p3_2; // fydx + A(1,1) = H(1,1)/p3 - p2*H(2,1)/p3_2; // fydx + } + else + A.setTo(Scalar::all(numeric_limits::max())); +} + +#ifndef AFFINE_COVARIANT_VERSION +/****************************************************************************************\ +* 1. Initial version of evaluating detectors. This version calculate repeatability * +* for scale invariant detectors (circular regions) * +\****************************************************************************************/ + +// Find the key points located in the part of the scene present in both images +// and project keypoints2 on img1 +void getCircularKeyPointsInCommonPart( const Mat& img1, const Mat img2, const Mat& H12, + const vector& keypoints1, const vector& keypoints2, + vector& ckeypoints1, vector& ckeypoints2t ) +{ + assert( !img1.empty() && !img2.empty() ); + assert( !H12.empty() && H12.cols==3 && H12.rows==3 && H12.type()==CV_64FC1 ); + ckeypoints1.clear(); + ckeypoints2t.clear(); + + Rect r1(0, 0, img1.cols, img1.rows), r2(0, 0, img2.cols, img2.rows); + Mat H21; invert( H12, H21 ); + + for( vector::const_iterator it = keypoints1.begin(); + it != keypoints1.end(); ++it ) + { + if( r2.contains(applyHomography(H12, it->pt)) ) + ckeypoints1.push_back(*it); + } + for( vector::const_iterator it = keypoints2.begin(); + it != keypoints2.end(); ++it ) + { + Point2f pt = applyHomography(H21, it->pt); + if( r1.contains(pt) ) + { + KeyPoint kp = *it; + kp.pt = pt; + Mat_ A, eval; + linearizeHomographyAt(H21, it->pt, A); + eigen(A, eval); + assert( eval.type()==CV_64FC1 && eval.cols==1 && eval.rows==2 ); + kp.size *= sqrt(eval(0,0) * eval(1,0)) /*scale from linearized homography matrix*/; + ckeypoints2t.push_back(kp); + } + } +} + +// Locations p1 and p2 are repeated if ||p1 - H21*p2|| < 1.5 pixels. +// Regions are repeated if Es < 0.4 (Es differs for scale invariant and affine invarian detectors). +// For more details see "Scale&Affine Invariant Interest Point Detectors", Mikolajczyk, Schmid. +void evaluateScaleInvDetectors( const Mat& img1, const Mat img2, const Mat& H12, + const vector& keypoints1, const vector& keypoints2, + int& repeatingLocationCount, float& repeatingLocationRltv, + int& repeatingRegionCount, float& repeatingRegionRltv ) +{ + const double locThreshold = 1.5, + regThreshold = 0.4; + assert( !img1.empty() && !img2.empty() ); + assert( !H12.empty() && H12.cols==3 && H12.rows==3 && H12.type()==CV_64FC1 ); + + Mat H21; invert( H12, H21 ); + + vector ckeypoints1, ckeypoints2t; + getCircularKeyPointsInCommonPart( img1, img2, H12, keypoints1, keypoints2, ckeypoints1, ckeypoints2t ); + + vector *smallKPSet = &ckeypoints1, *bigKPSet = &ckeypoints2t; + if( ckeypoints1.size() > ckeypoints2t.size() ) + { + smallKPSet = &ckeypoints2t; + bigKPSet = &ckeypoints1; + } + + if( smallKPSet->size() == 0 ) + { + repeatingLocationCount = repeatingRegionCount = -1; + repeatingLocationRltv = repeatingRegionRltv = -1.f; + } + else + { + vector matchedMask( bigKPSet->size(), false); + repeatingLocationCount = repeatingRegionCount = 0; + for( vector::const_iterator skpIt = smallKPSet->begin(); skpIt != smallKPSet->end(); ++skpIt ) + { + int nearestIdx = -1, bkpIdx = 0; + double minDist = numeric_limits::max(); + vector::const_iterator nearestBkp; + for( vector::const_iterator bkpIt = bigKPSet->begin(); bkpIt != bigKPSet->end(); ++bkpIt, bkpIdx++ ) + { + if( !matchedMask[bkpIdx] ) + { + Point p1(cvRound(skpIt->pt.x), cvRound(skpIt->pt.y)), + p2(cvRound(bkpIt->pt.x), cvRound(bkpIt->pt.y)); + double dist = norm(p1 - p2); + if( dist < minDist ) + { + nearestIdx = bkpIdx; + minDist = dist; + nearestBkp = bkpIt; + } + } + } + if( minDist < locThreshold ) + { + matchedMask[nearestIdx] = true; + repeatingLocationCount++; + double minRadius = min( skpIt->size, nearestBkp->size ), + maxRadius = max( skpIt->size, nearestBkp->size ); + double Es = abs(1 - (minRadius*minRadius)/(maxRadius*maxRadius)); + if( Es < regThreshold ) + repeatingRegionCount++; + } + } + repeatingLocationRltv = smallKPSet->size() ? (float)repeatingLocationCount / smallKPSet->size() : 0; + repeatingRegionRltv = smallKPSet->size() ? (float)repeatingRegionCount / smallKPSet->size() : 0; + } +} +#else +/****************************************************************************************\ +* 2. Functions to evaluate affine covariant detectors and descriptors. * +\****************************************************************************************/ +class EllipticKeyPoint +{ +public: + EllipticKeyPoint(); + EllipticKeyPoint( const Point2f& _center, const Scalar& _ellipse ); + + static Mat_ getSecondMomentsMatrix( const Scalar& _ellipse ); + Mat_ getSecondMomentsMatrix() const; + + void calcProjection( const Mat_& H, EllipticKeyPoint& projection ) const; + + Point2f center; + Scalar ellipse; // 3 elements a, b, c: ax^2+2bxy+cy^2=1 + Size_ axes; // half lenght of elipse axes + Size_ boundingBox; // half sizes of bounding box +}; + +EllipticKeyPoint::EllipticKeyPoint() +{ + *this = EllipticKeyPoint(Point2f(0,0), Scalar(1, 0, 1) ); +} + +EllipticKeyPoint::EllipticKeyPoint( const Point2f& _center, const Scalar& _ellipse ) +{ + center = _center; + ellipse = _ellipse; + + Mat_ M = getSecondMomentsMatrix(_ellipse), eval; + eigen( M, eval ); + assert( eval.rows == 2 && eval.cols == 1 ); + axes.width = 1.f / sqrt(eval(0,0)); + axes.height = 1.f / sqrt(eval(1,0)); + + float ac_b2 = ellipse[0]*ellipse[2] - ellipse[1]*ellipse[1]; + boundingBox.width = sqrt(ellipse[2]/ac_b2); + boundingBox.height = sqrt(ellipse[0]/ac_b2); +} + +Mat_ EllipticKeyPoint::getSecondMomentsMatrix( const Scalar& _ellipse ) +{ + Mat_ M(2, 2); + M(0,0) = _ellipse[0]; + M(1,0) = M(0,1) = _ellipse[1]; + M(1,1) = _ellipse[2]; + return M; +} + +Mat_ EllipticKeyPoint::getSecondMomentsMatrix() const +{ + return getSecondMomentsMatrix(ellipse); +} + +void EllipticKeyPoint::calcProjection( const Mat_& H, EllipticKeyPoint& projection ) const +{ + Point2f dstCenter = applyHomography(H, center); + + Mat_ invM; invert(getSecondMomentsMatrix(), invM); + Mat_ Aff; linearizeHomographyAt(H, center, Aff); + Mat_ dstM; invert(Aff*invM*Aff.t(), dstM); + + projection = EllipticKeyPoint( dstCenter, Scalar(dstM(0,0), dstM(0,1), dstM(1,1)) ); +} + +void calcEllipticKeyPointProjections( const vector& src, const Mat_& H, vector& dst ) +{ + assert( !src.empty() && !H.empty() && H.cols == 3 && H.rows == 3); + dst.resize(src.size()); + vector::const_iterator srcIt = src.begin(); + vector::iterator dstIt = dst.begin(); + for( ; srcIt != src.end(); ++srcIt, ++dstIt ) + srcIt->calcProjection(H, *dstIt); +} + +void transformToEllipticKeyPoints( const vector& src, vector& dst ) +{ + assert( !src.empty() ); + dst.resize(src.size()); + for( size_t i = 0; i < src.size(); i++ ) + { + float rad = src[i].size; + assert( rad ); + float fac = 1.f/(rad*rad); + dst[i] = EllipticKeyPoint( src[i].pt, Scalar(fac, 0, fac) ); + } +} + +void transformToKeyPoints( const vector& src, vector& dst ) +{ + assert( !src.empty() ); + dst.resize(src.size()); + for( size_t i = 0; i < src.size(); i++ ) + { + Size_ axes = src[i].axes; + float rad = sqrt(axes.height*axes.width); + dst[i] = KeyPoint(src[i].center, rad ); + } +} + +void calcKeyPointProjections( const vector& src, const Mat_& H, vector& dst ) +{ + assert( !src.empty() && !H.empty() && H.cols == 3 && H.rows == 3); + dst.resize(src.size()); + vector::const_iterator srcIt = src.begin(); + vector::iterator dstIt = dst.begin(); + for( ; srcIt != src.end(); ++srcIt, ++dstIt ) + { + Point2f dstPt = applyHomography(H, srcIt->pt); + + Mat_ Aff; linearizeHomographyAt(H, srcIt->pt, Aff); + Mat_ eval; eigen(Aff, eval); + assert( eval.type()==CV_64FC1 && eval.cols==1 && eval.rows==2 ); + float dstSize = srcIt->size * sqrt(eval(0,0) * eval(1,0)) /*scale from linearized homography matrix*/; + assert( dstSize ); + assert( dstSize < 0.5 ); // TODO check for surf + + // calculate new anngle + float srcAngleRad = srcIt->angle*CV_PI/180; + Point2f vec1(cos(srcAngleRad), sin(srcAngleRad)), vec2; + vec2 = applyHomography(H, vec1); + float w = 1.f/norm(vec2); + vec2 = vec2*w; + float dstAngleGrad = acos(vec2.x)*180.f/CV_PI; // 0..180 + if( asin(vec2.y) < 0 ) // -pi/2 .. pi/2 + dstAngleGrad += 180; + *dstIt = KeyPoint( dstPt, dstSize, dstAngleGrad, srcIt->response, srcIt->octave, srcIt->class_id ); + } +} + +void filterKeyPointsByImageSize( vector& keypoints, const Size& imgSize, vector& origIdxs ) +{ + vector filtered; + filtered.reserve(keypoints.size()); + Rect r(0, 0, imgSize.width, imgSize.height); + origIdxs.clear(); + vector::const_iterator it = keypoints.begin(); + for( int i = 0; it != keypoints.end(); ++it, i++ ) + { + if( r.contains(it->pt) ) + { + filtered.push_back(*it); + origIdxs.push_back(i); + } + } + keypoints.assign(filtered.begin(), filtered.end()); +} + +/* + * calulate ovelap errors + */ +void overlap( const vector& keypoints1, const vector& keypoints2t, bool commonPart, + SparseMat_& overlaps ) +{ + assert( !keypoints1.empty() && !keypoints2t.empty() ); + int size[] = { keypoints1.size(), keypoints2t.size() }; + overlaps.create( 2, size ); + + for( size_t i1 = 0; i1 < keypoints1.size(); i1++ ) + { + EllipticKeyPoint kp1 = keypoints1[i1]; + float maxDist = sqrt(kp1.axes.width*kp1.axes.height), + fac = 30.f/maxDist; + if( !commonPart) + fac=3; + + maxDist = maxDist*4; + fac = 1.0/(fac*fac); + + EllipticKeyPoint keypoint1a = EllipticKeyPoint( kp1.center, Scalar(fac*kp1.ellipse[0], fac*kp1.ellipse[1], fac*kp1.ellipse[2]) ); + + for( size_t i2 = 0; i2 < keypoints2t.size(); i2++ ) + { + EllipticKeyPoint kp2 = keypoints2t[i2]; + Point2f diff = kp2.center - kp1.center; + + if( norm(diff) < maxDist ) + { + EllipticKeyPoint keypoint2a = EllipticKeyPoint( kp2.center, Scalar(fac*kp2.ellipse[0], fac*kp2.ellipse[1], fac*kp2.ellipse[2]) ); + //find the largest eigenvalue + float maxx = ceil(( keypoint1a.boundingBox.width > (diff.x+keypoint2a.boundingBox.width)) ? + keypoint1a.boundingBox.width : (diff.x+keypoint2a.boundingBox.width)); + float minx = floor((-keypoint1a.boundingBox.width < (diff.x-keypoint2a.boundingBox.width)) ? + -keypoint1a.boundingBox.width : (diff.x-keypoint2a.boundingBox.width)); + + float maxy = ceil(( keypoint1a.boundingBox.height > (diff.y+keypoint2a.boundingBox.height)) ? + keypoint1a.boundingBox.height : (diff.y+keypoint2a.boundingBox.height)); + float miny = floor((-keypoint1a.boundingBox.height < (diff.y-keypoint2a.boundingBox.height)) ? + -keypoint1a.boundingBox.height : (diff.y-keypoint2a.boundingBox.height)); + float mina = (maxx-minx) < (maxy-miny) ? (maxx-minx) : (maxy-miny) ; + float dr = mina/50.0; + float bua = 0, bna = 0; + //compute the area + for( float rx1 = minx; rx1 <= maxx; rx1+=dr ) + { + float rx2 = rx1-diff.x; + for( float ry1=miny; ry1<=maxy; ry1+=dr ) + { + float ry2=ry1-diff.y; + //compute the distance from the ellipse center + float e1 = keypoint1a.ellipse[0]*rx1*rx1+2*keypoint1a.ellipse[1]*rx1*ry1+keypoint1a.ellipse[2]*ry1*ry1; + float e2 = keypoint2a.ellipse[0]*rx2*rx2+2*keypoint2a.ellipse[1]*rx2*ry2+keypoint2a.ellipse[2]*ry2*ry2; + //compute the area + if( e1<1 && e2<1 ) bna++; + if( e1<1 || e2<1 ) bua++; + } + } + if( bna > 0) + overlaps.ref(i1,i2) = 100.0*bna/bua; + } + } + } +} + +void filterEllipticKeyPointsByImageSize( vector& keypoints, const Size& imgSize ) +{ + vector filtered; + filtered.reserve(keypoints.size()); + vector::const_iterator it = keypoints.begin(); + for( int i = 0; it != keypoints.end(); ++it, i++ ) + { + if( it->center.x + it->boundingBox.width < imgSize.width && + it->center.x - it->boundingBox.width > 0 && + it->center.y + it->boundingBox.height < imgSize.height && + it->center.y - it->boundingBox.height > 0 ) + filtered.push_back(*it); + } + keypoints.assign(filtered.begin(), filtered.end()); +} + +void getEllipticKeyPointsInCommonPart( vector& keypoints1, vector& keypoints2, + vector& keypoints1t, vector& keypoints2t, + Size& imgSize1, const Size& imgSize2 ) +{ + assert( !keypoints1.empty() && !keypoints2.empty() ); + assert( keypoints1t.size() == keypoints1.size() && keypoints2t.size() == keypoints2.size() ); + + filterEllipticKeyPointsByImageSize( keypoints1, imgSize1 ); + filterEllipticKeyPointsByImageSize( keypoints1t, imgSize2 ); + filterEllipticKeyPointsByImageSize( keypoints2, imgSize2 ); + filterEllipticKeyPointsByImageSize( keypoints2t, imgSize1 ); +} + +void calculateRepeatability( const vector& _keypoints1, const vector& _keypoints2, + const Mat& img1, const Mat& img2, const Mat& H1to2, + float& repeatability, int& correspondencesCount, + SparseMat_* thresholdedOverlapMask=0 ) +{ + vector keypoints1( _keypoints1.begin(), _keypoints1.end() ), + keypoints2( _keypoints2.begin(), _keypoints2.end() ), + keypoints1t( keypoints1.size() ), + keypoints2t( keypoints2.size() ); + + // calculate projections of key points + calcEllipticKeyPointProjections( keypoints1, H1to2, keypoints1t ); + Mat H2to1; invert(H1to2, H2to1); + calcEllipticKeyPointProjections( keypoints2, H2to1, keypoints2t ); + + bool ifEvaluateDetectors = !thresholdedOverlapMask; // == commonPart + float overlapThreshold; + if( ifEvaluateDetectors ) + { + overlapThreshold = 100.f - 40.f; + + // remove key points from outside of the common image part + Size sz1 = img1.size(), sz2 = img2.size(); + getEllipticKeyPointsInCommonPart( keypoints1, keypoints2, keypoints1t, keypoints2t, sz1, sz2 ); + } + else + { + overlapThreshold = 100.f - 50.f; + } + int minCount = min( keypoints1.size(), keypoints2t.size() ); + + // calculate overlap errors + SparseMat_ overlaps; + overlap( keypoints1, keypoints2t, ifEvaluateDetectors, overlaps ); + + correspondencesCount = -1; + repeatability = -1.f; + const int* size = overlaps.size(); + if( !size || overlaps.nzcount() == 0 ) + return; + + if( ifEvaluateDetectors ) + { + // regions one-to-one matching + correspondencesCount = 0; + SparseMat_ currOverlaps( 2, size ); + for( int y = 0; y < size[0]; y++ ) + { + for( int x = 0; x < size[1]; x++ ) + { + float val = overlaps(y,x); + if ( val >= overlapThreshold ) + currOverlaps.ref(y,x) = val; + } + } + while( currOverlaps.nzcount() > 0 ) + { + double maxOverlap = 0; + int maxIdx[2]; + minMaxLoc( currOverlaps, 0, &maxOverlap, 0, maxIdx ); + for( size_t i1 = 0; i1 < keypoints1.size(); i1++ ) + currOverlaps.erase(i1, maxIdx[1]); + for( size_t i2 = 0; i2 < keypoints2t.size(); i2++ ) + currOverlaps.erase(maxIdx[0], i2); + correspondencesCount++; + } + repeatability = minCount ? (float)(correspondencesCount*100)/minCount : 0; + } + else + { + thresholdedOverlapMask->create( 2, size ); + for( int y = 0; y < size[0]; y++ ) + { + for( int x = 0; x < size[1]; x++ ) + { + float val = overlaps(y,x); + if ( val >= overlapThreshold ) + thresholdedOverlapMask->ref(y,x) = val; + } + } + } +} + + +void evaluateDetectors( const vector& keypoints1, const vector& keypoints2, + const Mat& img1, const Mat& img2, const Mat& H1to2, + float& repeatability, int& correspCount ) +{ + calculateRepeatability( keypoints1, keypoints2, + img1, img2, H1to2, + repeatability, correspCount ); +} + +inline float recall( int correctMatchCount, int correspondenceCount ) +{ + return correspondenceCount ? (float)correctMatchCount / (float)correspondenceCount : 0; +} + +inline float precision( int correctMatchCount, int falseMatchCount ) +{ + return correctMatchCount + falseMatchCount ? (float)correctMatchCount / (float)(correctMatchCount + falseMatchCount) : 0; +} + +void evaluateDescriptors( const vector& keypoints1, const vector& keypoints2, + const vector& matches1to2, + const Mat& img1, const Mat& img2, const Mat& H1to2, + int& correctMatchCount, int& falseMatchCount, int& correspondenceCount ) +{ + assert( !keypoints1.empty() && !keypoints2.empty() && !matches1to2.empty() ); + assert( keypoints1.size() == matches1to2.size() ); + + float repeatability; + int correspCount; + SparseMat_ thresholdedOverlapMask; // thresholded allOverlapErrors + calculateRepeatability( keypoints1, keypoints2, + img1, img2, H1to2, + repeatability, correspCount, + &thresholdedOverlapMask ); + correspondenceCount = thresholdedOverlapMask.nzcount(); + correctMatchCount = falseMatchCount = 0; + for( size_t i1 = 0; i1 < matches1to2.size(); i1++ ) + { + int i2 = matches1to2[i1]; + if( i2 > 0 ) + { + if( thresholdedOverlapMask(i1, i2) ) + correctMatchCount++; + else + falseMatchCount++; + } + } +} + +#endif +/****************************************************************************************\ +* Detectors evaluation * +\****************************************************************************************/ +const int DATASETS_COUNT = 8; +const int TEST_CASE_COUNT = 5; + +const string IMAGE_DATASETS_DIR = "detectors_descriptors_evaluation/images_datasets/"; +const string DETECTORS_DIR = "detectors_descriptors_evaluation/detectors/"; +const string DESCRIPTORS_DIR = "detectors_descriptors_evaluation/descriptors/"; +const string KEYPOINTS_DIR = "detectors_descriptors_evaluation/keypoints_datasets/"; + +const string PARAMS_POSTFIX = "_params.xml"; +const string RES_POSTFIX = "_res.xml"; + +#ifndef AFFINE_COVARIANT_VERSION +const string RLC = "repeating_locations_count"; +const string RLR = "repeating_locations_rltv"; +const string RRC = "repeating_regions_count"; +const string RRR = "repeating_regions_rltv"; +#else +const string REPEAT = "repeatability"; +const string CORRESP_COUNT = "correspondence_count"; +#endif + +string DATASET_NAMES[DATASETS_COUNT] = { "bark", "bikes", "boat", "graf", "leuven", "trees", "ubc", "wall"}; + +class BaseQualityTest : public CvTest +{ +public: + BaseQualityTest( const char* _algName, const char* _testName, const char* _testFuncs ) : + CvTest( _testName, _testFuncs ), algName(_algName) {} + +protected: + virtual string getRunParamsFilename() const = 0; + virtual string getResultsFilename() const = 0; + + virtual void validQualityClear( int datasetIdx ) = 0; + virtual void validQualityCreate( int datasetIdx ) = 0; + virtual bool isValidQualityEmpty( int datasetIdx ) const = 0; + virtual bool isCalcQualityEmpty( int datasetIdx ) const = 0; + + void readAllDatasetsRunParams(); + virtual void readDatasetRunParams( FileNode& fn, int datasetIdx ) = 0; + void writeAllDatasetsRunParams() const; + virtual void writeDatasetRunParams( FileStorage& fs, int datasetIdx ) const = 0; + void setDefaultAllDatasetsRunParams(); + virtual void setDefaultDatasetRunParams( int datasetIdx ) = 0; + + virtual void readResults(); + virtual void readResults( FileNode& fn, int datasetIdx, int caseIdx ) = 0; + void writeResults() const; + virtual void writeResults( FileStorage& fs, int datasetIdx, int caseIdx ) const = 0; + + bool readDataset( const string& datasetName, vector& Hs, vector& imgs ); + + virtual void processResults(); + virtual int processResults( int datasetIdx, int caseIdx ) = 0; + + string algName; + bool isWriteParams, isWriteResults; +}; + +void BaseQualityTest::readAllDatasetsRunParams() +{ + string filename = getRunParamsFilename(); + FileStorage fs( filename, FileStorage::READ ); + if( !fs.isOpened() ) + { + isWriteParams = true; + setDefaultAllDatasetsRunParams(); + ts->printf(CvTS::LOG, "all runParams are default\n"); + } + else + { + isWriteParams = false; + FileNode topfn = fs.getFirstTopLevelNode(); + for( int i = 0; i < DATASETS_COUNT; i++ ) + { + FileNode fn = topfn[DATASET_NAMES[i]]; + if( fn.empty() ) + { + ts->printf( CvTS::LOG, "%d-runParams is default\n", i); + setDefaultDatasetRunParams(i); + } + else + readDatasetRunParams(fn, i); + } + } +} + +void BaseQualityTest::writeAllDatasetsRunParams() const +{ + string filename = getRunParamsFilename(); + FileStorage fs( filename, FileStorage::WRITE ); + if( fs.isOpened() ) + { + fs << "run_params" << "{"; // top file node + for( int i = 0; i < DATASETS_COUNT; i++ ) + { + fs << DATASET_NAMES[i] << "{"; + writeDatasetRunParams(fs, i); + fs << "}"; + } + fs << "}"; + } + else + ts->printf(CvTS::LOG, "file %s for writing run params can not be opened\n", filename.c_str() ); +} + +void BaseQualityTest::setDefaultAllDatasetsRunParams() +{ + for( int i = 0; i < DATASETS_COUNT; i++ ) + setDefaultDatasetRunParams(i); +} + +bool BaseQualityTest::readDataset( const string& datasetName, vector& Hs, vector& imgs ) +{ + Hs.resize( TEST_CASE_COUNT ); + imgs.resize( TEST_CASE_COUNT+1 ); + string dirname = string(ts->get_data_path()) + IMAGE_DATASETS_DIR + datasetName + "/"; + + for( int i = 0; i < (int)Hs.size(); i++ ) + { + stringstream filename; filename << "H1to" << i+2 << "p.xml"; + FileStorage fs( dirname + filename.str(), FileStorage::READ ); + if( !fs.isOpened() ) + return false; + fs.getFirstTopLevelNode() >> Hs[i]; + } + + for( int i = 0; i < (int)imgs.size(); i++ ) + { + stringstream filename; filename << "img" << i+1 << ".png"; + imgs[i] = imread( dirname + filename.str(), 0 ); + if( imgs[i].empty() ) + return false; + } + return true; +} + +void BaseQualityTest::readResults() +{ + string filename = getResultsFilename(); + FileStorage fs( filename, FileStorage::READ ); + if( fs.isOpened() ) + { + isWriteResults = false; + FileNode topfn = fs.getFirstTopLevelNode(); + for( int di = 0; di < DATASETS_COUNT; di++ ) + { + FileNode datafn = topfn[DATASET_NAMES[di]]; + if( datafn.empty() ) + { + validQualityClear(di); + ts->printf( CvTS::LOG, "results for %s dataset were not read\n", + DATASET_NAMES[di].c_str() ); + } + else + { + validQualityCreate(di); + for( int ci = 0; ci < TEST_CASE_COUNT; ci++ ) + { + stringstream ss; ss << "case" << ci; + FileNode casefn = datafn[ss.str()]; + CV_Assert( !casefn.empty() ); + readResults( casefn , di, ci ); + } + } + } + } + else + isWriteResults = true; +} + +void BaseQualityTest::writeResults() const +{ + string filename = getResultsFilename();; + FileStorage fs( filename, FileStorage::WRITE ); + if( fs.isOpened() ) + { + fs << "results" << "{"; + for( int di = 0; di < DATASETS_COUNT; di++ ) + { + if( isCalcQualityEmpty(di) ) + { + ts->printf(CvTS::LOG, "results on %s dataset were not write because of empty\n", + DATASET_NAMES[di].c_str()); + } + else + { + fs << DATASET_NAMES[di] << "{"; + for( int ci = 0; ci < TEST_CASE_COUNT; ci++ ) + { + stringstream ss; ss << "case" << ci; + fs << ss.str() << "{"; + writeResults( fs, di, ci ); + fs << "}"; //ss.str() + } + fs << "}"; //DATASET_NAMES[di] + } + } + fs << "}"; //results + } + else + ts->printf(CvTS::LOG, "results were not written because file %s can not be opened\n", filename.c_str() ); +} + +void BaseQualityTest::processResults() +{ + if( isWriteParams ) + writeAllDatasetsRunParams(); + + int res = CvTS::OK; + if( isWriteResults ) + writeResults(); + else + { + for( int di = 0; di < DATASETS_COUNT; di++ ) + { + if( isValidQualityEmpty(di) || isCalcQualityEmpty(di) ) + continue; + + ts->printf(CvTS::LOG, "\nDataset: %s\n", DATASET_NAMES[di].c_str() ); + + for( int ci = 0; ci < TEST_CASE_COUNT; ci++ ) + { + ts->printf(CvTS::LOG, "case%d\n", ci); + int currRes = processResults( di, ci ); + res = currRes == CvTS::OK ? res : currRes; + } + } + } + + if( res != CvTS::OK ) + ts->printf(CvTS::LOG, "BAD ACCURACY\n"); + ts->set_failed_test_info( res ); +} + +class DetectorQualityTest : public BaseQualityTest +{ +public: + DetectorQualityTest( const char* _detectorName, const char* _testName ) : + BaseQualityTest( _detectorName, _testName, "quality-of-detector" ) + { + validQuality.resize(DATASETS_COUNT); + calcQuality.resize(DATASETS_COUNT); + } + +protected: + using BaseQualityTest::readResults; + using BaseQualityTest::writeResults; + using BaseQualityTest::processResults; + + virtual string getRunParamsFilename() const; + virtual string getResultsFilename() const; + + virtual void validQualityClear( int datasetIdx ); + virtual void validQualityCreate( int datasetIdx ); + virtual bool isValidQualityEmpty( int datasetIdx ) const; + virtual bool isCalcQualityEmpty( int datasetIdx ) const; + + virtual void readResults( FileNode& fn, int datasetIdx, int caseIdx ); + virtual void writeResults( FileStorage& fs, int datasetIdx, int caseIdx ) const; + + virtual void readDatasetRunParams( FileNode& fn, int datasetIdx ) = 0; + virtual void writeDatasetRunParams( FileStorage& fs, int datasetIdx ) const = 0; + virtual void setDefaultDatasetRunParams( int datasetIdx ) = 0; + + virtual FeatureDetector* createDetector( int datasetIdx ) = 0; + void openToWriteKeypointsFile( FileStorage& fs, int datasetIdx ); + + void run( int ); + virtual int processResults( int datasetIdx, int caseIdx ); + + struct Quality + { +#ifndef AFFINE_COVARIANT_VERSION + int repeatingLocationCount; + float repeatingLocationRltv; + int repeatingRegionCount; + float repeatingRegionRltv; +#else + float repeatability; + int correspondenceCount; +#endif + }; + vector > validQuality; + vector > calcQuality; +}; + +string DetectorQualityTest::getRunParamsFilename() const +{ + return string(ts->get_data_path()) + DETECTORS_DIR + algName + PARAMS_POSTFIX; +} + +string DetectorQualityTest::getResultsFilename() const +{ + return string(ts->get_data_path()) + DETECTORS_DIR + algName + RES_POSTFIX; +} + +void DetectorQualityTest::validQualityClear( int datasetIdx ) +{ + validQuality[datasetIdx].clear(); +} + +void DetectorQualityTest::validQualityCreate( int datasetIdx ) +{ + validQuality[datasetIdx].resize(TEST_CASE_COUNT); +} + +bool DetectorQualityTest::isValidQualityEmpty( int datasetIdx ) const +{ + return validQuality[datasetIdx].empty(); +} + +bool DetectorQualityTest::isCalcQualityEmpty( int datasetIdx ) const +{ + return calcQuality[datasetIdx].empty(); +} + +void DetectorQualityTest::readResults( FileNode& fn, int datasetIdx, int caseIdx ) +{ +#ifndef AFFINE_COVARIANT_VERSION + validQuality[datasetIdx][caseIdx].repeatingLocationCount = fn[RLC]; + validQuality[datasetIdx][caseIdx].repeatingLocationRltv = fn[RLR]; + validQuality[datasetIdx][caseIdx].repeatingRegionCount = fn[RRC]; + validQuality[datasetIdx][caseIdx].repeatingRegionRltv = fn[RRR]; +#else + validQuality[datasetIdx][caseIdx].repeatability = fn[REPEAT]; + validQuality[datasetIdx][caseIdx].correspondenceCount = fn[CORRESP_COUNT]; +#endif +} + +void DetectorQualityTest::writeResults( FileStorage& fs, int datasetIdx, int caseIdx ) const +{ +#ifndef AFFINE_COVARIANT_VERSION + fs << RLC << calcQuality[datasetIdx][caseIdx].repeatingLocationCount; + fs << RLR << calcQuality[datasetIdx][caseIdx].repeatingLocationRltv; + fs << RRC << calcQuality[datasetIdx][caseIdx].repeatingRegionCount; + fs << RRR << calcQuality[datasetIdx][caseIdx].repeatingRegionRltv; +#else + fs << REPEAT << calcQuality[datasetIdx][caseIdx].repeatability; + fs << CORRESP_COUNT << calcQuality[datasetIdx][caseIdx].correspondenceCount; +#endif +} + +void DetectorQualityTest::openToWriteKeypointsFile( FileStorage& fs, int datasetIdx ) +{ + string filename = string(ts->get_data_path()) + KEYPOINTS_DIR + algName + "_"+ + DATASET_NAMES[datasetIdx] + ".xml" ; + + // open file to write keypoints if there is not yet + fs.open(filename, FileStorage::READ); + if( fs.isOpened() ) + fs.release(); + else + { + fs.open(filename, FileStorage::WRITE); + if( !fs.isOpened() ) + ts->printf( CvTS::LOG, "keypoints can not be written in file %s because this file can not be opened\n", + filename.c_str()); + } +} + +inline void writeKeypoints( FileStorage& fs, const vector& keypoints, int imgIdx ) +{ + if( fs.isOpened() ) + { + stringstream imgName; imgName << "img" << imgIdx; + write( fs, imgName.str(), keypoints ); + } +} + +inline void readKeypoints( FileStorage& fs, vector& keypoints, int imgIdx ) +{ + assert( fs.isOpened() ); + stringstream imgName; imgName << "img" << imgIdx; + read( fs[imgName.str()], keypoints); +} + +void DetectorQualityTest::run( int ) +{ + readAllDatasetsRunParams(); + readResults(); + + int notReadDatasets = 0; + int progress = 0, progressCount = DATASETS_COUNT*TEST_CASE_COUNT; + for(int di = 0; di < DATASETS_COUNT; di++ ) + { + FileStorage keypontsFS; + openToWriteKeypointsFile( keypontsFS, di ); + + vector imgs, Hs; + if( !readDataset( DATASET_NAMES[di], Hs, imgs ) ) + { + calcQuality[di].clear(); + ts->printf( CvTS::LOG, "images or homography matrices of dataset named %s can not be read\n", + DATASET_NAMES[di].c_str()); + notReadDatasets++; + } + else + { + calcQuality[di].resize(TEST_CASE_COUNT); + Ptr detector = createDetector(di); + + vector keypoints1; vector ekeypoints1; + detector->detect( imgs[0], keypoints1 ); + writeKeypoints( keypontsFS, keypoints1, 0); + transformToEllipticKeyPoints( keypoints1, ekeypoints1 ); + for( int ci = 0; ci < TEST_CASE_COUNT; ci++ ) + { + progress = update_progress( progress, di*TEST_CASE_COUNT + ci, progressCount, 0 ); + vector keypoints2; + detector->detect( imgs[ci+1], keypoints2 ); + writeKeypoints( keypontsFS, keypoints2, ci+1); +#ifndef AFFINE_COVARIANT_VERSION + evaluateScaleInvDetectors( imgs[0], imgs[ci+1], Hs[ci], keypoints1, keypoints2, + calcQuality[di][ci].repeatingLocationCount, calcQuality[di][ci].repeatingLocationRltv, + calcQuality[di][ci].repeatingRegionCount, calcQuality[di][ci].repeatingRegionRltv ); +#else + vector ekeypoints2; + transformToEllipticKeyPoints( keypoints2, ekeypoints2 ); + evaluateDetectors( ekeypoints1, ekeypoints2, imgs[0], imgs[ci], Hs[ci], + calcQuality[di][ci].repeatability, calcQuality[di][ci].correspondenceCount ); +#endif + } + } + } + if( notReadDatasets == DATASETS_COUNT ) + { + ts->printf(CvTS::LOG, "All datasets were not be read\n"); + ts->set_failed_test_info( CvTS::FAIL_INVALID_TEST_DATA ); + } + else + processResults(); +} + +void testLog( CvTS* ts, bool isBadAccuracy ) +{ + if( isBadAccuracy ) + ts->printf(CvTS::LOG, " bad accuracy\n"); + else + ts->printf(CvTS::LOG, "\n"); +} + +int DetectorQualityTest::processResults( int datasetIdx, int caseIdx ) +{ + int res = CvTS::OK; + + Quality valid = validQuality[datasetIdx][caseIdx], calc = calcQuality[datasetIdx][caseIdx]; + + bool isBadAccuracy; + int countEps = 1; + const float rltvEps = 0.001; +#ifndef AFFINE_COVARIANT_VERSION + ts->printf(CvTS::LOG, "%s: calc=%d, valid=%d", RLC.c_str(), calc.repeatingLocationCount, valid.repeatingLocationCount ); + isBadAccuracy = valid.repeatingLocationCount - calc.repeatingLocationCount > countEps; + testLog( ts, isBadAccuracy ); + res = isBadAccuracy ? CvTS::FAIL_BAD_ACCURACY : res; + + ts->printf(CvTS::LOG, "%s: calc=%f, valid=%f", RLR.c_str(), calc.repeatingLocationRltv, valid.repeatingLocationRltv ); + isBadAccuracy = valid.repeatingLocationRltv - calc.repeatingLocationRltv > rltvEps; + testLog( ts, isBadAccuracy ); + res = isBadAccuracy ? CvTS::FAIL_BAD_ACCURACY : res; + + ts->printf(CvTS::LOG, "%s: calc=%d, valid=%d", RRC.c_str(), calc.repeatingRegionCount, valid.repeatingRegionCount ); + isBadAccuracy = valid.repeatingRegionCount - calc.repeatingRegionCount > countEps; + testLog( ts, isBadAccuracy ); + res = isBadAccuracy ? CvTS::FAIL_BAD_ACCURACY : res; + + ts->printf(CvTS::LOG, "%s: calc=%f, valid=%f", RRR.c_str(), calc.repeatingRegionRltv, valid.repeatingRegionRltv ); + isBadAccuracy = valid.repeatingRegionRltv - calc.repeatingRegionRltv > rltvEps; + testLog( ts, isBadAccuracy ); + res = isBadAccuracy ? CvTS::FAIL_BAD_ACCURACY : res; +#else + ts->printf(CvTS::LOG, "%s: calc=%f, valid=%f", REPEAT.c_str(), calc.repeatability, valid.repeatability ); + isBadAccuracy = valid.repeatability - calc.repeatability > rltvEps; + testLog( ts, isBadAccuracy ); + res = isBadAccuracy ? CvTS::FAIL_BAD_ACCURACY : res; + + ts->printf(CvTS::LOG, "%s: calc=%d, valid=%d", CORRESP_COUNT.c_str(), calc.correspondenceCount, valid.correspondenceCount ); + isBadAccuracy = valid.correspondenceCount - calc.correspondenceCount > countEps; + testLog( ts, isBadAccuracy ); + res = isBadAccuracy ? CvTS::FAIL_BAD_ACCURACY : res; +#endif + return res; +} + +//--------------------------------- FAST detector test -------------------------------------------- +class FastDetectorQualityTest : public DetectorQualityTest +{ +public: + FastDetectorQualityTest() : DetectorQualityTest( "fast", "quality-fast-detector" ) + { runParams.resize(DATASETS_COUNT); } + +protected: + virtual FeatureDetector* createDetector( int datasetIdx ); + virtual void readDatasetRunParams( FileNode& fn, int datasetIdx ); + virtual void writeDatasetRunParams( FileStorage& fs, int datasetIdx ) const; + virtual void setDefaultDatasetRunParams( int datasetIdx ); + + struct RunParams + { + int threshold; + bool nonmaxSuppression; + }; + vector runParams; +}; + +FeatureDetector* FastDetectorQualityTest::createDetector( int datasetIdx ) +{ + return new FastFeatureDetector( runParams[datasetIdx].threshold, runParams[datasetIdx].nonmaxSuppression ); +} + +void FastDetectorQualityTest::readDatasetRunParams( FileNode& fn, int datasetIdx ) +{ + runParams[datasetIdx].threshold = fn["threshold"]; + runParams[datasetIdx].nonmaxSuppression = (int)fn["nonmaxSuppression"] ? true : false;} + +void FastDetectorQualityTest::writeDatasetRunParams( FileStorage& fs, int datasetIdx ) const +{ + fs << "threshold" << runParams[datasetIdx].threshold; + fs << "nonmaxSuppression" << runParams[datasetIdx].nonmaxSuppression; +} + +void FastDetectorQualityTest::setDefaultDatasetRunParams( int datasetIdx ) +{ + runParams[datasetIdx].threshold = 1; + runParams[datasetIdx].nonmaxSuppression = true; +} + +FastDetectorQualityTest fastDetectorQuality; + +//--------------------------------- GFTT & HARRIS detectors tests -------------------------------------------- +class BaseGfttDetectorQualityTest : public DetectorQualityTest +{ +public: + BaseGfttDetectorQualityTest( const char* detectorName, const char* testName ) + : DetectorQualityTest( detectorName, testName ) + { + runParams.resize(DATASETS_COUNT); + useHarrisDetector = false; + } + +protected: + virtual FeatureDetector* createDetector( int datasetIdx ); + virtual void readDatasetRunParams( FileNode& fn, int datasetIdx ); + virtual void writeDatasetRunParams( FileStorage& fs, int datasetIdx ) const; + virtual void setDefaultDatasetRunParams( int datasetIdx ); + + struct RunParams + { + int maxCorners; + double qualityLevel; + double minDistance; + int blockSize; + double k; + }; + vector runParams; + bool useHarrisDetector; +}; + +FeatureDetector* BaseGfttDetectorQualityTest::createDetector( int datasetIdx ) +{ + return new GoodFeaturesToTrackDetector( runParams[datasetIdx].maxCorners, + runParams[datasetIdx].qualityLevel, + runParams[datasetIdx].minDistance, + runParams[datasetIdx].blockSize, + useHarrisDetector, + runParams[datasetIdx].k ); +} + +void BaseGfttDetectorQualityTest::readDatasetRunParams( FileNode& fn, int datasetIdx ) +{ + runParams[datasetIdx].maxCorners = fn["maxCorners"]; + runParams[datasetIdx].qualityLevel = fn["qualityLevel"]; + runParams[datasetIdx].minDistance = fn["minDistance"]; + runParams[datasetIdx].blockSize = fn["blockSize"]; + runParams[datasetIdx].k = fn["k"]; +} + +void BaseGfttDetectorQualityTest::writeDatasetRunParams( FileStorage& fs, int datasetIdx ) const +{ + fs << "maxCorners" << runParams[datasetIdx].maxCorners; + fs << "qualityLevel" << runParams[datasetIdx].qualityLevel; + fs << "minDistance" << runParams[datasetIdx].minDistance; + fs << "blockSize" << runParams[datasetIdx].blockSize; + fs << "k" << runParams[datasetIdx].k; +} + +void BaseGfttDetectorQualityTest::setDefaultDatasetRunParams( int datasetIdx ) +{ + runParams[datasetIdx].maxCorners = 1500; + runParams[datasetIdx].qualityLevel = 0.01; + runParams[datasetIdx].minDistance = 2.0; + runParams[datasetIdx].blockSize = 3; + runParams[datasetIdx].k = 0.04; +} + +class GfttDetectorQualityTest : public BaseGfttDetectorQualityTest +{ +public: + GfttDetectorQualityTest() : BaseGfttDetectorQualityTest( "gftt", "quality-gftt-detector" ) {} +}; + +GfttDetectorQualityTest gfttDetectorQuality; + +class HarrisDetectorQualityTest : public BaseGfttDetectorQualityTest +{ +public: + HarrisDetectorQualityTest() : BaseGfttDetectorQualityTest( "harris", "quality-harris-detector" ) + { useHarrisDetector = true; } +}; + +HarrisDetectorQualityTest harrisDetectorQuality; + +//--------------------------------- MSER detector test -------------------------------------------- +class MserDetectorQualityTest : public DetectorQualityTest +{ +public: + MserDetectorQualityTest() : DetectorQualityTest( "mser", "quality-mser-detector" ) + { runParams.resize(DATASETS_COUNT); } + +protected: + virtual FeatureDetector* createDetector( int datasetIdx ); + virtual void readDatasetRunParams( FileNode& fn, int datasetIdx ); + virtual void writeDatasetRunParams( FileStorage& fs, int datasetIdx ) const; + virtual void setDefaultDatasetRunParams( int datasetIdx ); + + struct RunParams + { + int delta; + int minArea; + int maxArea; + float maxVariation; + float minDiversity; + int maxEvolution; + double areaThreshold; + double minMargin; + int edgeBlurSize; + }; + vector runParams; +}; + +FeatureDetector* MserDetectorQualityTest::createDetector( int datasetIdx ) +{ + return new MserFeatureDetector( runParams[datasetIdx].delta, + runParams[datasetIdx].minArea, + runParams[datasetIdx].maxArea, + runParams[datasetIdx].maxVariation, + runParams[datasetIdx].minDiversity, + runParams[datasetIdx].maxEvolution, + runParams[datasetIdx].areaThreshold, + runParams[datasetIdx].minMargin, + runParams[datasetIdx].edgeBlurSize ); +} + +void MserDetectorQualityTest::readDatasetRunParams( FileNode& fn, int datasetIdx ) +{ + runParams[datasetIdx].delta = fn["delta"]; + runParams[datasetIdx].minArea = fn["minArea"]; + runParams[datasetIdx].maxArea = fn["maxArea"]; + runParams[datasetIdx].maxVariation = fn["maxVariation"]; + runParams[datasetIdx].minDiversity = fn["minDiversity"]; + runParams[datasetIdx].maxEvolution = fn["maxEvolution"]; + runParams[datasetIdx].areaThreshold = fn["areaThreshold"]; + runParams[datasetIdx].minMargin = fn["minMargin"]; + runParams[datasetIdx].edgeBlurSize = fn["edgeBlurSize"]; +} + +void MserDetectorQualityTest::writeDatasetRunParams( FileStorage& fs, int datasetIdx ) const +{ + fs << "delta" << runParams[datasetIdx].delta; + fs << "minArea" << runParams[datasetIdx].minArea; + fs << "maxArea" << runParams[datasetIdx].maxArea; + fs << "maxVariation" << runParams[datasetIdx].maxVariation; + fs << "minDiversity" << runParams[datasetIdx].minDiversity; + fs << "maxEvolution" << runParams[datasetIdx].maxEvolution; + fs << "areaThreshold" << runParams[datasetIdx].areaThreshold; + fs << "minMargin" << runParams[datasetIdx].minMargin; + fs << "edgeBlurSize" << runParams[datasetIdx].edgeBlurSize; +} + +void MserDetectorQualityTest::setDefaultDatasetRunParams( int datasetIdx ) +{ + runParams[datasetIdx].delta = 5; + runParams[datasetIdx].minArea = 60; + runParams[datasetIdx].maxArea = 14400; + runParams[datasetIdx].maxVariation = 0.25f; + runParams[datasetIdx].minDiversity = 0.2; + runParams[datasetIdx].maxEvolution = 200; + runParams[datasetIdx].areaThreshold = 1.01; + runParams[datasetIdx].minMargin = 0.003; + runParams[datasetIdx].edgeBlurSize = 5; +} + +MserDetectorQualityTest mserDetectorQuality; + +//--------------------------------- STAR detector test -------------------------------------------- +class StarDetectorQualityTest : public DetectorQualityTest +{ +public: + StarDetectorQualityTest() : DetectorQualityTest( "star", "quality-star-detector" ) + { runParams.resize(DATASETS_COUNT); } + +protected: + virtual FeatureDetector* createDetector( int datasetIdx ); + virtual void readDatasetRunParams( FileNode& fn, int datasetIdx ); + virtual void writeDatasetRunParams( FileStorage& fs, int datasetIdx ) const; + virtual void setDefaultDatasetRunParams( int datasetIdx ); + + struct RunParams + { + int maxSize; + int responseThreshold; + int lineThresholdProjected; + int lineThresholdBinarized; + int suppressNonmaxSize; + }; + vector runParams; +}; + +FeatureDetector* StarDetectorQualityTest::createDetector( int datasetIdx ) +{ + return new StarFeatureDetector( runParams[datasetIdx].maxSize, + runParams[datasetIdx].responseThreshold, + runParams[datasetIdx].lineThresholdProjected, + runParams[datasetIdx].lineThresholdBinarized, + runParams[datasetIdx].suppressNonmaxSize ); +} + +void StarDetectorQualityTest::readDatasetRunParams( FileNode& fn, int datasetIdx ) +{ + runParams[datasetIdx].maxSize = fn["maxSize"]; + runParams[datasetIdx].responseThreshold = fn["responseThreshold"]; + runParams[datasetIdx].lineThresholdProjected = fn["lineThresholdProjected"]; + runParams[datasetIdx].lineThresholdBinarized = fn["lineThresholdBinarized"]; + runParams[datasetIdx].suppressNonmaxSize = fn["suppressNonmaxSize"]; +} + +void StarDetectorQualityTest::writeDatasetRunParams( FileStorage& fs, int datasetIdx ) const +{ + fs << "maxSize" << runParams[datasetIdx].maxSize; + fs << "responseThreshold" << runParams[datasetIdx].responseThreshold; + fs << "lineThresholdProjected" << runParams[datasetIdx].lineThresholdProjected; + fs << "lineThresholdBinarized" << runParams[datasetIdx].lineThresholdBinarized; + fs << "suppressNonmaxSize" << runParams[datasetIdx].suppressNonmaxSize; +} + +void StarDetectorQualityTest::setDefaultDatasetRunParams( int datasetIdx ) +{ + runParams[datasetIdx].maxSize = 16; + runParams[datasetIdx].responseThreshold = 30; + runParams[datasetIdx].lineThresholdProjected = 10; + runParams[datasetIdx].lineThresholdBinarized = 8; + runParams[datasetIdx].suppressNonmaxSize = 5; +} + +StarDetectorQualityTest starDetectorQuality; + +//--------------------------------- SIFT detector test -------------------------------------------- +class SiftDetectorQualityTest : public DetectorQualityTest +{ +public: + SiftDetectorQualityTest() : DetectorQualityTest( "sift", "quality-sift-detector" ) + { runParams.resize(DATASETS_COUNT); } + +protected: + virtual FeatureDetector* createDetector( int datasetIdx ); + virtual void readDatasetRunParams( FileNode& fn, int datasetIdx ); + virtual void writeDatasetRunParams( FileStorage& fs, int datasetIdx ) const; + virtual void setDefaultDatasetRunParams( int datasetIdx ); + + struct RunParams + { + SIFT::CommonParams comm; + SIFT::DetectorParams detect; + }; + + vector runParams; +}; + +FeatureDetector* SiftDetectorQualityTest::createDetector( int datasetIdx ) +{ + return new SiftFeatureDetector( runParams[datasetIdx].detect.threshold, + runParams[datasetIdx].detect.edgeThreshold, + runParams[datasetIdx].detect.angleMode, + runParams[datasetIdx].comm.nOctaves, + runParams[datasetIdx].comm.nOctaveLayers, + runParams[datasetIdx].comm.firstOctave ); +} + +void SiftDetectorQualityTest::readDatasetRunParams( FileNode& fn, int datasetIdx ) +{ + runParams[datasetIdx].detect.threshold = fn["threshold"]; + runParams[datasetIdx].detect.edgeThreshold = fn["edgeThreshold"]; + runParams[datasetIdx].detect.angleMode = fn["angleMode"]; + runParams[datasetIdx].comm.nOctaves = fn["nOctaves"]; + runParams[datasetIdx].comm.nOctaveLayers = fn["nOctaveLayers"]; + runParams[datasetIdx].comm.firstOctave = fn["firstOctave"]; +} + +void SiftDetectorQualityTest::writeDatasetRunParams( FileStorage& fs, int datasetIdx ) const +{ + fs << "threshold" << runParams[datasetIdx].detect.threshold; + fs << "edgeThreshold" << runParams[datasetIdx].detect.edgeThreshold; + fs << "angleMode" << runParams[datasetIdx].detect.angleMode; + fs << "nOctaves" << runParams[datasetIdx].comm.nOctaves; + fs << "nOctaveLayers" << runParams[datasetIdx].comm.nOctaveLayers; + fs << "firstOctave" << runParams[datasetIdx].comm.firstOctave; + } + +void SiftDetectorQualityTest::setDefaultDatasetRunParams( int datasetIdx ) +{ + runParams[datasetIdx].detect = SIFT::DetectorParams(); + runParams[datasetIdx].comm = SIFT::CommonParams(); +} + +SiftDetectorQualityTest siftDetectorQuality; + +//--------------------------------- SURF detector test -------------------------------------------- +class SurfDetectorQualityTest : public DetectorQualityTest +{ +public: + SurfDetectorQualityTest() : DetectorQualityTest( "surf", "quality-surf-detector" ) + { runParams.resize(DATASETS_COUNT); } + +protected: + virtual FeatureDetector* createDetector( int datasetIdx ); + virtual void readDatasetRunParams( FileNode& fn, int datasetIdx ); + virtual void writeDatasetRunParams( FileStorage& fs, int datasetIdx ) const; + virtual void setDefaultDatasetRunParams( int datasetIdx ); + + struct RunParams + { + double hessianThreshold; + int octaves; + int octaveLayers; + }; + vector runParams; +}; + +FeatureDetector* SurfDetectorQualityTest::createDetector( int datasetIdx ) +{ + return new SurfFeatureDetector( runParams[datasetIdx].hessianThreshold, + runParams[datasetIdx].octaves, + runParams[datasetIdx].octaveLayers ); +} + +void SurfDetectorQualityTest::readDatasetRunParams( FileNode& fn, int datasetIdx ) +{ + runParams[datasetIdx].hessianThreshold = fn["hessianThreshold"]; + runParams[datasetIdx].octaves = fn["octaves"]; + runParams[datasetIdx].octaveLayers = fn["octaveLayers"]; +} + +void SurfDetectorQualityTest::writeDatasetRunParams( FileStorage& fs, int datasetIdx ) const +{ + fs << "hessianThreshold" << runParams[datasetIdx].hessianThreshold; + fs << "octaves" << runParams[datasetIdx].octaves; + fs << "octaveLayers" << runParams[datasetIdx].octaveLayers; +} + +void SurfDetectorQualityTest::setDefaultDatasetRunParams( int datasetIdx ) +{ + runParams[datasetIdx].hessianThreshold = 400.; + runParams[datasetIdx].octaves = 3; + runParams[datasetIdx].octaveLayers = 4; +} + +SurfDetectorQualityTest surfDetectorQuality; + +/****************************************************************************************\ +* Descriptors evaluation * +\****************************************************************************************/ + +const string RECALL = "recall"; +const string PRECISION = "precision"; + +const string KEYPOINTS_FILENAME = "keypointsFilename"; +const string PROJECT_KEYPOINTS_FROM_1IMAGE = "projectKeypointsFrom1Image"; +const string MATCH_FILTER = "matchFilter"; + +class DescriptorQualityTest : public BaseQualityTest +{ +public: + enum{ NO_MATCH_FILTER = 0 }; + DescriptorQualityTest( const char* _descriptorName, const char* _testName ) : + BaseQualityTest( _descriptorName, _testName, "quality-of-descriptor" ) + { + validQuality.resize(DATASETS_COUNT); + calcQuality.resize(DATASETS_COUNT); + commRunParams.resize(DATASETS_COUNT); + } + +protected: + using BaseQualityTest::readResults; + using BaseQualityTest::writeResults; + using BaseQualityTest::processResults; + + virtual string getRunParamsFilename() const; + virtual string getResultsFilename() const; + + virtual void validQualityClear( int datasetIdx ); + virtual void validQualityCreate( int datasetIdx ); + virtual bool isValidQualityEmpty( int datasetIdx ) const; + virtual bool isCalcQualityEmpty( int datasetIdx ) const; + + virtual void readResults( FileNode& fn, int datasetIdx, int caseIdx ); + virtual void writeResults( FileStorage& fs, int datasetIdx, int caseIdx ) const; + + virtual void readDatasetRunParams( FileNode& fn, int datasetIdx ); // + virtual void writeDatasetRunParams( FileStorage& fs, int datasetIdx ) const; + virtual void setDefaultDatasetRunParams( int datasetIdx ); + + virtual GenericDescriptorMatch* createDescriptorMatch( int datasetIdx ) = 0; + void run( int ); + virtual int processResults( int datasetIdx, int caseIdx ); + + struct Quality + { + float recall; + float precision; + }; + vector > validQuality; + vector > calcQuality; + + struct CommonRunParams + { + string keypontsFilename; + bool projectKeypointsFrom1Image; + int matchFilter; // not used now + }; + vector commRunParams; +}; + +string DescriptorQualityTest::getRunParamsFilename() const +{ + return string(ts->get_data_path()) + DESCRIPTORS_DIR + algName + PARAMS_POSTFIX; +} + +string DescriptorQualityTest::getResultsFilename() const +{ + return string(ts->get_data_path()) + DESCRIPTORS_DIR + algName + RES_POSTFIX; +} + +void DescriptorQualityTest::validQualityClear( int datasetIdx ) +{ + validQuality[datasetIdx].clear(); +} + +void DescriptorQualityTest::validQualityCreate( int datasetIdx ) +{ + validQuality[datasetIdx].resize(TEST_CASE_COUNT); +} + +bool DescriptorQualityTest::isValidQualityEmpty( int datasetIdx ) const +{ + return validQuality[datasetIdx].empty(); +} + +bool DescriptorQualityTest::isCalcQualityEmpty( int datasetIdx ) const +{ + return calcQuality[datasetIdx].empty(); +} + +void DescriptorQualityTest::readResults( FileNode& fn, int datasetIdx, int caseIdx ) +{ + validQuality[datasetIdx][caseIdx].recall = fn[RECALL]; + validQuality[datasetIdx][caseIdx].precision = fn[PRECISION]; +} + +void DescriptorQualityTest::writeResults( FileStorage& fs, int datasetIdx, int caseIdx ) const +{ + fs << RECALL << calcQuality[datasetIdx][caseIdx].recall; + fs << PRECISION << calcQuality[datasetIdx][caseIdx].precision; +} + +void DescriptorQualityTest::DescriptorQualityTest::readDatasetRunParams( FileNode& fn, int datasetIdx ) +{ + commRunParams[datasetIdx].keypontsFilename = (string)fn[KEYPOINTS_FILENAME]; + commRunParams[datasetIdx].projectKeypointsFrom1Image = (int)fn[PROJECT_KEYPOINTS_FROM_1IMAGE] != 0; + commRunParams[datasetIdx].matchFilter = (int)fn[MATCH_FILTER]; +} + +void DescriptorQualityTest::writeDatasetRunParams( FileStorage& fs, int datasetIdx ) const +{ + fs << KEYPOINTS_FILENAME << commRunParams[datasetIdx].keypontsFilename; + fs << PROJECT_KEYPOINTS_FROM_1IMAGE << commRunParams[datasetIdx].projectKeypointsFrom1Image; + fs << MATCH_FILTER << commRunParams[datasetIdx].matchFilter; +} + +void DescriptorQualityTest::setDefaultDatasetRunParams( int datasetIdx ) +{ + commRunParams[datasetIdx].keypontsFilename = "surf_" + DATASET_NAMES[datasetIdx] + ".xml"; + commRunParams[datasetIdx].projectKeypointsFrom1Image = false; + commRunParams[datasetIdx].matchFilter = NO_MATCH_FILTER; +} + +// if keyponts from first image are projected on second image using homography matrix +void evaluateDescriptors( const vector& origIdxs2, + const vector& matches1to2, + int& correctMatchCount, int& falseMatchCount, int& correspondenceCount ) +{ + assert( !origIdxs2.empty() > 0 && !matches1to2.empty() ); + correspondenceCount = origIdxs2.size(); + correctMatchCount = falseMatchCount = 0; + for( size_t i1 = 0; i1 < matches1to2.size(); i1++ ) + { + size_t i2 = matches1to2[i1]; + if( i2 > 0 ) + { + if( origIdxs2[i2] == (int)i1 ) + correctMatchCount++; + else + falseMatchCount++; + } + } +} + + +void DescriptorQualityTest::run( int ) +{ + readAllDatasetsRunParams(); + readResults(); + + int notReadDatasets = 0; + int progress = 0, progressCount = DATASETS_COUNT*TEST_CASE_COUNT; + for(int di = 0; di < DATASETS_COUNT; di++ ) + { + FileStorage keypontsFS( string(ts->get_data_path()) + KEYPOINTS_DIR + commRunParams[di].keypontsFilename, + FileStorage::READ ); + vector imgs, Hs; + if( !keypontsFS.isOpened() || !readDataset( DATASET_NAMES[di], Hs, imgs ) ) + { + calcQuality[di].clear(); + ts->printf( CvTS::LOG, "images or homography matrices of dataset named %s can not be read OR " + "keypoints from file %s can not be read\n", + DATASET_NAMES[di].c_str(), commRunParams[di].keypontsFilename.c_str() ); + notReadDatasets++; + } + else + { + calcQuality[di].resize(TEST_CASE_COUNT); + + vector keypoints1; vector ekeypoints1; + readKeypoints( keypontsFS, keypoints1, 0); + if( !commRunParams[di].projectKeypointsFrom1Image ) + transformToEllipticKeyPoints( keypoints1, ekeypoints1 ); + else + { + assert(0); + // TODO debug! + } + for( int ci = 0; ci < TEST_CASE_COUNT; ci++ ) + { + progress = update_progress( progress, di*TEST_CASE_COUNT + ci, progressCount, 0 ); + + vector keypoints2; vector ekeypoints2; + vector origIdxs2; + if( commRunParams[di].projectKeypointsFrom1Image ) + { + calcKeyPointProjections( keypoints1, Hs[ci], keypoints2 ); + filterKeyPointsByImageSize( keypoints2, imgs[ci+1].size(), origIdxs2 ); + } + else + { + readKeypoints( keypontsFS, keypoints2, ci+1 ); + transformToEllipticKeyPoints( keypoints2, ekeypoints2 ); + } + Ptr descMatch = createDescriptorMatch(di); + descMatch->add( imgs[ci+1], keypoints2 ); + vector matches1to2; + descMatch->match( imgs[0], keypoints1, matches1to2 ); + + // TODO if( commRunParams[di].matchFilter ) + + int correctMatchCount, falseMatchCount, correspCount; + if( commRunParams[di].projectKeypointsFrom1Image ) + evaluateDescriptors( origIdxs2, matches1to2, correctMatchCount, falseMatchCount, correspCount ); + else + evaluateDescriptors( ekeypoints1, ekeypoints2, matches1to2, imgs[0], imgs[ci+1], Hs[ci], + correctMatchCount, falseMatchCount, correspCount ); + calcQuality[di][ci].recall = recall( correctMatchCount, correspCount ); + calcQuality[di][ci].precision = precision( correctMatchCount, falseMatchCount ); + } + } + } + if( notReadDatasets == DATASETS_COUNT ) + { + ts->printf(CvTS::LOG, "All datasets were not be read\n"); + ts->set_failed_test_info( CvTS::FAIL_INVALID_TEST_DATA ); + } + else + processResults(); +} + +int DescriptorQualityTest::processResults( int datasetIdx, int caseIdx ) +{ + int res = CvTS::OK; + Quality valid = validQuality[datasetIdx][caseIdx], calc = calcQuality[datasetIdx][caseIdx]; + + bool isBadAccuracy; + const float rltvEps = 0.001; + ts->printf(CvTS::LOG, "%s: calc=%f, valid=%f", RECALL.c_str(), calc.recall, valid.recall ); + isBadAccuracy = valid.recall - calc.recall > rltvEps; + testLog( ts, isBadAccuracy ); + res = isBadAccuracy ? CvTS::FAIL_BAD_ACCURACY : res; + + ts->printf(CvTS::LOG, "%s: calc=%f, valid=%f", PRECISION.c_str(), calc.precision, valid.precision ); + isBadAccuracy = valid.precision - calc.precision > rltvEps; + testLog( ts, isBadAccuracy ); + res = isBadAccuracy ? CvTS::FAIL_BAD_ACCURACY : res; + + return res; +} + +//--------------------------------- SIFT descriptor test -------------------------------------------- +class SiftDescriptorQualityTest : public DescriptorQualityTest +{ +public: + SiftDescriptorQualityTest() : DescriptorQualityTest( "sift", "quality-sift-descriptor" ) + { runParams.resize(DATASETS_COUNT); } + +protected: + virtual GenericDescriptorMatch* createDescriptorMatch( int datasetIdx ); + virtual void readDatasetRunParams( FileNode& fn, int datasetIdx ); + virtual void writeDatasetRunParams( FileStorage& fs, int datasetIdx ) const; + virtual void setDefaultDatasetRunParams( int datasetIdx ); + + struct RunParams + { + double magnification; + bool isNormalize; + int nOctaves; + int nOctaveLayers; + int firstOctave; + }; + vector runParams; +}; + +GenericDescriptorMatch* SiftDescriptorQualityTest::createDescriptorMatch( int datasetIdx ) +{ + SiftDescriptorExtractor extractor( runParams[datasetIdx].magnification, + runParams[datasetIdx].isNormalize, + runParams[datasetIdx].nOctaves, + runParams[datasetIdx].nOctaveLayers, + runParams[datasetIdx].firstOctave ); + BruteForceMatcher > matcher; + return new VectorDescriptorMatch > >(extractor, matcher); +} + +void SiftDescriptorQualityTest::readDatasetRunParams( FileNode& fn, int datasetIdx ) +{ + DescriptorQualityTest::readDatasetRunParams( fn, datasetIdx); + runParams[datasetIdx].magnification = fn["magnification"]; + runParams[datasetIdx].isNormalize = (int)fn["isNormalize"] != 0; + runParams[datasetIdx].nOctaves = fn["nOctaves"]; + runParams[datasetIdx].nOctaveLayers = fn["nOctaveLayers"]; + runParams[datasetIdx].firstOctave = fn["firstOctave"]; +} + +void SiftDescriptorQualityTest::writeDatasetRunParams( FileStorage& fs, int datasetIdx ) const +{ + DescriptorQualityTest::writeDatasetRunParams( fs, datasetIdx ); + fs << "magnification" << runParams[datasetIdx].magnification; + fs << "isNormalize" << runParams[datasetIdx].isNormalize; + fs << "nOctaves" << runParams[datasetIdx].nOctaves; + fs << "nOctaveLayers" << runParams[datasetIdx].nOctaveLayers; + fs << "firstOctave" << runParams[datasetIdx].firstOctave; +} + +void SiftDescriptorQualityTest::setDefaultDatasetRunParams( int datasetIdx ) +{ + DescriptorQualityTest::setDefaultDatasetRunParams( datasetIdx ); + runParams[datasetIdx].magnification = SIFT::DescriptorParams::GET_DEFAULT_MAGNIFICATION(); + runParams[datasetIdx].isNormalize = SIFT::DescriptorParams::DEFAULT_IS_NORMALIZE; + runParams[datasetIdx].nOctaves = SIFT::CommonParams::DEFAULT_NOCTAVES; + runParams[datasetIdx].nOctaveLayers = SIFT::CommonParams::DEFAULT_NOCTAVE_LAYERS; + runParams[datasetIdx].firstOctave = SIFT::CommonParams::DEFAULT_FIRST_OCTAVE; +} + +SiftDescriptorQualityTest siftDescriptorQuality; + +//--------------------------------- SURF descriptor test -------------------------------------------- +class SurfDescriptorQualityTest : public DescriptorQualityTest +{ +public: + SurfDescriptorQualityTest() : DescriptorQualityTest( "surf", "quality-surf-descriptor" ) + { runParams.resize(DATASETS_COUNT); } + +protected: + virtual GenericDescriptorMatch* createDescriptorMatch( int datasetIdx ); + virtual void readDatasetRunParams( FileNode& fn, int datasetIdx ); + virtual void writeDatasetRunParams( FileStorage& fs, int datasetIdx ) const; + virtual void setDefaultDatasetRunParams( int datasetIdx ); + + struct RunParams + { + int nOctaves; + int nOctaveLayers; + bool extended; + }; + vector runParams; +}; + +GenericDescriptorMatch* SurfDescriptorQualityTest::createDescriptorMatch( int datasetIdx ) +{ + SurfDescriptorExtractor extractor( runParams[datasetIdx].nOctaves, + runParams[datasetIdx].nOctaveLayers, + runParams[datasetIdx].extended ); + BruteForceMatcher > matcher; + return new VectorDescriptorMatch > >(extractor, matcher); +} + +void SurfDescriptorQualityTest::readDatasetRunParams( FileNode& fn, int datasetIdx ) +{ + DescriptorQualityTest::readDatasetRunParams( fn, datasetIdx); + runParams[datasetIdx].nOctaves = fn["nOctaves"]; + runParams[datasetIdx].nOctaveLayers = fn["nOctaveLayers"]; + runParams[datasetIdx].extended = (int)fn["extended"] != 0; +} + +void SurfDescriptorQualityTest::writeDatasetRunParams( FileStorage& fs, int datasetIdx ) const +{ + DescriptorQualityTest::writeDatasetRunParams( fs, datasetIdx ); + fs << "nOctaves" << runParams[datasetIdx].nOctaves; + fs << "nOctaveLayers" << runParams[datasetIdx].nOctaveLayers; + fs << "extended" << runParams[datasetIdx].extended; +} + +void SurfDescriptorQualityTest::setDefaultDatasetRunParams( int datasetIdx ) +{ + DescriptorQualityTest::setDefaultDatasetRunParams( datasetIdx ); + runParams[datasetIdx].nOctaves = 4; + runParams[datasetIdx].nOctaveLayers = 2; + runParams[datasetIdx].extended = false; +} + +SurfDescriptorQualityTest surfDescriptorQuality; diff --git a/tests/cv/src/adetectors.cpp b/tests/cv/src/adetectors.cpp deleted file mode 100644 index 5121b2319..000000000 --- a/tests/cv/src/adetectors.cpp +++ /dev/null @@ -1,899 +0,0 @@ -/*M/////////////////////////////////////////////////////////////////////////////////////// -// -// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. -// -// By downloading, copying, installing or using the software you agree to this license. -// If you do not agree to this license, do not download, install, -// copy or use the software. -// -// -// Intel License Agreement -// For Open Source Computer Vision Library -// -// Copyright (C) 2000, Intel Corporation, all rights reserved. -// Third party copyrights are property of their respective owners. -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// -// * Redistribution's of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * Redistribution's in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// * The name of Intel Corporation may not be used to endorse or promote products -// derived from this software without specific prior written permission. -// -// This software is provided by the copyright holders and contributors "as is" and -// any express or implied warranties, including, but not limited to, the implied -// warranties of merchantability and fitness for a particular purpose are disclaimed. -// In no event shall the Intel Corporation or contributors be liable for any direct, -// indirect, incidental, special, exemplary, or consequential damages -// (including, but not limited to, procurement of substitute goods or services; -// loss of use, data, or profits; or business interruption) however caused -// and on any theory of liability, whether in contract, strict liability, -// or tort (including negligence or otherwise) arising in any way out of -// the use of this software, even if advised of the possibility of such damage. -// -//M*/ - -#include "cvtest.h" -#include -#include - -using namespace std; -using namespace cv; - -inline Point2f applyHomography( const Mat_& H, const Point2f& pt ) -{ - double w = 1./(H(2,0)*pt.x + H(2,1)*pt.y + H(2,2)); - return Point2f( (H(0,0)*pt.x + H(0,1)*pt.y + H(0,2))*w, (H(1,0)*pt.x + H(1,1)*pt.y + H(1,2))*w ); -} - -inline void linearizeHomographyAt( const Mat_& H, const Point2f& pt, Mat_& A ) -{ - A.create(2,2); - double p1 = H(0,0)*pt.x + H(0,1)*pt.y + H(0,2), - p2 = H(1,0)*pt.x + H(1,1)*pt.y + H(1,2), - p3 = H(2,0)*pt.x + H(2,1)*pt.y + H(2,2), - p3_2 = p3*p3; - A(0,0) = H(0,0)/p3 - p1*H(2,0)/p3_2; // fxdx - A(0,1) = H(0,1)/p3 - p1*H(2,1)/p3_2; // fxdy - - A(1,0) = H(1,0)/p3 - p2*H(2,0)/p3_2; // fydx - A(1,1) = H(1,1)/p3 - p2*H(2,1)/p3_2; // fydx -} - -//----------------------------------- Repeatability --------------------------------------------------- - -// Find the key points located in the part of the scene present in both images -// and project keypoints2 on img1 -void getCommonKeyPointsOnImg1( const Mat& img1, const Mat img2, const Mat& H12, - const vector& keypoints1, const vector& keypoints2, - vector& ckeypoints1, vector& hckeypoints2, - bool isAffineInvariant ) -{ - assert( !img1.empty() && !img2.empty() ); - assert( !H12.empty() && H12.cols==3 && H12.rows==3 && H12.type()==CV_64FC1 ); - ckeypoints1.clear(); - hckeypoints2.clear(); - - Rect r1(0, 0, img1.cols, img1.rows), r2(0, 0, img2.cols, img2.rows); - Mat H21; invert( H12, H21 ); - - for( vector::const_iterator it = keypoints1.begin(); - it != keypoints1.end(); ++it ) - { - if( r2.contains(applyHomography(H12, it->pt)) ) - ckeypoints1.push_back(*it); - } - for( vector::const_iterator it = keypoints2.begin(); - it != keypoints2.end(); ++it ) - { - Point2f pt = applyHomography(H21, it->pt); - if( r1.contains(pt) ) - { - KeyPoint kp = *it; - kp.pt = pt; - if( isAffineInvariant ) - assert(0); - else // scale invariant - { - Mat_ A, eval; - linearizeHomographyAt(H21, it->pt, A); - eigen(A, eval); - assert( eval.type()==CV_64FC1 && eval.cols==1 && eval.rows==2 ); - kp.size *= sqrt(eval(0,0) * eval(1,0)) /*scale from linearized homography matrix*/; - } - hckeypoints2.push_back(kp); - } - } -} - -// Locations p1 and p2 are repeated if ||p1 - H21*p2|| < 1.5 pixels. -// Regions are repeated if Es < 0.4 (Es differs for scale invariant and affine invarian detectors). -// For more details see "Scale&Affine Invariant Interest Point Detectors", Mikolajczyk, Schmid. -void repeatability( const Mat& img1, const Mat img2, const Mat& H12, - const vector& keypoints1, const vector& keypoints2, - int& repeatingLocationCount, float& repeatingLocationRltv, - int& repeatingRegionCount, float& repeatingRegionRltv, - bool isAffineInvariant ) -{ - const double locThreshold = 1.5, - regThreshold = 0.4; - assert( !img1.empty() && !img2.empty() ); - assert( !H12.empty() && H12.cols==3 && H12.rows==3 && H12.type()==CV_64FC1 ); - - Mat H21; invert( H12, H21 ); - - vector ckeypoints1, hckeypoints2; - getCommonKeyPointsOnImg1( img1, img2, H12, keypoints1, keypoints2, ckeypoints1, hckeypoints2, false ); - - vector *smallKPSet, *bigKPSet; - if( ckeypoints1.size() < hckeypoints2.size() ) - { - smallKPSet = &ckeypoints1; - bigKPSet = &hckeypoints2; - } - else - { - smallKPSet = &hckeypoints2; - bigKPSet = &ckeypoints1; - } - - if( smallKPSet->size() == 0 ) - { - repeatingLocationCount = repeatingRegionCount = -1; - repeatingLocationRltv = repeatingRegionRltv = -1.f; - } - else - { - vector matchedMask( bigKPSet->size(), false); - repeatingLocationCount = repeatingRegionCount = 0; - for( vector::const_iterator skpIt = smallKPSet->begin(); skpIt != smallKPSet->end(); ++skpIt ) - { - int nearestIdx = -1, bkpIdx = 0; - double minDist = numeric_limits::max(); - vector::const_iterator nearestBkp; - for( vector::const_iterator bkpIt = bigKPSet->begin(); bkpIt != bigKPSet->end(); ++bkpIt, bkpIdx++ ) - { - if( !matchedMask[bkpIdx] ) - { - Point p1(cvRound(skpIt->pt.x), cvRound(skpIt->pt.y)), - p2(cvRound(bkpIt->pt.x), cvRound(bkpIt->pt.y)); - double dist = norm(p1 - p2); - if( dist < minDist ) - { - nearestIdx = bkpIdx; - minDist = dist; - nearestBkp = bkpIt; - } - } - } - if( minDist < locThreshold ) - { - matchedMask[nearestIdx] = true; - repeatingLocationCount++; - if( isAffineInvariant ) - assert(0); - else // scale invariant - { - double minRadius = min( skpIt->size, nearestBkp->size ), - maxRadius = max( skpIt->size, nearestBkp->size ); - double Es = abs(1 - (minRadius*minRadius)/(maxRadius*maxRadius)); - if( Es < regThreshold ) - repeatingRegionCount++; - } - } - } - repeatingLocationRltv = (float)repeatingLocationCount / smallKPSet->size(); - repeatingRegionRltv = (float)repeatingRegionCount / smallKPSet->size(); - } -} - -//----------------------------------- base class of detector test ------------------------------------ - -const int DATASETS_COUNT = 8; -const int TEST_CASE_COUNT = 5; - -const string DATASET_DIR = "detectors/datasets/"; -const string ALGORITHMS_DIR = "detectors/algorithms/"; - -const string PARAMS_POSTFIX = "_params.xml"; -const string RES_POSTFIX = "_res.xml"; - -const string RLC = "repeating_locations_count"; -const string RLR = "repeating_locations_rltv"; -const string RRC = "repeating_regions_count"; -const string RRR = "repeating_regions_rltv"; - -string DATASET_NAMES[DATASETS_COUNT] = { "bark", "bikes", "boat", "graf", "leuven", "trees", "ubc", "wall"}; - -class CV_DetectorRepeatabilityTest : public CvTest -{ -public: - CV_DetectorRepeatabilityTest( const char* _detectorName, const char* testName ) : CvTest( testName, "repeatability-of-detector" ) - { - detectorName = _detectorName; - isAffineInvariant = false; - - validRepeatability.resize(DATASETS_COUNT); - calcRepeatability.resize(DATASETS_COUNT); - } - -protected: - virtual FeatureDetector* createDetector( int datasetIdx ) = 0; - - void readAllRunParams(); - virtual void readRunParams( FileNode& fn, int datasetIdx ) = 0; - void writeAllRunParams(); - virtual void writeRunParams( FileStorage& fs, int datasetIdx ) = 0; - void setDefaultAllRunParams(); - virtual void setDefaultRunParams( int datasetIdx ) = 0; - - void readResults(); - void writeResults(); - - bool readDataset( const string& datasetName, vector& Hs, vector& imgs ); - - void run( int ); - void processResults(); - - bool isAffineInvariant; - string detectorName; - bool isWriteParams, isWriteResults; - - struct Repeatability - { - int repeatingLocationCount; - float repeatingLocationRltv; - int repeatingRegionCount; - float repeatingRegionRltv; - }; - vector > validRepeatability; - vector > calcRepeatability; -}; - -void CV_DetectorRepeatabilityTest::readAllRunParams() -{ - string filename = string(ts->get_data_path()) + ALGORITHMS_DIR + detectorName + PARAMS_POSTFIX; - FileStorage fs( filename, FileStorage::READ ); - if( !fs.isOpened() ) - { - isWriteParams = true; - setDefaultAllRunParams(); - ts->printf(CvTS::LOG, "all runParams are default\n"); - } - else - { - isWriteParams = false; - FileNode topfn = fs.getFirstTopLevelNode(); - for( int i = 0; i < DATASETS_COUNT; i++ ) - { - FileNode fn = topfn[DATASET_NAMES[i]]; - if( fn.empty() ) - { - ts->printf( CvTS::LOG, "%d-runParams is default\n", i); - setDefaultRunParams(i); - } - else - readRunParams(fn, i); - } - } -} - -void CV_DetectorRepeatabilityTest::writeAllRunParams() -{ - string filename = string(ts->get_data_path()) + ALGORITHMS_DIR + detectorName + PARAMS_POSTFIX; - FileStorage fs( filename, FileStorage::WRITE ); - if( fs.isOpened() ) - { - fs << "run_params" << "{"; // top file node - for( int i = 0; i < DATASETS_COUNT; i++ ) - { - fs << DATASET_NAMES[i] << "{"; - writeRunParams(fs, i); - fs << "}"; - } - fs << "}"; - } - else - ts->printf(CvTS::LOG, "file %s for writing run params can not be opened\n", filename.c_str() ); -} - -void CV_DetectorRepeatabilityTest::setDefaultAllRunParams() -{ - for( int i = 0; i < DATASETS_COUNT; i++ ) - setDefaultRunParams(i); -} - -bool CV_DetectorRepeatabilityTest::readDataset( const string& datasetName, vector& Hs, vector& imgs ) -{ - Hs.resize( TEST_CASE_COUNT ); - imgs.resize( TEST_CASE_COUNT+1 ); - string dirname = string(ts->get_data_path()) + DATASET_DIR + datasetName + "/"; - - for( int i = 0; i < (int)Hs.size(); i++ ) - { - stringstream filename; filename << "H1to" << i+2 << "p.xml"; - FileStorage fs( dirname + filename.str(), FileStorage::READ ); - if( !fs.isOpened() ) - return false; - fs.getFirstTopLevelNode() >> Hs[i]; - } - - for( int i = 0; i < (int)imgs.size(); i++ ) - { - stringstream filename; filename << "img" << i+1 << ".png"; - imgs[i] = imread( dirname + filename.str(), 0 ); - if( imgs[i].empty() ) - return false; - } - return true; -} - -void CV_DetectorRepeatabilityTest::readResults() -{ - string filename = string(ts->get_data_path()) + ALGORITHMS_DIR + detectorName + RES_POSTFIX; - FileStorage fs( filename, FileStorage::READ ); - if( fs.isOpened() ) - { - isWriteResults = false; - FileNode topfn = fs.getFirstTopLevelNode(); - for( int di = 0; di < DATASETS_COUNT; di++ ) - { - FileNode datafn = topfn[DATASET_NAMES[di]]; - if( datafn.empty() ) - { - validRepeatability[di].clear(); - ts->printf( CvTS::LOG, "results for %s dataset were not read\n", - DATASET_NAMES[di].c_str()); - } - else - { - validRepeatability[di].resize(TEST_CASE_COUNT); - for( int ci = 0; ci < TEST_CASE_COUNT; ci++ ) - { - stringstream ss; ss << "case" << ci; - FileNode casefn = datafn[ss.str()]; - CV_Assert( !casefn.empty() ); - validRepeatability[di][ci].repeatingLocationCount = casefn[RLC]; - validRepeatability[di][ci].repeatingLocationRltv = casefn[RLR]; - validRepeatability[di][ci].repeatingRegionCount = casefn[RRC]; - validRepeatability[di][ci].repeatingRegionRltv = casefn[RRR]; - } - } - } - } - else - isWriteResults = true; -} - -void CV_DetectorRepeatabilityTest::writeResults() -{ - string filename = string(ts->get_data_path()) + ALGORITHMS_DIR + detectorName + RES_POSTFIX; - FileStorage fs( filename, FileStorage::WRITE ); - if( fs.isOpened() ) - { - fs << "results" << "{"; - for( int di = 0; di < DATASETS_COUNT; di++ ) - { - if( calcRepeatability[di].empty() ) - { - ts->printf(CvTS::LOG, "results on %s dataset were not write because of empty\n", - DATASET_NAMES[di].c_str()); - } - else - { - fs << DATASET_NAMES[di] << "{"; - for( int ci = 0; ci < TEST_CASE_COUNT; ci++ ) - { - stringstream ss; ss << "case" << ci; - fs << ss.str() << "{"; - fs << RLC << calcRepeatability[di][ci].repeatingLocationCount; - fs << RLR << calcRepeatability[di][ci].repeatingLocationRltv; - fs << RRC << calcRepeatability[di][ci].repeatingRegionCount; - fs << RRR << calcRepeatability[di][ci].repeatingRegionRltv; - fs << "}"; //ss.str() - } - fs << "}"; //DATASET_NAMES[di] - } - } - fs << "}"; //results - } - else - ts->printf(CvTS::LOG, "results were not written because file %s can not be opened\n", filename.c_str() ); -} - -void CV_DetectorRepeatabilityTest::run( int ) -{ - readAllRunParams(); - readResults(); - - int notReadDatasets = 0; - int progress = 0, progressCount = DATASETS_COUNT*TEST_CASE_COUNT; - for(int di = 0; di < DATASETS_COUNT; di++ ) - { - vector imgs, Hs; - if( !readDataset( DATASET_NAMES[di], Hs, imgs ) ) - { - calcRepeatability[di].clear(); - ts->printf( CvTS::LOG, "images or homography matrices of dataset named %s can not be read\n", - DATASET_NAMES[di].c_str()); - notReadDatasets++; - } - else - { - calcRepeatability[di].resize(TEST_CASE_COUNT); - Ptr detector = createDetector(di); - - vector keypoints1; - detector->detect( imgs[0], keypoints1 ); - for( int ci = 0; ci < TEST_CASE_COUNT; ci++ ) - { - progress = update_progress( progress, di*TEST_CASE_COUNT + ci, progressCount, 0 ); - vector keypoints2; - detector->detect( imgs[ci+1], keypoints2 ); - repeatability( imgs[0], imgs[ci+1], Hs[ci], keypoints1, keypoints2, - calcRepeatability[di][ci].repeatingLocationCount, calcRepeatability[di][ci].repeatingLocationRltv, - calcRepeatability[di][ci].repeatingRegionCount, calcRepeatability[di][ci].repeatingRegionRltv, - isAffineInvariant ); - } - } - } - if( notReadDatasets == DATASETS_COUNT ) - { - ts->printf(CvTS::LOG, "All datasets were not be read\n"); - ts->set_failed_test_info( CvTS::FAIL_INVALID_TEST_DATA ); - } - else - processResults(); -} - -void testLog( CvTS* ts, bool isBadAccuracy ) -{ - if( isBadAccuracy ) - ts->printf(CvTS::LOG, " bad accuracy\n"); - else - ts->printf(CvTS::LOG, "\n"); -} - -void CV_DetectorRepeatabilityTest::processResults() -{ - if( isWriteParams ) - writeAllRunParams(); - - bool isBadAccuracy; - int res = CvTS::OK; - if( isWriteResults ) - writeResults(); - else - { - for( int di = 0; di < DATASETS_COUNT; di++ ) - { - if( validRepeatability[di].empty() || calcRepeatability[di].empty() ) - continue; - - ts->printf(CvTS::LOG, "\nDataset: %s\n", DATASET_NAMES[di].c_str() ); - - int countEps = 1; - float rltvEps = 0.001f; - for( int ci = 0; ci < TEST_CASE_COUNT; ci++ ) - { - ts->printf(CvTS::LOG, "case%d\n", ci); - Repeatability valid = validRepeatability[di][ci], calc = calcRepeatability[di][ci]; - - ts->printf(CvTS::LOG, "%s: calc=%d, valid=%d", RLC.c_str(), calc.repeatingLocationCount, valid.repeatingLocationCount ); - isBadAccuracy = valid.repeatingLocationCount - calc.repeatingLocationCount > countEps; - testLog( ts, isBadAccuracy ); - res = isBadAccuracy ? CvTS::FAIL_BAD_ACCURACY : res; - - ts->printf(CvTS::LOG, "%s: calc=%f, valid=%f", RLR.c_str(), calc.repeatingLocationRltv, valid.repeatingLocationRltv ); - isBadAccuracy = valid.repeatingLocationRltv - calc.repeatingLocationRltv > rltvEps; - testLog( ts, isBadAccuracy ); - res = isBadAccuracy ? CvTS::FAIL_BAD_ACCURACY : res; - - ts->printf(CvTS::LOG, "%s: calc=%d, valid=%d", RRC.c_str(), calc.repeatingRegionCount, valid.repeatingRegionCount ); - isBadAccuracy = valid.repeatingRegionCount - calc.repeatingRegionCount > countEps; - testLog( ts, isBadAccuracy ); - res = isBadAccuracy ? CvTS::FAIL_BAD_ACCURACY : res; - - ts->printf(CvTS::LOG, "%s: calc=%f, valid=%f", RRR.c_str(), calc.repeatingRegionRltv, valid.repeatingRegionRltv ); - isBadAccuracy = valid.repeatingRegionRltv - calc.repeatingRegionRltv > rltvEps; - testLog( ts, isBadAccuracy ); - res = isBadAccuracy ? CvTS::FAIL_BAD_ACCURACY : res; - } - } - } - - if( res != CvTS::OK ) - ts->printf(CvTS::LOG, "BAD ACCURACY\n"); - ts->set_failed_test_info( res ); -} - -//--------------------------------- FAST detector test -------------------------------------------- -class CV_FastDetectorTest : public CV_DetectorRepeatabilityTest -{ -public: - CV_FastDetectorTest() : CV_DetectorRepeatabilityTest( "fast", "repeatability-fast-detector" ) - { runParams.resize(DATASETS_COUNT); } - -protected: - virtual FeatureDetector* createDetector( int datasetIdx ); - virtual void readRunParams( FileNode& fn, int datasetIdx ); - virtual void writeRunParams( FileStorage& fs, int datasetIdx ); - virtual void setDefaultRunParams( int datasetIdx ); - - struct RunParams - { - int threshold; - bool nonmaxSuppression; - }; - vector runParams; -}; - -FeatureDetector* CV_FastDetectorTest::createDetector( int datasetIdx ) -{ - return new FastFeatureDetector( runParams[datasetIdx].threshold, runParams[datasetIdx].nonmaxSuppression ); -} - -void CV_FastDetectorTest::readRunParams( FileNode& fn, int datasetIdx ) -{ - runParams[datasetIdx].threshold = fn["threshold"]; - runParams[datasetIdx].nonmaxSuppression = (int)fn["nonmaxSuppression"] ? true : false; -} - -void CV_FastDetectorTest::writeRunParams( FileStorage& fs, int datasetIdx ) -{ - fs << "threshold" << runParams[datasetIdx].threshold; - fs << "nonmaxSuppression" << runParams[datasetIdx].nonmaxSuppression; -} - -void CV_FastDetectorTest::setDefaultRunParams( int datasetIdx ) -{ - runParams[datasetIdx].threshold = 1; - runParams[datasetIdx].nonmaxSuppression = true; -} - -CV_FastDetectorTest fastDetector; - -//--------------------------------- GFTT & HARRIS detectors tests -------------------------------------------- -class CV_BaseGfttDetectorTest : public CV_DetectorRepeatabilityTest -{ -public: - CV_BaseGfttDetectorTest( const char* detectorName, const char* testName ) - : CV_DetectorRepeatabilityTest( detectorName, testName ) - { - runParams.resize(DATASETS_COUNT); - useHarrisDetector = false; - } - -protected: - virtual FeatureDetector* createDetector( int datasetIdx ); - virtual void readRunParams( FileNode& fn, int datasetIdx ); - virtual void writeRunParams( FileStorage& fs, int datasetIdx ); - virtual void setDefaultRunParams( int datasetIdx ); - - struct RunParams - { - int maxCorners; - double qualityLevel; - double minDistance; - int blockSize; - double k; - }; - vector runParams; - bool useHarrisDetector; -}; - -FeatureDetector* CV_BaseGfttDetectorTest::createDetector( int datasetIdx ) -{ - return new GoodFeaturesToTrackDetector( runParams[datasetIdx].maxCorners, - runParams[datasetIdx].qualityLevel, - runParams[datasetIdx].minDistance, - runParams[datasetIdx].blockSize, - useHarrisDetector, - runParams[datasetIdx].k ); -} - -void CV_BaseGfttDetectorTest::readRunParams( FileNode& fn, int datasetIdx ) -{ - runParams[datasetIdx].maxCorners = fn["maxCorners"]; - runParams[datasetIdx].qualityLevel = fn["qualityLevel"]; - runParams[datasetIdx].minDistance = fn["minDistance"]; - runParams[datasetIdx].blockSize = fn["blockSize"]; - runParams[datasetIdx].k = fn["k"]; -} - -void CV_BaseGfttDetectorTest::writeRunParams( FileStorage& fs, int datasetIdx ) -{ - fs << "maxCorners" << runParams[datasetIdx].maxCorners; - fs << "qualityLevel" << runParams[datasetIdx].qualityLevel; - fs << "minDistance" << runParams[datasetIdx].minDistance; - fs << "blockSize" << runParams[datasetIdx].blockSize; - fs << "k" << runParams[datasetIdx].k; -} - -void CV_BaseGfttDetectorTest::setDefaultRunParams( int datasetIdx ) -{ - runParams[datasetIdx].maxCorners = 1500; - runParams[datasetIdx].qualityLevel = 0.01; - runParams[datasetIdx].minDistance = 2.0; - runParams[datasetIdx].blockSize = 3; - runParams[datasetIdx].k = 0.04; -} - -class CV_GfttDetectorTest : public CV_BaseGfttDetectorTest -{ -public: - CV_GfttDetectorTest() : CV_BaseGfttDetectorTest( "gftt", "repeatability-gftt-detector" ) {} -}; - -CV_GfttDetectorTest gfttDetector; - -class CV_HarrisDetectorTest : public CV_BaseGfttDetectorTest -{ -public: - CV_HarrisDetectorTest() : CV_BaseGfttDetectorTest( "harris", "repeatability-harris-detector" ) - { useHarrisDetector = true; } -}; - -CV_HarrisDetectorTest harrisDetector; - -//--------------------------------- MSER detector test -------------------------------------------- -class CV_MserDetectorTest : public CV_DetectorRepeatabilityTest -{ -public: - CV_MserDetectorTest() : CV_DetectorRepeatabilityTest( "mser", "repeatability-mser-detector" ) - { runParams.resize(DATASETS_COUNT); } - -protected: - virtual FeatureDetector* createDetector( int datasetIdx ); - virtual void readRunParams( FileNode& fn, int datasetIdx ); - virtual void writeRunParams( FileStorage& fs, int datasetIdx ); - virtual void setDefaultRunParams( int datasetIdx ); - - struct RunParams - { - int delta; - int minArea; - int maxArea; - float maxVariation; - float minDiversity; - int maxEvolution; - double areaThreshold; - double minMargin; - int edgeBlurSize; - }; - vector runParams; -}; - -FeatureDetector* CV_MserDetectorTest::createDetector( int datasetIdx ) -{ - return new MserFeatureDetector( runParams[datasetIdx].delta, - runParams[datasetIdx].minArea, - runParams[datasetIdx].maxArea, - runParams[datasetIdx].maxVariation, - runParams[datasetIdx].minDiversity, - runParams[datasetIdx].maxEvolution, - runParams[datasetIdx].areaThreshold, - runParams[datasetIdx].minMargin, - runParams[datasetIdx].edgeBlurSize ); -} - -void CV_MserDetectorTest::readRunParams( FileNode& fn, int datasetIdx ) -{ - runParams[datasetIdx].delta = fn["delta"]; - runParams[datasetIdx].minArea = fn["minArea"]; - runParams[datasetIdx].maxArea = fn["maxArea"]; - runParams[datasetIdx].maxVariation = fn["maxVariation"]; - runParams[datasetIdx].minDiversity = fn["minDiversity"]; - runParams[datasetIdx].maxEvolution = fn["maxEvolution"]; - runParams[datasetIdx].areaThreshold = fn["areaThreshold"]; - runParams[datasetIdx].minMargin = fn["minMargin"]; - runParams[datasetIdx].edgeBlurSize = fn["edgeBlurSize"]; -} - -void CV_MserDetectorTest::writeRunParams( FileStorage& fs, int datasetIdx ) -{ - fs << "delta" << runParams[datasetIdx].delta; - fs << "minArea" << runParams[datasetIdx].minArea; - fs << "maxArea" << runParams[datasetIdx].maxArea; - fs << "maxVariation" << runParams[datasetIdx].maxVariation; - fs << "minDiversity" << runParams[datasetIdx].minDiversity; - fs << "maxEvolution" << runParams[datasetIdx].maxEvolution; - fs << "areaThreshold" << runParams[datasetIdx].areaThreshold; - fs << "minMargin" << runParams[datasetIdx].minMargin; - fs << "edgeBlurSize" << runParams[datasetIdx].edgeBlurSize; -} - -void CV_MserDetectorTest::setDefaultRunParams( int datasetIdx ) -{ - runParams[datasetIdx].delta = 5; - runParams[datasetIdx].minArea = 60; - runParams[datasetIdx].maxArea = 14400; - runParams[datasetIdx].maxVariation = 0.25f; - runParams[datasetIdx].minDiversity = 0.2; - runParams[datasetIdx].maxEvolution = 200; - runParams[datasetIdx].areaThreshold = 1.01; - runParams[datasetIdx].minMargin = 0.003; - runParams[datasetIdx].edgeBlurSize = 5; -} - -CV_MserDetectorTest mserDetector; - -//--------------------------------- STAR detector test -------------------------------------------- -class CV_StarDetectorTest : public CV_DetectorRepeatabilityTest -{ -public: - CV_StarDetectorTest() : CV_DetectorRepeatabilityTest( "star", "repeatability-star-detector" ) - { runParams.resize(DATASETS_COUNT); } - -protected: - virtual FeatureDetector* createDetector( int datasetIdx ); - virtual void readRunParams( FileNode& fn, int datasetIdx ); - virtual void writeRunParams( FileStorage& fs, int datasetIdx ); - virtual void setDefaultRunParams( int datasetIdx ); - - struct RunParams - { - int maxSize; - int responseThreshold; - int lineThresholdProjected; - int lineThresholdBinarized; - int suppressNonmaxSize; - }; - vector runParams; -}; - -FeatureDetector* CV_StarDetectorTest::createDetector( int datasetIdx ) -{ - return new StarFeatureDetector( runParams[datasetIdx].maxSize, - runParams[datasetIdx].responseThreshold, - runParams[datasetIdx].lineThresholdProjected, - runParams[datasetIdx].lineThresholdBinarized, - runParams[datasetIdx].suppressNonmaxSize ); -} - -void CV_StarDetectorTest::readRunParams( FileNode& fn, int datasetIdx ) -{ - runParams[datasetIdx].maxSize = fn["maxSize"]; - runParams[datasetIdx].responseThreshold = fn["responseThreshold"]; - runParams[datasetIdx].lineThresholdProjected = fn["lineThresholdProjected"]; - runParams[datasetIdx].lineThresholdBinarized = fn["lineThresholdBinarized"]; - runParams[datasetIdx].suppressNonmaxSize = fn["suppressNonmaxSize"]; -} - -void CV_StarDetectorTest::writeRunParams( FileStorage& fs, int datasetIdx ) -{ - fs << "maxSize" << runParams[datasetIdx].maxSize; - fs << "responseThreshold" << runParams[datasetIdx].responseThreshold; - fs << "lineThresholdProjected" << runParams[datasetIdx].lineThresholdProjected; - fs << "lineThresholdBinarized" << runParams[datasetIdx].lineThresholdBinarized; - fs << "suppressNonmaxSize" << runParams[datasetIdx].suppressNonmaxSize; -} - -void CV_StarDetectorTest::setDefaultRunParams( int datasetIdx ) -{ - runParams[datasetIdx].maxSize = 16; - runParams[datasetIdx].responseThreshold = 30; - runParams[datasetIdx].lineThresholdProjected = 10; - runParams[datasetIdx].lineThresholdBinarized = 8; - runParams[datasetIdx].suppressNonmaxSize = 5; -} - -CV_StarDetectorTest starDetector; - -//--------------------------------- SIFT detector test -------------------------------------------- -class CV_SiftDetectorTest : public CV_DetectorRepeatabilityTest -{ -public: - CV_SiftDetectorTest() : CV_DetectorRepeatabilityTest( "sift", "repeatability-sift-detector" ) - { runParams.resize(DATASETS_COUNT); } - -protected: - virtual FeatureDetector* createDetector( int datasetIdx ); - virtual void readRunParams( FileNode& fn, int datasetIdx ); - virtual void writeRunParams( FileStorage& fs, int datasetIdx ); - virtual void setDefaultRunParams( int datasetIdx ); - - struct RunParams - { - SIFT::CommonParams comm; - SIFT::DetectorParams detect; - }; - - vector runParams; -}; - -FeatureDetector* CV_SiftDetectorTest::createDetector( int datasetIdx ) -{ - return new SiftFeatureDetector( runParams[datasetIdx].detect.threshold, - runParams[datasetIdx].detect.edgeThreshold, - runParams[datasetIdx].detect.angleMode, - runParams[datasetIdx].comm.nOctaves, - runParams[datasetIdx].comm.nOctaveLayers, - runParams[datasetIdx].comm.firstOctave ); -} - -void CV_SiftDetectorTest::readRunParams( FileNode& fn, int datasetIdx ) -{ - runParams[datasetIdx].detect.threshold = fn["threshold"]; - runParams[datasetIdx].detect.edgeThreshold = fn["edgeThreshold"]; - runParams[datasetIdx].detect.angleMode = fn["angleMode"]; - runParams[datasetIdx].comm.nOctaves = fn["nOctaves"]; - runParams[datasetIdx].comm.nOctaveLayers = fn["nOctaveLayers"]; - runParams[datasetIdx].comm.firstOctave = fn["firstOctave"]; -} - -void CV_SiftDetectorTest::writeRunParams( FileStorage& fs, int datasetIdx ) -{ - fs << "threshold" << runParams[datasetIdx].detect.threshold; - fs << "edgeThreshold" << runParams[datasetIdx].detect.edgeThreshold; - fs << "angleMode" << runParams[datasetIdx].detect.angleMode; - fs << "nOctaves" << runParams[datasetIdx].comm.nOctaves; - fs << "nOctaveLayers" << runParams[datasetIdx].comm.nOctaveLayers; - fs << "firstOctave" << runParams[datasetIdx].comm.firstOctave; - } - -void CV_SiftDetectorTest::setDefaultRunParams( int datasetIdx ) -{ - runParams[datasetIdx].detect = SIFT::DetectorParams(); - runParams[datasetIdx].comm = SIFT::CommonParams(); -} - -CV_SiftDetectorTest siftDetector; - -//--------------------------------- SURF detector test -------------------------------------------- -class CV_SurfDetectorTest : public CV_DetectorRepeatabilityTest -{ -public: - CV_SurfDetectorTest() : CV_DetectorRepeatabilityTest( "surf", "repeatability-surf-detector" ) - { runParams.resize(DATASETS_COUNT); } - -protected: - virtual FeatureDetector* createDetector( int datasetIdx ); - virtual void readRunParams( FileNode& fn, int datasetIdx ); - virtual void writeRunParams( FileStorage& fs, int datasetIdx ); - virtual void setDefaultRunParams( int datasetIdx ); - - struct RunParams - { - double hessianThreshold; - int octaves; - int octaveLayers; - }; - vector runParams; -}; - -FeatureDetector* CV_SurfDetectorTest::createDetector( int datasetIdx ) -{ - return new SurfFeatureDetector( runParams[datasetIdx].hessianThreshold, - runParams[datasetIdx].octaves, - runParams[datasetIdx].octaveLayers ); -} - -void CV_SurfDetectorTest::readRunParams( FileNode& fn, int datasetIdx ) -{ - runParams[datasetIdx].hessianThreshold = fn["hessianThreshold"]; - runParams[datasetIdx].octaves = fn["octaves"]; - runParams[datasetIdx].octaveLayers = fn["octaveLayers"]; -} - -void CV_SurfDetectorTest::writeRunParams( FileStorage& fs, int datasetIdx ) -{ - fs << "hessianThreshold" << runParams[datasetIdx].hessianThreshold; - fs << "octaves" << runParams[datasetIdx].octaves; - fs << "octaveLayers" << runParams[datasetIdx].octaveLayers; -} - -void CV_SurfDetectorTest::setDefaultRunParams( int datasetIdx ) -{ - runParams[datasetIdx].hessianThreshold = 400.; - runParams[datasetIdx].octaves = 3; - runParams[datasetIdx].octaveLayers = 4; -} - -CV_SurfDetectorTest surfDetector;