mirror of
https://github.com/introlab/rtabmap.git
synced 2026-10-04 00:57:46 +08:00
improving features2d tests
This commit is contained in:
@@ -270,6 +270,15 @@ public:
|
||||
virtual const ParametersMap & getParameters() const {return parameters_;}
|
||||
virtual Feature2D::Type getType() const = 0;
|
||||
|
||||
/** @brief Returns true when a GPU/CUDA code path **could** be used by
|
||||
* this detector on this host: i.e. the build was compiled with the
|
||||
* matching GPU support AND a CUDA-capable device is detected at
|
||||
* runtime. This is a capability probe -- it does NOT reflect whether
|
||||
* the current instance is actually configured to run on GPU (that
|
||||
* depends on per-detector parameters like SURF/GpuVersion). Defaults
|
||||
* to false; subclasses with a GPU backend override it. */
|
||||
virtual bool isGpuAvailable() const {return false;}
|
||||
|
||||
protected:
|
||||
Feature2D(const ParametersMap & parameters = ParametersMap());
|
||||
|
||||
@@ -302,6 +311,7 @@ public:
|
||||
|
||||
virtual void parseParameters(const ParametersMap & parameters);
|
||||
virtual Feature2D::Type getType() const {return kFeatureSurf;}
|
||||
virtual bool isGpuAvailable() const override;
|
||||
|
||||
private:
|
||||
virtual std::vector<cv::KeyPoint> generateKeypointsImpl(const cv::Mat & image, const cv::Rect & roi, const cv::Mat & mask = cv::Mat());
|
||||
@@ -329,6 +339,7 @@ public:
|
||||
|
||||
virtual void parseParameters(const ParametersMap & parameters);
|
||||
virtual Feature2D::Type getType() const {return kFeatureSift;}
|
||||
virtual bool isGpuAvailable() const override;
|
||||
|
||||
private:
|
||||
virtual std::vector<cv::KeyPoint> generateKeypointsImpl(const cv::Mat & image, const cv::Rect & roi, const cv::Mat & mask = cv::Mat());
|
||||
@@ -363,6 +374,7 @@ public:
|
||||
|
||||
virtual void parseParameters(const ParametersMap & parameters);
|
||||
virtual Feature2D::Type getType() const {return kFeatureOrb;}
|
||||
virtual bool isGpuAvailable() const override;
|
||||
|
||||
private:
|
||||
virtual std::vector<cv::KeyPoint> generateKeypointsImpl(const cv::Mat & image, const cv::Rect & roi, const cv::Mat & mask = cv::Mat());
|
||||
@@ -394,6 +406,7 @@ public:
|
||||
|
||||
virtual void parseParameters(const ParametersMap & parameters);
|
||||
virtual Feature2D::Type getType() const {return kFeatureUndef;}
|
||||
virtual bool isGpuAvailable() const override;
|
||||
|
||||
private:
|
||||
virtual std::vector<cv::KeyPoint> generateKeypointsImpl(const cv::Mat & image, const cv::Rect & roi, const cv::Mat & mask = cv::Mat());
|
||||
@@ -470,6 +483,7 @@ public:
|
||||
virtual ~GFTT();
|
||||
|
||||
virtual void parseParameters(const ParametersMap & parameters);
|
||||
virtual bool isGpuAvailable() const override;
|
||||
|
||||
private:
|
||||
virtual std::vector<cv::KeyPoint> generateKeypointsImpl(const cv::Mat & image, const cv::Rect & roi, const cv::Mat & mask = cv::Mat());
|
||||
@@ -650,6 +664,7 @@ public:
|
||||
|
||||
virtual void parseParameters(const ParametersMap & parameters);
|
||||
virtual Feature2D::Type getType() const { return kFeatureSuperPointTorch; }
|
||||
virtual bool isGpuAvailable() const override;
|
||||
|
||||
private:
|
||||
virtual std::vector<cv::KeyPoint> generateKeypointsImpl(const cv::Mat & image, const cv::Rect & roi, const cv::Mat & mask = cv::Mat());
|
||||
@@ -673,6 +688,7 @@ public:
|
||||
|
||||
virtual void parseParameters(const ParametersMap & parameters);
|
||||
virtual Feature2D::Type getType() const { return kFeatureSuperPointRpautrat; }
|
||||
virtual bool isGpuAvailable() const override;
|
||||
|
||||
private:
|
||||
virtual std::vector<cv::KeyPoint> generateKeypointsImpl(const cv::Mat & image, const cv::Rect & roi, const cv::Mat & mask = cv::Mat());
|
||||
|
||||
@@ -1240,6 +1240,19 @@ void SURF::parseParameters(const ParametersMap & parameters)
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SURF::isGpuAvailable() const
|
||||
{
|
||||
#ifdef RTABMAP_NONFREE
|
||||
#if CV_MAJOR_VERSION < 3
|
||||
return cv::gpu::getCudaEnabledDeviceCount() > 0;
|
||||
#else
|
||||
return cv::cuda::getCudaEnabledDeviceCount() > 0;
|
||||
#endif
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::vector<cv::KeyPoint> SURF::generateKeypointsImpl(const cv::Mat & image, const cv::Rect & roi, const cv::Mat & mask)
|
||||
{
|
||||
UASSERT(!image.empty() && image.channels() == 1 && image.depth() == CV_8U);
|
||||
@@ -1404,6 +1417,15 @@ void SIFT::parseParameters(const ParametersMap & parameters)
|
||||
|
||||
}
|
||||
|
||||
bool SIFT::isGpuAvailable() const
|
||||
{
|
||||
#ifdef RTABMAP_CUDASIFT
|
||||
return cv::cuda::getCudaEnabledDeviceCount() > 0;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::vector<cv::KeyPoint> SIFT::generateKeypointsImpl(const cv::Mat & image, const cv::Rect & roi, const cv::Mat & mask)
|
||||
{
|
||||
UASSERT(!image.empty() && image.channels() == 1 && image.depth() == CV_8U);
|
||||
@@ -1681,6 +1703,15 @@ void ORB::parseParameters(const ParametersMap & parameters)
|
||||
}
|
||||
}
|
||||
|
||||
bool ORB::isGpuAvailable() const
|
||||
{
|
||||
#ifdef HAVE_OPENCV_CUDAFEATURES2D
|
||||
return cv::cuda::getCudaEnabledDeviceCount() > 0;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::vector<cv::KeyPoint> ORB::generateKeypointsImpl(const cv::Mat & image, const cv::Rect & roi, const cv::Mat & mask)
|
||||
{
|
||||
UASSERT(!image.empty() && image.channels() == 1 && image.depth() == CV_8U);
|
||||
@@ -1945,6 +1976,15 @@ void FAST::parseParameters(const ParametersMap & parameters)
|
||||
}
|
||||
}
|
||||
|
||||
bool FAST::isGpuAvailable() const
|
||||
{
|
||||
#ifdef HAVE_OPENCV_CUDAFEATURES2D
|
||||
return cv::cuda::getCudaEnabledDeviceCount() > 0;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::vector<cv::KeyPoint> FAST::generateKeypointsImpl(const cv::Mat & image, const cv::Rect & roi, const cv::Mat & mask)
|
||||
{
|
||||
UASSERT(!image.empty() && image.channels() == 1 && image.depth() == CV_8U);
|
||||
@@ -2208,6 +2248,15 @@ void GFTT::parseParameters(const ParametersMap & parameters)
|
||||
}
|
||||
}
|
||||
|
||||
bool GFTT::isGpuAvailable() const
|
||||
{
|
||||
#ifdef HAVE_OPENCV_CUDAIMGPROC
|
||||
return cv::cuda::getCudaEnabledDeviceCount() > 0;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::vector<cv::KeyPoint> GFTT::generateKeypointsImpl(const cv::Mat & image, const cv::Rect & roi, const cv::Mat & mask)
|
||||
{
|
||||
UASSERT(!image.empty() && image.channels() == 1 && image.depth() == CV_8U);
|
||||
@@ -2659,6 +2708,15 @@ SuperPointTorch::~SuperPointTorch()
|
||||
{
|
||||
}
|
||||
|
||||
bool SuperPointTorch::isGpuAvailable() const
|
||||
{
|
||||
#ifdef RTABMAP_TORCH
|
||||
return torch::cuda::is_available();
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void SuperPointTorch::parseParameters(const ParametersMap & parameters)
|
||||
{
|
||||
Feature2D::parseParameters(parameters);
|
||||
@@ -2693,7 +2751,7 @@ std::vector<cv::KeyPoint> SuperPointTorch::generateKeypointsImpl(const cv::Mat &
|
||||
{
|
||||
#ifdef RTABMAP_TORCH
|
||||
UASSERT(!image.empty() && image.channels() == 1 && image.depth() == CV_8U);
|
||||
if(roi.x!=0 || roi.y !=0)
|
||||
if(roi.x!=0 || roi.y !=0 || roi.width!=image.cols || roi.height!=image.rows)
|
||||
{
|
||||
UERROR("SuperPoint: Not supporting ROI (%d,%d,%d,%d). Make sure %s, %s, %s, %s, %s, %s are all set to default values.",
|
||||
roi.x, roi.y, roi.width, roi.height,
|
||||
@@ -2743,6 +2801,15 @@ SuperPointRpautrat::~SuperPointRpautrat()
|
||||
{
|
||||
}
|
||||
|
||||
bool SuperPointRpautrat::isGpuAvailable() const
|
||||
{
|
||||
#if defined(RTABMAP_TORCH) && defined(RTABMAP_PYTHON)
|
||||
return torch::cuda::is_available();
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void SuperPointRpautrat::parseParameters(const ParametersMap & parameters)
|
||||
{
|
||||
Feature2D::parseParameters(parameters);
|
||||
@@ -2795,7 +2862,7 @@ std::vector<cv::KeyPoint> SuperPointRpautrat::generateKeypointsImpl(const cv::Ma
|
||||
{
|
||||
#if defined(RTABMAP_TORCH) && defined(RTABMAP_PYTHON)
|
||||
UASSERT(!image.empty() && image.channels() == 1 && image.depth() == CV_8U);
|
||||
if(roi.x!=0 || roi.y !=0)
|
||||
if(roi.x!=0 || roi.y !=0 || roi.width!=image.cols || roi.height!=image.rows)
|
||||
{
|
||||
UERROR("SuperPoint Rpautrat: Not supporting ROI (%d,%d,%d,%d). Make sure %s, %s, %s, %s, %s, %s are all set to default values.",
|
||||
roi.x, roi.y, roi.width, roi.height,
|
||||
|
||||
@@ -546,8 +546,10 @@ void ExtractorNode::DivideNode(ExtractorNode &n1, ExtractorNode &n2, ExtractorNo
|
||||
vector<cv::KeyPoint> ORBextractor::DistributeOctTree(const vector<cv::KeyPoint>& vToDistributeKeys, const int &minX,
|
||||
const int &maxX, const int &minY, const int &maxY, const int &N, const int &level)
|
||||
{
|
||||
// Compute how many initial nodes
|
||||
const int nIni = round(static_cast<float>(maxX-minX)/(maxY-minY));
|
||||
// Compute how many initial nodes. For regions taller than wide the
|
||||
// raw ratio rounds down to 0; clamp to at least 1 so hX stays finite
|
||||
// and vpIniNodes is non-empty.
|
||||
const int nIni = std::max(1, (int)round(static_cast<float>(maxX-minX)/(maxY-minY)));
|
||||
|
||||
const float hX = static_cast<float>(maxX-minX)/nIni;
|
||||
|
||||
@@ -569,11 +571,14 @@ vector<cv::KeyPoint> ORBextractor::DistributeOctTree(const vector<cv::KeyPoint>&
|
||||
vpIniNodes[i] = &lNodes.back();
|
||||
}
|
||||
|
||||
//Associate points to childs
|
||||
//Associate points to childs. A keypoint sitting exactly on the right
|
||||
//border has kp.pt.x == maxX-minX, so kp.pt.x/hX == nIni -- one past
|
||||
//the last bucket. Clamp to nIni-1 to keep it in range.
|
||||
for(size_t i=0;i<vToDistributeKeys.size();i++)
|
||||
{
|
||||
const cv::KeyPoint &kp = vToDistributeKeys[i];
|
||||
vpIniNodes[kp.pt.x/hX]->vKeys.push_back(kp);
|
||||
const int bucket = std::min(static_cast<int>(kp.pt.x/hX), nIni-1);
|
||||
vpIniNodes[bucket]->vKeys.push_back(kp);
|
||||
}
|
||||
|
||||
list<ExtractorNode>::iterator lit = lNodes.begin();
|
||||
|
||||
@@ -26,6 +26,11 @@ public:
|
||||
|
||||
virtual void parseParameters(const ParametersMap & parameters);
|
||||
virtual Feature2D::Type getType() const {return kFeaturePyDetector;}
|
||||
// PyDetector hands off CUDA usage to the python script -- the C++
|
||||
// wrapper can't introspect it, so we report capability is "possible"
|
||||
// whenever the python interpreter is built in. The actual hardware
|
||||
// presence + the script's own decision is out of our hands.
|
||||
virtual bool isGpuAvailable() const override {return true;}
|
||||
|
||||
private:
|
||||
virtual std::vector<cv::KeyPoint> generateKeypointsImpl(const cv::Mat & image, const cv::Rect & roi, const cv::Mat & mask = cv::Mat());
|
||||
|
||||
@@ -6,14 +6,40 @@
|
||||
#include <rtabmap/core/Parameters.h>
|
||||
#include <rtabmap/utilite/UConversion.h>
|
||||
#include <rtabmap/utilite/UException.h>
|
||||
#include <rtabmap/utilite/UFile.h>
|
||||
#include <rtabmap/core/Version.h>
|
||||
#ifdef RTABMAP_PYTHON
|
||||
#include <rtabmap/core/PythonInterface.h>
|
||||
#endif
|
||||
#include <opencv2/core.hpp>
|
||||
#include <opencv2/core/version.hpp>
|
||||
#include <opencv2/imgcodecs.hpp>
|
||||
#include <cmath>
|
||||
|
||||
using namespace rtabmap;
|
||||
|
||||
#ifdef RTABMAP_PYTHON
|
||||
// PythonInterface is a Meyers singleton that asserts construction on the
|
||||
// main thread and embeds a pybind11 scoped_interpreter for the rest of
|
||||
// the process. Triggering it lazily inside a gtest body has caused
|
||||
// `take_gil: PyMUTEX_LOCK failed` deadlocks across SuperPoint reconstruct
|
||||
// cycles; eagerly initialising once before the first test bypasses the
|
||||
// problem. The Environment hook runs on the gtest main thread immediately
|
||||
// before `RUN_ALL_TESTS()` starts.
|
||||
class PythonInterfaceEnv : public ::testing::Environment {
|
||||
public:
|
||||
void SetUp() override { PythonInterface::instance("test_features2d"); }
|
||||
};
|
||||
static ::testing::Environment * const kPythonEnv =
|
||||
::testing::AddGlobalTestEnvironment(new PythonInterfaceEnv);
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
// 200x200 default is intentionally different from the natural-image sample
|
||||
// so the Generate* tests exercise both sizes. The 200x200 + half-image ROI
|
||||
// also locks in the ORBextractor::DistributeOctTree regression: keypoints
|
||||
// landing on the right ROI border used to OOB-write the per-column bucket.
|
||||
static cv::Mat checkerboardImage(int rows = 200, int cols = 200, int cell = 20)
|
||||
{
|
||||
cv::Mat image(rows, cols, CV_8UC1);
|
||||
@@ -42,29 +68,38 @@ static ParametersMap orbTestParams()
|
||||
return params;
|
||||
}
|
||||
|
||||
// Forward-declare helpers defined near the Generate* tests, so the earlier
|
||||
// detector-iterating tests can share the same fixtures.
|
||||
ParametersMap detectorAssetParams(Feature2D::Type t);
|
||||
bool isGenericGenerateCandidate(Feature2D::Type t);
|
||||
struct NamedImage { const char * label; cv::Mat image; };
|
||||
std::vector<NamedImage> generateTestImages();
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(Feature2DTest, TypeNameKnownTypes)
|
||||
// Smoke-test that isGpuAvailable() compiles and is callable for every available
|
||||
// detector. We don't assert what it should return -- that depends on the
|
||||
// host (GPU/CUDA presence, build flags). What we DO check: a detector that
|
||||
// reports isGpuAvailable()=true must have actually selected a GPU codepath
|
||||
// (i.e. its build was compiled with GPU support).
|
||||
TEST(Feature2DTest, IsGpuAvailable)
|
||||
{
|
||||
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), "PyDetector");
|
||||
EXPECT_EQ(Feature2D::typeName(Feature2D::kFeatureSuperPointRpautrat), "SUPERPOINT-RPAUTRAT");
|
||||
EXPECT_EQ(Feature2D::typeName((Feature2D::Type)99), "Unknown");
|
||||
for(int strategy = Feature2D::kFeatureSurf; strategy < Feature2D::kFeatureEnd; ++strategy)
|
||||
{
|
||||
const Feature2D::Type t = static_cast<Feature2D::Type>(strategy);
|
||||
if(!Feature2D::isAvailable(t)) continue;
|
||||
SCOPED_TRACE(Feature2D::typeName(t));
|
||||
// isGpuAvailable() only checks build flags + runtime CUDA, so we
|
||||
// don't need an actual model loaded -- construct SuperPoint /
|
||||
// PyDetector even when their assets aren't present.
|
||||
std::unique_ptr<Feature2D> detector(Feature2D::create(t, detectorAssetParams(t)));
|
||||
ASSERT_TRUE(detector.get() != NULL);
|
||||
const bool gpu = detector->isGpuAvailable();
|
||||
// Smoke: the call returned without crashing or hanging. Actual
|
||||
// value depends on the host (GPU/CUDA presence, build flags),
|
||||
// so we don't assert true/false here.
|
||||
(void)gpu;
|
||||
}
|
||||
}
|
||||
|
||||
// Invariant: every type in [kFeatureSurf, kFeatureEnd) is named explicitly in
|
||||
@@ -84,6 +119,9 @@ TEST(Feature2DTest, TypeNameCoverage)
|
||||
EXPECT_FALSE(name.empty())
|
||||
<< "Feature2D::typeName() returns empty string for enum value " << strategy;
|
||||
}
|
||||
// Sentinel + out-of-range values fall through to "Unknown".
|
||||
EXPECT_EQ(Feature2D::typeName(Feature2D::kFeatureUndef), "Unknown");
|
||||
EXPECT_EQ(Feature2D::typeName((Feature2D::Type)99), "Unknown");
|
||||
}
|
||||
|
||||
TEST(Feature2DTest, ComputeRoiFromRatios)
|
||||
@@ -168,6 +206,42 @@ TEST(Feature2DTest, LimitKeypointsWithDescriptors)
|
||||
EXPECT_EQ(keptResponses, expectedTop3);
|
||||
}
|
||||
|
||||
// Verify Kp/MaxFeatures caps detector output across every available
|
||||
// detector and both test images. Cap = 20: every detector currently
|
||||
// finds more than that on both images, so the cap is genuinely
|
||||
// exercised in all combinations. Also verifies generateDescriptors()
|
||||
// produces one row per surviving keypoint (no silent drop).
|
||||
TEST(Feature2DTest, KpMaxFeaturesCapsDetectorOutput)
|
||||
{
|
||||
const int kMaxKeypoints = 20;
|
||||
int tested = 0;
|
||||
for(const auto & img : generateTestImages())
|
||||
{
|
||||
SCOPED_TRACE(std::string("image=") + img.label);
|
||||
for(int s = Feature2D::kFeatureSurf; s < Feature2D::kFeatureEnd; ++s)
|
||||
{
|
||||
const Feature2D::Type t = static_cast<Feature2D::Type>(s);
|
||||
if(!isGenericGenerateCandidate(t)) continue;
|
||||
SCOPED_TRACE(Feature2D::typeName(t));
|
||||
|
||||
ParametersMap params = detectorAssetParams(t);
|
||||
params[Parameters::kKpMaxFeatures()] = uNumber2Str(kMaxKeypoints);
|
||||
std::unique_ptr<Feature2D> detector(Feature2D::create(t, params));
|
||||
ASSERT_TRUE(detector.get() != NULL);
|
||||
std::vector<cv::KeyPoint> capped = detector->generateKeypoints(img.image);
|
||||
EXPECT_LE(static_cast<int>(capped.size()), kMaxKeypoints)
|
||||
<< "capped=" << capped.size();
|
||||
|
||||
cv::Mat descriptors = detector->generateDescriptors(img.image, capped);
|
||||
EXPECT_EQ(descriptors.rows, static_cast<int>(capped.size()))
|
||||
<< "descriptors=" << descriptors.rows
|
||||
<< " keypoints=" << capped.size();
|
||||
++tested;
|
||||
}
|
||||
}
|
||||
ASSERT_GT(tested, 0) << "no Features2D detector was available";
|
||||
}
|
||||
|
||||
TEST(Feature2DTest, FilterKeypointsByDepth)
|
||||
{
|
||||
std::vector<cv::KeyPoint> keypoints;
|
||||
@@ -283,88 +357,274 @@ TEST(Feature2DTest, ParseParametersUpdatesMaxFeatures)
|
||||
EXPECT_EQ(detector->getMaxFeatures(), 50);
|
||||
}
|
||||
|
||||
namespace {
|
||||
// Load `data/samples/17.jpg` -- a real outdoor frame with strong corner
|
||||
// content for blob-based descriptors (SIFT/SURF/KAZE) and stable enough
|
||||
// for binary ones (ORB/BRIEF). Test data ships with the repo so this is
|
||||
// always available, no fetch_test_data.sh required.
|
||||
cv::Mat loadSampleImage()
|
||||
{
|
||||
const std::string path = std::string(RTABMAP_TEST_DATA_ROOT) + "/samples/17.jpg";
|
||||
return cv::imread(path, cv::IMREAD_GRAYSCALE);
|
||||
}
|
||||
|
||||
// Paths fetched by scripts/fetch_test_data.sh into data/tests/.
|
||||
inline std::string superpointTorchModel()
|
||||
{
|
||||
return std::string(RTABMAP_TEST_DATA_ROOT) + "/tests/superpoint_v1.pt";
|
||||
}
|
||||
inline std::string superpointRpautratWeights()
|
||||
{
|
||||
return std::string(RTABMAP_TEST_DATA_ROOT) + "/tests/superpoint_v6_from_tf.pth";
|
||||
}
|
||||
inline std::string superpointRpautratModel()
|
||||
{
|
||||
return std::string(RTABMAP_TEST_DATA_ROOT) + "/tests/superpoint_pytorch.py";
|
||||
}
|
||||
|
||||
// Detector types iterated by the Generate* tests. PyDetector needs a
|
||||
// user-supplied script (skipped). SuperPoint variants need libtorch
|
||||
// weights + (for Rpautrat) a Python model file; included when those
|
||||
// assets are on disk.
|
||||
bool isGenericGenerateCandidate(Feature2D::Type t)
|
||||
{
|
||||
if(!Feature2D::isAvailable(t)) return false;
|
||||
if(t == Feature2D::kFeaturePyDetector) return false;
|
||||
if(t == Feature2D::kFeatureSuperPointTorch)
|
||||
{
|
||||
return UFile::exists(superpointTorchModel());
|
||||
}
|
||||
if(t == Feature2D::kFeatureSuperPointRpautrat)
|
||||
{
|
||||
return UFile::exists(superpointRpautratWeights())
|
||||
&& UFile::exists(superpointRpautratModel());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ParametersMap detectorAssetParams(Feature2D::Type t)
|
||||
{
|
||||
ParametersMap params;
|
||||
if(t == Feature2D::kFeatureSuperPointTorch)
|
||||
{
|
||||
params[Parameters::kSuperPointModelPath()] = superpointTorchModel();
|
||||
params[Parameters::kSuperPointCuda()] = "false";
|
||||
}
|
||||
else if(t == Feature2D::kFeatureSuperPointRpautrat)
|
||||
{
|
||||
params[Parameters::kSuperPointRpautratWeightsPath()] = superpointRpautratWeights();
|
||||
params[Parameters::kSuperPointRpautratModelPath()] = superpointRpautratModel();
|
||||
params[Parameters::kSuperPointRpautratCuda()] = "false";
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
// Flips the per-detector "use GPU" parameter on. Each backend has its own
|
||||
// key (SURF/GpuVersion, SIFT/Gpu, ORB/Gpu, FAST/Gpu, GFTT/Gpu,
|
||||
// SuperPoint/Cuda, SuperPointRpautrat/Cuda). Detectors that share a key
|
||||
// (FAST_BRIEF/FAST_FREAK share FAST/Gpu; the GFTT variants share GFTT/Gpu)
|
||||
// are grouped accordingly. Detectors without a GPU backend pass through
|
||||
// unchanged.
|
||||
void setDetectorGpu(Feature2D::Type t, ParametersMap & params)
|
||||
{
|
||||
switch(t)
|
||||
{
|
||||
case Feature2D::kFeatureSurf: params[Parameters::kSURFGpuVersion()] = "true"; break;
|
||||
case Feature2D::kFeatureSift: params[Parameters::kSIFTGpu()] = "true"; break;
|
||||
case Feature2D::kFeatureOrb: params[Parameters::kORBGpu()] = "true"; break;
|
||||
case Feature2D::kFeatureFastBrief:
|
||||
case Feature2D::kFeatureFastFreak: params[Parameters::kFASTGpu()] = "true"; break;
|
||||
case Feature2D::kFeatureGfttFreak:
|
||||
case Feature2D::kFeatureGfttBrief:
|
||||
case Feature2D::kFeatureGfttOrb:
|
||||
case Feature2D::kFeatureGfttDaisy: params[Parameters::kGFTTGpu()] = "true"; break;
|
||||
case Feature2D::kFeatureSuperPointTorch: params[Parameters::kSuperPointCuda()] = "true"; break;
|
||||
case Feature2D::kFeatureSuperPointRpautrat: params[Parameters::kSuperPointRpautratCuda()] = "true"; break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
// Each (test, detector) pair runs against both a real outdoor frame (17.jpg)
|
||||
// and a synthetic high-contrast checkerboard. The pair-iteration catches
|
||||
// detectors that handle one image well and crash on the other (e.g. an
|
||||
// edge case in ORB-OCTREE on uniform synthetic patterns with ROI).
|
||||
// NamedImage is forward-declared near the top of this file so earlier
|
||||
// tests can share the helper.
|
||||
std::vector<NamedImage> generateTestImages()
|
||||
{
|
||||
return {
|
||||
{"sample17", loadSampleImage()},
|
||||
{"checkerboard", checkerboardImage()},
|
||||
};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST(Feature2DTest, GenerateKeypointsAndDescriptors)
|
||||
{
|
||||
const cv::Mat image = checkerboardImage();
|
||||
std::unique_ptr<Feature2D> detector(Feature2D::create(Feature2D::kFeatureOrb, orbTestParams()));
|
||||
ASSERT_TRUE(detector.get() != NULL);
|
||||
int tested = 0;
|
||||
for(const auto & img : generateTestImages())
|
||||
{
|
||||
SCOPED_TRACE(std::string("image=") + img.label);
|
||||
for(int strategy = Feature2D::kFeatureSurf; strategy < Feature2D::kFeatureEnd; ++strategy)
|
||||
{
|
||||
const Feature2D::Type t = static_cast<Feature2D::Type>(strategy);
|
||||
if(!isGenericGenerateCandidate(t)) continue;
|
||||
SCOPED_TRACE(Feature2D::typeName(t));
|
||||
|
||||
std::vector<cv::KeyPoint> keypoints = detector->generateKeypoints(image);
|
||||
EXPECT_GT(keypoints.size(), 0u);
|
||||
// CPU pass.
|
||||
ParametersMap cpuParams = detectorAssetParams(t);
|
||||
std::unique_ptr<Feature2D> cpuDetector(Feature2D::create(t, cpuParams));
|
||||
ASSERT_TRUE(cpuDetector.get() != NULL);
|
||||
|
||||
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);
|
||||
std::vector<cv::KeyPoint> cpuKeypoints = cpuDetector->generateKeypoints(img.image);
|
||||
EXPECT_GT(cpuKeypoints.size(), 0u);
|
||||
|
||||
cv::Mat cpuDescriptors = cpuDetector->generateDescriptors(img.image, cpuKeypoints);
|
||||
ASSERT_FALSE(cpuDescriptors.empty());
|
||||
EXPECT_EQ(cpuDescriptors.rows, static_cast<int>(cpuKeypoints.size()));
|
||||
// Descriptor dtype is detector-specific (binary CV_8UC1 for
|
||||
// ORB/BRIEF/BRISK/FREAK, float CV_32FC1 for SURF/SIFT/KAZE/DAISY),
|
||||
// so we just sanity-check it's one of those two.
|
||||
EXPECT_TRUE(cpuDescriptors.type() == CV_8UC1 || cpuDescriptors.type() == CV_32FC1)
|
||||
<< "unexpected descriptor type " << cpuDescriptors.type();
|
||||
++tested;
|
||||
|
||||
// GPU pass (only when the build + runtime supports it; the
|
||||
// per-instance flag is the per-detector GPU param we set
|
||||
// below). Compare keypoint counts between the two paths:
|
||||
// the CPU and GPU implementations don't have to be
|
||||
// bit-identical, but they should detect a similar order
|
||||
// of magnitude of keypoints on the same image.
|
||||
if(!cpuDetector->isGpuAvailable()) continue;
|
||||
cpuDetector.reset(); // free CUDA resources before reconstructing
|
||||
SCOPED_TRACE("variant=GPU");
|
||||
|
||||
ParametersMap gpuParams = detectorAssetParams(t);
|
||||
setDetectorGpu(t, gpuParams);
|
||||
std::unique_ptr<Feature2D> gpuDetector(Feature2D::create(t, gpuParams));
|
||||
ASSERT_TRUE(gpuDetector.get() != NULL);
|
||||
|
||||
std::vector<cv::KeyPoint> gpuKeypoints = gpuDetector->generateKeypoints(img.image);
|
||||
EXPECT_GT(gpuKeypoints.size(), 0u);
|
||||
|
||||
cv::Mat gpuDescriptors = gpuDetector->generateDescriptors(img.image, gpuKeypoints);
|
||||
ASSERT_FALSE(gpuDescriptors.empty());
|
||||
EXPECT_EQ(gpuDescriptors.rows, static_cast<int>(gpuKeypoints.size()));
|
||||
|
||||
// CPU/GPU implementations differ in sub-pixel refinement,
|
||||
// thresholding, and NMS, so the counts won't match exactly.
|
||||
// Allow up to 30% relative difference -- catches a backend
|
||||
// that returns 0 or a huge spread, lets small drift through.
|
||||
const float cpuCount = static_cast<float>(cpuKeypoints.size());
|
||||
const float gpuCount = static_cast<float>(gpuKeypoints.size());
|
||||
const float relDiff = std::fabs(gpuCount - cpuCount) / std::max(cpuCount, 1.0f);
|
||||
EXPECT_LE(relDiff, 0.30f)
|
||||
<< "CPU keypoints=" << cpuKeypoints.size()
|
||||
<< " vs GPU keypoints=" << gpuKeypoints.size()
|
||||
<< " (relDiff=" << relDiff << ")";
|
||||
}
|
||||
}
|
||||
ASSERT_GT(tested, 0) << "no detector was available to exercise";
|
||||
}
|
||||
|
||||
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)
|
||||
int tested = 0;
|
||||
for(const auto & img : generateTestImages())
|
||||
{
|
||||
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";
|
||||
}
|
||||
SCOPED_TRACE(std::string("image=") + img.label);
|
||||
// Valid region: left half only (OpenCV mask: non-zero = detect).
|
||||
cv::Mat mask(img.image.rows, img.image.cols, CV_8UC1, cv::Scalar(0));
|
||||
mask(cv::Rect(0, 0, img.image.cols / 2, img.image.rows)).setTo(255);
|
||||
|
||||
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);
|
||||
for(int strategy = Feature2D::kFeatureSurf; strategy < Feature2D::kFeatureEnd; ++strategy)
|
||||
{
|
||||
const Feature2D::Type t = static_cast<Feature2D::Type>(strategy);
|
||||
if(!isGenericGenerateCandidate(t)) continue;
|
||||
SCOPED_TRACE(Feature2D::typeName(t));
|
||||
|
||||
std::unique_ptr<Feature2D> detector(Feature2D::create(t, detectorAssetParams(t)));
|
||||
ASSERT_TRUE(detector.get() != NULL);
|
||||
|
||||
std::vector<cv::KeyPoint> keypoints = detector->generateKeypoints(img.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(img.image, keypoints);
|
||||
ASSERT_FALSE(descriptors.empty());
|
||||
EXPECT_EQ(descriptors.rows, static_cast<int>(keypoints.size()));
|
||||
++tested;
|
||||
}
|
||||
}
|
||||
ASSERT_GT(tested, 0) << "no detector was available to exercise";
|
||||
}
|
||||
|
||||
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)
|
||||
int tested = 0;
|
||||
for(const auto & img : generateTestImages())
|
||||
{
|
||||
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);
|
||||
}
|
||||
SCOPED_TRACE(std::string("image=") + img.label);
|
||||
// Left half only (Kp/RoiRatios: left right top bottom).
|
||||
const cv::Rect roi = Feature2D::computeRoi(img.image, "0 0.5 0 0");
|
||||
ASSERT_GT(roi.width, 0);
|
||||
ASSERT_GT(roi.height, 0);
|
||||
|
||||
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);
|
||||
for(int strategy = Feature2D::kFeatureSurf; strategy < Feature2D::kFeatureEnd; ++strategy)
|
||||
{
|
||||
const Feature2D::Type t = static_cast<Feature2D::Type>(strategy);
|
||||
if(!isGenericGenerateCandidate(t)) continue;
|
||||
// SuperPointTorch / SuperPointRpautrat ignore the ROI extent
|
||||
// (they only refuse non-zero offsets; they don't crop the
|
||||
// image to roi.width/height). Skipping until that's fixed.
|
||||
if(t == Feature2D::kFeatureSuperPointTorch
|
||||
|| t == Feature2D::kFeatureSuperPointRpautrat) continue;
|
||||
SCOPED_TRACE(Feature2D::typeName(t));
|
||||
|
||||
ParametersMap params = detectorAssetParams(t);
|
||||
params[Parameters::kKpRoiRatios()] = "0 0.5 0 0";
|
||||
|
||||
std::unique_ptr<Feature2D> detector(Feature2D::create(t, params));
|
||||
ASSERT_TRUE(detector.get() != NULL);
|
||||
|
||||
std::vector<cv::KeyPoint> keypoints = detector->generateKeypoints(img.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(img.image, keypoints);
|
||||
ASSERT_FALSE(descriptors.empty());
|
||||
EXPECT_EQ(descriptors.rows, static_cast<int>(keypoints.size()));
|
||||
++tested;
|
||||
}
|
||||
}
|
||||
ASSERT_GT(tested, 0) << "no detector was available to exercise";
|
||||
}
|
||||
|
||||
TEST(Feature2DTest, GenerateDescriptorsEmptyForNoKeypoints)
|
||||
{
|
||||
const cv::Mat image = checkerboardImage();
|
||||
const cv::Mat image = loadSampleImage();
|
||||
std::unique_ptr<Feature2D> detector(Feature2D::create(Feature2D::kFeatureOrb, orbTestParams()));
|
||||
std::vector<cv::KeyPoint> keypoints;
|
||||
const cv::Mat descriptors = detector->generateDescriptors(image, keypoints);
|
||||
|
||||
@@ -1645,30 +1645,28 @@ TEST_F(RtabmapIntegrationFixture, AppearanceOnly_PrecisionRecall)
|
||||
const std::string superpointRpautratWeights = std::string(RTABMAP_TEST_DATA_ROOT) + "/tests/superpoint_v6_from_tf.pth";
|
||||
const std::string superpointRpautratModel = std::string(RTABMAP_TEST_DATA_ROOT) + "/tests/superpoint_pytorch.py";
|
||||
|
||||
// Two BoW likelihood variants: the rtabmap default (raw word-count
|
||||
// likelihood) and the TF-IDF-weighted variant. They produce different
|
||||
// hypothesis distributions, so each detector is exercised under both.
|
||||
const std::vector<bool> tfIdfVariants = {false, true};
|
||||
// One pass per detector: prefer GPU if available, otherwise CPU. To
|
||||
// keep wall-clock low, the two BoW likelihood variants (default raw
|
||||
// word-count vs TF-IDF-weighted) are exercised only on rtabmap's
|
||||
// default detector (Kp/DetectorStrategy default = kFeatureGfttOrb).
|
||||
const Feature2D::Type defaultDetector =
|
||||
static_cast<Feature2D::Type>(Parameters::defaultKpDetectorStrategy());
|
||||
|
||||
int detectorsTested = 0;
|
||||
for(bool tfIdfUsed : tfIdfVariants)
|
||||
for(int strategy = Feature2D::kFeatureSurf; strategy < Feature2D::kFeatureEnd; ++strategy)
|
||||
{
|
||||
const Feature2D::Type detectorType = static_cast<Feature2D::Type>(strategy);
|
||||
// Label includes the variant so per-detector logs / output BMPs /
|
||||
// work DBs don't clobber each other across the two iterations.
|
||||
const std::string detectorLabel = Feature2D::typeName(detectorType)
|
||||
+ (tfIdfUsed ? "[TfIdf]" : "[Likelihood]");
|
||||
const std::string typeName = Feature2D::typeName(detectorType);
|
||||
|
||||
if(!Feature2D::isAvailable(detectorType))
|
||||
{
|
||||
std::cerr << "[skip] detector " << detectorLabel << " not available in this build\n";
|
||||
std::cerr << "[skip] detector " << typeName << " not available in this build\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
if(detectorType == Feature2D::kFeaturePyDetector)
|
||||
{
|
||||
std::cerr << "[skip] detector " << detectorLabel
|
||||
std::cerr << "[skip] detector " << typeName
|
||||
<< " requires a user-supplied Py/DetectorPath script\n";
|
||||
continue;
|
||||
}
|
||||
@@ -1678,7 +1676,7 @@ TEST_F(RtabmapIntegrationFixture, AppearanceOnly_PrecisionRecall)
|
||||
// script writes them under data/tests/; skip cleanly if absent.
|
||||
if(detectorType == Feature2D::kFeatureSuperPointTorch && !UFile::exists(superpointTorchModel))
|
||||
{
|
||||
std::cerr << "[skip] detector " << detectorLabel
|
||||
std::cerr << "[skip] detector " << typeName
|
||||
<< " missing weights: " << superpointTorchModel
|
||||
<< " (run scripts/fetch_test_data.sh)\n";
|
||||
continue;
|
||||
@@ -1687,13 +1685,45 @@ TEST_F(RtabmapIntegrationFixture, AppearanceOnly_PrecisionRecall)
|
||||
(!UFile::exists(superpointRpautratWeights) ||
|
||||
!UFile::exists(superpointRpautratModel)))
|
||||
{
|
||||
std::cerr << "[skip] detector " << detectorLabel
|
||||
std::cerr << "[skip] detector " << typeName
|
||||
<< " missing assets: weights=" << superpointRpautratWeights
|
||||
<< " model=" << superpointRpautratModel
|
||||
<< " (run scripts/fetch_test_data.sh)\n";
|
||||
continue;
|
||||
}
|
||||
SCOPED_TRACE(std::string("detector=") + detectorLabel);
|
||||
|
||||
// Probe GPU availability once per detector. Build the temp instance
|
||||
// with the same asset paths the actual run uses; otherwise SuperPoint
|
||||
// variants log a (harmless) load failure.
|
||||
bool useGpu = false;
|
||||
{
|
||||
ParametersMap probeParams;
|
||||
if(detectorType == Feature2D::kFeatureSuperPointTorch)
|
||||
{
|
||||
probeParams[Parameters::kSuperPointModelPath()] = superpointTorchModel;
|
||||
}
|
||||
else if(detectorType == Feature2D::kFeatureSuperPointRpautrat)
|
||||
{
|
||||
probeParams[Parameters::kSuperPointRpautratWeightsPath()] = superpointRpautratWeights;
|
||||
probeParams[Parameters::kSuperPointRpautratModelPath()] = superpointRpautratModel;
|
||||
}
|
||||
std::unique_ptr<Feature2D> probe(Feature2D::create(detectorType, probeParams));
|
||||
if(probe) useGpu = probe->isGpuAvailable();
|
||||
}
|
||||
|
||||
// Run only the default likelihood variant for every detector;
|
||||
// the TF-IDF variant adds a second run on the default detector
|
||||
// (kFeatureGfttOrb) so the alternative likelihood path stays
|
||||
// exercised without quadrupling the test runtime.
|
||||
std::vector<bool> tfIdfVariants = {false};
|
||||
if(detectorType == defaultDetector) tfIdfVariants.push_back(true);
|
||||
|
||||
for(bool tfIdfUsed : tfIdfVariants)
|
||||
{
|
||||
const std::string detectorLabel = typeName
|
||||
+ (tfIdfUsed ? "[TfIdf]" : "[Likelihood]")
|
||||
+ (useGpu ? "[GPU]" : "");
|
||||
SCOPED_TRACE(std::string("detector=") + detectorLabel);
|
||||
|
||||
ParametersMap params;
|
||||
params[Parameters::kRGBDEnabled()] = "false";
|
||||
@@ -1718,17 +1748,36 @@ TEST_F(RtabmapIntegrationFixture, AppearanceOnly_PrecisionRecall)
|
||||
params[Parameters::kMemBadSignaturesIgnored()] = "true";
|
||||
params[Parameters::kMemRehearsalSimilarity()] = "0.20";
|
||||
|
||||
// Backend-specific asset paths.
|
||||
// Backend-specific asset paths + GPU/CUDA toggle. `useGpu` only
|
||||
// reaches here when this detector reports a usable GPU path; we
|
||||
// then flip the per-detector "use GPU" parameter on.
|
||||
const std::string gpuFlag = useGpu ? "true" : "false";
|
||||
if(detectorType == Feature2D::kFeatureSuperPointTorch)
|
||||
{
|
||||
params[Parameters::kSuperPointModelPath()] = superpointTorchModel;
|
||||
params[Parameters::kSuperPointCuda()] = "false";
|
||||
params[Parameters::kSuperPointCuda()] = gpuFlag;
|
||||
}
|
||||
else if(detectorType == Feature2D::kFeatureSuperPointRpautrat)
|
||||
{
|
||||
params[Parameters::kSuperPointRpautratWeightsPath()] = superpointRpautratWeights;
|
||||
params[Parameters::kSuperPointRpautratModelPath()] = superpointRpautratModel;
|
||||
params[Parameters::kSuperPointRpautratCuda()] = "false";
|
||||
params[Parameters::kSuperPointRpautratCuda()] = gpuFlag;
|
||||
}
|
||||
else if(useGpu)
|
||||
{
|
||||
switch(detectorType)
|
||||
{
|
||||
case Feature2D::kFeatureSurf: params[Parameters::kSURFGpuVersion()] = "true"; break;
|
||||
case Feature2D::kFeatureSift: params[Parameters::kSIFTGpu()] = "true"; break;
|
||||
case Feature2D::kFeatureOrb: params[Parameters::kORBGpu()] = "true"; break;
|
||||
case Feature2D::kFeatureFastBrief:
|
||||
case Feature2D::kFeatureFastFreak: params[Parameters::kFASTGpu()] = "true"; break;
|
||||
case Feature2D::kFeatureGfttFreak:
|
||||
case Feature2D::kFeatureGfttBrief:
|
||||
case Feature2D::kFeatureGfttOrb:
|
||||
case Feature2D::kFeatureGfttDaisy: params[Parameters::kGFTTGpu()] = "true"; break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
const std::string workDb = workDbForCurrentTest(detectorLabel);
|
||||
@@ -1908,6 +1957,7 @@ TEST_F(RtabmapIntegrationFixture, AppearanceOnly_PrecisionRecall)
|
||||
<< " is below 0.9 (sortedTP=" << tp << ", sortedFP=" << fp << ")";
|
||||
|
||||
++detectorsTested;
|
||||
} // end for(tfIdfUsed)
|
||||
}
|
||||
ASSERT_GT(detectorsTested, 0) << "no Features2D detector was available in this build";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user