diff --git a/modules/core/src/stat.cpp b/modules/core/src/stat.cpp index 8ad2aabb8..8564134d2 100644 --- a/modules/core/src/stat.cpp +++ b/modules/core/src/stat.cpp @@ -2463,14 +2463,14 @@ struct BatchDistInvoker : public ParallelLoopBody } void cv::batchDistance( InputArray _src1, InputArray _src2, - OutputArray _dist, int dtype, OutputArray _nidx, - int normType, int K, InputArray _mask, - int update, bool crosscheck ) + OutputArray _dist, int dtype, OutputArray _nidx, + int normType, int K, InputArray _mask, + int update, bool crosscheck ) { Mat src1 = _src1.getMat(), src2 = _src2.getMat(), mask = _mask.getMat(); int type = src1.type(); CV_Assert( type == src2.type() && src1.cols == src2.cols && - (type == CV_32F || type == CV_8U)); + (type == CV_32F || type == CV_8U)); CV_Assert( _nidx.needed() == (K > 0) ); if( dtype == -1 ) diff --git a/modules/features2d/src/matchers.cpp b/modules/features2d/src/matchers.cpp index 40612f8ee..893ad7743 100644 --- a/modules/features2d/src/matchers.cpp +++ b/modules/features2d/src/matchers.cpp @@ -352,18 +352,27 @@ void BFMatcher::knnMatchImpl( const Mat& queryDescriptors, vector matches.reserve(queryDescriptors.rows); - Mat dist, nidx; - int iIdx, imgCount = (int)trainDescCollection.size(), update = 0; int dtype = normType == NORM_HAMMING || normType == NORM_HAMMING2 || (normType == NORM_L1 && queryDescriptors.type() == CV_8U) ? CV_32S : CV_32F; + int maxRows = 0; CV_Assert( (int64)imgCount*IMGIDX_ONE < INT_MAX ); + for( iIdx = 0; iIdx < imgCount; iIdx++ ) + maxRows = std::max(maxRows, trainDescCollection[iIdx].rows); + + int m = queryDescriptors.rows; + Mat dist(m, knn, dtype), nidx(m, knn, CV_32S); + dist = Scalar::all(dtype == CV_32S ? (double)INT_MAX : (double)FLT_MAX); + nidx = Scalar::all(-1); + for( iIdx = 0; iIdx < imgCount; iIdx++ ) { CV_Assert( trainDescCollection[iIdx].rows < IMGIDX_ONE ); - batchDistance(queryDescriptors, trainDescCollection[iIdx], dist, dtype, nidx, + int n = std::min(knn, trainDescCollection[iIdx].rows); + Mat dist_i = dist.colRange(0, n), nidx_i = nidx.colRange(0, n); + batchDistance(queryDescriptors, trainDescCollection[iIdx], dist_i, dtype, nidx_i, normType, knn, masks.empty() ? Mat() : masks[iIdx], update, crossCheck); update += IMGIDX_ONE; } diff --git a/modules/features2d/test/test_matchers_algorithmic.cpp b/modules/features2d/test/test_matchers_algorithmic.cpp index d76715dd9..853546124 100644 --- a/modules/features2d/test/test_matchers_algorithmic.cpp +++ b/modules/features2d/test/test_matchers_algorithmic.cpp @@ -57,6 +57,9 @@ public: CV_DescriptorMatcherTest( const string& _name, const Ptr& _dmatcher, float _badPart ) : badPart(_badPart), name(_name), dmatcher(_dmatcher) {} + + static void generateData( Mat& query, Mat& train ); + protected: static const int dim = 500; static const int queryDescCount = 300; // must be even number because we split train data in some cases in two @@ -64,7 +67,6 @@ protected: const float badPart; virtual void run( int ); - void generateData( Mat& query, Mat& train ); void emptyDataTest(); void matchTest( const Mat& query, const Mat& train ); @@ -526,6 +528,81 @@ void CV_DescriptorMatcherTest::run( int ) radiusMatchTest( query, train ); } +// bug #3172: test that knnMatch() can handle images with fewer than knn keypoints +class CV_DescriptorMatcherLowKeypointTest : public cvtest::BaseTest +{ +public: + CV_DescriptorMatcherLowKeypointTest( const string& _name, const Ptr& _dmatcher ) : + name(_name), dmatcher(_dmatcher) + {} +protected: + virtual void run(int); + + void knnMatchTest( const Mat& query, const Mat& train ); + +private: + string name; + Ptr dmatcher; +}; + +void CV_DescriptorMatcherLowKeypointTest::knnMatchTest( const Mat& query, const Mat& train ) +{ + const int knn = 6; + const int queryDescCount = query.rows; + vector > matches; + + // three train images, the third one with only one keypoint + dmatcher->add( vector(1,train.rowRange(0, train.rows/2)) ); + dmatcher->add( vector(1,train.rowRange(train.rows/2, train.rows-1)) ); + dmatcher->add( vector(1,train.rowRange(train.rows-1, train.rows)) ); + const int trainImgCount = (int)dmatcher->getTrainDescriptors().size(); + + dmatcher->knnMatch( query, matches, knn, std::vector(), true ); + + if( matches.empty() ) + { + ts->printf(cvtest::TS::LOG, "No matches while testing knnMatch() function (3).\n"); + ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT ); + } + else + { + int badImgIdxCount = 0, badQueryIdxCount = 0, badTrainIdxCount = 0; + for( size_t i = 0; i < matches.size(); i++ ) + { + for( size_t j = 0; j < matches[i].size(); j++ ) + { + const DMatch& match = matches[i][j]; + if( match.imgIdx < 0 || match.imgIdx >= trainImgCount ) + { + ++badImgIdxCount; + } + if( match.queryIdx < 0 || match.queryIdx >= queryDescCount ) + { + ++badQueryIdxCount; + } + if( match.trainIdx < 0 ) + { + ++badTrainIdxCount; + } + } + } + if( badImgIdxCount > 0 || badQueryIdxCount > 0 || badTrainIdxCount > 0 ) + { + ts->printf( cvtest::TS::LOG, "%d/%d/%d - wrong image/query/train indices while testing knnMatch() function (3).\n", + badImgIdxCount, badQueryIdxCount, badTrainIdxCount ); + ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT ); + } + } +} + +void CV_DescriptorMatcherLowKeypointTest::run( int ) +{ + Mat query, train; + CV_DescriptorMatcherTest::generateData( query, train ); + + knnMatchTest( query, train ); +} + /****************************************************************************************\ * Tests registrations * \****************************************************************************************/ @@ -541,3 +618,15 @@ TEST( Features2d_DescriptorMatcher_FlannBased, regression ) CV_DescriptorMatcherTest test( "descriptor-matcher-flann-based", Algorithm::create("DescriptorMatcher.FlannBasedMatcher"), 0.04f ); test.safe_run(); } + +TEST( Features2d_DescriptorMatcher_LowKeypoint_BruteForce, regression ) +{ + CV_DescriptorMatcherLowKeypointTest test( "descriptor-matcher-low-keypoint-brute-force", Algorithm::create("DescriptorMatcher.BFMatcher") ); + test.safe_run(); +} + +TEST(Features2d_DescriptorMatcher_LowKeypoint_FlannBased, regression) +{ + CV_DescriptorMatcherLowKeypointTest test( "descriptor-matcher-low-keypoint-flann-based", Algorithm::create("DescriptorMatcher.FlannBasedMatcher") ); + test.safe_run(); +}