Added Features2D tests

This commit is contained in:
matlabbe
2026-05-17 17:47:13 -07:00
parent 9a8b99f361
commit a9cc4fcebf
3 changed files with 391 additions and 20 deletions
+47 -20
View File
@@ -111,9 +111,26 @@ class Stereo;
class CV_ORB; class CV_ORB;
#endif #endif
// Feature2D /**
* @class Feature2D
* @brief Abstract 2D feature detector and descriptor extractor for visual SLAM.
*
* Factory @ref create() builds a concrete detector from @ref Type or from
* **Kp/DetectorStrategy** in a @ref ParametersMap. Common tuning keys include
* **Kp/MaxFeatures**, **Kp/GridRows**, **Kp/GridCols**, **Kp/SSC**, depth filters
* (**Kp/MinDepth**, **Kp/MaxDepth**), ROI (**Kp/RoiRatios**), and sub-pixel refinement.
*
* Pipeline: @ref generateKeypoints() (grid + ROI + optional mask) then
* @ref generateDescriptors(). Static helpers filter or cap keypoints before/after
* matching. @ref generateKeypoints3D() projects features using stereo or depth when
* available in @ref SensorData.
*
* @see Memory
* @see RegistrationVis
*/
class RTABMAP_CORE_EXPORT Feature2D { class RTABMAP_CORE_EXPORT Feature2D {
public: public:
/** @brief Built-in detector/descriptor strategy (Kp/DetectorStrategy). */
enum Type {kFeatureUndef=-1, enum Type {kFeatureUndef=-1,
kFeatureSurf=0, kFeatureSurf=0,
kFeatureSift=1, kFeatureSift=1,
@@ -133,6 +150,7 @@ public:
kFeaturePyDetector=15, //new 0.20.8 kFeaturePyDetector=15, //new 0.20.8
kFeatureSuperPointRpautrat=16}; // new 0.23.3 kFeatureSuperPointRpautrat=16}; // new 0.23.3
/** @return Human-readable name for @p type (e.g. `"ORB"`, `"GFTT+BRIEF"`). */
static std::string typeName(Type type) static std::string typeName(Type type)
{ {
switch(type){ switch(type){
@@ -173,9 +191,12 @@ public:
} }
} }
/** @brief Creates a detector from **Kp/DetectorStrategy** in @p parameters. Caller owns the pointer. */
static Feature2D * create(const ParametersMap & parameters = ParametersMap()); static Feature2D * create(const ParametersMap & parameters = ParametersMap());
static Feature2D * create(Feature2D::Type type, const ParametersMap & parameters = ParametersMap()); // for convenience /** @brief Creates a detector of the given @p type. Caller owns the pointer. */
static Feature2D * create(Feature2D::Type type, const ParametersMap & parameters = ParametersMap());
/** @brief Keeps keypoints whose depth at (u,v) is in (@p minDepth, @p maxDepth). */
static void filterKeypointsByDepth( static void filterKeypointsByDepth(
std::vector<cv::KeyPoint> & keypoints, std::vector<cv::KeyPoint> & keypoints,
const cv::Mat & depth, const cv::Mat & depth,
@@ -194,6 +215,7 @@ public:
float minDepth, float minDepth,
float maxDepth); float maxDepth);
/** @brief Keeps keypoints with stereo disparity ≥ @p minDisparity. */
static void filterKeypointsByDisparity( static void filterKeypointsByDisparity(
std::vector<cv::KeyPoint> & keypoints, std::vector<cv::KeyPoint> & keypoints,
const cv::Mat & disparity, const cv::Mat & disparity,
@@ -204,12 +226,14 @@ public:
const cv::Mat & disparity, const cv::Mat & disparity,
float minDisparity); float minDisparity);
/** @brief Reduces keypoint count (by response or SSC spatial distribution). */
static void limitKeypoints(std::vector<cv::KeyPoint> & keypoints, int maxKeypoints, const cv::Size & imageSize = cv::Size(), bool ssc = false); static void limitKeypoints(std::vector<cv::KeyPoint> & keypoints, int maxKeypoints, const cv::Size & imageSize = cv::Size(), bool ssc = false);
static void limitKeypoints(std::vector<cv::KeyPoint> & keypoints, cv::Mat & descriptors, int maxKeypoints, const cv::Size & imageSize = cv::Size(), bool ssc = false); static void limitKeypoints(std::vector<cv::KeyPoint> & keypoints, cv::Mat & descriptors, int maxKeypoints, const cv::Size & imageSize = cv::Size(), bool ssc = false);
static void limitKeypoints(std::vector<cv::KeyPoint> & keypoints, std::vector<cv::Point3f> & keypoints3D, cv::Mat & descriptors, int maxKeypoints, const cv::Size & imageSize = cv::Size(), bool ssc = false); static void limitKeypoints(std::vector<cv::KeyPoint> & keypoints, std::vector<cv::Point3f> & keypoints3D, cv::Mat & descriptors, int maxKeypoints, const cv::Size & imageSize = cv::Size(), bool ssc = false);
static void limitKeypoints(const std::vector<cv::KeyPoint> & keypoints, std::vector<bool> & inliers, int maxKeypoints, const cv::Size & imageSize = cv::Size(), bool ssc = false); static void limitKeypoints(const std::vector<cv::KeyPoint> & keypoints, std::vector<bool> & inliers, int maxKeypoints, const cv::Size & imageSize = cv::Size(), bool ssc = false);
static void limitKeypoints(const std::vector<cv::KeyPoint> & keypoints, std::vector<bool> & inliers, int maxKeypoints, const cv::Size & imageSize, int gridRows, int gridCols, bool ssc = false); static void limitKeypoints(const std::vector<cv::KeyPoint> & keypoints, std::vector<bool> & inliers, int maxKeypoints, const cv::Size & imageSize, int gridRows, int gridCols, bool ssc = false);
/** @brief ROI from **Kp/RoiRatios** string (`"left top right bottom"` fractions). */
static cv::Rect computeRoi(const cv::Mat & image, const std::string & roiRatios); static cv::Rect computeRoi(const cv::Mat & image, const std::string & roiRatios);
static cv::Rect computeRoi(const cv::Mat & image, const std::vector<float> & roiRatios); static cv::Rect computeRoi(const cv::Mat & image, const std::vector<float> & roiRatios);
@@ -223,12 +247,15 @@ public:
public: public:
virtual ~Feature2D(); virtual ~Feature2D();
/** @brief Detects keypoints in a grayscale @p image (CV_8UC1); optional depth or 8U mask. */
std::vector<cv::KeyPoint> generateKeypoints( std::vector<cv::KeyPoint> generateKeypoints(
const cv::Mat & image, const cv::Mat & image,
const cv::Mat & mask = cv::Mat()); const cv::Mat & mask = cv::Mat());
/** @brief Computes descriptors for @p keypoints (may shrink the list in some detectors). */
cv::Mat generateDescriptors( cv::Mat generateDescriptors(
const cv::Mat & image, const cv::Mat & image,
std::vector<cv::KeyPoint> & keypoints) const; std::vector<cv::KeyPoint> & keypoints) const;
/** @brief Back-projects keypoints to 3D using depth or stereo in @p data. */
std::vector<cv::Point3f> generateKeypoints3D( std::vector<cv::Point3f> generateKeypoints3D(
const SensorData & data, const SensorData & data,
const std::vector<cv::KeyPoint> & keypoints) const; const std::vector<cv::KeyPoint> & keypoints) const;
@@ -260,7 +287,7 @@ private:
Stereo * _stereo; Stereo * _stereo;
}; };
//SURF /** @brief SURF detector and descriptor (non-free / xfeatures2d depending on OpenCV build). */
class RTABMAP_CORE_EXPORT SURF : public Feature2D class RTABMAP_CORE_EXPORT SURF : public Feature2D
{ {
public: public:
@@ -287,7 +314,7 @@ private:
cv::Ptr<CV_SURF_GPU> _gpuSurf; cv::Ptr<CV_SURF_GPU> _gpuSurf;
}; };
//SIFT /** @brief SIFT detector and descriptor (optional GPU / CudaSift). */
class RTABMAP_CORE_EXPORT SIFT : public Feature2D class RTABMAP_CORE_EXPORT SIFT : public Feature2D
{ {
public: public:
@@ -321,7 +348,7 @@ private:
bool cudaSiftUpscaling_; bool cudaSiftUpscaling_;
}; };
//ORB /** @brief ORB detector and descriptor (optional GPU). */
class RTABMAP_CORE_EXPORT ORB : public Feature2D class RTABMAP_CORE_EXPORT ORB : public Feature2D
{ {
public: public:
@@ -352,7 +379,7 @@ private:
cv::Ptr<CV_ORB_GPU> _gpuOrb; cv::Ptr<CV_ORB_GPU> _gpuOrb;
}; };
//FAST /** @brief FAST corner detector only (no descriptor). */
class RTABMAP_CORE_EXPORT FAST : public Feature2D class RTABMAP_CORE_EXPORT FAST : public Feature2D
{ {
public: public:
@@ -388,7 +415,7 @@ private:
cv::Ptr<CV_FAST_GPU> _gpuFast; cv::Ptr<CV_FAST_GPU> _gpuFast;
}; };
//FAST_BRIEF /** @brief FAST corners + BRIEF descriptors. */
class RTABMAP_CORE_EXPORT FAST_BRIEF : public FAST class RTABMAP_CORE_EXPORT FAST_BRIEF : public FAST
{ {
public: public:
@@ -407,7 +434,7 @@ private:
cv::Ptr<CV_BRIEF> _brief; cv::Ptr<CV_BRIEF> _brief;
}; };
//FAST_FREAK /** @brief FAST corners + FREAK descriptors. */
class RTABMAP_CORE_EXPORT FAST_FREAK : public FAST class RTABMAP_CORE_EXPORT FAST_FREAK : public FAST
{ {
public: public:
@@ -429,7 +456,7 @@ private:
cv::Ptr<CV_FREAK> _freak; cv::Ptr<CV_FREAK> _freak;
}; };
//GFTT /** @brief Good-features-to-track detector (Shi–Tomasi / Harris). */
class RTABMAP_CORE_EXPORT GFTT : public Feature2D class RTABMAP_CORE_EXPORT GFTT : public Feature2D
{ {
public: public:
@@ -453,7 +480,7 @@ private:
cv::Ptr<CV_GFTT_GPU> _gpuGftt; cv::Ptr<CV_GFTT_GPU> _gpuGftt;
}; };
//GFTT_BRIEF /** @brief GFTT corners + BRIEF descriptors. */
class RTABMAP_CORE_EXPORT GFTT_BRIEF : public GFTT class RTABMAP_CORE_EXPORT GFTT_BRIEF : public GFTT
{ {
public: public:
@@ -472,7 +499,7 @@ private:
cv::Ptr<CV_BRIEF> _brief; cv::Ptr<CV_BRIEF> _brief;
}; };
//GFTT_FREAK /** @brief GFTT corners + FREAK descriptors. */
class RTABMAP_CORE_EXPORT GFTT_FREAK : public GFTT class RTABMAP_CORE_EXPORT GFTT_FREAK : public GFTT
{ {
public: public:
@@ -494,7 +521,7 @@ private:
cv::Ptr<CV_FREAK> _freak; cv::Ptr<CV_FREAK> _freak;
}; };
//SURF_FREAK /** @brief SURF detector + FREAK descriptors. */
class RTABMAP_CORE_EXPORT SURF_FREAK : public SURF class RTABMAP_CORE_EXPORT SURF_FREAK : public SURF
{ {
public: public:
@@ -516,7 +543,7 @@ private:
cv::Ptr<CV_FREAK> _freak; cv::Ptr<CV_FREAK> _freak;
}; };
//GFTT_ORB /** @brief GFTT corners + ORB descriptors (common default when SURF is unavailable). */
class RTABMAP_CORE_EXPORT GFTT_ORB : public GFTT class RTABMAP_CORE_EXPORT GFTT_ORB : public GFTT
{ {
public: public:
@@ -533,7 +560,7 @@ private:
ORB _orb; ORB _orb;
}; };
//BRISK /** @brief BRISK detector and descriptor. */
class RTABMAP_CORE_EXPORT BRISK : public Feature2D class RTABMAP_CORE_EXPORT BRISK : public Feature2D
{ {
public: public:
@@ -555,7 +582,7 @@ private:
cv::Ptr<CV_BRISK> brisk_; cv::Ptr<CV_BRISK> brisk_;
}; };
//KAZE /** @brief KAZE detector and descriptor (OpenCV 3+). */
class RTABMAP_CORE_EXPORT KAZE : public Feature2D class RTABMAP_CORE_EXPORT KAZE : public Feature2D
{ {
public: public:
@@ -582,7 +609,7 @@ private:
#endif #endif
}; };
//ORB OCTREE /** @brief ORB with octree spatial distribution (RTAB-Map must be built with OCTREE enabled). */
class RTABMAP_CORE_EXPORT ORBOctree : public Feature2D class RTABMAP_CORE_EXPORT ORBOctree : public Feature2D
{ {
public: public:
@@ -608,7 +635,7 @@ private:
cv::Mat descriptors_; cv::Mat descriptors_;
}; };
//SuperPointTorch /** @brief SuperPoint via LibTorch (RTAB-Map must be built with libtorch support). */
class RTABMAP_CORE_EXPORT SuperPointTorch : public Feature2D class RTABMAP_CORE_EXPORT SuperPointTorch : public Feature2D
{ {
public: public:
@@ -631,7 +658,7 @@ private:
bool cuda_; bool cuda_;
}; };
//SuperPointRpautrat /** @brief SuperPoint (rpautrat) via Torch + Python (RTAB-Map must be built with libtorch and Python support). */
class RTABMAP_CORE_EXPORT SuperPointRpautrat : public Feature2D class RTABMAP_CORE_EXPORT SuperPointRpautrat : public Feature2D
{ {
public: public:
@@ -656,7 +683,7 @@ private:
bool cuda_; bool cuda_;
}; };
//GFTT_DAISY /** @brief GFTT corners + DAISY descriptors (OpenCV 3+ xfeatures2d). */
class RTABMAP_CORE_EXPORT GFTT_DAISY : public GFTT class RTABMAP_CORE_EXPORT GFTT_DAISY : public GFTT
{ {
public: public:
@@ -680,7 +707,7 @@ private:
#endif #endif
}; };
//SURF_DAISY /** @brief SURF detector + DAISY descriptors (OpenCV 3+ xfeatures2d). */
class RTABMAP_CORE_EXPORT SURF_DAISY : public SURF class RTABMAP_CORE_EXPORT SURF_DAISY : public SURF
{ {
public: public:
+5
View File
@@ -131,6 +131,11 @@ add_executable(test_localgrid test_localgrid.cpp)
target_link_libraries(test_localgrid gtest_main rtabmap_core) target_link_libraries(test_localgrid gtest_main rtabmap_core)
add_test(NAME test_localgrid COMMAND test_localgrid) add_test(NAME test_localgrid COMMAND test_localgrid)
#Features2d.h
add_executable(test_features2d test_features2d.cpp)
target_link_libraries(test_features2d gtest_main rtabmap_core)
add_test(NAME test_features2d COMMAND test_features2d)
#GlobalMap.h #GlobalMap.h
add_executable(test_globalmap test_globalmap.cpp) add_executable(test_globalmap test_globalmap.cpp)
target_link_libraries(test_globalmap gtest_main rtabmap_core) target_link_libraries(test_globalmap gtest_main rtabmap_core)
+339
View File
@@ -0,0 +1,339 @@
#include <gtest/gtest.h>
#include <algorithm>
#include <memory>
#include <vector>
#include <rtabmap/core/Features2d.h>
#include <rtabmap/core/Parameters.h>
#include <rtabmap/utilite/UConversion.h>
#include <rtabmap/utilite/UException.h>
#include <opencv2/core.hpp>
#include <opencv2/core/version.hpp>
#include <cmath>
using namespace rtabmap;
namespace {
static cv::Mat checkerboardImage(int rows = 200, int cols = 200, int cell = 20)
{
cv::Mat image(rows, cols, CV_8UC1);
for(int y = 0; y < rows; ++y)
{
for(int x = 0; x < cols; ++x)
{
image.at<uchar>(y, x) = uchar((((x / cell) + (y / cell)) % 2) ? 255 : 0);
}
}
// Add blobs so binary descriptors (ORB) also find stable corners.
for(int i = 0; i < 8; ++i)
{
cv::circle(image, cv::Point(25 + i * 22, 30 + (i % 3) * 40), 6, cv::Scalar(180), -1);
}
return image;
}
static ParametersMap orbTestParams()
{
ParametersMap params;
params.insert(ParametersPair(Parameters::kKpMaxFeatures(), "200"));
params.insert(ParametersPair(Parameters::kKpSubPixWinSize(), "0"));
params.insert(ParametersPair(Parameters::kKpGridRows(), "1"));
params.insert(ParametersPair(Parameters::kKpGridCols(), "1"));
return params;
}
} // namespace
TEST(Feature2DTest, TypeNameKnownTypes)
{
EXPECT_EQ(Feature2D::typeName(Feature2D::kFeatureUndef), "Unknown");
EXPECT_EQ(Feature2D::typeName(Feature2D::kFeatureSurf), "SURF");
EXPECT_EQ(Feature2D::typeName(Feature2D::kFeatureSift), "SIFT");
EXPECT_EQ(Feature2D::typeName(Feature2D::kFeatureOrb), "ORB");
EXPECT_EQ(Feature2D::typeName(Feature2D::kFeatureFastFreak), "FAST+FREAK");
EXPECT_EQ(Feature2D::typeName(Feature2D::kFeatureFastBrief), "FAST+BRIEF");
EXPECT_EQ(Feature2D::typeName(Feature2D::kFeatureGfttFreak), "GFTT+Freak");
EXPECT_EQ(Feature2D::typeName(Feature2D::kFeatureGfttBrief), "GFTT+Brief");
EXPECT_EQ(Feature2D::typeName(Feature2D::kFeatureBrisk), "BRISK");
EXPECT_EQ(Feature2D::typeName(Feature2D::kFeatureGfttOrb), "GFTT+ORB");
EXPECT_EQ(Feature2D::typeName(Feature2D::kFeatureKaze), "KAZE");
EXPECT_EQ(Feature2D::typeName(Feature2D::kFeatureOrbOctree), "ORB-OCTREE");
EXPECT_EQ(Feature2D::typeName(Feature2D::kFeatureSuperPointTorch), "SUPERPOINT");
EXPECT_EQ(Feature2D::typeName(Feature2D::kFeatureSurfFreak), "SURF+Freak");
EXPECT_EQ(Feature2D::typeName(Feature2D::kFeatureGfttDaisy), "GFTT+Daisy");
EXPECT_EQ(Feature2D::typeName(Feature2D::kFeatureSurfDaisy), "SURF+Daisy");
EXPECT_EQ(Feature2D::typeName(Feature2D::kFeaturePyDetector), "Unknown");
EXPECT_EQ(Feature2D::typeName(Feature2D::kFeatureSuperPointRpautrat), "SUPERPOINT-RPAUTRAT");
EXPECT_EQ(Feature2D::typeName((Feature2D::Type)99), "Unknown");
}
TEST(Feature2DTest, ComputeRoiFromRatios)
{
const cv::Mat image = checkerboardImage(100, 100);
std::vector<float> ratios;
ratios.push_back(0.1f);
ratios.push_back(0.1f);
ratios.push_back(0.1f);
ratios.push_back(0.1f);
const cv::Rect roi = Feature2D::computeRoi(image, ratios);
EXPECT_EQ(roi.x, 10);
EXPECT_EQ(roi.y, 10);
EXPECT_EQ(roi.width, 80);
EXPECT_EQ(roi.height, 80);
}
TEST(Feature2DTest, ComputeRoiFromString)
{
const cv::Mat image = checkerboardImage(100, 100);
const cv::Rect roi = Feature2D::computeRoi(image, "0.1 0.1 0.1 0.1");
EXPECT_EQ(roi.width, 80);
EXPECT_EQ(roi.height, 80);
}
TEST(Feature2DTest, LimitKeypointsByResponse)
{
std::vector<cv::KeyPoint> keypoints;
for(int i = 0; i < 10; ++i)
{
cv::KeyPoint kpt;
kpt.pt = cv::Point2f(float(i * 10), float(i * 10));
kpt.response = float(i);
keypoints.push_back(kpt);
}
Feature2D::limitKeypoints(keypoints, 5, cv::Size(200, 200), false);
EXPECT_EQ(keypoints.size(), 5u);
std::vector<float> keptResponses;
keptResponses.reserve(keypoints.size());
for(size_t i = 0; i < keypoints.size(); ++i)
{
keptResponses.push_back(keypoints[i].response);
}
std::sort(keptResponses.begin(), keptResponses.end());
const std::vector<float> expectedTop5 = {5.f, 6.f, 7.f, 8.f, 9.f};
EXPECT_EQ(keptResponses, expectedTop5);
}
TEST(Feature2DTest, LimitKeypointsWithDescriptors)
{
std::vector<cv::KeyPoint> keypoints;
cv::Mat descriptors(8, 32, CV_8UC1, cv::Scalar(0));
for(int i = 0; i < 8; ++i)
{
cv::KeyPoint kpt;
kpt.pt = cv::Point2f(float(i), float(i));
kpt.response = float(i);
keypoints.push_back(kpt);
descriptors.at<uchar>(i, 0) = uchar(i);
}
Feature2D::limitKeypoints(keypoints, descriptors, 3, cv::Size(100, 100), false);
EXPECT_EQ(keypoints.size(), 3u);
EXPECT_EQ(descriptors.rows, 3);
for(int k = 0; k < descriptors.rows; ++k)
{
const int index = int(keypoints[k].pt.x);
EXPECT_EQ(index, int(keypoints[k].pt.y));
EXPECT_FLOAT_EQ(keypoints[k].response, float(index));
EXPECT_EQ(descriptors.at<uchar>(k, 0), uchar(index));
}
std::vector<float> keptResponses;
for(size_t i = 0; i < keypoints.size(); ++i)
{
keptResponses.push_back(keypoints[i].response);
}
std::sort(keptResponses.begin(), keptResponses.end());
const std::vector<float> expectedTop3 = {5.f, 6.f, 7.f};
EXPECT_EQ(keptResponses, expectedTop3);
}
TEST(Feature2DTest, FilterKeypointsByDepth)
{
std::vector<cv::KeyPoint> keypoints;
keypoints.push_back(cv::KeyPoint(50.f, 50.f, 1.f));
keypoints.push_back(cv::KeyPoint(60.f, 60.f, 1.f));
cv::Mat depth(100, 100, CV_32FC1, cv::Scalar(2.f));
depth.at<float>(60, 60) = 0.05f;
Feature2D::filterKeypointsByDepth(keypoints, depth, 0.1f, 5.f);
EXPECT_EQ(keypoints.size(), 1u);
EXPECT_FLOAT_EQ(keypoints[0].pt.x, 50.f);
}
TEST(Feature2DTest, FilterKeypointsByDisparity)
{
std::vector<cv::KeyPoint> keypoints;
keypoints.push_back(cv::KeyPoint(10.f, 10.f, 1.f));
keypoints.push_back(cv::KeyPoint(20.f, 20.f, 1.f));
cv::Mat disparity(50, 50, CV_32FC1, cv::Scalar(0.f));
disparity.at<float>(10, 10) = 5.f;
Feature2D::filterKeypointsByDisparity(keypoints, disparity, 1.f);
EXPECT_EQ(keypoints.size(), 1u);
EXPECT_FLOAT_EQ(keypoints[0].pt.x, 10.f);
}
TEST(Feature2DTest, CreateOrbDetector)
{
ParametersMap params = orbTestParams();
std::unique_ptr<Feature2D> detector(Feature2D::create(Feature2D::kFeatureOrb, params));
ASSERT_TRUE(detector.get() != NULL);
EXPECT_EQ(detector->getType(), Feature2D::kFeatureOrb);
EXPECT_EQ(detector->getMaxFeatures(), 200);
}
// Smoke test: create() must not crash or return null for every strategy (fallbacks allowed).
TEST(Feature2DTest, CreateAllDetectorStrategiesSmoke)
{
const Feature2D::Type strategies[] = {
Feature2D::kFeatureUndef,
Feature2D::kFeatureSurf,
Feature2D::kFeatureSift,
Feature2D::kFeatureOrb,
Feature2D::kFeatureFastFreak,
Feature2D::kFeatureFastBrief,
Feature2D::kFeatureGfttFreak,
Feature2D::kFeatureGfttBrief,
Feature2D::kFeatureBrisk,
Feature2D::kFeatureGfttOrb,
Feature2D::kFeatureKaze,
Feature2D::kFeatureOrbOctree,
Feature2D::kFeatureSuperPointTorch,
Feature2D::kFeatureSurfFreak,
Feature2D::kFeatureGfttDaisy,
Feature2D::kFeatureSurfDaisy,
Feature2D::kFeaturePyDetector,
Feature2D::kFeatureSuperPointRpautrat,
};
const ParametersMap params = orbTestParams();
for(size_t i = 0; i < sizeof(strategies) / sizeof(strategies[0]); ++i)
{
const Feature2D::Type requested = strategies[i];
std::unique_ptr<Feature2D> detector(Feature2D::create(requested, params));
ASSERT_TRUE(detector.get() != NULL)
<< "create() returned null for " << Feature2D::typeName(requested);
}
for(int strategy = Feature2D::kFeatureSurf; strategy <= Feature2D::kFeatureSuperPointRpautrat; ++strategy)
{
ParametersMap paramsWithStrategy = params;
paramsWithStrategy[Parameters::kKpDetectorStrategy()] = uNumber2Str(strategy);
std::unique_ptr<Feature2D> detector(Feature2D::create(paramsWithStrategy));
ASSERT_TRUE(detector.get() != NULL)
<< "create(ParametersMap) returned null for Kp/DetectorStrategy=" << strategy;
}
}
TEST(Feature2DTest, CreateFromParametersMap)
{
ParametersMap params = orbTestParams();
params.insert(ParametersPair(Parameters::kKpDetectorStrategy(), "2")); // ORB
std::unique_ptr<Feature2D> detector(Feature2D::create(params));
ASSERT_TRUE(detector.get() != NULL);
EXPECT_EQ(detector->getType(), Feature2D::kFeatureOrb);
}
TEST(Feature2DTest, ParseParametersUpdatesMaxFeatures)
{
ParametersMap params = orbTestParams();
std::unique_ptr<Feature2D> detector(Feature2D::create(Feature2D::kFeatureOrb, params));
ASSERT_TRUE(detector.get() != NULL);
ParametersMap update;
update.insert(ParametersPair(Parameters::kKpMaxFeatures(), "50"));
detector->parseParameters(update);
EXPECT_EQ(detector->getMaxFeatures(), 50);
}
TEST(Feature2DTest, GenerateKeypointsAndDescriptors)
{
const cv::Mat image = checkerboardImage();
std::unique_ptr<Feature2D> detector(Feature2D::create(Feature2D::kFeatureOrb, orbTestParams()));
ASSERT_TRUE(detector.get() != NULL);
std::vector<cv::KeyPoint> keypoints = detector->generateKeypoints(image);
EXPECT_GT(keypoints.size(), 0u);
cv::Mat descriptors = detector->generateDescriptors(image, keypoints);
ASSERT_FALSE(descriptors.empty());
EXPECT_EQ(descriptors.rows, static_cast<int>(keypoints.size()));
EXPECT_EQ(descriptors.type(), CV_8UC1);
}
TEST(Feature2DTest, GenerateKeypointsAndDescriptorsWithMask)
{
const cv::Mat image = checkerboardImage();
// Valid region: left half only (OpenCV mask: non-zero = detect).
cv::Mat mask(image.rows, image.cols, CV_8UC1, cv::Scalar(0));
mask(cv::Rect(0, 0, image.cols / 2, image.rows)).setTo(255);
std::unique_ptr<Feature2D> detector(Feature2D::create(Feature2D::kFeatureOrb, orbTestParams()));
ASSERT_TRUE(detector.get() != NULL);
std::vector<cv::KeyPoint> keypoints = detector->generateKeypoints(image, mask);
EXPECT_GT(keypoints.size(), 0u);
for(size_t i = 0; i < keypoints.size(); ++i)
{
const int x = cvRound(keypoints[i].pt.x);
const int y = cvRound(keypoints[i].pt.y);
ASSERT_GE(x, 0);
ASSERT_GE(y, 0);
ASSERT_LT(x, mask.cols);
ASSERT_LT(y, mask.rows);
EXPECT_GT(mask.at<uchar>(y, x), 0)
<< "keypoint " << i << " at (" << x << "," << y << ") outside mask";
}
cv::Mat descriptors = detector->generateDescriptors(image, keypoints);
ASSERT_FALSE(descriptors.empty());
EXPECT_EQ(descriptors.rows, static_cast<int>(keypoints.size()));
EXPECT_EQ(descriptors.type(), CV_8UC1);
}
TEST(Feature2DTest, GenerateKeypointsAndDescriptorsWithRoi)
{
const cv::Mat image = checkerboardImage();
// Left half only (Kp/RoiRatios: left right top bottom).
const cv::Rect roi = Feature2D::computeRoi(image, "0 0.5 0 0");
ASSERT_GT(roi.width, 0);
ASSERT_GT(roi.height, 0);
ParametersMap params = orbTestParams();
params.insert(ParametersPair(Parameters::kKpRoiRatios(), "0 0.5 0 0"));
std::unique_ptr<Feature2D> detector(Feature2D::create(Feature2D::kFeatureOrb, params));
ASSERT_TRUE(detector.get() != NULL);
std::vector<cv::KeyPoint> keypoints = detector->generateKeypoints(image);
EXPECT_GT(keypoints.size(), 0u);
for(size_t i = 0; i < keypoints.size(); ++i)
{
const int x = cvRound(keypoints[i].pt.x);
const int y = cvRound(keypoints[i].pt.y);
EXPECT_GE(x, roi.x) << "keypoint " << i << " at (" << x << "," << y << ")";
EXPECT_GE(y, roi.y);
EXPECT_LT(x, roi.x + roi.width);
EXPECT_LT(y, roi.y + roi.height);
}
cv::Mat descriptors = detector->generateDescriptors(image, keypoints);
ASSERT_FALSE(descriptors.empty());
EXPECT_EQ(descriptors.rows, static_cast<int>(keypoints.size()));
EXPECT_EQ(descriptors.type(), CV_8UC1);
}
TEST(Feature2DTest, GenerateDescriptorsEmptyForNoKeypoints)
{
const cv::Mat image = checkerboardImage();
std::unique_ptr<Feature2D> detector(Feature2D::create(Feature2D::kFeatureOrb, orbTestParams()));
std::vector<cv::KeyPoint> keypoints;
const cv::Mat descriptors = detector->generateDescriptors(image, keypoints);
EXPECT_TRUE(descriptors.empty());
}