opencv/modules/features2d/test/test_detectors_invariance.cpp
Jiri Horner 3166d0c667 Merge pull request #9249 from hrnr:akaze_part3
[GSOC] Speeding-up AKAZE, part #3 (#9249)

* use finding of scale extremas from fast_akaze

* incorporade finding of extremas and subpixel refinement from Hideaki Suzuki's fast_akaze (https://github.com/h2suzuki/fast_akaze)
* use opencv parallel framework
* do not search for keypoints near the border, where we can't compute sensible descriptors (bugs fixed in ffd9ad99f4946e31508677dab09bddbecb82ae9f, 2c5389594bb560b62097de3602755ef97e60135f), but the descriptors were not 100% correct. this is a better solution

this version produces less keypoints with the same treshold. It is more effective in pruning similar keypoints (which do not bring any new information), so we have less keypoints, but with high quality. Accuracy is about the same.

* incorporate bugfix from upstream

* fix bug in subpixel refinement
* see commit db3dc22981e856ca8111f2f7fe57d9c2e0286efc in Pablo's repo

* rework finding of scale space extremas

* store just keypoints positions
* store positions in uchar mask for effective spatial search for neighbours
* construct keypoints structs at the very end

* lower inlier threshold in test

* win32 has lower accuracy
2017-08-03 08:35:07 +00:00

256 lines
9.3 KiB
C++

// 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"
#include "test_invariance_utils.hpp"
using namespace std;
using namespace cv;
using std::tr1::make_tuple;
using std::tr1::get;
using namespace testing;
#define SHOW_DEBUG_LOG 0
typedef std::tr1::tuple<std::string, Ptr<FeatureDetector>, 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<KeyPoint>& keypoints0, const Mat& H,
const vector<KeyPoint>& keypoints1,
vector<DMatch>& matches)
{
vector<Point2f> points0;
KeyPoint::convert(keypoints0, points0);
Mat points0t;
if(H.empty())
points0t = Mat(points0);
else
perspectiveTransform(Mat(points0), points0t, H);
matches.clear();
vector<uchar> usedMask(keypoints1.size(), 0);
for(int i0 = 0; i0 < static_cast<int>(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<Point2f>(i0), r0,
keypoints1[i1].pt, r1);
if(intersectRatio > maxIntersectRatio)
{
maxIntersectRatio = intersectRatio;
nearestPointIndex = static_cast<int>(i1);
}
}
matches.push_back(DMatch(i0, nearestPointIndex, maxIntersectRatio));
if(nearestPointIndex >= 0)
usedMask[nearestPointIndex] = 1;
}
}
class DetectorInvariance : public TestWithParam<String_FeatureDetector_Float_Float_t>
{
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> 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<KeyPoint> 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<float>(angle), image1, mask1);
vector<KeyPoint> keypoints1;
featureDetector->detect(image1, keypoints1, mask1);
vector<DMatch> 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<float>(360.f - angleDiff));
ASSERT_GE(angleDiff, 0.f);
bool isAngleCorrect = angleDiff < maxAngleDiff;
if(isAngleCorrect)
angleInliersCount++;
}
float keyPointMatchesRatio = static_cast<float>(keyPointMatchesCount) / keypoints0.size();
EXPECT_GE(keyPointMatchesRatio, minKeyPointMatchesRatio) << "angle: " << angle;
if(keyPointMatchesCount)
{
float angleInliersRatio = static_cast<float>(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<float>(angleInliersCount) / keyPointMatchesCount) : 0)
<< std::endl;
#endif
}
}
TEST_P(DetectorScaleInvariance, scale)
{
vector<KeyPoint> 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);
vector<KeyPoint> 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<DMatch> 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<float>(keyPointMatchesCount) / keypoints1.size();
EXPECT_GE(keyPointMatchesRatio, minKeyPointMatchesRatio);
if(keyPointMatchesCount)
{
float scaleInliersRatio = static_cast<float>(scaleInliersCount) / keyPointMatchesCount;
EXPECT_GE(scaleInliersRatio, minInliersRatio);
}
#if SHOW_DEBUG_LOG
std::cout
<< "scale = " << scale
<< ", keyPointMatchesRatio = " << keyPointMatchesRatio
<< ", scaleInliersRatio = " << (keyPointMatchesCount ? static_cast<float>(scaleInliersCount) / keyPointMatchesCount : 0)
<< std::endl;
#endif
}
}
/*
* Detector's rotation invariance check
*/
INSTANTIATE_TEST_CASE_P(BRISK, DetectorRotationInvariance,
Value(IMAGE_TSUKUBA, BRISK::create(), 0.45f, 0.76f));
INSTANTIATE_TEST_CASE_P(ORB, DetectorRotationInvariance,
Value(IMAGE_TSUKUBA, ORB::create(), 0.5f, 0.76f));
INSTANTIATE_TEST_CASE_P(AKAZE, DetectorRotationInvariance,
Value(IMAGE_TSUKUBA, AKAZE::create(), 0.5f, 0.71f));
INSTANTIATE_TEST_CASE_P(AKAZE_DESCRIPTOR_KAZE, DetectorRotationInvariance,
Value(IMAGE_TSUKUBA, AKAZE::create(AKAZE::DESCRIPTOR_KAZE), 0.5f, 0.71f));
/*
* Detector's scale invariance check
*/
INSTANTIATE_TEST_CASE_P(BRISK, DetectorScaleInvariance,
Value(IMAGE_BIKES, BRISK::create(), 0.08f, 0.49f));
INSTANTIATE_TEST_CASE_P(ORB, DetectorScaleInvariance,
Value(IMAGE_BIKES, ORB::create(), 0.08f, 0.49f));
INSTANTIATE_TEST_CASE_P(KAZE, DetectorScaleInvariance,
Value(IMAGE_BIKES, KAZE::create(), 0.08f, 0.49f));
INSTANTIATE_TEST_CASE_P(AKAZE, DetectorScaleInvariance,
Value(IMAGE_BIKES, AKAZE::create(), 0.08f, 0.49f));
INSTANTIATE_TEST_CASE_P(AKAZE_DESCRIPTOR_KAZE, DetectorScaleInvariance,
Value(IMAGE_BIKES, AKAZE::create(AKAZE::DESCRIPTOR_KAZE), 0.08f, 0.49f));