diff --git a/modules/features2d/test/test_descriptors_invariance.cpp b/modules/features2d/test/test_descriptors_invariance.cpp index f9c65067c5..1487ee9d94 100644 --- a/modules/features2d/test/test_descriptors_invariance.cpp +++ b/modules/features2d/test/test_descriptors_invariance.cpp @@ -5,163 +5,13 @@ #include "test_precomp.hpp" #include "test_invariance_utils.hpp" -namespace opencv_test { namespace { +#include "test_descriptors_invariance.impl.hpp" -#define SHOW_DEBUG_LOG 1 +namespace opencv_test { namespace { -typedef tuple, Ptr, float> - String_FeatureDetector_DescriptorExtractor_Float_t; const static std::string IMAGE_TSUKUBA = "features2d/tsukuba.png"; const static std::string IMAGE_BIKES = "detectors_descriptors_evaluation/images_datasets/bikes/img1.png"; -#define Value(...) Values(String_FeatureDetector_DescriptorExtractor_Float_t(__VA_ARGS__)) - -static -void rotateKeyPoints(const vector& src, const Mat& H, float angle, vector& dst) -{ - // suppose that H is rotation given from rotateImage() and angle has value passed to rotateImage() - vector srcCenters, dstCenters; - KeyPoint::convert(src, srcCenters); - - perspectiveTransform(srcCenters, dstCenters, H); - - dst = src; - for(size_t i = 0; i < dst.size(); i++) - { - dst[i].pt = dstCenters[i]; - float dstAngle = src[i].angle + angle; - if(dstAngle >= 360.f) - dstAngle -= 360.f; - dst[i].angle = dstAngle; - } -} - -class DescriptorInvariance : public TestWithParam -{ -protected: - virtual void SetUp() { - // Read test data - const std::string filename = cvtest::TS::ptr()->get_data_path() + get<0>(GetParam()); - image0 = imread(filename); - ASSERT_FALSE(image0.empty()) << "couldn't read input image"; - - featureDetector = get<1>(GetParam()); - descriptorExtractor = get<2>(GetParam()); - minInliersRatio = get<3>(GetParam()); - } - - Ptr featureDetector; - Ptr descriptorExtractor; - float minInliersRatio; - Mat image0; -}; - -typedef DescriptorInvariance DescriptorScaleInvariance; -typedef DescriptorInvariance DescriptorRotationInvariance; - -TEST_P(DescriptorRotationInvariance, rotation) -{ - Mat image1, mask1; - const int borderSize = 16; - Mat mask0(image0.size(), CV_8UC1, Scalar(0)); - mask0(Rect(borderSize, borderSize, mask0.cols - 2*borderSize, mask0.rows - 2*borderSize)).setTo(Scalar(255)); - - vector keypoints0; - Mat descriptors0; - featureDetector->detect(image0, keypoints0, mask0); - std::cout << "Keypoints: " << keypoints0.size() << std::endl; - EXPECT_GE(keypoints0.size(), 15u); - descriptorExtractor->compute(image0, keypoints0, descriptors0); - - BFMatcher bfmatcher(descriptorExtractor->defaultNorm()); - - const float minIntersectRatio = 0.5f; - const int maxAngle = 360, angleStep = 15; - for(int angle = 0; angle < maxAngle; angle += angleStep) - { - Mat H = rotateImage(image0, mask0, static_cast(angle), image1, mask1); - - vector keypoints1; - rotateKeyPoints(keypoints0, H, static_cast(angle), keypoints1); - Mat descriptors1; - descriptorExtractor->compute(image1, keypoints1, descriptors1); - - vector descMatches; - bfmatcher.match(descriptors0, descriptors1, descMatches); - - int descInliersCount = 0; - for(size_t m = 0; m < descMatches.size(); m++) - { - const KeyPoint& transformed_p0 = keypoints1[descMatches[m].queryIdx]; - const KeyPoint& p1 = keypoints1[descMatches[m].trainIdx]; - if(calcIntersectRatio(transformed_p0.pt, 0.5f * transformed_p0.size, - p1.pt, 0.5f * p1.size) >= minIntersectRatio) - { - descInliersCount++; - } - } - - float descInliersRatio = static_cast(descInliersCount) / keypoints0.size(); - EXPECT_GE(descInliersRatio, minInliersRatio); -#if SHOW_DEBUG_LOG - std::cout - << "angle = " << angle - << ", inliers = " << descInliersCount - << ", descInliersRatio = " << static_cast(descInliersCount) / keypoints0.size() - << std::endl; -#endif - } -} - - -TEST_P(DescriptorScaleInvariance, scale) -{ - vector keypoints0; - featureDetector->detect(image0, keypoints0); - std::cout << "Keypoints: " << keypoints0.size() << std::endl; - EXPECT_GE(keypoints0.size(), 15u); - Mat descriptors0; - descriptorExtractor->compute(image0, keypoints0, descriptors0); - - BFMatcher bfmatcher(descriptorExtractor->defaultNorm()); - for(int scaleIdx = 1; scaleIdx <= 3; scaleIdx++) - { - float scale = 1.f + scaleIdx * 0.5f; - - Mat image1; - resize(image0, image1, Size(), 1./scale, 1./scale, INTER_LINEAR_EXACT); - - vector keypoints1; - scaleKeyPoints(keypoints0, keypoints1, 1.0f/scale); - Mat descriptors1; - descriptorExtractor->compute(image1, keypoints1, descriptors1); - - vector descMatches; - bfmatcher.match(descriptors0, descriptors1, descMatches); - - const float minIntersectRatio = 0.5f; - int descInliersCount = 0; - for(size_t m = 0; m < descMatches.size(); m++) - { - const KeyPoint& transformed_p0 = keypoints0[descMatches[m].queryIdx]; - const KeyPoint& p1 = keypoints0[descMatches[m].trainIdx]; - if(calcIntersectRatio(transformed_p0.pt, 0.5f * transformed_p0.size, - p1.pt, 0.5f * p1.size) >= minIntersectRatio) - { - descInliersCount++; - } - } - - float descInliersRatio = static_cast(descInliersCount) / keypoints0.size(); - EXPECT_GE(descInliersRatio, minInliersRatio); -#if SHOW_DEBUG_LOG - std::cout - << "scale = " << scale - << ", inliers = " << descInliersCount - << ", descInliersRatio = " << static_cast(descInliersCount) / keypoints0.size() - << std::endl; -#endif - } -} +#define Value(...) Values(make_tuple(__VA_ARGS__)) /* * Descriptors's rotation invariance check diff --git a/modules/features2d/test/test_descriptors_invariance.impl.hpp b/modules/features2d/test/test_descriptors_invariance.impl.hpp new file mode 100644 index 0000000000..f252f1ea90 --- /dev/null +++ b/modules/features2d/test/test_descriptors_invariance.impl.hpp @@ -0,0 +1,174 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html + +#include "test_invariance_utils.hpp" + +namespace opencv_test { namespace { + +#define SHOW_DEBUG_LOG 1 + +typedef tuple, Ptr, float> + String_FeatureDetector_DescriptorExtractor_Float_t; + + +static +void rotateKeyPoints(const vector& src, const Mat& H, float angle, vector& dst) +{ + // suppose that H is rotation given from rotateImage() and angle has value passed to rotateImage() + vector srcCenters, dstCenters; + KeyPoint::convert(src, srcCenters); + + perspectiveTransform(srcCenters, dstCenters, H); + + dst = src; + for(size_t i = 0; i < dst.size(); i++) + { + dst[i].pt = dstCenters[i]; + float dstAngle = src[i].angle + angle; + if(dstAngle >= 360.f) + dstAngle -= 360.f; + dst[i].angle = dstAngle; + } +} + +class DescriptorInvariance : public TestWithParam +{ +protected: + virtual void SetUp() { + // Read test data + const std::string filename = cvtest::TS::ptr()->get_data_path() + get<0>(GetParam()); + image0 = imread(filename); + ASSERT_FALSE(image0.empty()) << "couldn't read input image"; + + featureDetector = get<1>(GetParam()); + descriptorExtractor = get<2>(GetParam()); + minInliersRatio = get<3>(GetParam()); + } + + Ptr featureDetector; + Ptr descriptorExtractor; + float minInliersRatio; + Mat image0; +}; + +typedef DescriptorInvariance DescriptorScaleInvariance; +typedef DescriptorInvariance DescriptorRotationInvariance; + +TEST_P(DescriptorRotationInvariance, rotation) +{ + Mat image1, mask1; + const int borderSize = 16; + Mat mask0(image0.size(), CV_8UC1, Scalar(0)); + mask0(Rect(borderSize, borderSize, mask0.cols - 2*borderSize, mask0.rows - 2*borderSize)).setTo(Scalar(255)); + + vector keypoints0; + Mat descriptors0; + featureDetector->detect(image0, keypoints0, mask0); + std::cout << "Keypoints: " << keypoints0.size() << std::endl; + EXPECT_GE(keypoints0.size(), 15u); + descriptorExtractor->compute(image0, keypoints0, descriptors0); + + BFMatcher bfmatcher(descriptorExtractor->defaultNorm()); + + const float minIntersectRatio = 0.5f; + const int maxAngle = 360, angleStep = 15; + for(int angle = 0; angle < maxAngle; angle += angleStep) + { + Mat H = rotateImage(image0, mask0, static_cast(angle), image1, mask1); + + vector keypoints1; + rotateKeyPoints(keypoints0, H, static_cast(angle), keypoints1); + Mat descriptors1; + descriptorExtractor->compute(image1, keypoints1, descriptors1); + + vector descMatches; + bfmatcher.match(descriptors0, descriptors1, descMatches); + + int descInliersCount = 0; + for(size_t m = 0; m < descMatches.size(); m++) + { + const KeyPoint& transformed_p0 = keypoints1[descMatches[m].queryIdx]; + const KeyPoint& p1 = keypoints1[descMatches[m].trainIdx]; + if(calcIntersectRatio(transformed_p0.pt, 0.5f * transformed_p0.size, + p1.pt, 0.5f * p1.size) >= minIntersectRatio) + { + descInliersCount++; + } + } + + float descInliersRatio = static_cast(descInliersCount) / keypoints0.size(); + EXPECT_GE(descInliersRatio, minInliersRatio); +#if SHOW_DEBUG_LOG + std::cout + << "angle = " << angle + << ", inliers = " << descInliersCount + << ", descInliersRatio = " << static_cast(descInliersCount) / keypoints0.size() + << std::endl; +#endif + } +} + + +TEST_P(DescriptorScaleInvariance, scale) +{ + vector keypoints0; + featureDetector->detect(image0, keypoints0); + std::cout << "Keypoints: " << keypoints0.size() << std::endl; + EXPECT_GE(keypoints0.size(), 15u); + Mat descriptors0; + descriptorExtractor->compute(image0, keypoints0, descriptors0); + + BFMatcher bfmatcher(descriptorExtractor->defaultNorm()); + for(int scaleIdx = 1; scaleIdx <= 3; scaleIdx++) + { + float scale = 1.f + scaleIdx * 0.5f; + + Mat image1; + resize(image0, image1, Size(), 1./scale, 1./scale, INTER_LINEAR_EXACT); + + vector keypoints1; + scaleKeyPoints(keypoints0, keypoints1, 1.0f/scale); + Mat descriptors1; + descriptorExtractor->compute(image1, keypoints1, descriptors1); + + vector descMatches; + bfmatcher.match(descriptors0, descriptors1, descMatches); + + const float minIntersectRatio = 0.5f; + int descInliersCount = 0; + for(size_t m = 0; m < descMatches.size(); m++) + { + const KeyPoint& transformed_p0 = keypoints0[descMatches[m].queryIdx]; + const KeyPoint& p1 = keypoints0[descMatches[m].trainIdx]; + if(calcIntersectRatio(transformed_p0.pt, 0.5f * transformed_p0.size, + p1.pt, 0.5f * p1.size) >= minIntersectRatio) + { + descInliersCount++; + } + } + + float descInliersRatio = static_cast(descInliersCount) / keypoints0.size(); + EXPECT_GE(descInliersRatio, minInliersRatio); +#if SHOW_DEBUG_LOG + std::cout + << "scale = " << scale + << ", inliers = " << descInliersCount + << ", descInliersRatio = " << static_cast(descInliersCount) / keypoints0.size() + << std::endl; +#endif + } +} + +#undef SHOW_DEBUG_LOG +}} // namespace + +namespace std { +using namespace opencv_test; +static inline void PrintTo(const String_FeatureDetector_DescriptorExtractor_Float_t& v, std::ostream* os) +{ + *os << "(\"" << get<0>(v) + << "\", " << get<3>(v) + << ")"; +} +} // namespace diff --git a/modules/features2d/test/test_descriptors_regression.cpp b/modules/features2d/test/test_descriptors_regression.cpp index c63b9a56ce..ffacbe3f43 100644 --- a/modules/features2d/test/test_descriptors_regression.cpp +++ b/modules/features2d/test/test_descriptors_regression.cpp @@ -1,342 +1,18 @@ -/*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*/ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html #include "test_precomp.hpp" namespace opencv_test { namespace { - const string FEATURES2D_DIR = "features2d"; const string IMAGE_FILENAME = "tsukuba.png"; const string DESCRIPTOR_DIR = FEATURES2D_DIR + "/descriptor_extractors"; +}} // namespace -/****************************************************************************************\ -* Regression tests for descriptor extractors. * -\****************************************************************************************/ -static void writeMatInBin( const Mat& mat, const string& filename ) -{ - FILE* f = fopen( filename.c_str(), "wb"); - if( f ) - { - CV_Assert(4 == sizeof(int)); - int type = mat.type(); - fwrite( (void*)&mat.rows, sizeof(int), 1, f ); - fwrite( (void*)&mat.cols, sizeof(int), 1, f ); - fwrite( (void*)&type, sizeof(int), 1, f ); - int dataSize = (int)(mat.step * mat.rows); - fwrite( (void*)&dataSize, sizeof(int), 1, f ); - fwrite( (void*)mat.ptr(), 1, dataSize, f ); - fclose(f); - } -} - -static Mat readMatFromBin( const string& filename ) -{ - FILE* f = fopen( filename.c_str(), "rb" ); - if( f ) - { - CV_Assert(4 == sizeof(int)); - int rows, cols, type, dataSize; - size_t elements_read1 = fread( (void*)&rows, sizeof(int), 1, f ); - size_t elements_read2 = fread( (void*)&cols, sizeof(int), 1, f ); - size_t elements_read3 = fread( (void*)&type, sizeof(int), 1, f ); - size_t elements_read4 = fread( (void*)&dataSize, sizeof(int), 1, f ); - CV_Assert(elements_read1 == 1 && elements_read2 == 1 && elements_read3 == 1 && elements_read4 == 1); - - int step = dataSize / rows / CV_ELEM_SIZE(type); - CV_Assert(step >= cols); - - Mat returnMat = Mat(rows, step, type).colRange(0, cols); - - size_t elements_read = fread( returnMat.ptr(), 1, dataSize, f ); - CV_Assert(elements_read == (size_t)(dataSize)); - - fclose(f); - - return returnMat; - } - return Mat(); -} - -template -class CV_DescriptorExtractorTest : public cvtest::BaseTest -{ -public: - typedef typename Distance::ValueType ValueType; - typedef typename Distance::ResultType DistanceType; - - CV_DescriptorExtractorTest( const string _name, DistanceType _maxDist, const Ptr& _dextractor, - Distance d = Distance(), Ptr _detector = Ptr()): - name(_name), maxDist(_maxDist), dextractor(_dextractor), distance(d) , detector(_detector) {} - - ~CV_DescriptorExtractorTest() - { - } -protected: - virtual void createDescriptorExtractor() {} - - void compareDescriptors( const Mat& validDescriptors, const Mat& calcDescriptors ) - { - if( validDescriptors.size != calcDescriptors.size || validDescriptors.type() != calcDescriptors.type() ) - { - ts->printf(cvtest::TS::LOG, "Valid and computed descriptors matrices must have the same size and type.\n"); - ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_TEST_DATA ); - return; - } - - CV_Assert( DataType::type == validDescriptors.type() ); - - int dimension = validDescriptors.cols; - DistanceType curMaxDist = 0; - size_t exact_count = 0, failed_count = 0; - for( int y = 0; y < validDescriptors.rows; y++ ) - { - DistanceType dist = distance( validDescriptors.ptr(y), calcDescriptors.ptr(y), dimension ); - if (dist == 0) - exact_count++; - if( dist > curMaxDist ) - { - if (dist > maxDist) - failed_count++; - curMaxDist = dist; - } -#if 0 - if (dist > 0) - { - std::cout << "i=" << y << " fail_count=" << failed_count << " dist=" << dist << std::endl; - std::cout << "valid: " << validDescriptors.row(y) << std::endl; - std::cout << " calc: " << calcDescriptors.row(y) << std::endl; - } -#endif - } - - float exact_percents = (100 * (float)exact_count / validDescriptors.rows); - float failed_percents = (100 * (float)failed_count / validDescriptors.rows); - std::stringstream ss; - ss << "Exact count (dist == 0): " << exact_count << " (" << (int)exact_percents << "%)" << std::endl - << "Failed count (dist > " << maxDist << "): " << failed_count << " (" << (int)failed_percents << "%)" << std::endl - << "Max distance between valid and computed descriptors (" << validDescriptors.size() << "): " << curMaxDist; - EXPECT_LE(failed_percents, 20.0f); - std::cout << ss.str() << std::endl; - } - - void emptyDataTest() - { - assert( dextractor ); - - // One image. - Mat image; - vector keypoints; - Mat descriptors; - - try - { - dextractor->compute( image, keypoints, descriptors ); - } - catch(...) - { - ts->printf( cvtest::TS::LOG, "compute() on empty image and empty keypoints must not generate exception (1).\n"); - ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_TEST_DATA ); - } - - RNG rng; - image = cvtest::randomMat(rng, Size(50, 50), CV_8UC3, 0, 255, false); - try - { - dextractor->compute( image, keypoints, descriptors ); - } - catch(...) - { - ts->printf( cvtest::TS::LOG, "compute() on nonempty image and empty keypoints must not generate exception (1).\n"); - ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_TEST_DATA ); - } - - // Several images. - vector images; - vector > keypointsCollection; - vector descriptorsCollection; - try - { - dextractor->compute( images, keypointsCollection, descriptorsCollection ); - } - catch(...) - { - ts->printf( cvtest::TS::LOG, "compute() on empty images and empty keypoints collection must not generate exception (2).\n"); - ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_TEST_DATA ); - } - } - - void regressionTest() - { - assert( dextractor ); - - // Read the test image. - string imgFilename = string(ts->get_data_path()) + FEATURES2D_DIR + "/" + IMAGE_FILENAME; - Mat img = imread( imgFilename ); - if( img.empty() ) - { - ts->printf( cvtest::TS::LOG, "Image %s can not be read.\n", imgFilename.c_str() ); - ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_TEST_DATA ); - return; - } - const std::string keypoints_filename = string(ts->get_data_path()) + - (detector.empty() - ? (FEATURES2D_DIR + "/" + std::string("keypoints.xml.gz")) - : (DESCRIPTOR_DIR + "/" + name + "_keypoints.xml.gz")); - FileStorage fs(keypoints_filename, FileStorage::READ); - - vector keypoints; - EXPECT_TRUE(fs.isOpened()) << "Keypoint testdata is missing. Re-computing and re-writing keypoints testdata..."; - if (!fs.isOpened()) - { - fs.open(keypoints_filename, FileStorage::WRITE); - ASSERT_TRUE(fs.isOpened()) << "File for writing keypoints can not be opened."; - if (detector.empty()) - { - Ptr fd = ORB::create(); - fd->detect(img, keypoints); - } - else - { - detector->detect(img, keypoints); - } - write(fs, "keypoints", keypoints); - fs.release(); - } - else - { - read(fs.getFirstTopLevelNode(), keypoints); - fs.release(); - } - - if(!detector.empty()) - { - vector calcKeypoints; - detector->detect(img, calcKeypoints); - // TODO validate received keypoints - int diff = abs((int)calcKeypoints.size() - (int)keypoints.size()); - if (diff > 0) - { - std::cout << "Keypoints difference: " << diff << std::endl; - EXPECT_LE(diff, (int)(keypoints.size() * 0.03f)); - } - } - ASSERT_FALSE(keypoints.empty()); - { - Mat calcDescriptors; - double t = (double)getTickCount(); - dextractor->compute(img, keypoints, calcDescriptors); - t = getTickCount() - t; - ts->printf(cvtest::TS::LOG, "\nAverage time of computing one descriptor = %g ms.\n", t/((double)getTickFrequency()*1000.)/calcDescriptors.rows); - - if (calcDescriptors.rows != (int)keypoints.size()) - { - ts->printf( cvtest::TS::LOG, "Count of computed descriptors and keypoints count must be equal.\n" ); - ts->printf( cvtest::TS::LOG, "Count of keypoints is %d.\n", (int)keypoints.size() ); - ts->printf( cvtest::TS::LOG, "Count of computed descriptors is %d.\n", calcDescriptors.rows ); - ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT ); - return; - } - - if (calcDescriptors.cols != dextractor->descriptorSize() || calcDescriptors.type() != dextractor->descriptorType()) - { - ts->printf( cvtest::TS::LOG, "Incorrect descriptor size or descriptor type.\n" ); - ts->printf( cvtest::TS::LOG, "Expected size is %d.\n", dextractor->descriptorSize() ); - ts->printf( cvtest::TS::LOG, "Calculated size is %d.\n", calcDescriptors.cols ); - ts->printf( cvtest::TS::LOG, "Expected type is %d.\n", dextractor->descriptorType() ); - ts->printf( cvtest::TS::LOG, "Calculated type is %d.\n", calcDescriptors.type() ); - ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT ); - return; - } - - // TODO read and write descriptor extractor parameters and check them - Mat validDescriptors = readDescriptors(); - EXPECT_FALSE(validDescriptors.empty()) << "Descriptors testdata is missing. Re-writing descriptors testdata..."; - if (!validDescriptors.empty()) - { - compareDescriptors(validDescriptors, calcDescriptors); - } - else - { - ASSERT_TRUE(writeDescriptors(calcDescriptors)) << "Descriptors can not be written."; - } - } - } - - void run(int) - { - createDescriptorExtractor(); - if( !dextractor ) - { - ts->printf(cvtest::TS::LOG, "Descriptor extractor is empty.\n"); - ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_TEST_DATA ); - return; - } - - emptyDataTest(); - regressionTest(); - - ts->set_failed_test_info( cvtest::TS::OK ); - } - - virtual Mat readDescriptors() - { - Mat res = readMatFromBin( string(ts->get_data_path()) + DESCRIPTOR_DIR + "/" + string(name) ); - return res; - } - - virtual bool writeDescriptors( Mat& descs ) - { - writeMatInBin( descs, string(ts->get_data_path()) + DESCRIPTOR_DIR + "/" + string(name) ); - return true; - } - - string name; - const DistanceType maxDist; - Ptr dextractor; - Distance distance; - Ptr detector; +#include "test_descriptors_regression.impl.hpp" -private: - CV_DescriptorExtractorTest& operator=(const CV_DescriptorExtractorTest&) { return *this; } -}; +namespace opencv_test { namespace { /****************************************************************************************\ * Tests registrations * diff --git a/modules/features2d/test/test_descriptors_regression.impl.hpp b/modules/features2d/test/test_descriptors_regression.impl.hpp new file mode 100644 index 0000000000..e60b5a0691 --- /dev/null +++ b/modules/features2d/test/test_descriptors_regression.impl.hpp @@ -0,0 +1,298 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html + +namespace opencv_test { namespace { + +/****************************************************************************************\ +* Regression tests for descriptor extractors. * +\****************************************************************************************/ +static void writeMatInBin( const Mat& mat, const string& filename ) +{ + FILE* f = fopen( filename.c_str(), "wb"); + if( f ) + { + CV_Assert(4 == sizeof(int)); + int type = mat.type(); + fwrite( (void*)&mat.rows, sizeof(int), 1, f ); + fwrite( (void*)&mat.cols, sizeof(int), 1, f ); + fwrite( (void*)&type, sizeof(int), 1, f ); + int dataSize = (int)(mat.step * mat.rows); + fwrite( (void*)&dataSize, sizeof(int), 1, f ); + fwrite( (void*)mat.ptr(), 1, dataSize, f ); + fclose(f); + } +} + +static Mat readMatFromBin( const string& filename ) +{ + FILE* f = fopen( filename.c_str(), "rb" ); + if( f ) + { + CV_Assert(4 == sizeof(int)); + int rows, cols, type, dataSize; + size_t elements_read1 = fread( (void*)&rows, sizeof(int), 1, f ); + size_t elements_read2 = fread( (void*)&cols, sizeof(int), 1, f ); + size_t elements_read3 = fread( (void*)&type, sizeof(int), 1, f ); + size_t elements_read4 = fread( (void*)&dataSize, sizeof(int), 1, f ); + CV_Assert(elements_read1 == 1 && elements_read2 == 1 && elements_read3 == 1 && elements_read4 == 1); + + int step = dataSize / rows / CV_ELEM_SIZE(type); + CV_Assert(step >= cols); + + Mat returnMat = Mat(rows, step, type).colRange(0, cols); + + size_t elements_read = fread( returnMat.ptr(), 1, dataSize, f ); + CV_Assert(elements_read == (size_t)(dataSize)); + + fclose(f); + + return returnMat; + } + return Mat(); +} + +template +class CV_DescriptorExtractorTest : public cvtest::BaseTest +{ +public: + typedef typename Distance::ValueType ValueType; + typedef typename Distance::ResultType DistanceType; + + CV_DescriptorExtractorTest( const string _name, DistanceType _maxDist, const Ptr& _dextractor, + Distance d = Distance(), Ptr _detector = Ptr()): + name(_name), maxDist(_maxDist), dextractor(_dextractor), distance(d) , detector(_detector) {} + + ~CV_DescriptorExtractorTest() + { + } +protected: + virtual void createDescriptorExtractor() {} + + void compareDescriptors( const Mat& validDescriptors, const Mat& calcDescriptors ) + { + if( validDescriptors.size != calcDescriptors.size || validDescriptors.type() != calcDescriptors.type() ) + { + ts->printf(cvtest::TS::LOG, "Valid and computed descriptors matrices must have the same size and type.\n"); + ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_TEST_DATA ); + return; + } + + CV_Assert( DataType::type == validDescriptors.type() ); + + int dimension = validDescriptors.cols; + DistanceType curMaxDist = 0; + size_t exact_count = 0, failed_count = 0; + for( int y = 0; y < validDescriptors.rows; y++ ) + { + DistanceType dist = distance( validDescriptors.ptr(y), calcDescriptors.ptr(y), dimension ); + if (dist == 0) + exact_count++; + if( dist > curMaxDist ) + { + if (dist > maxDist) + failed_count++; + curMaxDist = dist; + } +#if 0 + if (dist > 0) + { + std::cout << "i=" << y << " fail_count=" << failed_count << " dist=" << dist << std::endl; + std::cout << "valid: " << validDescriptors.row(y) << std::endl; + std::cout << " calc: " << calcDescriptors.row(y) << std::endl; + } +#endif + } + + float exact_percents = (100 * (float)exact_count / validDescriptors.rows); + float failed_percents = (100 * (float)failed_count / validDescriptors.rows); + std::stringstream ss; + ss << "Exact count (dist == 0): " << exact_count << " (" << (int)exact_percents << "%)" << std::endl + << "Failed count (dist > " << maxDist << "): " << failed_count << " (" << (int)failed_percents << "%)" << std::endl + << "Max distance between valid and computed descriptors (" << validDescriptors.size() << "): " << curMaxDist; + EXPECT_LE(failed_percents, 20.0f); + std::cout << ss.str() << std::endl; + } + + void emptyDataTest() + { + assert( dextractor ); + + // One image. + Mat image; + vector keypoints; + Mat descriptors; + + try + { + dextractor->compute( image, keypoints, descriptors ); + } + catch(...) + { + ts->printf( cvtest::TS::LOG, "compute() on empty image and empty keypoints must not generate exception (1).\n"); + ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_TEST_DATA ); + } + + RNG rng; + image = cvtest::randomMat(rng, Size(50, 50), CV_8UC3, 0, 255, false); + try + { + dextractor->compute( image, keypoints, descriptors ); + } + catch(...) + { + ts->printf( cvtest::TS::LOG, "compute() on nonempty image and empty keypoints must not generate exception (1).\n"); + ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_TEST_DATA ); + } + + // Several images. + vector images; + vector > keypointsCollection; + vector descriptorsCollection; + try + { + dextractor->compute( images, keypointsCollection, descriptorsCollection ); + } + catch(...) + { + ts->printf( cvtest::TS::LOG, "compute() on empty images and empty keypoints collection must not generate exception (2).\n"); + ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_TEST_DATA ); + } + } + + void regressionTest() + { + assert( dextractor ); + + // Read the test image. + string imgFilename = string(ts->get_data_path()) + FEATURES2D_DIR + "/" + IMAGE_FILENAME; + Mat img = imread( imgFilename ); + if( img.empty() ) + { + ts->printf( cvtest::TS::LOG, "Image %s can not be read.\n", imgFilename.c_str() ); + ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_TEST_DATA ); + return; + } + const std::string keypoints_filename = string(ts->get_data_path()) + + (detector.empty() + ? (FEATURES2D_DIR + "/" + std::string("keypoints.xml.gz")) + : (DESCRIPTOR_DIR + "/" + name + "_keypoints.xml.gz")); + FileStorage fs(keypoints_filename, FileStorage::READ); + + vector keypoints; + EXPECT_TRUE(fs.isOpened()) << "Keypoint testdata is missing. Re-computing and re-writing keypoints testdata..."; + if (!fs.isOpened()) + { + fs.open(keypoints_filename, FileStorage::WRITE); + ASSERT_TRUE(fs.isOpened()) << "File for writing keypoints can not be opened."; + if (detector.empty()) + { + Ptr fd = ORB::create(); + fd->detect(img, keypoints); + } + else + { + detector->detect(img, keypoints); + } + write(fs, "keypoints", keypoints); + fs.release(); + } + else + { + read(fs.getFirstTopLevelNode(), keypoints); + fs.release(); + } + + if(!detector.empty()) + { + vector calcKeypoints; + detector->detect(img, calcKeypoints); + // TODO validate received keypoints + int diff = abs((int)calcKeypoints.size() - (int)keypoints.size()); + if (diff > 0) + { + std::cout << "Keypoints difference: " << diff << std::endl; + EXPECT_LE(diff, (int)(keypoints.size() * 0.03f)); + } + } + ASSERT_FALSE(keypoints.empty()); + { + Mat calcDescriptors; + double t = (double)getTickCount(); + dextractor->compute(img, keypoints, calcDescriptors); + t = getTickCount() - t; + ts->printf(cvtest::TS::LOG, "\nAverage time of computing one descriptor = %g ms.\n", t/((double)getTickFrequency()*1000.)/calcDescriptors.rows); + + if (calcDescriptors.rows != (int)keypoints.size()) + { + ts->printf( cvtest::TS::LOG, "Count of computed descriptors and keypoints count must be equal.\n" ); + ts->printf( cvtest::TS::LOG, "Count of keypoints is %d.\n", (int)keypoints.size() ); + ts->printf( cvtest::TS::LOG, "Count of computed descriptors is %d.\n", calcDescriptors.rows ); + ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT ); + return; + } + + if (calcDescriptors.cols != dextractor->descriptorSize() || calcDescriptors.type() != dextractor->descriptorType()) + { + ts->printf( cvtest::TS::LOG, "Incorrect descriptor size or descriptor type.\n" ); + ts->printf( cvtest::TS::LOG, "Expected size is %d.\n", dextractor->descriptorSize() ); + ts->printf( cvtest::TS::LOG, "Calculated size is %d.\n", calcDescriptors.cols ); + ts->printf( cvtest::TS::LOG, "Expected type is %d.\n", dextractor->descriptorType() ); + ts->printf( cvtest::TS::LOG, "Calculated type is %d.\n", calcDescriptors.type() ); + ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT ); + return; + } + + // TODO read and write descriptor extractor parameters and check them + Mat validDescriptors = readDescriptors(); + EXPECT_FALSE(validDescriptors.empty()) << "Descriptors testdata is missing. Re-writing descriptors testdata..."; + if (!validDescriptors.empty()) + { + compareDescriptors(validDescriptors, calcDescriptors); + } + else + { + ASSERT_TRUE(writeDescriptors(calcDescriptors)) << "Descriptors can not be written."; + } + } + } + + void run(int) + { + createDescriptorExtractor(); + if( !dextractor ) + { + ts->printf(cvtest::TS::LOG, "Descriptor extractor is empty.\n"); + ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_TEST_DATA ); + return; + } + + emptyDataTest(); + regressionTest(); + + ts->set_failed_test_info( cvtest::TS::OK ); + } + + virtual Mat readDescriptors() + { + Mat res = readMatFromBin( string(ts->get_data_path()) + DESCRIPTOR_DIR + "/" + string(name) ); + return res; + } + + virtual bool writeDescriptors( Mat& descs ) + { + writeMatInBin( descs, string(ts->get_data_path()) + DESCRIPTOR_DIR + "/" + string(name) ); + return true; + } + + string name; + const DistanceType maxDist; + Ptr dextractor; + Distance distance; + Ptr detector; + +private: + CV_DescriptorExtractorTest& operator=(const CV_DescriptorExtractorTest&) { return *this; } +}; + +}} // namespace diff --git a/modules/features2d/test/test_detectors_invariance.cpp b/modules/features2d/test/test_detectors_invariance.cpp index c4c6874509..be89f0c65f 100644 --- a/modules/features2d/test/test_detectors_invariance.cpp +++ b/modules/features2d/test/test_detectors_invariance.cpp @@ -5,216 +5,13 @@ #include "test_precomp.hpp" #include "test_invariance_utils.hpp" -namespace opencv_test { namespace { -using namespace perf; +#include "test_detectors_invariance.impl.hpp" -#define SHOW_DEBUG_LOG 1 +namespace opencv_test { namespace { -typedef tuple, float, float> String_FeatureDetector_Float_Float_t; const static std::string IMAGE_TSUKUBA = "features2d/tsukuba.png"; const static std::string IMAGE_BIKES = "detectors_descriptors_evaluation/images_datasets/bikes/img1.png"; -#define Value(...) Values(String_FeatureDetector_Float_Float_t(__VA_ARGS__)) - -static -void matchKeyPoints(const vector& keypoints0, const Mat& H, - const vector& keypoints1, - vector& matches) -{ - vector points0; - KeyPoint::convert(keypoints0, points0); - Mat points0t; - if(H.empty()) - points0t = Mat(points0); - else - perspectiveTransform(Mat(points0), points0t, H); - - matches.clear(); - vector usedMask(keypoints1.size(), 0); - for(int i0 = 0; i0 < static_cast(keypoints0.size()); i0++) - { - int nearestPointIndex = -1; - float maxIntersectRatio = 0.f; - const float r0 = 0.5f * keypoints0[i0].size; - for(size_t i1 = 0; i1 < keypoints1.size(); i1++) - { - if(nearestPointIndex >= 0 && usedMask[i1]) - continue; - - float r1 = 0.5f * keypoints1[i1].size; - float intersectRatio = calcIntersectRatio(points0t.at(i0), r0, - keypoints1[i1].pt, r1); - if(intersectRatio > maxIntersectRatio) - { - maxIntersectRatio = intersectRatio; - nearestPointIndex = static_cast(i1); - } - } - - matches.push_back(DMatch(i0, nearestPointIndex, maxIntersectRatio)); - if(nearestPointIndex >= 0) - usedMask[nearestPointIndex] = 1; - } -} - -class DetectorInvariance : public TestWithParam -{ -protected: - virtual void SetUp() { - // Read test data - const std::string filename = cvtest::TS::ptr()->get_data_path() + get<0>(GetParam()); - image0 = imread(filename); - ASSERT_FALSE(image0.empty()) << "couldn't read input image"; - - featureDetector = get<1>(GetParam()); - minKeyPointMatchesRatio = get<2>(GetParam()); - minInliersRatio = get<3>(GetParam()); - } - - Ptr featureDetector; - float minKeyPointMatchesRatio; - float minInliersRatio; - Mat image0; -}; - -typedef DetectorInvariance DetectorScaleInvariance; -typedef DetectorInvariance DetectorRotationInvariance; - -TEST_P(DetectorRotationInvariance, rotation) -{ - Mat image1, mask1; - const int borderSize = 16; - Mat mask0(image0.size(), CV_8UC1, Scalar(0)); - mask0(Rect(borderSize, borderSize, mask0.cols - 2*borderSize, mask0.rows - 2*borderSize)).setTo(Scalar(255)); - - vector keypoints0; - featureDetector->detect(image0, keypoints0, mask0); - EXPECT_GE(keypoints0.size(), 15u); - - const int maxAngle = 360, angleStep = 15; - for(int angle = 0; angle < maxAngle; angle += angleStep) - { - Mat H = rotateImage(image0, mask0, static_cast(angle), image1, mask1); - - vector keypoints1; - featureDetector->detect(image1, keypoints1, mask1); - - vector matches; - matchKeyPoints(keypoints0, H, keypoints1, matches); - - int angleInliersCount = 0; - - const float minIntersectRatio = 0.5f; - int keyPointMatchesCount = 0; - for(size_t m = 0; m < matches.size(); m++) - { - if(matches[m].distance < minIntersectRatio) - continue; - - keyPointMatchesCount++; - - // Check does this inlier have consistent angles - const float maxAngleDiff = 15.f; // grad - float angle0 = keypoints0[matches[m].queryIdx].angle; - float angle1 = keypoints1[matches[m].trainIdx].angle; - ASSERT_FALSE(angle0 == -1 || angle1 == -1) << "Given FeatureDetector is not rotation invariant, it can not be tested here."; - ASSERT_GE(angle0, 0.f); - ASSERT_LT(angle0, 360.f); - ASSERT_GE(angle1, 0.f); - ASSERT_LT(angle1, 360.f); - - float rotAngle0 = angle0 + angle; - if(rotAngle0 >= 360.f) - rotAngle0 -= 360.f; - - float angleDiff = std::max(rotAngle0, angle1) - std::min(rotAngle0, angle1); - angleDiff = std::min(angleDiff, static_cast(360.f - angleDiff)); - ASSERT_GE(angleDiff, 0.f); - bool isAngleCorrect = angleDiff < maxAngleDiff; - if(isAngleCorrect) - angleInliersCount++; - } - - float keyPointMatchesRatio = static_cast(keyPointMatchesCount) / keypoints0.size(); - EXPECT_GE(keyPointMatchesRatio, minKeyPointMatchesRatio) << "angle: " << angle; - - if(keyPointMatchesCount) - { - float angleInliersRatio = static_cast(angleInliersCount) / keyPointMatchesCount; - EXPECT_GE(angleInliersRatio, minInliersRatio) << "angle: " << angle; - } -#if SHOW_DEBUG_LOG - std::cout - << "angle = " << angle - << ", keypoints = " << keypoints1.size() - << ", keyPointMatchesRatio = " << keyPointMatchesRatio - << ", angleInliersRatio = " << (keyPointMatchesCount ? (static_cast(angleInliersCount) / keyPointMatchesCount) : 0) - << std::endl; -#endif - } -} - -TEST_P(DetectorScaleInvariance, scale) -{ - vector keypoints0; - featureDetector->detect(image0, keypoints0); - EXPECT_GE(keypoints0.size(), 15u); - - for(int scaleIdx = 1; scaleIdx <= 3; scaleIdx++) - { - float scale = 1.f + scaleIdx * 0.5f; - Mat image1; - resize(image0, image1, Size(), 1./scale, 1./scale, INTER_LINEAR_EXACT); - - vector keypoints1, osiKeypoints1; // osi - original size image - featureDetector->detect(image1, keypoints1); - EXPECT_GE(keypoints1.size(), 15u); - EXPECT_LE(keypoints1.size(), keypoints0.size()) << "Strange behavior of the detector. " - "It gives more points count in an image of the smaller size."; - - scaleKeyPoints(keypoints1, osiKeypoints1, scale); - vector matches; - // image1 is query image (it's reduced image0) - // image0 is train image - matchKeyPoints(osiKeypoints1, Mat(), keypoints0, matches); - - const float minIntersectRatio = 0.5f; - int keyPointMatchesCount = 0; - int scaleInliersCount = 0; - - for(size_t m = 0; m < matches.size(); m++) - { - if(matches[m].distance < minIntersectRatio) - continue; - - keyPointMatchesCount++; - - // Check does this inlier have consistent sizes - const float maxSizeDiff = 0.8f;//0.9f; // grad - float size0 = keypoints0[matches[m].trainIdx].size; - float size1 = osiKeypoints1[matches[m].queryIdx].size; - ASSERT_GT(size0, 0); - ASSERT_GT(size1, 0); - if(std::min(size0, size1) > maxSizeDiff * std::max(size0, size1)) - scaleInliersCount++; - } - - float keyPointMatchesRatio = static_cast(keyPointMatchesCount) / keypoints1.size(); - EXPECT_GE(keyPointMatchesRatio, minKeyPointMatchesRatio); - - if(keyPointMatchesCount) - { - float scaleInliersRatio = static_cast(scaleInliersCount) / keyPointMatchesCount; - EXPECT_GE(scaleInliersRatio, minInliersRatio); - } -#if SHOW_DEBUG_LOG - std::cout - << "scale = " << scale - << ", keyPointMatchesRatio = " << keyPointMatchesRatio - << ", scaleInliersRatio = " << (keyPointMatchesCount ? static_cast(scaleInliersCount) / keyPointMatchesCount : 0) - << std::endl; -#endif - } -} +#define Value(...) Values(make_tuple(__VA_ARGS__)) /* * Detector's rotation invariance check diff --git a/modules/features2d/test/test_detectors_invariance.impl.hpp b/modules/features2d/test/test_detectors_invariance.impl.hpp new file mode 100644 index 0000000000..8a571cb718 --- /dev/null +++ b/modules/features2d/test/test_detectors_invariance.impl.hpp @@ -0,0 +1,227 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html + +#include "test_invariance_utils.hpp" + +namespace opencv_test { namespace { + +#define SHOW_DEBUG_LOG 1 + +typedef tuple, float, float> String_FeatureDetector_Float_Float_t; + + +static +void matchKeyPoints(const vector& keypoints0, const Mat& H, + const vector& keypoints1, + vector& matches) +{ + vector points0; + KeyPoint::convert(keypoints0, points0); + Mat points0t; + if(H.empty()) + points0t = Mat(points0); + else + perspectiveTransform(Mat(points0), points0t, H); + + matches.clear(); + vector usedMask(keypoints1.size(), 0); + for(int i0 = 0; i0 < static_cast(keypoints0.size()); i0++) + { + int nearestPointIndex = -1; + float maxIntersectRatio = 0.f; + const float r0 = 0.5f * keypoints0[i0].size; + for(size_t i1 = 0; i1 < keypoints1.size(); i1++) + { + if(nearestPointIndex >= 0 && usedMask[i1]) + continue; + + float r1 = 0.5f * keypoints1[i1].size; + float intersectRatio = calcIntersectRatio(points0t.at(i0), r0, + keypoints1[i1].pt, r1); + if(intersectRatio > maxIntersectRatio) + { + maxIntersectRatio = intersectRatio; + nearestPointIndex = static_cast(i1); + } + } + + matches.push_back(DMatch(i0, nearestPointIndex, maxIntersectRatio)); + if(nearestPointIndex >= 0) + usedMask[nearestPointIndex] = 1; + } +} + +class DetectorInvariance : public TestWithParam +{ +protected: + virtual void SetUp() { + // Read test data + const std::string filename = cvtest::TS::ptr()->get_data_path() + get<0>(GetParam()); + image0 = imread(filename); + ASSERT_FALSE(image0.empty()) << "couldn't read input image"; + + featureDetector = get<1>(GetParam()); + minKeyPointMatchesRatio = get<2>(GetParam()); + minInliersRatio = get<3>(GetParam()); + } + + Ptr featureDetector; + float minKeyPointMatchesRatio; + float minInliersRatio; + Mat image0; +}; + +typedef DetectorInvariance DetectorScaleInvariance; +typedef DetectorInvariance DetectorRotationInvariance; + +TEST_P(DetectorRotationInvariance, rotation) +{ + Mat image1, mask1; + const int borderSize = 16; + Mat mask0(image0.size(), CV_8UC1, Scalar(0)); + mask0(Rect(borderSize, borderSize, mask0.cols - 2*borderSize, mask0.rows - 2*borderSize)).setTo(Scalar(255)); + + vector keypoints0; + featureDetector->detect(image0, keypoints0, mask0); + EXPECT_GE(keypoints0.size(), 15u); + + const int maxAngle = 360, angleStep = 15; + for(int angle = 0; angle < maxAngle; angle += angleStep) + { + Mat H = rotateImage(image0, mask0, static_cast(angle), image1, mask1); + + vector keypoints1; + featureDetector->detect(image1, keypoints1, mask1); + + vector matches; + matchKeyPoints(keypoints0, H, keypoints1, matches); + + int angleInliersCount = 0; + + const float minIntersectRatio = 0.5f; + int keyPointMatchesCount = 0; + for(size_t m = 0; m < matches.size(); m++) + { + if(matches[m].distance < minIntersectRatio) + continue; + + keyPointMatchesCount++; + + // Check does this inlier have consistent angles + const float maxAngleDiff = 15.f; // grad + float angle0 = keypoints0[matches[m].queryIdx].angle; + float angle1 = keypoints1[matches[m].trainIdx].angle; + ASSERT_FALSE(angle0 == -1 || angle1 == -1) << "Given FeatureDetector is not rotation invariant, it can not be tested here."; + ASSERT_GE(angle0, 0.f); + ASSERT_LT(angle0, 360.f); + ASSERT_GE(angle1, 0.f); + ASSERT_LT(angle1, 360.f); + + float rotAngle0 = angle0 + angle; + if(rotAngle0 >= 360.f) + rotAngle0 -= 360.f; + + float angleDiff = std::max(rotAngle0, angle1) - std::min(rotAngle0, angle1); + angleDiff = std::min(angleDiff, static_cast(360.f - angleDiff)); + ASSERT_GE(angleDiff, 0.f); + bool isAngleCorrect = angleDiff < maxAngleDiff; + if(isAngleCorrect) + angleInliersCount++; + } + + float keyPointMatchesRatio = static_cast(keyPointMatchesCount) / keypoints0.size(); + EXPECT_GE(keyPointMatchesRatio, minKeyPointMatchesRatio) << "angle: " << angle; + + if(keyPointMatchesCount) + { + float angleInliersRatio = static_cast(angleInliersCount) / keyPointMatchesCount; + EXPECT_GE(angleInliersRatio, minInliersRatio) << "angle: " << angle; + } +#if SHOW_DEBUG_LOG + std::cout + << "angle = " << angle + << ", keypoints = " << keypoints1.size() + << ", keyPointMatchesRatio = " << keyPointMatchesRatio + << ", angleInliersRatio = " << (keyPointMatchesCount ? (static_cast(angleInliersCount) / keyPointMatchesCount) : 0) + << std::endl; +#endif + } +} + +TEST_P(DetectorScaleInvariance, scale) +{ + vector keypoints0; + featureDetector->detect(image0, keypoints0); + EXPECT_GE(keypoints0.size(), 15u); + + for(int scaleIdx = 1; scaleIdx <= 3; scaleIdx++) + { + float scale = 1.f + scaleIdx * 0.5f; + Mat image1; + resize(image0, image1, Size(), 1./scale, 1./scale, INTER_LINEAR_EXACT); + + vector keypoints1, osiKeypoints1; // osi - original size image + featureDetector->detect(image1, keypoints1); + EXPECT_GE(keypoints1.size(), 15u); + EXPECT_LE(keypoints1.size(), keypoints0.size()) << "Strange behavior of the detector. " + "It gives more points count in an image of the smaller size."; + + scaleKeyPoints(keypoints1, osiKeypoints1, scale); + vector matches; + // image1 is query image (it's reduced image0) + // image0 is train image + matchKeyPoints(osiKeypoints1, Mat(), keypoints0, matches); + + const float minIntersectRatio = 0.5f; + int keyPointMatchesCount = 0; + int scaleInliersCount = 0; + + for(size_t m = 0; m < matches.size(); m++) + { + if(matches[m].distance < minIntersectRatio) + continue; + + keyPointMatchesCount++; + + // Check does this inlier have consistent sizes + const float maxSizeDiff = 0.8f;//0.9f; // grad + float size0 = keypoints0[matches[m].trainIdx].size; + float size1 = osiKeypoints1[matches[m].queryIdx].size; + ASSERT_GT(size0, 0); + ASSERT_GT(size1, 0); + if(std::min(size0, size1) > maxSizeDiff * std::max(size0, size1)) + scaleInliersCount++; + } + + float keyPointMatchesRatio = static_cast(keyPointMatchesCount) / keypoints1.size(); + EXPECT_GE(keyPointMatchesRatio, minKeyPointMatchesRatio); + + if(keyPointMatchesCount) + { + float scaleInliersRatio = static_cast(scaleInliersCount) / keyPointMatchesCount; + EXPECT_GE(scaleInliersRatio, minInliersRatio); + } +#if SHOW_DEBUG_LOG + std::cout + << "scale = " << scale + << ", keyPointMatchesRatio = " << keyPointMatchesRatio + << ", scaleInliersRatio = " << (keyPointMatchesCount ? static_cast(scaleInliersCount) / keyPointMatchesCount : 0) + << std::endl; +#endif + } +} + +#undef SHOW_DEBUG_LOG +}} // namespace + +namespace std { +using namespace opencv_test; +static inline void PrintTo(const String_FeatureDetector_Float_Float_t& v, std::ostream* os) +{ + *os << "(\"" << get<0>(v) + << "\", " << get<2>(v) + << ", " << get<3>(v) + << ")"; +} +} // namespace diff --git a/modules/features2d/test/test_detectors_regression.cpp b/modules/features2d/test/test_detectors_regression.cpp index 733841287a..6542783d40 100644 --- a/modules/features2d/test/test_detectors_regression.cpp +++ b/modules/features2d/test/test_detectors_regression.cpp @@ -1,245 +1,18 @@ -/*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*/ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html #include "test_precomp.hpp" namespace opencv_test { namespace { - const string FEATURES2D_DIR = "features2d"; const string IMAGE_FILENAME = "tsukuba.png"; const string DETECTOR_DIR = FEATURES2D_DIR + "/feature_detectors"; +}} // namespace -/****************************************************************************************\ -* Regression tests for feature detectors comparing keypoints. * -\****************************************************************************************/ - -class CV_FeatureDetectorTest : public cvtest::BaseTest -{ -public: - CV_FeatureDetectorTest( const string& _name, const Ptr& _fdetector ) : - name(_name), fdetector(_fdetector) {} - -protected: - bool isSimilarKeypoints( const KeyPoint& p1, const KeyPoint& p2 ); - void compareKeypointSets( const vector& validKeypoints, const vector& calcKeypoints ); - - void emptyDataTest(); - void regressionTest(); // TODO test of detect() with mask - - virtual void run( int ); - - string name; - Ptr fdetector; -}; - -void CV_FeatureDetectorTest::emptyDataTest() -{ - // One image. - Mat image; - vector keypoints; - try - { - fdetector->detect( image, keypoints ); - } - catch(...) - { - ts->printf( cvtest::TS::LOG, "detect() on empty image must not generate exception (1).\n" ); - ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT ); - } - - if( !keypoints.empty() ) - { - ts->printf( cvtest::TS::LOG, "detect() on empty image must return empty keypoints vector (1).\n" ); - ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT ); - return; - } - - // Several images. - vector images; - vector > keypointCollection; - try - { - fdetector->detect( images, keypointCollection ); - } - catch(...) - { - ts->printf( cvtest::TS::LOG, "detect() on empty image vector must not generate exception (2).\n" ); - ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT ); - } -} - -bool CV_FeatureDetectorTest::isSimilarKeypoints( const KeyPoint& p1, const KeyPoint& p2 ) -{ - const float maxPtDif = 1.f; - const float maxSizeDif = 1.f; - const float maxAngleDif = 2.f; - const float maxResponseDif = 0.1f; - - float dist = (float)cv::norm( p1.pt - p2.pt ); - return (dist < maxPtDif && - fabs(p1.size - p2.size) < maxSizeDif && - abs(p1.angle - p2.angle) < maxAngleDif && - abs(p1.response - p2.response) < maxResponseDif && - p1.octave == p2.octave && - p1.class_id == p2.class_id ); -} - -void CV_FeatureDetectorTest::compareKeypointSets( const vector& validKeypoints, const vector& calcKeypoints ) -{ - const float maxCountRatioDif = 0.01f; - - // Compare counts of validation and calculated keypoints. - float countRatio = (float)validKeypoints.size() / (float)calcKeypoints.size(); - if( countRatio < 1 - maxCountRatioDif || countRatio > 1.f + maxCountRatioDif ) - { - ts->printf( cvtest::TS::LOG, "Bad keypoints count ratio (validCount = %d, calcCount = %d).\n", - validKeypoints.size(), calcKeypoints.size() ); - ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT ); - return; - } - - int progress = 0, progressCount = (int)(validKeypoints.size() * calcKeypoints.size()); - int badPointCount = 0, commonPointCount = max((int)validKeypoints.size(), (int)calcKeypoints.size()); - for( size_t v = 0; v < validKeypoints.size(); v++ ) - { - int nearestIdx = -1; - float minDist = std::numeric_limits::max(); - - for( size_t c = 0; c < calcKeypoints.size(); c++ ) - { - progress = update_progress( progress, (int)(v*calcKeypoints.size() + c), progressCount, 0 ); - float curDist = (float)cv::norm( calcKeypoints[c].pt - validKeypoints[v].pt ); - if( curDist < minDist ) - { - minDist = curDist; - nearestIdx = (int)c; - } - } - - assert( minDist >= 0 ); - if( !isSimilarKeypoints( validKeypoints[v], calcKeypoints[nearestIdx] ) ) - badPointCount++; - } - ts->printf( cvtest::TS::LOG, "badPointCount = %d; validPointCount = %d; calcPointCount = %d\n", - badPointCount, validKeypoints.size(), calcKeypoints.size() ); - if( badPointCount > 0.9 * commonPointCount ) - { - ts->printf( cvtest::TS::LOG, " - Bad accuracy!\n" ); - ts->set_failed_test_info( cvtest::TS::FAIL_BAD_ACCURACY ); - return; - } - ts->printf( cvtest::TS::LOG, " - OK\n" ); -} - -void CV_FeatureDetectorTest::regressionTest() -{ - assert( !fdetector.empty() ); - string imgFilename = string(ts->get_data_path()) + FEATURES2D_DIR + "/" + IMAGE_FILENAME; - string resFilename = string(ts->get_data_path()) + DETECTOR_DIR + "/" + string(name) + ".xml.gz"; - - // Read the test image. - Mat image = imread( imgFilename ); - if( image.empty() ) - { - ts->printf( cvtest::TS::LOG, "Image %s can not be read.\n", imgFilename.c_str() ); - ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_TEST_DATA ); - return; - } - - FileStorage fs( resFilename, FileStorage::READ ); - - // Compute keypoints. - vector calcKeypoints; - fdetector->detect( image, calcKeypoints ); - - if( fs.isOpened() ) // Compare computed and valid keypoints. - { - // TODO compare saved feature detector params with current ones - - // Read validation keypoints set. - vector validKeypoints; - read( fs["keypoints"], validKeypoints ); - if( validKeypoints.empty() ) - { - ts->printf( cvtest::TS::LOG, "Keypoints can not be read.\n" ); - ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_TEST_DATA ); - return; - } - - compareKeypointSets( validKeypoints, calcKeypoints ); - } - else // Write detector parameters and computed keypoints as validation data. - { - fs.open( resFilename, FileStorage::WRITE ); - if( !fs.isOpened() ) - { - ts->printf( cvtest::TS::LOG, "File %s can not be opened to write.\n", resFilename.c_str() ); - ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_TEST_DATA ); - return; - } - else - { - fs << "detector_params" << "{"; - fdetector->write( fs ); - fs << "}"; - - write( fs, "keypoints", calcKeypoints ); - } - } -} - -void CV_FeatureDetectorTest::run( int /*start_from*/ ) -{ - if( !fdetector ) - { - ts->printf( cvtest::TS::LOG, "Feature detector is empty.\n" ); - ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_TEST_DATA ); - return; - } - - emptyDataTest(); - regressionTest(); +#include "test_detectors_regression.impl.hpp" - ts->set_failed_test_info( cvtest::TS::OK ); -} +namespace opencv_test { namespace { /****************************************************************************************\ * Tests registrations * diff --git a/modules/features2d/test/test_detectors_regression.impl.hpp b/modules/features2d/test/test_detectors_regression.impl.hpp new file mode 100644 index 0000000000..317a06f93e --- /dev/null +++ b/modules/features2d/test/test_detectors_regression.impl.hpp @@ -0,0 +1,201 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html + +namespace opencv_test { namespace { + +/****************************************************************************************\ +* Regression tests for feature detectors comparing keypoints. * +\****************************************************************************************/ + +class CV_FeatureDetectorTest : public cvtest::BaseTest +{ +public: + CV_FeatureDetectorTest( const string& _name, const Ptr& _fdetector ) : + name(_name), fdetector(_fdetector) {} + +protected: + bool isSimilarKeypoints( const KeyPoint& p1, const KeyPoint& p2 ); + void compareKeypointSets( const vector& validKeypoints, const vector& calcKeypoints ); + + void emptyDataTest(); + void regressionTest(); // TODO test of detect() with mask + + virtual void run( int ); + + string name; + Ptr fdetector; +}; + +void CV_FeatureDetectorTest::emptyDataTest() +{ + // One image. + Mat image; + vector keypoints; + try + { + fdetector->detect( image, keypoints ); + } + catch(...) + { + ts->printf( cvtest::TS::LOG, "detect() on empty image must not generate exception (1).\n" ); + ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT ); + } + + if( !keypoints.empty() ) + { + ts->printf( cvtest::TS::LOG, "detect() on empty image must return empty keypoints vector (1).\n" ); + ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT ); + return; + } + + // Several images. + vector images; + vector > keypointCollection; + try + { + fdetector->detect( images, keypointCollection ); + } + catch(...) + { + ts->printf( cvtest::TS::LOG, "detect() on empty image vector must not generate exception (2).\n" ); + ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT ); + } +} + +bool CV_FeatureDetectorTest::isSimilarKeypoints( const KeyPoint& p1, const KeyPoint& p2 ) +{ + const float maxPtDif = 1.f; + const float maxSizeDif = 1.f; + const float maxAngleDif = 2.f; + const float maxResponseDif = 0.1f; + + float dist = (float)cv::norm( p1.pt - p2.pt ); + return (dist < maxPtDif && + fabs(p1.size - p2.size) < maxSizeDif && + abs(p1.angle - p2.angle) < maxAngleDif && + abs(p1.response - p2.response) < maxResponseDif && + p1.octave == p2.octave && + p1.class_id == p2.class_id ); +} + +void CV_FeatureDetectorTest::compareKeypointSets( const vector& validKeypoints, const vector& calcKeypoints ) +{ + const float maxCountRatioDif = 0.01f; + + // Compare counts of validation and calculated keypoints. + float countRatio = (float)validKeypoints.size() / (float)calcKeypoints.size(); + if( countRatio < 1 - maxCountRatioDif || countRatio > 1.f + maxCountRatioDif ) + { + ts->printf( cvtest::TS::LOG, "Bad keypoints count ratio (validCount = %d, calcCount = %d).\n", + validKeypoints.size(), calcKeypoints.size() ); + ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT ); + return; + } + + int progress = 0, progressCount = (int)(validKeypoints.size() * calcKeypoints.size()); + int badPointCount = 0, commonPointCount = max((int)validKeypoints.size(), (int)calcKeypoints.size()); + for( size_t v = 0; v < validKeypoints.size(); v++ ) + { + int nearestIdx = -1; + float minDist = std::numeric_limits::max(); + + for( size_t c = 0; c < calcKeypoints.size(); c++ ) + { + progress = update_progress( progress, (int)(v*calcKeypoints.size() + c), progressCount, 0 ); + float curDist = (float)cv::norm( calcKeypoints[c].pt - validKeypoints[v].pt ); + if( curDist < minDist ) + { + minDist = curDist; + nearestIdx = (int)c; + } + } + + assert( minDist >= 0 ); + if( !isSimilarKeypoints( validKeypoints[v], calcKeypoints[nearestIdx] ) ) + badPointCount++; + } + ts->printf( cvtest::TS::LOG, "badPointCount = %d; validPointCount = %d; calcPointCount = %d\n", + badPointCount, validKeypoints.size(), calcKeypoints.size() ); + if( badPointCount > 0.9 * commonPointCount ) + { + ts->printf( cvtest::TS::LOG, " - Bad accuracy!\n" ); + ts->set_failed_test_info( cvtest::TS::FAIL_BAD_ACCURACY ); + return; + } + ts->printf( cvtest::TS::LOG, " - OK\n" ); +} + +void CV_FeatureDetectorTest::regressionTest() +{ + assert( !fdetector.empty() ); + string imgFilename = string(ts->get_data_path()) + FEATURES2D_DIR + "/" + IMAGE_FILENAME; + string resFilename = string(ts->get_data_path()) + DETECTOR_DIR + "/" + string(name) + ".xml.gz"; + + // Read the test image. + Mat image = imread( imgFilename ); + if( image.empty() ) + { + ts->printf( cvtest::TS::LOG, "Image %s can not be read.\n", imgFilename.c_str() ); + ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_TEST_DATA ); + return; + } + + FileStorage fs( resFilename, FileStorage::READ ); + + // Compute keypoints. + vector calcKeypoints; + fdetector->detect( image, calcKeypoints ); + + if( fs.isOpened() ) // Compare computed and valid keypoints. + { + // TODO compare saved feature detector params with current ones + + // Read validation keypoints set. + vector validKeypoints; + read( fs["keypoints"], validKeypoints ); + if( validKeypoints.empty() ) + { + ts->printf( cvtest::TS::LOG, "Keypoints can not be read.\n" ); + ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_TEST_DATA ); + return; + } + + compareKeypointSets( validKeypoints, calcKeypoints ); + } + else // Write detector parameters and computed keypoints as validation data. + { + fs.open( resFilename, FileStorage::WRITE ); + if( !fs.isOpened() ) + { + ts->printf( cvtest::TS::LOG, "File %s can not be opened to write.\n", resFilename.c_str() ); + ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_TEST_DATA ); + return; + } + else + { + fs << "detector_params" << "{"; + fdetector->write( fs ); + fs << "}"; + + write( fs, "keypoints", calcKeypoints ); + } + } +} + +void CV_FeatureDetectorTest::run( int /*start_from*/ ) +{ + if( !fdetector ) + { + ts->printf( cvtest::TS::LOG, "Feature detector is empty.\n" ); + ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_TEST_DATA ); + return; + } + + emptyDataTest(); + regressionTest(); + + ts->set_failed_test_info( cvtest::TS::OK ); +} + +}} // namespace