From bbe48eaac6c01c86323b2efa1df932f803221085 Mon Sep 17 00:00:00 2001 From: E Braun Date: Fri, 1 Aug 2014 00:16:58 +0200 Subject: [PATCH 1/4] regression test for bug 3172 --- .../test/test_matchers_algorithmic.cpp | 91 ++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/modules/features2d/test/test_matchers_algorithmic.cpp b/modules/features2d/test/test_matchers_algorithmic.cpp index d76715dd95..853546124c 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(); +} From ed2cdb71e5821db85b57cf60d88689c2bf32aeeb Mon Sep 17 00:00:00 2001 From: E Braun Date: Fri, 1 Aug 2014 00:17:56 +0200 Subject: [PATCH 2/4] fix for bug 3172 --- modules/core/src/stat.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/core/src/stat.cpp b/modules/core/src/stat.cpp index 8ad2aabb8b..cafdf0fd3b 100644 --- a/modules/core/src/stat.cpp +++ b/modules/core/src/stat.cpp @@ -2463,7 +2463,7 @@ struct BatchDistInvoker : public ParallelLoopBody } void cv::batchDistance( InputArray _src1, InputArray _src2, - OutputArray _dist, int dtype, OutputArray _nidx, + InputOutputArray _dist, int dtype, InputOutputArray _nidx, int normType, int K, InputArray _mask, int update, bool crosscheck ) { @@ -2479,8 +2479,6 @@ void cv::batchDistance( InputArray _src1, InputArray _src2, } CV_Assert( (type == CV_8U && dtype == CV_32S) || dtype == CV_32F); - K = std::min(K, src2.rows); - _dist.create(src1.rows, (K > 0 ? K : src2.rows), dtype); Mat dist = _dist.getMat(), nidx; if( _nidx.needed() ) From 89833853fa8991261c0d4415a4c91a0af0130e28 Mon Sep 17 00:00:00 2001 From: E Braun Date: Tue, 2 Sep 2014 13:56:13 +0200 Subject: [PATCH 3/4] Revert "fix for bug 3172" This reverts commit ed2cdb71e5821db85b57cf60d88689c2bf32aeeb. --- modules/core/src/stat.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/core/src/stat.cpp b/modules/core/src/stat.cpp index cafdf0fd3b..8ad2aabb8b 100644 --- a/modules/core/src/stat.cpp +++ b/modules/core/src/stat.cpp @@ -2463,7 +2463,7 @@ struct BatchDistInvoker : public ParallelLoopBody } void cv::batchDistance( InputArray _src1, InputArray _src2, - InputOutputArray _dist, int dtype, InputOutputArray _nidx, + OutputArray _dist, int dtype, OutputArray _nidx, int normType, int K, InputArray _mask, int update, bool crosscheck ) { @@ -2479,6 +2479,8 @@ void cv::batchDistance( InputArray _src1, InputArray _src2, } CV_Assert( (type == CV_8U && dtype == CV_32S) || dtype == CV_32F); + K = std::min(K, src2.rows); + _dist.create(src1.rows, (K > 0 ? K : src2.rows), dtype); Mat dist = _dist.getMat(), nidx; if( _nidx.needed() ) From bdb82d181f4396e91a8404606b59a9f09f910f46 Mon Sep 17 00:00:00 2001 From: E Braun Date: Tue, 2 Sep 2014 18:05:23 +0200 Subject: [PATCH 4/4] fix for bug 3172 --- modules/core/include/opencv2/core/core.hpp | 7 +++ modules/core/src/stat.cpp | 68 +++++++++++++++++----- modules/features2d/src/matchers.cpp | 4 +- 3 files changed, 62 insertions(+), 17 deletions(-) diff --git a/modules/core/include/opencv2/core/core.hpp b/modules/core/include/opencv2/core/core.hpp index a0a7392799..141e58ee8c 100644 --- a/modules/core/include/opencv2/core/core.hpp +++ b/modules/core/include/opencv2/core/core.hpp @@ -2192,6 +2192,13 @@ CV_EXPORTS_W void batchDistance(InputArray src1, InputArray src2, InputArray mask=noArray(), int update=0, bool crosscheck=false); +//! naive nearest neighbor finder which incrementally updates dist and nidx when called repeatedly with the same K and src1, but varying src2 +CV_EXPORTS_W void batchDistanceForBFMatcher(InputArray src1, InputArray src2, + InputOutputArray dist, int dtype, InputOutputArray nidx, + int normType=NORM_L2, int K=1, + InputArray mask=noArray(), int update=0, + bool crosscheck=false); + //! scales and shifts array elements so that either the specified norm (alpha) or the minimum (alpha) and maximum (beta) array values get the specified values CV_EXPORTS_W void normalize( InputArray src, OutputArray dst, double alpha=1, double beta=0, int norm_type=NORM_L2, int dtype=-1, InputArray mask=noArray()); diff --git a/modules/core/src/stat.cpp b/modules/core/src/stat.cpp index 8ad2aabb8b..193eaf61a9 100644 --- a/modules/core/src/stat.cpp +++ b/modules/core/src/stat.cpp @@ -2460,32 +2460,21 @@ struct BatchDistInvoker : public ParallelLoopBody BatchDistFunc func; }; -} - -void cv::batchDistance( InputArray _src1, InputArray _src2, - OutputArray _dist, int dtype, OutputArray _nidx, - int normType, int K, InputArray _mask, - int update, bool crosscheck ) +static void batchDistanceImpl( InputArray _src1, InputArray _src2, + InputOutputArray _dist, int dtype, InputOutputArray _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)); - CV_Assert( _nidx.needed() == (K > 0) ); - if( dtype == -1 ) - { - dtype = normType == NORM_HAMMING || normType == NORM_HAMMING2 ? CV_32S : CV_32F; - } CV_Assert( (type == CV_8U && dtype == CV_32S) || dtype == CV_32F); - K = std::min(K, src2.rows); - - _dist.create(src1.rows, (K > 0 ? K : src2.rows), dtype); Mat dist = _dist.getMat(), nidx; if( _nidx.needed() ) { - _nidx.create(dist.size(), CV_32S); nidx = _nidx.getMat(); } @@ -2573,6 +2562,55 @@ void cv::batchDistance( InputArray _src1, InputArray _src2, BatchDistInvoker(src1, src2, dist, nidx, K, mask, update, func)); } +} + + +void cv::batchDistance( InputArray _src1, InputArray _src2, + OutputArray _dist, int dtype, OutputArray _nidx, + int normType, int K, InputArray _mask, + int update, bool crosscheck ) +{ + if( dtype == -1 ) + { + dtype = normType == NORM_HAMMING || normType == NORM_HAMMING2 ? CV_32S : CV_32F; + } + + // K == 0: return all matches; K > 0: return K best matches, but never more than the number of candidates in _src2 + CV_Assert( _nidx.needed() == (K > 0) ); + int candidates = _src2.size().height; + K = std::min( K, candidates ); + _dist.create( _src1.size().height, (K > 0 ? K : candidates), dtype ); + if( _nidx.needed() ) + { + _nidx.create( _dist.size(), CV_32S ); + } + + batchDistanceImpl( _src1, _src2, _dist, dtype, _nidx, normType, K, _mask, update, crosscheck ); +} + + +void cv::batchDistanceForBFMatcher( InputArray _src1, InputArray _src2, + InputOutputArray _dist, int dtype, InputOutputArray _nidx, + int normType, int K, InputArray _mask, + int update, bool crosscheck ) +{ + if( dtype == -1 ) + { + dtype = normType == NORM_HAMMING || normType == NORM_HAMMING2 ? CV_32S : CV_32F; + } + + // always work with K matches (unlike cv::batchDistance), even if _src2 has fewer candidates + // if this function is called in a loop, then the other loop iterations may require all K + CV_Assert( K > 0 && _nidx.needed() ); + cv::Size size( K, _src1.size().height ); + CV_Assert( update == 0 || (_dist.size() == size && _nidx.size() == size) ); + _dist.create( size, dtype ); + _nidx.create( size, CV_32S ); + + batchDistanceImpl( _src1, _src2, _dist, dtype, _nidx, normType, K, _mask, update, crosscheck ); + CV_Assert( _dist.size() == size && _nidx.size() == size ); +} + void cv::findNonZero( InputArray _src, OutputArray _idx ) { diff --git a/modules/features2d/src/matchers.cpp b/modules/features2d/src/matchers.cpp index 40612f8eeb..9b73eb4f3c 100644 --- a/modules/features2d/src/matchers.cpp +++ b/modules/features2d/src/matchers.cpp @@ -363,8 +363,8 @@ void BFMatcher::knnMatchImpl( const Mat& queryDescriptors, vector for( iIdx = 0; iIdx < imgCount; iIdx++ ) { CV_Assert( trainDescCollection[iIdx].rows < IMGIDX_ONE ); - batchDistance(queryDescriptors, trainDescCollection[iIdx], dist, dtype, nidx, - normType, knn, masks.empty() ? Mat() : masks[iIdx], update, crossCheck); + batchDistanceForBFMatcher(queryDescriptors, trainDescCollection[iIdx], dist, dtype, nidx, + normType, knn, masks.empty() ? Mat() : masks[iIdx], update, crossCheck); update += IMGIDX_ONE; }