mirror of
https://github.com/introlab/rtabmap.git
synced 2026-10-12 12:59:51 +08:00
Memory::computeTransform(): keep global descriptors when reloading node data (#1788)
When a registration needs data that a node no longer has in RAM, its SensorData is replaced by getNodeData(), which doesn't load global descriptors from the database. The next Signature::compareTo() with that node then fails its assertion on the number of global descriptors. Same issue as #1686 in copyData().
This commit is contained in:
@@ -3453,13 +3453,18 @@ Transform Memory::computeTransform(
|
||||
(_registrationPipeline->isScanRequired() && fromS.sensorData().imageCompressed().empty() && fromS.sensorData().laserScanCompressed().isEmpty()) ||
|
||||
(_registrationPipeline->isUserDataRequired() && fromS.sensorData().imageCompressed().empty() && fromS.sensorData().userDataCompressed().empty()))
|
||||
{
|
||||
// getNodeData() doesn't load global descriptors from the database, keep those in RAM
|
||||
std::vector<GlobalDescriptor> globalDescriptors = fromS.sensorData().globalDescriptors();
|
||||
fromS.sensorData() = getNodeData(fromS.id(), true, true, true, true);
|
||||
fromS.sensorData().setGlobalDescriptors(globalDescriptors);
|
||||
}
|
||||
if(((_reextractLoopClosureFeatures && (_registrationPipeline->isImageRequired() || guess.isNull())) && toS.sensorData().imageCompressed().empty()) ||
|
||||
(_registrationPipeline->isScanRequired() && toS.sensorData().imageCompressed().empty() && toS.sensorData().laserScanCompressed().isEmpty()) ||
|
||||
(_registrationPipeline->isUserDataRequired() && toS.sensorData().imageCompressed().empty() && toS.sensorData().userDataCompressed().empty()))
|
||||
{
|
||||
std::vector<GlobalDescriptor> globalDescriptors = toS.sensorData().globalDescriptors();
|
||||
toS.sensorData() = getNodeData(toS.id(), true, true, true, true);
|
||||
toS.sensorData().setGlobalDescriptors(globalDescriptors);
|
||||
}
|
||||
// uncompress only what we need
|
||||
cv::Mat imgBuf, depthBuf, userBuf;
|
||||
|
||||
@@ -4437,6 +4437,65 @@ TEST_F(MemoryFixture, ComputeIcpTransformMultiRejectsScansTooFarApart)
|
||||
<< "unexpected reason: " << info.rejectedMsg;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Memory::computeTransform(): reloading a saved node's data for a registration.
|
||||
//
|
||||
// Once a node is saved to the database, its compressed data is dropped from RAM,
|
||||
// and a registration that needs it (here ICP) reloads it with getNodeData(),
|
||||
// which doesn't load global descriptors. The node must keep them: a later
|
||||
// Signature::compareTo() asserts that both nodes have the same number of global
|
||||
// descriptors (same issue as #1686 in copyData()).
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(MemoryTest, ComputeTransformKeepsGlobalDescriptorsWhenReloadingData)
|
||||
{
|
||||
const std::string dbPath = uniqueDbPath();
|
||||
{
|
||||
Memory memory(icpMemoryParams());
|
||||
ASSERT_TRUE(memory.init(dbPath));
|
||||
|
||||
const cv::Mat image(8, 8, CV_8UC1, cv::Scalar(128));
|
||||
const cv::Mat covariance = cv::Mat::eye(6, 6, CV_64FC1) * 0.01;
|
||||
const Transform motion(0.10f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
|
||||
const LaserScan corner = memoryCorner2D();
|
||||
const cv::Mat descriptor(1, 4, CV_32FC1, cv::Scalar(0.5f)); // normalized
|
||||
|
||||
SensorData first(image);
|
||||
first.setLaserScan(corner);
|
||||
first.addGlobalDescriptor(GlobalDescriptor(1, descriptor));
|
||||
ASSERT_TRUE(memory.update(first, Transform::getIdentity(), covariance));
|
||||
const int oldId = memory.getLastSignatureId();
|
||||
|
||||
SensorData second(image);
|
||||
second.setLaserScan(util3d::transformLaserScan(corner, motion.inverse()));
|
||||
second.addGlobalDescriptor(GlobalDescriptor(1, descriptor));
|
||||
ASSERT_TRUE(memory.update(second, motion, covariance));
|
||||
const int newId = memory.getLastSignatureId();
|
||||
|
||||
// As Rtabmap::process() does after adding a node, with a database on disk.
|
||||
memory.saveLocationData(oldId);
|
||||
memory.emptyTrash();
|
||||
memory.joinTrashThread(); // emptyTrash() is async; wait for the DB write
|
||||
ASSERT_TRUE(memory.getSignature(oldId)->isSaved());
|
||||
ASSERT_TRUE(memory.getSignature(oldId)->sensorData().laserScanCompressed().isEmpty());
|
||||
ASSERT_EQ(memory.getSignature(oldId)->sensorData().globalDescriptors().size(), 1u);
|
||||
|
||||
RegistrationInfo info;
|
||||
memory.computeTransform(oldId, newId, motion, &info);
|
||||
|
||||
const Signature * oldS = memory.getSignature(oldId);
|
||||
const Signature * newS = memory.getSignature(newId);
|
||||
ASSERT_NE(oldS, nullptr);
|
||||
ASSERT_NE(newS, nullptr);
|
||||
EXPECT_FALSE(oldS->sensorData().laserScanCompressed().isEmpty()); // reloaded
|
||||
EXPECT_EQ(oldS->sensorData().globalDescriptors().size(), 1u);
|
||||
EXPECT_NO_THROW(newS->compareTo(*oldS));
|
||||
|
||||
memory.close(false);
|
||||
}
|
||||
UFile::erase(dbPath);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// createSignature() reuses the caller's compressed blob instead of
|
||||
// re-compressing, but only while the pixels it would store are provably the
|
||||
|
||||
Reference in New Issue
Block a user