From f1347db4f27d82c658636b41499cc56eccd2e09e Mon Sep 17 00:00:00 2001 From: matlabbe Date: Sat, 19 Sep 2026 17:07:47 -0700 Subject: [PATCH] Gating negative octave scaling on decimation --- corelib/src/Memory.cpp | 7 +++-- corelib/src/Odometry.cpp | 4 ++- corelib/src/SensorCaptureThread.cpp | 2 +- corelib/test/test_memory.cpp | 43 +++++++++++++++++++++++++++++ 4 files changed, 52 insertions(+), 4 deletions(-) diff --git a/corelib/src/Memory.cpp b/corelib/src/Memory.cpp index f12bf0b2..ef4bc453 100644 --- a/corelib/src/Memory.cpp +++ b/corelib/src/Memory.cpp @@ -5673,7 +5673,10 @@ Signature * Memory::createSignature(const SensorData & inputData, const Transfor kpt.pt.x *= decimationRatio; kpt.pt.y *= decimationRatio; kpt.size *= decimationRatio; - kpt.octave += log2value; + // Never below the finest level of the image it is now + // expressed in: the detail it was found at is not in there + // any more, and ORB refuses a negative octave outright. + kpt.octave = std::max(0, int(kpt.octave + log2value)); } if(useProvided3dPoints) { @@ -6263,7 +6266,7 @@ Signature * Memory::createSignature(const SensorData & inputData, const Transfor kpt.pt.x *= decimationRatio; kpt.pt.y *= decimationRatio; kpt.size *= decimationRatio; - kpt.octave += log2value; + kpt.octave = std::max(0, int(kpt.octave + log2value)); } words.insert(std::make_pair(*iter, words.size())); wordsKpts.push_back(kpt); diff --git a/corelib/src/Odometry.cpp b/corelib/src/Odometry.cpp index ad27eafd..4b71a7f4 100644 --- a/corelib/src/Odometry.cpp +++ b/corelib/src/Odometry.cpp @@ -792,7 +792,9 @@ Transform Odometry::process(SensorData & data, const Transform & guessIn, Odomet decimatedKpts[i].pt.x /= _imageDecimation; decimatedKpts[i].pt.y /= _imageDecimation; decimatedKpts[i].size /= _imageDecimation; - decimatedKpts[i].octave -= log2value; + // Never below the finest level of the decimated image, which is as fine + // as its detail goes; ORB refuses a negative octave outright. + decimatedKpts[i].octave = std::max(0, int(decimatedKpts[i].octave - log2value)); } decimatedData.setFeatures(decimatedKpts, decimatedData.keypoints3D(), decimatedData.descriptors()); } diff --git a/corelib/src/SensorCaptureThread.cpp b/corelib/src/SensorCaptureThread.cpp index 9246755a..ab284816 100644 --- a/corelib/src/SensorCaptureThread.cpp +++ b/corelib/src/SensorCaptureThread.cpp @@ -700,7 +700,7 @@ void SensorCaptureThread::postUpdate(SensorData * dataPtr, SensorCaptureInfo * i kpts[i].pt.x /= _imageDecimation; kpts[i].pt.y /= _imageDecimation; kpts[i].size /= _imageDecimation; - kpts[i].octave -= log2value; + kpts[i].octave = std::max(0, int(kpts[i].octave - log2value)); } data.setFeatures(kpts, data.keypoints3D(), data.descriptors()); } diff --git a/corelib/test/test_memory.cpp b/corelib/test/test_memory.cpp index 5c5f1598..98fe10dc 100644 --- a/corelib/test/test_memory.cpp +++ b/corelib/test/test_memory.cpp @@ -2891,6 +2891,49 @@ TEST(MemoryTest, PreDecimationGivesBackProvidedKeypointsAsTheyCameIn) EXPECT_EQ(s->getWordsKpts()[0].octave, kpt.octave); } +TEST(MemoryTest, PreDecimationKeepsProvidedKeypointsAtTheFinestLevelAvailable) +{ + // The companion of the test above, for a keypoint found at the finest level there is. + // Scaling it into a decimated image would put it below level 0, which does not exist + // -- the detail it was found at was decimated away -- and which ORB rejects outright + // rather than describing. It stays at 0 instead, and so cannot come back at 0: the + // level it would need to return to is the one that was lost. + ParametersMap params = defaultMemoryParams(); + params[Parameters::kKpMaxFeatures()] = "100"; + params[Parameters::kMemUseOdomFeatures()] = "true"; + params[Parameters::kMemImagePreDecimation()] = "2"; + params[Parameters::kMemImagePostDecimation()] = "1"; + params[Parameters::kRtabmapImagesAlreadyRectified()] = "true"; + Memory memory(params); + + cv::Mat image(256, 256, CV_8UC1); + cv::RNG rng(7); + rng.fill(image, cv::RNG::UNIFORM, 0, 255); + const cv::Mat covariance = cv::Mat::eye(6, 6, CV_64FC1) * 0.01; + const CameraModel model(100.0, 100.0, 128.0, 128.0, + CameraModel::opticalRotation(), 0.0, cv::Size(256, 256)); + + SensorData data; + data.setRGBDImage(image, cv::Mat(), std::vector{model}); + data.setId(0); + + cv::KeyPoint kpt(128.0f, 120.0f, 8.0f); + kpt.octave = 0; + data.setFeatures(std::vector(1, kpt), + std::vector(1, cv::Point3f(0.0f, 0.0f, 1.0f)), + cv::Mat()); + + ASSERT_TRUE(memory.update(data, Transform(0, 0, 0, 0, 0, 0), covariance)); + const Signature * s = memory.getSignature(memory.getLastSignatureId()); + ASSERT_NE(s, nullptr); + ASSERT_EQ(s->getWordsKpts().size(), 1u); + // Where it is and how big it is are unaffected, those having room to scale. + EXPECT_FLOAT_EQ(s->getWordsKpts()[0].pt.x, kpt.pt.x); + EXPECT_FLOAT_EQ(s->getWordsKpts()[0].pt.y, kpt.pt.y); + EXPECT_FLOAT_EQ(s->getWordsKpts()[0].size, kpt.size); + EXPECT_GE(s->getWordsKpts()[0].octave, 0); +} + TEST(MemoryTest, CreateSignaturePostDecimatesImageWhenPostDecimationGreaterThanOne) { // kMemImagePostDecimation > 1 causes createSignature to downsample the RGB image