Updated rehearsal behavior when there are intermediate nodes and RGBD/LinearUpdate,RGBD/AngularUpdate are set.

This commit is contained in:
matlabbe
2015-08-28 23:05:45 -04:00
parent ce2bbd8feb
commit a2b9c9f9a0
8 changed files with 168 additions and 131 deletions

View File

@@ -118,25 +118,29 @@ public:
infMatrix_.at<double>(5,5) = 1.0/rotVariance; infMatrix_.at<double>(5,5) = 1.0/rotVariance;
} }
Link merge(const Link & link) const Link merge(const Link & link, Type outputType) const
{ {
UASSERT(to_ == link.from()); UASSERT(to_ == link.from());
UASSERT(type_ == link.type()); UASSERT(outputType != Link::kUndef);
UASSERT(!transform_.isNull()); UASSERT((link.transform().isNull() && transform_.isNull()) || (!link.transform().isNull() && !transform_.isNull()));
UASSERT(!link.transform().isNull());
UASSERT(infMatrix_.cols == 6 && infMatrix_.rows == 6 && infMatrix_.type() == CV_64FC1); UASSERT(infMatrix_.cols == 6 && infMatrix_.rows == 6 && infMatrix_.type() == CV_64FC1);
UASSERT(link.infMatrix().cols == 6 && link.infMatrix().rows == 6 && link.infMatrix().type() == CV_64FC1); UASSERT(link.infMatrix().cols == 6 && link.infMatrix().rows == 6 && link.infMatrix().type() == CV_64FC1);
return Link( return Link(
from_, from_,
link.to(), link.to(),
type_, outputType,
transform_ * link.transform(), // FIXME, should be inf1^-1(inf1*t1 + inf2*t2) transform_.isNull()?Transform():transform_ * link.transform(), // FIXME, should be inf1^-1(inf1*t1 + inf2*t2)
infMatrix_ + link.infMatrix()); transform_.isNull()?cv::Mat::eye(6,6,CV_64FC1):infMatrix_ + link.infMatrix());
} }
Link inverse() const Link inverse() const
{ {
return Link(to_, from_, type_, transform_.inverse(), infMatrix_); return Link(
to_,
from_,
type_,
transform_.isNull()?Transform():transform_.inverse(),
transform_.isNull()?cv::Mat::eye(6,6,CV_64FC1):infMatrix_);
} }
private: private:

View File

@@ -286,8 +286,8 @@ class RTABMAP_EXP Parameters
// RGB-D SLAM // RGB-D SLAM
RTABMAP_PARAM(RGBD, Enabled, bool, true, ""); RTABMAP_PARAM(RGBD, Enabled, bool, true, "");
RTABMAP_PARAM(RGBD, PoseScanMatching, bool, false, "Laser scan matching for odometry pose correction (laser scans are required)."); RTABMAP_PARAM(RGBD, PoseScanMatching, bool, false, "Laser scan matching for odometry pose correction (laser scans are required).");
RTABMAP_PARAM(RGBD, LinearUpdate, float, 0.0, "Min linear displacement to update the map. Rehearsal is done prior to this, so weights are still updated."); RTABMAP_PARAM(RGBD, LinearUpdate, float, 0.0, "Minimum linear displacement to update the map. Rehearsal is done prior to this, so weights are still updated.");
RTABMAP_PARAM(RGBD, AngularUpdate, float, 0.0, "Min angular displacement to update the map. Rehearsal is done prior to this, so weights are still updated."); RTABMAP_PARAM(RGBD, AngularUpdate, float, 0.0, "Minimum angular displacement to update the map. Rehearsal is done prior to this, so weights are still updated.");
RTABMAP_PARAM(RGBD, NewMapOdomChangeDistance, float, 0, "A new map is created if a change of odometry translation greater than X m is detected (0 m = disabled)."); RTABMAP_PARAM(RGBD, NewMapOdomChangeDistance, float, 0, "A new map is created if a change of odometry translation greater than X m is detected (0 m = disabled).");
RTABMAP_PARAM(RGBD, OptimizeFromGraphEnd, bool, false, "Optimize graph from the newest node. If false, the graph is optimized from the oldest node of the current graph (this adds an overhead computation to detect to oldest mode of the current graph, but it can be useful to preserve the map referential from the oldest node). Warning when set to false: when some nodes are transferred, the first referential of the local map may change, resulting in momentary changes in robot/map position (which are annoying in teleoperation)."); RTABMAP_PARAM(RGBD, OptimizeFromGraphEnd, bool, false, "Optimize graph from the newest node. If false, the graph is optimized from the oldest node of the current graph (this adds an overhead computation to detect to oldest mode of the current graph, but it can be useful to preserve the map referential from the oldest node). Warning when set to false: when some nodes are transferred, the first referential of the local map may change, resulting in momentary changes in robot/map position (which are annoying in teleoperation).");
RTABMAP_PARAM(RGBD, GoalReachedRadius, float, 0.5, "Goal reached radius (m)."); RTABMAP_PARAM(RGBD, GoalReachedRadius, float, 0.5, "Goal reached radius (m).");

View File

@@ -83,6 +83,7 @@ class RTABMAP_EXP Statistics
RTABMAP_STATS(Memory, Signatures_retrieved,); RTABMAP_STATS(Memory, Signatures_retrieved,);
RTABMAP_STATS(Memory, Images_buffered,); RTABMAP_STATS(Memory, Images_buffered,);
RTABMAP_STATS(Memory, Rehearsal_sim,); RTABMAP_STATS(Memory, Rehearsal_sim,);
RTABMAP_STATS(Memory, Rehearsal_id,);
RTABMAP_STATS(Memory, Rehearsal_merged,); RTABMAP_STATS(Memory, Rehearsal_merged,);
RTABMAP_STATS(Memory, Local_graph_size,); RTABMAP_STATS(Memory, Local_graph_size,);

View File

@@ -420,6 +420,8 @@ void Memory::parseParameters(const ParametersMap & parameters)
UASSERT_MSG(_similarityThreshold >= 0.0f && _similarityThreshold <= 1.0f, uFormat("value=%f", _similarityThreshold).c_str()); UASSERT_MSG(_similarityThreshold >= 0.0f && _similarityThreshold <= 1.0f, uFormat("value=%f", _similarityThreshold).c_str());
UASSERT_MSG(_recentWmRatio >= 0.0f && _recentWmRatio <= 1.0f, uFormat("value=%f", _recentWmRatio).c_str()); UASSERT_MSG(_recentWmRatio >= 0.0f && _recentWmRatio <= 1.0f, uFormat("value=%f", _recentWmRatio).c_str());
UASSERT(_imageDecimation >= 1); UASSERT(_imageDecimation >= 1);
UASSERT(_rehearsalMaxDistance >= 0.0f);
UASSERT(_rehearsalMaxAngle >= 0.0f);
// SLAM mode vs Localization mode // SLAM mode vs Localization mode
iter = parameters.find(Parameters::kMemIncrementalMemory()); iter = parameters.find(Parameters::kMemIncrementalMemory());
@@ -3091,7 +3093,7 @@ void Memory::rehearsal(Signature * signature, Statistics * stats)
} }
//============================================================ //============================================================
// Compare with the last (not null) // Compare with the last (not intermediate node)
//============================================================ //============================================================
Signature * sB = 0; Signature * sB = 0;
for(std::set<int>::reverse_iterator iter=_stMem.rbegin(); iter!=_stMem.rend(); ++iter) for(std::set<int>::reverse_iterator iter=_stMem.rbegin(); iter!=_stMem.rend(); ++iter)
@@ -3115,10 +3117,6 @@ void Memory::rehearsal(Signature * signature, Statistics * stats)
if(sim >= _similarityThreshold) if(sim >= _similarityThreshold)
{ {
if(_incrementalMemory) if(_incrementalMemory)
{
if(signature->hasLink(id))
{
if(signature->getLinks().begin()->second.transform().isNull())
{ {
if(this->rehearsalMerge(id, signature->id())) if(this->rehearsalMerge(id, signature->id()))
{ {
@@ -3126,48 +3124,6 @@ void Memory::rehearsal(Signature * signature, Statistics * stats)
} }
} }
else else
{
float x,y,z, roll,pitch,yaw;
signature->getLinks().begin()->second.transform().getTranslationAndEulerAngles(x,y,z, roll,pitch,yaw);
if((_rehearsalMaxDistance>0.0f && (
fabs(x) > _rehearsalMaxDistance ||
fabs(y) > _rehearsalMaxDistance ||
fabs(z) > _rehearsalMaxDistance)) ||
(_rehearsalMaxAngle>0.0f && (
fabs(roll) > _rehearsalMaxAngle ||
fabs(pitch) > _rehearsalMaxAngle ||
fabs(yaw) > _rehearsalMaxAngle)))
{
if(_rehearsalWeightIgnoredWhileMoving)
{
UINFO("Rehearsal ignored because the robot has moved more than %f m or %f rad",
_rehearsalMaxDistance, _rehearsalMaxAngle);
}
else
{
// if the robot has moved, increase only weight of the new one
signature->setWeight(sB->getWeight() + signature->getWeight() + 1);
sB->setWeight(0);
UINFO("Only updated weight to %d of %d (old=%d) because the robot has moved. (d=%f a=%f)",
signature->getWeight(), signature->id(), sB->id(), _rehearsalMaxDistance, _rehearsalMaxAngle);
}
}
else if(this->rehearsalMerge(id, signature->id()))
{
merged = id;
}
}
}
else
{
// cannot merge not neighbor signatures, just update weight
signature->setWeight(sB->getWeight() + signature->getWeight() + 1);
sB->setWeight(0);
UINFO("Only updated weight to %d of %d (old=%d) because the signatures are not neighbors.",
signature->getWeight(), signature->id(), sB->id());
}
}
else
{ {
signature->setWeight(signature->getWeight() + 1 + sB->getWeight()); signature->setWeight(signature->getWeight() + 1 + sB->getWeight());
} }
@@ -3175,6 +3131,7 @@ void Memory::rehearsal(Signature * signature, Statistics * stats)
if(stats) stats->addStatistic(Statistics::kMemoryRehearsal_merged(), merged); if(stats) stats->addStatistic(Statistics::kMemoryRehearsal_merged(), merged);
if(stats) stats->addStatistic(Statistics::kMemoryRehearsal_sim(), sim); if(stats) stats->addStatistic(Statistics::kMemoryRehearsal_sim(), sim);
if(stats) stats->addStatistic(Statistics::kMemoryRehearsal_id(), sim >= _similarityThreshold?id:0);
UDEBUG("merged=%d, sim=%f t=%fs", merged, sim, timer.ticks()); UDEBUG("merged=%d, sim=%f t=%fs", merged, sim, timer.ticks());
} }
else else
@@ -3202,7 +3159,39 @@ bool Memory::rehearsalMerge(int oldId, int newId)
UINFO("Rehearsal merging %d and %d", oldS->id(), newS->id()); UINFO("Rehearsal merging %d and %d", oldS->id(), newS->id());
bool fullMerge;
bool intermediateMerge = false;
if(!newS->getLinks().begin()->second.transform().isNull())
{
// we are in metric SLAM mode:
// 1) Normal merge if not moving AND has direct link
// 2) Transform to intermediate node (weight = -1) if not moving AND hasn't direct link.
float x,y,z, roll,pitch,yaw;
newS->getLinks().begin()->second.transform().getTranslationAndEulerAngles(x,y,z, roll,pitch,yaw);
bool isMoving = fabs(x) > _rehearsalMaxDistance ||
fabs(y) > _rehearsalMaxDistance ||
fabs(z) > _rehearsalMaxDistance ||
fabs(roll) > _rehearsalMaxAngle ||
fabs(pitch) > _rehearsalMaxAngle ||
fabs(yaw) > _rehearsalMaxAngle;
if(isMoving && _rehearsalWeightIgnoredWhileMoving)
{
UINFO("Rehearsal ignored because the robot has moved more than %f m or %f rad (\"Mem/RehearsalWeightIgnoredWhileMoving\"=true)",
_rehearsalMaxDistance, _rehearsalMaxAngle);
return false;
}
fullMerge = !isMoving && newS->hasLink(oldS->id());
intermediateMerge = !isMoving && !newS->hasLink(oldS->id());
}
else
{
fullMerge = newS->hasLink(oldS->id()) && newS->getLinks().begin()->second.transform().isNull();
}
if(fullMerge)
{
//remove mutual links //remove mutual links
Link newToOldLink = newS->getLinks().at(oldS->id());
oldS->removeLink(newId); oldS->removeLink(newId);
newS->removeLink(oldId); newS->removeLink(oldId);
@@ -3213,15 +3202,17 @@ bool Memory::rehearsalMerge(int oldId, int newId)
for(std::map<int, Link>::const_iterator iter = links.begin(); iter!=links.end(); ++iter) for(std::map<int, Link>::const_iterator iter = links.begin(); iter!=links.end(); ++iter)
{ {
Link link = iter->second; Link link = iter->second;
link.setFrom(newS->id()); Link mergedLink = newToOldLink.merge(link, link.type());
UASSERT(mergedLink.from() == newS->id() && mergedLink.to() == link.to());
Signature * s = this->_getSignature(link.to()); Signature * s = this->_getSignature(link.to());
if(s) if(s)
{ {
// modify neighbor "from" // modify neighbor "from"
s->changeLinkIds(oldS->id(), newS->id()); s->removeLink(oldS->id());
s->addLink(mergedLink.inverse());
newS->addLink(link); newS->addLink(mergedLink);
} }
else else
{ {
@@ -3263,6 +3254,29 @@ bool Memory::rehearsalMerge(int oldId, int newId)
return true; return true;
} }
else else
{
// update only weights
if(_idUpdatedToNewOneRehearsal)
{
// just update weight
int w = oldS->getWeight()>=0?oldS->getWeight():0;
newS->setWeight(w + newS->getWeight() + 1);
oldS->setWeight(intermediateMerge?-1:0); // convert to intermediate node
if(_lastGlobalLoopClosureId == oldS->id())
{
_lastGlobalLoopClosureId = newS->id();
}
}
else // !_idUpdatedToNewOneRehearsal
{
int w = newS->getWeight()>=0?newS->getWeight():0;
oldS->setWeight(w + oldS->getWeight() + 1);
newS->setWeight(intermediateMerge?-1:0); // convert to intermediate node
}
}
}
else
{ {
if(!newS) if(!newS)
{ {
@@ -3273,7 +3287,7 @@ bool Memory::rehearsalMerge(int oldId, int newId)
UERROR("newId=%d, oldId=%d, Signature %d not found in working/st memories", newId, oldId, oldId); UERROR("newId=%d, oldId=%d, Signature %d not found in working/st memories", newId, oldId, oldId);
} }
} }
return false; return false; // means that the newS can be removed without problem
} }
Transform Memory::getOdomPose(int signatureId, bool lookInDatabase) const Transform Memory::getOdomPose(int signatureId, bool lookInDatabase) const
@@ -4599,7 +4613,7 @@ void Memory::getMetricConstraints(
const Signature * s2 = this->getSignature(uter->first); const Signature * s2 = this->getSignature(uter->first);
if(s2) if(s2)
{ {
link = link.merge(uter->second); link = link.merge(uter->second, uter->second.type());
poses.erase(s->id()); poses.erase(s->id());
s = s2; s = s2;
} }

View File

@@ -409,6 +409,9 @@ void Rtabmap::parseParameters(const ParametersMap & parameters)
Parameters::parse(parameters, Parameters::kRGBDPlanVirtualLinks(), _planVirtualLinks); Parameters::parse(parameters, Parameters::kRGBDPlanVirtualLinks(), _planVirtualLinks);
Parameters::parse(parameters, Parameters::kRGBDGoalsSavedInUserData(), _goalsSavedInUserData); Parameters::parse(parameters, Parameters::kRGBDGoalsSavedInUserData(), _goalsSavedInUserData);
UASSERT(_rgbdLinearUpdate >= 0.0f);
UASSERT(_rgbdAngularUpdate >= 0.0f);
// RGB-D SLAM stuff // RGB-D SLAM stuff
if((iter=parameters.find(Parameters::kLccIcpType())) != parameters.end()) if((iter=parameters.find(Parameters::kLccIcpType())) != parameters.end())
{ {
@@ -986,7 +989,7 @@ bool Rtabmap::process(
UFATAL("Not supposed to be here...last signature is null?!?"); UFATAL("Not supposed to be here...last signature is null?!?");
} }
ULOGGER_INFO("Processing signature %d", signature->id()); ULOGGER_INFO("Processing signature %d w=%d", signature->id(), signature->getWeight());
timeMemoryUpdate = timer.ticks(); timeMemoryUpdate = timer.ticks();
ULOGGER_INFO("timeMemoryUpdate=%fs", timeMemoryUpdate); ULOGGER_INFO("timeMemoryUpdate=%fs", timeMemoryUpdate);
@@ -1002,24 +1005,28 @@ bool Rtabmap::process(
{ {
_optimizedPoses.erase(rehearsedId); _optimizedPoses.erase(rehearsedId);
} }
else if(_rgbdLinearUpdate > 0.0f && _rgbdAngularUpdate > 0.0f) else if(signature->getWeight() >= 0 && _rgbdLinearUpdate > 0.0f && _rgbdAngularUpdate > 0.0f)
{ {
//============================================================ //============================================================
// Minimum displacement required to add to Memory // Minimum displacement required to add to Memory
//============================================================ //============================================================
const std::map<int, Link> & links = signature->getLinks(); const std::map<int, Link> & links = signature->getLinks();
if(links.size() == 1) if(links.size() == 1)
{
// don't do this if there are intermediate nodes
const Signature * s = _memory->getSignature(links.begin()->second.to());
UASSERT(s!=0);
if(s->getWeight() >= 0)
{ {
float x,y,z, roll,pitch,yaw; float x,y,z, roll,pitch,yaw;
links.begin()->second.transform().getTranslationAndEulerAngles(x,y,z, roll,pitch,yaw); links.begin()->second.transform().getTranslationAndEulerAngles(x,y,z, roll,pitch,yaw);
if((_rgbdLinearUpdate==0.0f || ( bool isMoving = fabs(x) > _rgbdLinearUpdate ||
fabs(x) < _rgbdLinearUpdate && fabs(y) > _rgbdLinearUpdate ||
fabs(y) < _rgbdLinearUpdate && fabs(z) > _rgbdLinearUpdate ||
fabs(z) < _rgbdLinearUpdate)) && fabs(roll) > _rgbdAngularUpdate ||
(_rgbdAngularUpdate==0.0f || ( fabs(pitch) > _rgbdAngularUpdate ||
fabs(roll) < _rgbdAngularUpdate && fabs(yaw) > _rgbdAngularUpdate;
fabs(pitch) < _rgbdAngularUpdate && if(!isMoving)
fabs(yaw) < _rgbdAngularUpdate)))
{ {
// This will disable global loop closure detection, only retrieval will be done. // This will disable global loop closure detection, only retrieval will be done.
// The location will also be deleted at the end. // The location will also be deleted at the end.
@@ -1027,6 +1034,7 @@ bool Rtabmap::process(
} }
} }
} }
}
Transform newPose = _mapCorrection * signature->getPose(); Transform newPose = _mapCorrection * signature->getPose();
_optimizedPoses.insert(std::make_pair(signature->id(), newPose)); _optimizedPoses.insert(std::make_pair(signature->id(), newPose));
@@ -1088,7 +1096,7 @@ bool Rtabmap::process(
UASSERT(s!=0); UASSERT(s!=0);
if(s->getWeight() == -1) if(s->getWeight() == -1)
{ {
tmp = _constraints.rbegin()->second.merge(tmp); tmp = _constraints.rbegin()->second.merge(tmp, tmp.type());
_optimizedPoses.erase(s->id()); _optimizedPoses.erase(s->id());
_constraints.erase(--_constraints.end()); _constraints.erase(--_constraints.end());
} }
@@ -1156,7 +1164,7 @@ bool Rtabmap::process(
// Bayes filter update // Bayes filter update
//============================================================ //============================================================
int previousId = signature->getLinks().size() == 1?signature->getLinks().begin()->first:0; int previousId = signature->getLinks().size() == 1?signature->getLinks().begin()->first:0;
// Not a bad signature, not a small displacemnt unless the previous signature didn't have a loop closure // Not a bad signature, not a small displacement unless the previous signature didn't have a loop closure
if(!signature->isBadSignature() && (!smallDisplacement || _memory->getLoopClosureLinks(previousId, false).size() == 0)) if(!signature->isBadSignature() && (!smallDisplacement || _memory->getLoopClosureLinks(previousId, false).size() == 0))
{ {
// If the working memory is empty, don't do the detection. It happens when it // If the working memory is empty, don't do the detection. It happens when it

View File

@@ -205,6 +205,7 @@ public:
bool isStatisticsPublished() const; bool isStatisticsPublished() const;
double getLoopThr() const; double getLoopThr() const;
double getVpThr() const; double getVpThr() const;
double getSimThr() const;
int getOdomStrategy() const; int getOdomStrategy() const;
int getOdomBufferSize() const; int getOdomBufferSize() const;
QString getCameraInfoDir() const; // "workinfDir/camera_info" QString getCameraInfoDir() const; // "workinfDir/camera_info"

View File

@@ -1075,23 +1075,28 @@ void MainWindow::processStats(const rtabmap::Statistics & stat)
_ui->label_matchId->clear(); _ui->label_matchId->clear();
} }
int rehearsed = (int)uValue(stat.data(), Statistics::kMemoryRehearsal_merged(), 0.0f); int rehearsalMerged = (int)uValue(stat.data(), Statistics::kMemoryRehearsal_merged(), 0.0f);
bool rehearsedSimilarity = (float)uValue(stat.data(), Statistics::kMemoryRehearsal_id(), 0.0f) != 0.0f;
int localTimeClosures = (int)uValue(stat.data(), Statistics::kLocalLoopTime_closures(), 0.0f); int localTimeClosures = (int)uValue(stat.data(), Statistics::kLocalLoopTime_closures(), 0.0f);
bool scanMatchingSuccess = (bool)uValue(stat.data(), Statistics::kOdomCorrectionAccepted(), 0.0f); bool scanMatchingSuccess = (bool)uValue(stat.data(), Statistics::kOdomCorrectionAccepted(), 0.0f);
_ui->label_stats_imageNumber->setText(QString("%1 [%2]").arg(stat.refImageId()).arg(refMapId)); _ui->label_stats_imageNumber->setText(QString("%1 [%2]").arg(stat.refImageId()).arg(refMapId));
if(rehearsed > 0) if(rehearsalMerged > 0)
{ {
_ui->imageView_source->setBackgroundColor(Qt::blue); _ui->imageView_source->setBackgroundColor(Qt::blue);
} }
else if(localTimeClosures > 0) else if(localTimeClosures > 0)
{ {
_ui->imageView_source->setBackgroundColor(Qt::darkCyan); _ui->imageView_source->setBackgroundColor(Qt::darkYellow);
} }
else if(scanMatchingSuccess) else if(scanMatchingSuccess)
{ {
_ui->imageView_source->setBackgroundColor(Qt::gray); _ui->imageView_source->setBackgroundColor(Qt::gray);
} }
else if(rehearsedSimilarity)
{
_ui->imageView_source->setBackgroundColor(Qt::darkBlue);
}
UDEBUG("time= %d ms", time.restart()); UDEBUG("time= %d ms", time.restart());

View File

@@ -3670,6 +3670,10 @@ double PreferencesDialog::getVpThr() const
{ {
return _ui->general_doubleSpinBox_vp->value(); return _ui->general_doubleSpinBox_vp->value();
} }
double PreferencesDialog::getSimThr() const
{
return _ui->doubleSpinBox_similarityThreshold->value();
}
int PreferencesDialog::getOdomStrategy() const int PreferencesDialog::getOdomStrategy() const
{ {
return _ui->odom_strategy->currentIndex(); return _ui->odom_strategy->currentIndex();