diff --git a/modules/features2d/src/matchers.cpp b/modules/features2d/src/matchers.cpp index 893ad7743a..f001ef4882 100644 --- a/modules/features2d/src/matchers.cpp +++ b/modules/features2d/src/matchers.cpp @@ -531,10 +531,20 @@ void FlannBasedMatcher::clear() void FlannBasedMatcher::train() { - if( flannIndex.empty() || mergedDescriptors.size() < addedDescCount ) + int trained = mergedDescriptors.size(); + if (flannIndex.empty() || trained < addedDescCount) { mergedDescriptors.set( trainDescCollection ); - flannIndex = new flann::Index( mergedDescriptors.getDescriptors(), *indexParams ); + + // construct flannIndex class, if empty or Algorithm not equal FLANN_INDEX_LSH + if (flannIndex.empty() || flannIndex->getAlgorithm() != cvflann::FLANN_INDEX_LSH) + { + flannIndex = new flann::Index(mergedDescriptors.getDescriptors(), *indexParams); + } + else + { + flannIndex->build(mergedDescriptors.getDescriptors(), mergedDescriptors.getDescriptors().rowRange(trained, mergedDescriptors.size()), *indexParams, cvflann::FLANN_DIST_HAMMING); + } } } diff --git a/modules/features2d/test/test_lshindex_flannbased_matcher.cpp b/modules/features2d/test/test_lshindex_flannbased_matcher.cpp new file mode 100644 index 0000000000..ddc81b0c5d --- /dev/null +++ b/modules/features2d/test/test_lshindex_flannbased_matcher.cpp @@ -0,0 +1,533 @@ +/*********************************************************************** + * Software License Agreement (BSD License) + * + * Copyright (c) 2015 Ippei Ito. All rights reserved. + * + * THE BSD LICENSE + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + *************************************************************************/ + +/* + For OpenCV2.4/OpenCV3.0 + + Test for Pull Request # 3829 + https://github.com/Itseez/opencv/pull/3829 + + This test code creates brute force matcher for accuracy of reference, and the test target matcher. + Then, add() and train() transformed query image descriptors, and some outlier images descriptors to both matchers. + Then, compared with the query image by match() and findHomography() to detect outlier and calculate accuracy. + And each drawMatches() images are saved, if SAVE_DRAW_MATCHES_IMAGES is true. + Finally, compare accuracies between the brute force matcher and the test target matcher. + + The lsh algorithm uses std::random_shuffle in lsh_index.h to make the random indexes table. + So, in relation to default random seed value of the execution environment or by using "srand(time(0)) function", + the match time and accuracy of the match results are different, each time the code ran. + And the match time becomes late in relation to the number of the hash collision times. +*/ + +#include "test_precomp.hpp" +#include "opencv2/ts.hpp" // for FilePath::CreateFolder() +#include // for time() + +// If defined, the match time and accuracy of the match results are a little different, each time the code ran. +//#define INIT_RANDOM_SEED + +// If defined, some outlier images descriptors add() the matcher. +#define TRAIN_WITH_OUTLIER_IMAGES + +// If true, save drawMatches() images. +#define SAVE_DRAW_MATCHES_IMAGES false + +// if true, verbose output +#define SHOW_DEBUG_LOG true + +#if CV_MAJOR_VERSION==2 +#define OrbCreate new cv::ORB(4000) +#elif CV_MAJOR_VERSION==3 +#define OrbCreate cv::ORB::create(4000) +#define AKazeCreate cv::AKAZE::create() +#endif + +using namespace std; + +int testno_for_make_filename = 0; + +// -------------------------------------------------------------------------------------- +// Parameter class to transform query image +// -------------------------------------------------------------------------------------- +class testparam +{ +public: + string transname; + void(*transfunc)(float, const cv::Mat&, cv::Mat&); + float from, to, step; + testparam(string _transname, void(*_transfunc)(float, const cv::Mat&, cv::Mat&), float _from, float _to, float _step) : + transname(_transname), + transfunc(_transfunc), + from(_from), + to(_to), + step(_step) + {} +}; + +// -------------------------------------------------------------------------------------- +// from matching_to_many_images.cpp +// -------------------------------------------------------------------------------------- +int maskMatchesByTrainImgIdx(const vector& matches, int trainImgIdx, vector& mask) +{ + int matchcnt = 0; + mask.resize(matches.size()); + fill(mask.begin(), mask.end(), 0); + for (size_t i = 0; i < matches.size(); i++) + { + if (matches[i].imgIdx == trainImgIdx) + { + mask[i] = 1; + matchcnt++; + } + } + return matchcnt; +} + +int calcHomographyAndInlierCount(const vector& query_kp, const vector& train_kp, const vector& match, vector &mask, cv::Mat &homography) +{ + // make query and current train image keypoint pairs + std::vector srcPoints, dstPoints; + for (unsigned int i = 0; i < match.size(); ++i) + { + if (mask[i] != 0) // is current train image ? + { + srcPoints.push_back(query_kp[match[i].queryIdx].pt); + dstPoints.push_back(train_kp[match[i].trainIdx].pt); + } + } + // calc homography + vector inlierMask; + homography = findHomography(srcPoints, dstPoints, cv::RANSAC, 3.0, inlierMask); + + // update outlier mask + int j = 0; + for (unsigned int i = 0; i < match.size(); ++i) + { + if (mask[i] != 0) // is current train image ? + { + if (inlierMask.size() == 0 || inlierMask[j] == 0) // is outlier ? + { + mask[i] = 0; + } + j++; + } + } + + // count inlier + int inlierCnt = 0; + for (unsigned int i = 0; i < mask.size(); ++i) + { + if (mask[i] != 0) + { + inlierCnt++; + } + } + return inlierCnt; +} + +void drawDetectedRectangle(cv::Mat& imgResult, const cv::Mat& homography, const cv::Mat& imgQuery) +{ + std::vector query_corners(4); + query_corners[0] = cv::Point(0, 0); + query_corners[1] = cv::Point(imgQuery.cols, 0); + query_corners[2] = cv::Point(imgQuery.cols, imgQuery.rows); + query_corners[3] = cv::Point(0, imgQuery.rows); + std::vector train_corners(4); + perspectiveTransform(query_corners, train_corners, homography); + line(imgResult, train_corners[0] + query_corners[1], train_corners[1] + query_corners[1], cv::Scalar(0, 255, 0), 4); + line(imgResult, train_corners[1] + query_corners[1], train_corners[2] + query_corners[1], cv::Scalar(0, 255, 0), 4); + line(imgResult, train_corners[2] + query_corners[1], train_corners[3] + query_corners[1], cv::Scalar(0, 255, 0), 4); + line(imgResult, train_corners[3] + query_corners[1], train_corners[0] + query_corners[1], cv::Scalar(0, 255, 0), 4); +} + +// -------------------------------------------------------------------------------------- +// transform query image, extract&compute, train, matching and save result image function +// -------------------------------------------------------------------------------------- +typedef struct tagTrainInfo +{ + int traindesccnt; + double traintime; + double matchtime; + double accuracy; +}TrainInfo; + +TrainInfo transImgAndTrain( + cv::Feature2D *fe, + cv::DescriptorMatcher *matcher, + const string &matchername, + const cv::Mat& imgQuery, const vector& query_kp, const cv::Mat& query_desc, + const vector& imgOutliers, const vector >& outliers_kp, const vector& outliers_desc, const int totalOutlierDescCnt, + const float t, const testparam *tp, + const int testno, const bool bVerboseOutput, const bool bSaveDrawMatches) +{ + TrainInfo ti; + + // transform query image + cv::Mat imgTransform; + (tp->transfunc)(t, imgQuery, imgTransform); + + // extract kp and compute desc from transformed query image + vector trans_query_kp; + cv::Mat trans_query_desc; +#if CV_MAJOR_VERSION==2 + (*fe)(imgTransform, cv::Mat(), trans_query_kp, trans_query_desc); +#elif CV_MAJOR_VERSION==3 + fe->detectAndCompute(imgTransform, Mat(), trans_query_kp, trans_query_desc); +#endif + // add&train transformed query desc and outlier desc + matcher->clear(); + matcher->add(vector(1, trans_query_desc)); + double s = (double)cv::getTickCount(); + matcher->train(); + ti.traintime = 1000.0*((double)cv::getTickCount() - s) / cv::getTickFrequency(); + ti.traindesccnt = trans_query_desc.rows; +#if defined(TRAIN_WITH_OUTLIER_IMAGES) + // same as matcher->add(outliers_desc); matcher->train(); + for (unsigned int i = 0; i < outliers_desc.size(); ++i) + { + matcher->add(vector(1, outliers_desc[i])); + s = (double)cv::getTickCount(); + matcher->train(); + ti.traintime += 1000.0*((double)cv::getTickCount() - s) / cv::getTickFrequency(); + } + ti.traindesccnt += totalOutlierDescCnt; +#endif + // matching + vector match; + s = (double)cv::getTickCount(); + matcher->match(query_desc, match); + ti.matchtime = 1000.0*((double)cv::getTickCount() - s) / cv::getTickFrequency(); + + // prepare a directory and variables for save matching images + vector mask; + cv::Mat imgResult; + const char resultDir[] = "result"; + if (bSaveDrawMatches) + { + testing::internal::FilePath fp = testing::internal::FilePath(resultDir); + fp.CreateFolder(); + } + + char buff[2048]; + int matchcnt; + + // save query vs transformed query matching image with detected rectangle + matchcnt = maskMatchesByTrainImgIdx(match, (int)0, mask); + // calc homography and inlier + cv::Mat homography; + int inlierCnt = calcHomographyAndInlierCount(query_kp, trans_query_kp, match, mask, homography); + ti.accuracy = (double)inlierCnt / (double)mask.size()*100.0; + drawMatches(imgQuery, query_kp, imgTransform, trans_query_kp, match, imgResult, cv::Scalar::all(-1), cv::Scalar::all(128), mask, cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS); + if (inlierCnt) + { + // draw detected rectangle + drawDetectedRectangle(imgResult, homography, imgQuery); + } + // draw status + sprintf(buff, "%s accuracy:%-3.2f%% %d descriptors training time:%-3.2fms matching :%-3.2fms", matchername.c_str(), ti.accuracy, ti.traindesccnt, ti.traintime, ti.matchtime); + putText(imgResult, buff, cv::Point(0, 12), cv::FONT_HERSHEY_PLAIN, 0.8, cv::Scalar(0., 0., 255.)); + sprintf(buff, "%s/res%03d_%s_%s%.1f_inlier.png", resultDir, testno, matchername.c_str(), tp->transname.c_str(), t); + if (bSaveDrawMatches && !imwrite(buff, imgResult)) cout << "Image " << buff << " can not be saved (may be because directory " << resultDir << " does not exist)." << endl; + +#if defined(TRAIN_WITH_OUTLIER_IMAGES) + // save query vs outlier matching image(s) + for (unsigned int i = 0; i transname.c_str(), t, i); + if (bSaveDrawMatches && !imwrite(buff, imgResult)) cout << "Image " << buff << " can not be saved (may be because directory " << resultDir << " does not exist)." << endl; + } +#endif + if (bVerboseOutput) + { + cout << tp->transname <<" image matching accuracy:" << ti.accuracy << "% " << ti.traindesccnt << " train:" << ti.traintime << "ms match:" << ti.matchtime << "ms" << endl; + } + + return ti; +} + +// -------------------------------------------------------------------------------------- +// Main Test Class +// -------------------------------------------------------------------------------------- +class CV_FeatureDetectorMatcherBaseTest : public cvtest::BaseTest +{ +private: + + testparam *tp; + double target_accuracy_margin_from_bfmatcher; + cv::Feature2D* fe; // feature detector extractor + + cv::DescriptorMatcher* bfmatcher; // brute force matcher for accuracy of reference + cv::DescriptorMatcher* flmatcher; // flann matcher to test + cv::Mat imgQuery; // query image + vector imgOutliers; // outlier image + vector query_kp; // query key points detect from imgQuery + cv::Mat query_desc; // query descriptors extract from imgQuery + vector > outliers_kp; + vector outliers_desc; + int totalOutlierDescCnt; + + string flmatchername; + +public: + + // + // constructor + // + CV_FeatureDetectorMatcherBaseTest(testparam* _tp, double _accuracy_margin, cv::Feature2D* _fe, cv::DescriptorMatcher *_flmatcher, string _flmatchername, int norm_type_for_bfmatcher) : + tp(_tp), + target_accuracy_margin_from_bfmatcher(_accuracy_margin), + fe(_fe), + flmatcher(_flmatcher), + flmatchername(_flmatchername) + { +#if defined(INIT_RANDOM_SEED) + // from test/test_eigen.cpp + srand((unsigned int)time(0)); +#endif + // create brute force matcher for accuracy of reference + bfmatcher = new cv::BFMatcher(norm_type_for_bfmatcher); + } + + // + // Main Test method + // + virtual void run(int) + { + // load query image + string strQueryFile = string(cvtest::TS::ptr()->get_data_path()) + "shared/lena.png"; + imgQuery = cv::imread(strQueryFile, 0); + if (imgQuery.empty()) + { + ts->printf(cvtest::TS::LOG, "Image %s can not be read.\n", strQueryFile.c_str()); + ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_TEST_DATA); + return; + } + + // load outlier images + char* outliers[] = { (char*)"baboon.png", (char*)"fruits.png", (char*)"airplane.png" }; + for (unsigned int i = 0; i < sizeof(outliers) / sizeof(char*); i++) + { + string strOutlierFile = string(cvtest::TS::ptr()->get_data_path()) + "shared/" + outliers[i]; + cv::Mat imgOutlier = cv::imread(strOutlierFile, 0); + if (imgQuery.empty()) + { + ts->printf(cvtest::TS::LOG, "Image %s can not be read.\n", strOutlierFile.c_str()); + ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_TEST_DATA); + return; + } + imgOutliers.push_back(imgOutlier); + } + + // extract and compute keypoints and descriptors from query image +#if CV_MAJOR_VERSION==2 + (*fe)(imgQuery, cv::Mat(), query_kp, query_desc); +#elif CV_MAJOR_VERSION==3 + fe->detectAndCompute(imgQuery, Mat(), query_kp, query_desc); +#endif + // extract and compute keypoints and descriptors from outlier images + fe->detect(imgOutliers, outliers_kp); + ((cv::DescriptorExtractor*)fe)->compute(imgOutliers, outliers_kp, outliers_desc); + totalOutlierDescCnt = 0; + for (unsigned int i = 0; i < outliers_desc.size(); ++i) totalOutlierDescCnt += outliers_desc[i].rows; + + if (SHOW_DEBUG_LOG) + { + cout << query_kp.size() << " keypoints extracted from query image." << endl; +#if defined(TRAIN_WITH_OUTLIER_IMAGES) + cout << totalOutlierDescCnt << " keypoints extracted from outlier image(s)." << endl; +#endif + } + // compute brute force matcher accuracy for reference + double totalTrainTime = 0.; + double totalMatchTime = 0.; + double totalAccuracy = 0.; + int cnt = 0; + for (float t = tp->from; t <= tp->to; t += tp->step, ++testno_for_make_filename, ++cnt) + { + if (SHOW_DEBUG_LOG) cout << "Test No." << testno_for_make_filename << " BFMatcher " << t; + + TrainInfo ti = transImgAndTrain(fe, bfmatcher, "BFMatcher", + imgQuery, query_kp, query_desc, + imgOutliers, outliers_kp, outliers_desc, + totalOutlierDescCnt, + t, tp, testno_for_make_filename, SHOW_DEBUG_LOG, SAVE_DRAW_MATCHES_IMAGES); + totalTrainTime += ti.traintime; + totalMatchTime += ti.matchtime; + totalAccuracy += ti.accuracy; + } + double bf_average_accuracy = totalAccuracy / cnt; + if (SHOW_DEBUG_LOG) + { + cout << "total training time: " << totalTrainTime << "ms" << endl; + cout << "total matching time: " << totalMatchTime << "ms" << endl; + cout << "average accuracy:" << bf_average_accuracy << "%" << endl; + } + + // test the target matcher + totalTrainTime = 0.; + totalMatchTime = 0.; + totalAccuracy = 0.; + cnt = 0; + for (float t = tp->from; t <= tp->to; t += tp->step, ++testno_for_make_filename, ++cnt) + { + if (SHOW_DEBUG_LOG) cout << "Test No." << testno_for_make_filename << " " << flmatchername << " " << t; + + TrainInfo ti = transImgAndTrain(fe, flmatcher, flmatchername, + imgQuery, query_kp, query_desc, + imgOutliers, outliers_kp, outliers_desc, + totalOutlierDescCnt, + t, tp, testno_for_make_filename, SHOW_DEBUG_LOG, SAVE_DRAW_MATCHES_IMAGES); + + totalTrainTime += ti.traintime; + totalMatchTime += ti.matchtime; + totalAccuracy += ti.accuracy; + } + double average_accuracy = totalAccuracy / cnt; + double target_average_accuracy = bf_average_accuracy * target_accuracy_margin_from_bfmatcher; + + if (SHOW_DEBUG_LOG) + { + cout << "total training time: " << totalTrainTime << "ms" << endl; + cout << "total matching time: " << totalMatchTime << "ms" << endl; + cout << "average accuracy:" << average_accuracy << "%" << endl; + cout << "threshold of the target matcher average accuracy as error :" << target_average_accuracy << "%" << endl; + cout << "accuracy degraded " << (100.0 - (average_accuracy / bf_average_accuracy *100.0)) << "% from BFMatcher.(lower percentage is better)" << endl; + } + // compare accuracies between the brute force matcher and the test target matcher + if (average_accuracy < target_average_accuracy) + { + ts->printf(cvtest::TS::LOG, "Bad average accuracy %f < %f while test %s %s query\n", average_accuracy, target_average_accuracy, flmatchername.c_str(), tp->transname.c_str()); + ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY); + } + return; + } + +}; + +// -------------------------------------------------------------------------------------- +// Transform Functions +// -------------------------------------------------------------------------------------- +static void rotate(float deg, const cv::Mat& src, cv::Mat& dst) +{ + cv::warpAffine(src, dst, getRotationMatrix2D(cv::Point2f(src.cols / 2.0f, src.rows / 2.0f), deg, 1), src.size(), cv::INTER_CUBIC); +} +static void scale(float scale, const cv::Mat& src, cv::Mat& dst) +{ + cv::resize(src, dst, cv::Size((int)(src.cols*scale), (int)(src.rows*scale)), cv::INTER_CUBIC); +} +static void blur(float k, const cv::Mat& src, cv::Mat& dst) +{ + GaussianBlur(src, dst, cv::Size((int)k, (int)k), 0); +} + +// -------------------------------------------------------------------------------------- +// Tests Registrations +// -------------------------------------------------------------------------------------- +#define SHORT_LSH_KEY_ACCURACY_MARGIN 0.72 // The margin for FlannBasedMatcher. 28% degraded from BFMatcher(Actually, about 10..24% measured.lower percentage is better.) for lsh key size=16. +#define MIDDLE_LSH_KEY_ACCURACY_MARGIN 0.72 // The margin for FlannBasedMatcher. 28% degraded from BFMatcher(Actually, about 7..24% measured.lower percentage is better.) for lsh key size=24. +#define LONG_LSH_KEY_ACCURACY_MARGIN 0.90 // The margin for FlannBasedMatcher. 10% degraded from BFMatcher(Actually, about -29...7% measured.lower percentage is better.) for lsh key size=31. + +TEST(BlurredQueryFlannBasedLshShortKeyMatcherAdditionalTrainTest, accuracy) +{ + cv::Ptr fe = OrbCreate; + cv::Ptr fl = cv::makePtr(cv::makePtr(1, 16, 2)); + testparam tp("blurred", blur, 1.0f, 11.0f, 2.0f); + CV_FeatureDetectorMatcherBaseTest test(&tp, SHORT_LSH_KEY_ACCURACY_MARGIN, fe, fl, "FlannLsh(1, 16, 2)", cv::NORM_HAMMING); + test.safe_run(); +} +TEST(BlurredQueryFlannBasedLshMiddleKeyMatcherAdditionalTrainTest, accuracy) +{ + cv::Ptr fe = OrbCreate; + cv::Ptr fl = cv::makePtr(cv::makePtr(1, 24, 2)); + testparam tp("blurred", blur, 1.0f, 11.0f, 2.0f); + CV_FeatureDetectorMatcherBaseTest test(&tp, MIDDLE_LSH_KEY_ACCURACY_MARGIN, fe, fl, "FlannLsh(1, 24, 2)", cv::NORM_HAMMING); + test.safe_run(); +} +TEST(BlurredQueryFlannBasedLshLongKeyMatcherAdditionalTrainTest, accuracy) +{ + cv::Ptr fe = OrbCreate; + cv::Ptr fl = cv::makePtr(cv::makePtr(1, 31, 2)); + testparam tp("blurred", blur, 1.0f, 11.0f, 2.0f); + CV_FeatureDetectorMatcherBaseTest test(&tp, LONG_LSH_KEY_ACCURACY_MARGIN, fe, fl, "FlannLsh(1, 31, 2)", cv::NORM_HAMMING); + test.safe_run(); +} + +TEST(ScaledQueryFlannBasedLshShortKeyMatcherAdditionalTrainTest, accuracy) +{ + cv::Ptr fe = OrbCreate; + cv::Ptr fl = cv::makePtr(cv::makePtr(1, 16, 2)); + testparam tp("scaled", scale, 0.5f, 1.5f, 0.1f); + CV_FeatureDetectorMatcherBaseTest test(&tp, SHORT_LSH_KEY_ACCURACY_MARGIN, fe, fl, "FlannLsh(1, 16, 2)", cv::NORM_HAMMING); + test.safe_run(); +} +TEST(ScaledQueryFlannBasedLshMiddleKeyMatcherAdditionalTrainTest, accuracy) +{ + cv::Ptr fe = OrbCreate; + cv::Ptr fl = cv::makePtr(cv::makePtr(1, 24, 2)); + testparam tp("scaled", scale, 0.5f, 1.5f, 0.1f); + CV_FeatureDetectorMatcherBaseTest test(&tp, MIDDLE_LSH_KEY_ACCURACY_MARGIN, fe, fl, "FlannLsh(1, 24, 2)", cv::NORM_HAMMING); + test.safe_run(); +} +TEST(ScaledQueryFlannBasedLshLongKeyMatcherAdditionalTrainTest, accuracy) +{ + cv::Ptr fe = OrbCreate; + cv::Ptr fl = cv::makePtr(cv::makePtr(1, 31, 2)); + testparam tp("scaled", scale, 0.5f, 1.5f, 0.1f); + CV_FeatureDetectorMatcherBaseTest test(&tp, LONG_LSH_KEY_ACCURACY_MARGIN, fe, fl, "FlannLsh(1, 31, 2)", cv::NORM_HAMMING); + test.safe_run(); +} + +TEST(RotatedQueryFlannBasedLshShortKeyMatcherAdditionalTrainTest, accuracy) +{ + cv::Ptr fe = OrbCreate; + cv::Ptr fl = cv::makePtr(cv::makePtr(1, 16, 2)); + testparam tp("rotated", rotate, 0.0f, 359.0f, 30.0f); + CV_FeatureDetectorMatcherBaseTest test(&tp, SHORT_LSH_KEY_ACCURACY_MARGIN, fe, fl, "FlannLsh(1, 16, 2)", cv::NORM_HAMMING); + test.safe_run(); +} +TEST(RotatedQueryFlannBasedLshMiddleKeyMatcherAdditionalTrainTest, accuracy) +{ + cv::Ptr fe = OrbCreate; + cv::Ptr fl = cv::makePtr(cv::makePtr(1, 24, 2)); + testparam tp("rotated", rotate, 0.0f, 359.0f, 30.0f); + CV_FeatureDetectorMatcherBaseTest test(&tp, MIDDLE_LSH_KEY_ACCURACY_MARGIN, fe, fl, "FlannLsh(1, 24, 2)", cv::NORM_HAMMING); + test.safe_run(); +} +TEST(RotatedQueryFlannBasedLshLongKeyMatcherAdditionalTrainTest, accuracy) +{ + cv::Ptr fe = OrbCreate; + cv::Ptr fl = cv::makePtr(cv::makePtr(1, 31, 2)); + testparam tp("rotated", rotate, 0.0f, 359.0f, 30.0f); + CV_FeatureDetectorMatcherBaseTest test(&tp, LONG_LSH_KEY_ACCURACY_MARGIN, fe, fl, "FlannLsh(1, 31, 2)", cv::NORM_HAMMING); + test.safe_run(); +} diff --git a/modules/features2d/test/test_precomp.hpp b/modules/features2d/test/test_precomp.hpp index bc2d8be6e9..b0fb9a593e 100644 --- a/modules/features2d/test/test_precomp.hpp +++ b/modules/features2d/test/test_precomp.hpp @@ -14,6 +14,7 @@ #include "opencv2/imgproc/imgproc_c.h" #include "opencv2/features2d/features2d.hpp" #include "opencv2/highgui/highgui.hpp" +#include "opencv2/calib3d/calib3d.hpp" #include #endif diff --git a/modules/flann/include/opencv2/flann/autotuned_index.h b/modules/flann/include/opencv2/flann/autotuned_index.h index 2b9ca91a42..43a7bcd920 100644 --- a/modules/flann/include/opencv2/flann/autotuned_index.h +++ b/modules/flann/include/opencv2/flann/autotuned_index.h @@ -94,6 +94,13 @@ public: } } + /** + * Dummy implementation for other algorithms of addable indexes after that. + */ + void addIndex(const Matrix& /*wholeData*/, const Matrix& /*additionalData*/) + { + } + /** * Method responsible with building the index. */ diff --git a/modules/flann/include/opencv2/flann/composite_index.h b/modules/flann/include/opencv2/flann/composite_index.h index 527ca1ad77..02b7bc1f31 100644 --- a/modules/flann/include/opencv2/flann/composite_index.h +++ b/modules/flann/include/opencv2/flann/composite_index.h @@ -130,6 +130,13 @@ public: return kmeans_index_->usedMemory() + kdtree_index_->usedMemory(); } + /** + * Dummy implementation for other algorithms of addable indexes after that. + */ + void addIndex(const Matrix& /*wholeData*/, const Matrix& /*additionalData*/) + { + } + /** * \brief Builds the index */ diff --git a/modules/flann/include/opencv2/flann/flann_base.hpp b/modules/flann/include/opencv2/flann/flann_base.hpp index b5ba7d79e2..bb5b120099 100644 --- a/modules/flann/include/opencv2/flann/flann_base.hpp +++ b/modules/flann/include/opencv2/flann/flann_base.hpp @@ -124,6 +124,16 @@ public: delete nnIndex_; } + /** + * implementation for algorithms of addable indexes after that. + */ + void addIndex(const Matrix& wholeData, const Matrix& additionalData) + { + if (!loaded_) { + nnIndex_->addIndex(wholeData, additionalData); + } + } + /** * Builds the index. */ diff --git a/modules/flann/include/opencv2/flann/hierarchical_clustering_index.h b/modules/flann/include/opencv2/flann/hierarchical_clustering_index.h index 54cb8e55fc..59423ae0cd 100644 --- a/modules/flann/include/opencv2/flann/hierarchical_clustering_index.h +++ b/modules/flann/include/opencv2/flann/hierarchical_clustering_index.h @@ -378,6 +378,14 @@ public: return pool.usedMemory+pool.wastedMemory+memoryCounter; } + + /** + * Dummy implementation for other algorithms of addable indexes after that. + */ + void addIndex(const Matrix& /*wholeData*/, const Matrix& /*additionalData*/) + { + } + /** * Builds the index */ diff --git a/modules/flann/include/opencv2/flann/kdtree_index.h b/modules/flann/include/opencv2/flann/kdtree_index.h index dc0971c9ef..1b8af4a597 100644 --- a/modules/flann/include/opencv2/flann/kdtree_index.h +++ b/modules/flann/include/opencv2/flann/kdtree_index.h @@ -117,6 +117,13 @@ public: delete[] var_; } + /** + * Dummy implementation for other algorithms of addable indexes after that. + */ + void addIndex(const Matrix& /*wholeData*/, const Matrix& /*additionalData*/) + { + } + /** * Builds the index */ diff --git a/modules/flann/include/opencv2/flann/kdtree_single_index.h b/modules/flann/include/opencv2/flann/kdtree_single_index.h index 30488ad567..252fc4c5e1 100644 --- a/modules/flann/include/opencv2/flann/kdtree_single_index.h +++ b/modules/flann/include/opencv2/flann/kdtree_single_index.h @@ -110,6 +110,13 @@ public: if (reorder_) delete[] data_.data; } + /** + * Dummy implementation for other algorithms of addable indexes after that. + */ + void addIndex(const Matrix& /*wholeData*/, const Matrix& /*additionalData*/) + { + } + /** * Builds the index */ diff --git a/modules/flann/include/opencv2/flann/kmeans_index.h b/modules/flann/include/opencv2/flann/kmeans_index.h index 8bcb63d72f..9c0f4e2df8 100644 --- a/modules/flann/include/opencv2/flann/kmeans_index.h +++ b/modules/flann/include/opencv2/flann/kmeans_index.h @@ -362,6 +362,13 @@ public: return pool_.usedMemory+pool_.wastedMemory+memoryCounter_; } + /** + * Dummy implementation for other algorithms of addable indexes after that. + */ + void addIndex(const Matrix& /*wholeData*/, const Matrix& /*additionalData*/) + { + } + /** * Builds the index */ diff --git a/modules/flann/include/opencv2/flann/linear_index.h b/modules/flann/include/opencv2/flann/linear_index.h index 5aa7a5cfac..0ea084a5f5 100644 --- a/modules/flann/include/opencv2/flann/linear_index.h +++ b/modules/flann/include/opencv2/flann/linear_index.h @@ -85,6 +85,13 @@ public: return 0; } + /** + * Dummy implementation for other algorithms of addable indexes after that. + */ + void addIndex(const Matrix& /*wholeData*/, const Matrix& /*additionalData*/) + { + } + void buildIndex() { /* nothing to do here for linear search */ diff --git a/modules/flann/include/opencv2/flann/lsh_index.h b/modules/flann/include/opencv2/flann/lsh_index.h index 23988d436a..2b89337d91 100644 --- a/modules/flann/include/opencv2/flann/lsh_index.h +++ b/modules/flann/include/opencv2/flann/lsh_index.h @@ -104,6 +104,22 @@ public: LshIndex(const LshIndex&); LshIndex& operator=(const LshIndex&); + /** + * Implementation for the LSH addable indexes after that. + * @param wholeData whole dataset with the input features + * @param additionalData additional dataset with the input features + */ + void addIndex(const Matrix& wholeData, const Matrix& additionalData) + { + tables_.resize(table_number_); + for (unsigned int i = 0; i < table_number_; ++i) { + lsh::LshTable& table = tables_[i]; + // Add the features to the table with indexed offset + table.add((int)(wholeData.rows - additionalData.rows), additionalData); + } + dataset_ = wholeData; + } + /** * Builds the index */ @@ -126,8 +142,8 @@ public: lsh::LshTable& table = tables_[i]; table = lsh::LshTable(feature_size_, key_size_, indices); - // Add the features to the table - table.add(dataset_); + // Add the features to the table with offset 0 + table.add(0, dataset_); } } diff --git a/modules/flann/include/opencv2/flann/lsh_table.h b/modules/flann/include/opencv2/flann/lsh_table.h index 0defa52a77..cef01b2e1b 100644 --- a/modules/flann/include/opencv2/flann/lsh_table.h +++ b/modules/flann/include/opencv2/flann/lsh_table.h @@ -192,15 +192,16 @@ public: } /** Add a set of features to the table + * @param indexed_ofst previous indexed offset * @param dataset the values to store */ - void add(Matrix dataset) + void add(int indexed_ofst, Matrix dataset) { #if USE_UNORDERED_MAP buckets_space_.rehash((buckets_space_.size() + dataset.rows) * 1.2); #endif // Add the features to the table - for (unsigned int i = 0; i < dataset.rows; ++i) add(i, dataset[i]); + for (unsigned int i = 0; i < dataset.rows; ++i) add(i + indexed_ofst, dataset[i]); // Now that the table is full, optimize it for speed/space optimize(); } diff --git a/modules/flann/include/opencv2/flann/miniflann.hpp b/modules/flann/include/opencv2/flann/miniflann.hpp index ada375691c..121f8d05c3 100644 --- a/modules/flann/include/opencv2/flann/miniflann.hpp +++ b/modules/flann/include/opencv2/flann/miniflann.hpp @@ -134,7 +134,8 @@ public: CV_WRAP Index(InputArray features, const IndexParams& params, cvflann::flann_distance_t distType=cvflann::FLANN_DIST_L2); virtual ~Index(); - CV_WRAP virtual void build(InputArray features, const IndexParams& params, cvflann::flann_distance_t distType=cvflann::FLANN_DIST_L2); + CV_WRAP virtual void build(InputArray wholefeatures, InputArray additionalfeatures, const IndexParams& params, cvflann::flann_distance_t distType=cvflann::FLANN_DIST_L2); + CV_WRAP virtual void knnSearch(InputArray query, OutputArray indices, OutputArray dists, int knn, const SearchParams& params=SearchParams()); diff --git a/modules/flann/include/opencv2/flann/nn_index.h b/modules/flann/include/opencv2/flann/nn_index.h index d14e83a92c..4a874f5867 100644 --- a/modules/flann/include/opencv2/flann/nn_index.h +++ b/modules/flann/include/opencv2/flann/nn_index.h @@ -59,6 +59,11 @@ public: */ virtual void buildIndex() = 0; + /** + * \brief implementation for algorithms of addable indexes after that. + */ + virtual void addIndex(const Matrix& wholeData, const Matrix& additionalData) = 0; + /** * \brief Perform k-nearest neighbor search * \param[in] queries The query points for which to find the nearest neighbors diff --git a/modules/flann/src/miniflann.cpp b/modules/flann/src/miniflann.cpp index 3747102728..5ce3e9051a 100644 --- a/modules/flann/src/miniflann.cpp +++ b/modules/flann/src/miniflann.cpp @@ -308,7 +308,7 @@ SearchParams::SearchParams( int checks, float eps, bool sorted ) template void -buildIndex_(void*& index, const Mat& data, const IndexParams& params, const Distance& dist = Distance()) +buildIndex_(void*& index, const Mat& wholedata, const Mat& data, const IndexParams& params, const Distance& dist = Distance()) { typedef typename Distance::ElementType ElementType; if(DataType::type != data.type()) @@ -317,15 +317,25 @@ buildIndex_(void*& index, const Mat& data, const IndexParams& params, const Dist CV_Error(CV_StsBadArg, "Only continuous arrays are supported"); ::cvflann::Matrix dataset((ElementType*)data.data, data.rows, data.cols); - IndexType* _index = new IndexType(dataset, get_params(params), dist); - _index->buildIndex(); - index = _index; + + IndexType* _index = NULL; + if( !index || getParam(params, "algorithm", FLANN_INDEX_LINEAR) != FLANN_INDEX_LSH) // currently, additional index support is the lsh algorithm only. + { + _index = new IndexType(dataset, get_params(params), dist); + _index->buildIndex(); + index = _index; + } + else // build additional lsh index + { + ::cvflann::Matrix wholedataset((ElementType*)wholedata.data, wholedata.rows, wholedata.cols); + ((IndexType*)index)->addIndex(wholedataset, dataset); + } } template void -buildIndex(void*& index, const Mat& data, const IndexParams& params, const Distance& dist = Distance()) +buildIndex(void*& index, const Mat& wholedata, const Mat& data, const IndexParams& params, const Distance& dist = Distance()) { - buildIndex_ >(index, data, params, dist); + buildIndex_ >(index, wholedata, data, params, dist); } #if CV_NEON @@ -348,21 +358,28 @@ Index::Index(InputArray _data, const IndexParams& params, flann_distance_t _dist featureType = CV_32F; algo = FLANN_INDEX_LINEAR; distType = FLANN_DIST_L2; - build(_data, params, _distType); + build(_data, _data, params, _distType); } -void Index::build(InputArray _data, const IndexParams& params, flann_distance_t _distType) +void Index::build(InputArray _wholedata, InputArray _data, const IndexParams& params, flann_distance_t _distType) { - release(); algo = getParam(params, "algorithm", FLANN_INDEX_LINEAR); - if( algo == FLANN_INDEX_SAVED ) + + if (algo != FLANN_INDEX_LSH) // do not release if algo == FLANN_INDEX_LSH + { + release(); + } + if (algo == FLANN_INDEX_SAVED) { load(_data, getParam(params, "filename", std::string())); return; } Mat data = _data.getMat(); - index = 0; + if (algo != FLANN_INDEX_LSH) // do not clear if algo == FLANN_INDEX_LSH + { + index = 0; + } featureType = data.type(); distType = _distType; @@ -374,29 +391,29 @@ void Index::build(InputArray _data, const IndexParams& params, flann_distance_t switch( distType ) { case FLANN_DIST_HAMMING: - buildIndex< HammingDistance >(index, data, params); + buildIndex< HammingDistance >(index, _wholedata.getMat(), data, params); break; case FLANN_DIST_L2: - buildIndex< ::cvflann::L2 >(index, data, params); + buildIndex< ::cvflann::L2 >(index, _wholedata.getMat(), data, params); break; case FLANN_DIST_L1: - buildIndex< ::cvflann::L1 >(index, data, params); + buildIndex< ::cvflann::L1 >(index, _wholedata.getMat(), data, params); break; #if MINIFLANN_SUPPORT_EXOTIC_DISTANCE_TYPES case FLANN_DIST_MAX: - buildIndex< ::cvflann::MaxDistance >(index, data, params); + buildIndex< ::cvflann::MaxDistance >(index, _wholedata.getMat(), data, params); break; case FLANN_DIST_HIST_INTERSECT: - buildIndex< ::cvflann::HistIntersectionDistance >(index, data, params); + buildIndex< ::cvflann::HistIntersectionDistance >(index, _wholedata.getMat(), data, params); break; case FLANN_DIST_HELLINGER: - buildIndex< ::cvflann::HellingerDistance >(index, data, params); + buildIndex< ::cvflann::HellingerDistance >(index, _wholedata.getMat(), data, params); break; case FLANN_DIST_CHI_SQUARE: - buildIndex< ::cvflann::ChiSquareDistance >(index, data, params); + buildIndex< ::cvflann::ChiSquareDistance >(index, _wholedata.getMat(), data, params); break; case FLANN_DIST_KL: - buildIndex< ::cvflann::KL_Divergence >(index, data, params); + buildIndex< ::cvflann::KL_Divergence >(index, _wholedata.getMat(), data, params); break; #endif default: diff --git a/modules/ts/CMakeLists.txt b/modules/ts/CMakeLists.txt index bf7da104de..4d4bebda80 100644 --- a/modules/ts/CMakeLists.txt +++ b/modules/ts/CMakeLists.txt @@ -9,7 +9,7 @@ set(OPENCV_MODULE_IS_PART_OF_WORLD FALSE) ocv_warnings_disable(CMAKE_CXX_FLAGS -Wundef) -ocv_add_module(ts opencv_core opencv_features2d opencv_highgui opencv_imgproc opencv_video) +ocv_add_module(ts opencv_core opencv_features2d opencv_highgui opencv_imgproc opencv_video opencv_calib3d) ocv_glob_module_sources() ocv_module_include_directories()