mirror of
https://github.com/introlab/rtabmap.git
synced 2026-10-05 17:47:49 +08:00
* Fixed backward compatibility with bionic (Qt::ItemFlags) * dont remove occupancy grid with not publish last signature data
7648 lines
285 KiB
C++
7648 lines
285 KiB
C++
/*
|
|
Copyright (c) 2010-2016, Mathieu Labbe - IntRoLab - Universite de Sherbrooke
|
|
All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions are met:
|
|
* Redistributions of source code must retain the above copyright
|
|
notice, this list of conditions and the following disclaimer.
|
|
* Redistributions in binary form must reproduce the above copyright
|
|
notice, this list of conditions and the following disclaimer in the
|
|
documentation and/or other materials provided with the distribution.
|
|
* Neither the name of the Universite de Sherbrooke nor the
|
|
names of its contributors may be used to endorse or promote products
|
|
derived from this software without specific prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
|
|
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "rtabmap/core/Rtabmap.h"
|
|
#include "rtabmap/core/Version.h"
|
|
#include "rtabmap/core/Features2d.h"
|
|
#include "rtabmap/core/Optimizer.h"
|
|
#include "rtabmap/core/Graph.h"
|
|
#include "rtabmap/core/Signature.h"
|
|
|
|
#include "rtabmap/core/EpipolarGeometry.h"
|
|
|
|
#include "rtabmap/core/util3d.h"
|
|
#include "rtabmap/core/util3d_transforms.h"
|
|
#include "rtabmap/core/util3d_filtering.h"
|
|
#include "rtabmap/core/util3d_surface.h"
|
|
|
|
#include "rtabmap/core/DBDriver.h"
|
|
#include "rtabmap/core/Memory.h"
|
|
#include "rtabmap/core/VWDictionary.h"
|
|
#include "rtabmap/core/BayesFilter.h"
|
|
#include "rtabmap/core/Compression.h"
|
|
#include "rtabmap/core/Registration.h"
|
|
#include "rtabmap/core/RegistrationInfo.h"
|
|
|
|
#include <rtabmap/utilite/ULogger.h>
|
|
#include <rtabmap/utilite/UFile.h>
|
|
#include <rtabmap/utilite/UTimer.h>
|
|
#include <rtabmap/utilite/UConversion.h>
|
|
#include <rtabmap/utilite/UMath.h>
|
|
#include <rtabmap/utilite/UProcessInfo.h>
|
|
|
|
#ifdef RTABMAP_PYTHON
|
|
#include "rtabmap/core/PythonInterface.h"
|
|
#endif
|
|
|
|
#ifdef RTABMAP_MRPT
|
|
// Used for odometry error propagation
|
|
#include <mrpt/poses/CPose3DPDFGaussian.h>
|
|
#endif
|
|
|
|
#include <pcl/search/kdtree.h>
|
|
#include <pcl/filters/crop_box.h>
|
|
#include <pcl/io/pcd_io.h>
|
|
#include <pcl/common/common.h>
|
|
#include <pcl/TextureMesh.h>
|
|
|
|
#include <stdlib.h>
|
|
#include <set>
|
|
|
|
#define LOG_F "LogF.txt"
|
|
#define LOG_I "LogI.txt"
|
|
|
|
#define GRAPH_FILE_NAME "Graph.dot"
|
|
|
|
|
|
//
|
|
//
|
|
//
|
|
// =======================================================
|
|
// MAIN LOOP, see method "void Rtabmap::process();" below.
|
|
// =======================================================
|
|
//
|
|
//
|
|
//
|
|
|
|
namespace rtabmap
|
|
{
|
|
|
|
Rtabmap::Rtabmap() :
|
|
_publishStats(Parameters::defaultRtabmapPublishStats()),
|
|
_publishLastSignatureData(Parameters::defaultRtabmapPublishLastSignature()),
|
|
_publishPdf(Parameters::defaultRtabmapPublishPdf()),
|
|
_publishLikelihood(Parameters::defaultRtabmapPublishLikelihood()),
|
|
_publishRAMUsage(Parameters::defaultRtabmapPublishRAMUsage()),
|
|
_computeRMSE(Parameters::defaultRtabmapComputeRMSE()),
|
|
_saveWMState(Parameters::defaultRtabmapSaveWMState()),
|
|
_maxTimeAllowed(Parameters::defaultRtabmapTimeThr()), // 700 ms
|
|
_maxMemoryAllowed(Parameters::defaultRtabmapMemoryThr()), // 0=inf
|
|
_loopThr(Parameters::defaultRtabmapLoopThr()),
|
|
_loopRatio(Parameters::defaultRtabmapLoopRatio()),
|
|
_aggressiveLoopThr(Parameters::defaultRGBDAggressiveLoopThr()),
|
|
_virtualPlaceLikelihoodRatio(Parameters::defaultRtabmapVirtualPlaceLikelihoodRatio()),
|
|
_maxLoopClosureDistance(Parameters::defaultRGBDMaxLoopClosureDistance()),
|
|
_verifyLoopClosureHypothesis(Parameters::defaultVhEpEnabled()),
|
|
_maxRetrieved(Parameters::defaultRtabmapMaxRetrieved()),
|
|
_maxLocalRetrieved(Parameters::defaultRGBDMaxLocalRetrieved()),
|
|
_maxRepublished(Parameters::defaultRtabmapMaxRepublished()),
|
|
_rawDataKept(Parameters::defaultMemImageKept()),
|
|
_statisticLogsBufferedInRAM(Parameters::defaultRtabmapStatisticLogsBufferedInRAM()),
|
|
_statisticLogged(Parameters::defaultRtabmapStatisticLogged()),
|
|
_statisticLoggedHeaders(Parameters::defaultRtabmapStatisticLoggedHeaders()),
|
|
_rgbdSlamMode(Parameters::defaultRGBDEnabled()),
|
|
_rgbdLinearUpdate(Parameters::defaultRGBDLinearUpdate()),
|
|
_rgbdAngularUpdate(Parameters::defaultRGBDAngularUpdate()),
|
|
_rgbdLinearSpeedUpdate(Parameters::defaultRGBDLinearSpeedUpdate()),
|
|
_rgbdAngularSpeedUpdate(Parameters::defaultRGBDAngularSpeedUpdate()),
|
|
_newMapOdomChangeDistance(Parameters::defaultRGBDNewMapOdomChangeDistance()),
|
|
_neighborLinkRefining(Parameters::defaultRGBDNeighborLinkRefining()),
|
|
_proximityByTime(Parameters::defaultRGBDProximityByTime()),
|
|
_proximityBySpace(Parameters::defaultRGBDProximityBySpace()),
|
|
_scanMatchingIdsSavedInLinks(Parameters::defaultRGBDScanMatchingIdsSavedInLinks()),
|
|
_loopClosureIdentityGuess(Parameters::defaultRGBDLoopClosureIdentityGuess()),
|
|
_localRadius(Parameters::defaultRGBDLocalRadius()),
|
|
_localImmunizationRatio(Parameters::defaultRGBDLocalImmunizationRatio()),
|
|
_proximityMaxGraphDepth(Parameters::defaultRGBDProximityMaxGraphDepth()),
|
|
_proximityMaxPaths(Parameters::defaultRGBDProximityMaxPaths()),
|
|
_proximityMaxNeighbors(Parameters::defaultRGBDProximityPathMaxNeighbors()),
|
|
_proximityFilteringRadius(Parameters::defaultRGBDProximityPathFilteringRadius()),
|
|
_proximityRawPosesUsed(Parameters::defaultRGBDProximityPathRawPosesUsed()),
|
|
_proximityAngle(Parameters::defaultRGBDProximityAngle()*M_PI/180.0f),
|
|
_proximityOdomGuess(Parameters::defaultRGBDProximityOdomGuess()),
|
|
_proximityMergedScanCovFactor(Parameters::defaultRGBDProximityMergedScanCovFactor()),
|
|
_databasePath(""),
|
|
_optimizeFromGraphEnd(Parameters::defaultRGBDOptimizeFromGraphEnd()),
|
|
_optimizationMaxError(Parameters::defaultRGBDOptimizeMaxError()),
|
|
_optimizationMaxErrorRepairRadius(Parameters::defaultRGBDOptimizeMaxErrorRepairRadius()),
|
|
_startNewMapOnLoopClosure(Parameters::defaultRtabmapStartNewMapOnLoopClosure()),
|
|
_startNewMapOnGoodSignature(Parameters::defaultRtabmapStartNewMapOnGoodSignature()),
|
|
_goalReachedRadius(Parameters::defaultRGBDGoalReachedRadius()),
|
|
_goalsSavedInUserData(Parameters::defaultRGBDGoalsSavedInUserData()),
|
|
_pathStuckIterations(Parameters::defaultRGBDPlanStuckIterations()),
|
|
_pathLinearVelocity(Parameters::defaultRGBDPlanLinearVelocity()),
|
|
_pathAngularVelocity(Parameters::defaultRGBDPlanAngularVelocity()),
|
|
_forceOdom3doF(Parameters::defaultRGBDForceOdom3DoF()),
|
|
_restartAtOrigin(Parameters::defaultRGBDStartAtOrigin()),
|
|
_loopCovLimited(Parameters::defaultRGBDLoopCovLimited()),
|
|
_loopGPS(Parameters::defaultRtabmapLoopGPS()),
|
|
_maxOdomCacheSize(Parameters::defaultRGBDMaxOdomCacheSize()),
|
|
_localizationSmoothing(Parameters::defaultRGBDLocalizationSmoothing()),
|
|
_localizationPriorInf(1.0/(Parameters::defaultRGBDLocalizationPriorError()*Parameters::defaultRGBDLocalizationPriorError())),
|
|
_localizationSecondTryWithoutProximityLinks(Parameters::defaultRGBDLocalizationSecondTryWithoutProximityLinks()),
|
|
_createGlobalScanMap(Parameters::defaultRGBDProximityGlobalScanMap()),
|
|
_markerPriorsLinearVariance(Parameters::defaultMarkerPriorsVarianceLinear()),
|
|
_markerPriorsAngularVariance(Parameters::defaultMarkerPriorsVarianceAngular()),
|
|
_loopClosureHypothesis(0,0.0f),
|
|
_highestHypothesis(0,0.0f),
|
|
_lastProcessTime(0.0),
|
|
_someNodesHaveBeenTransferred(false),
|
|
_distanceTravelled(0.0f),
|
|
_distanceTravelledSinceLastLocalization(0.0f),
|
|
_optimizeFromGraphEndChanged(false),
|
|
_epipolarGeometry(0),
|
|
_bayesFilter(0),
|
|
_graphOptimizer(0),
|
|
_memory(0),
|
|
_foutFloat(0),
|
|
_foutInt(0),
|
|
_wDir(""),
|
|
_mapCorrection(Transform::getIdentity()),
|
|
_lastLocalizationNodeId(0),
|
|
_currentSessionHasGPS(false),
|
|
_lastRejectedLoopClosureIds(0,0),
|
|
_pathStatus(0),
|
|
_pathCurrentIndex(0),
|
|
_pathGoalIndex(0),
|
|
_pathTransformToGoal(Transform::getIdentity()),
|
|
_pathStuckCount(0),
|
|
_pathStuckDistance(0.0f)
|
|
#ifdef RTABMAP_PYTHON
|
|
,_python(new PythonInterface())
|
|
#endif
|
|
{
|
|
}
|
|
|
|
Rtabmap::~Rtabmap() {
|
|
UDEBUG("");
|
|
this->close();
|
|
}
|
|
|
|
void Rtabmap::setupLogFiles(bool overwrite)
|
|
{
|
|
flushStatisticLogs();
|
|
// Log files
|
|
if(_foutFloat)
|
|
{
|
|
fclose(_foutFloat);
|
|
_foutFloat = 0;
|
|
}
|
|
if(_foutInt)
|
|
{
|
|
fclose(_foutInt);
|
|
_foutInt = 0;
|
|
}
|
|
|
|
if(_statisticLogged && !_wDir.empty())
|
|
{
|
|
std::string attributes = "a+"; // append to log files
|
|
if(overwrite)
|
|
{
|
|
// If a file with the same name already exists
|
|
// its content is erased and the file is treated
|
|
// as a new empty file.
|
|
attributes = "w";
|
|
}
|
|
|
|
bool addLogFHeader = overwrite || !UFile::exists(_wDir+"/"+LOG_F);
|
|
bool addLogIHeader = overwrite || !UFile::exists(_wDir+"/"+LOG_I);
|
|
|
|
#ifdef _MSC_VER
|
|
fopen_s(&_foutFloat, (_wDir+"/"+LOG_F).c_str(), attributes.c_str());
|
|
fopen_s(&_foutInt, (_wDir+"/"+LOG_I).c_str(), attributes.c_str());
|
|
#else
|
|
_foutFloat = fopen((_wDir+"/"+LOG_F).c_str(), attributes.c_str());
|
|
_foutInt = fopen((_wDir+"/"+LOG_I).c_str(), attributes.c_str());
|
|
#endif
|
|
// add header (column identification)
|
|
if(_statisticLoggedHeaders && addLogFHeader && _foutFloat)
|
|
{
|
|
fprintf(_foutFloat, "Column headers:\n");
|
|
fprintf(_foutFloat, " 1-Total iteration time (s)\n");
|
|
fprintf(_foutFloat, " 2-Memory update time (s)\n");
|
|
fprintf(_foutFloat, " 3-Retrieval time (s)\n");
|
|
fprintf(_foutFloat, " 4-Likelihood time (s)\n");
|
|
fprintf(_foutFloat, " 5-Posterior time (s)\n");
|
|
fprintf(_foutFloat, " 6-Hypothesis selection time (s)\n");
|
|
fprintf(_foutFloat, " 7-Hypothesis validation time (s)\n");
|
|
fprintf(_foutFloat, " 8-Transfer time (s)\n");
|
|
fprintf(_foutFloat, " 9-Statistics creation time (s)\n");
|
|
fprintf(_foutFloat, " 10-Loop closure hypothesis value\n");
|
|
fprintf(_foutFloat, " 11-NAN\n");
|
|
fprintf(_foutFloat, " 12-NAN\n");
|
|
fprintf(_foutFloat, " 13-NAN\n");
|
|
fprintf(_foutFloat, " 14-NAN\n");
|
|
fprintf(_foutFloat, " 15-NAN\n");
|
|
fprintf(_foutFloat, " 16-Virtual place hypothesis\n");
|
|
fprintf(_foutFloat, " 17-Join trash time (s)\n");
|
|
fprintf(_foutFloat, " 18-Weight Update (rehearsal) similarity\n");
|
|
fprintf(_foutFloat, " 19-Empty trash time (s)\n");
|
|
fprintf(_foutFloat, " 20-Retrieval database access time (s)\n");
|
|
fprintf(_foutFloat, " 21-Add loop closure link time (s)\n");
|
|
fprintf(_foutFloat, " 22-Memory cleanup time (s)\n");
|
|
fprintf(_foutFloat, " 23-Scan matching (odometry correction) time (s)\n");
|
|
fprintf(_foutFloat, " 24-Local time loop closure detection time (s)\n");
|
|
fprintf(_foutFloat, " 25-Local space loop closure detection time (s)\n");
|
|
fprintf(_foutFloat, " 26-Map optimization (s)\n");
|
|
}
|
|
if(_statisticLoggedHeaders && addLogIHeader && _foutInt)
|
|
{
|
|
fprintf(_foutInt, "Column headers:\n");
|
|
fprintf(_foutInt, " 1-Loop closure ID\n");
|
|
fprintf(_foutInt, " 2-Highest loop closure hypothesis\n");
|
|
fprintf(_foutInt, " 3-Locations transferred\n");
|
|
fprintf(_foutInt, " 4-NAN\n");
|
|
fprintf(_foutInt, " 5-Words extracted from the last image\n");
|
|
fprintf(_foutInt, " 6-Vocabulary size\n");
|
|
fprintf(_foutInt, " 7-Working memory size\n");
|
|
fprintf(_foutInt, " 8-Is loop closure hypothesis rejected?\n");
|
|
fprintf(_foutInt, " 9-NAN\n");
|
|
fprintf(_foutInt, " 10-NAN\n");
|
|
fprintf(_foutInt, " 11-Locations retrieved\n");
|
|
fprintf(_foutInt, " 12-Retrieval location ID\n");
|
|
fprintf(_foutInt, " 13-Unique words extraced from last image\n");
|
|
fprintf(_foutInt, " 14-Retrieval ID\n");
|
|
fprintf(_foutInt, " 15-Non-null likelihood values\n");
|
|
fprintf(_foutInt, " 16-Weight Update ID\n");
|
|
fprintf(_foutInt, " 17-Is last location merged through Weight Update?\n");
|
|
fprintf(_foutInt, " 18-Local graph size\n");
|
|
fprintf(_foutInt, " 19-Sensor data id\n");
|
|
fprintf(_foutInt, " 20-Indexed words\n");
|
|
fprintf(_foutInt, " 21-Index memory usage (KB)\n");
|
|
}
|
|
|
|
ULOGGER_DEBUG("Log file (int)=%s", (_wDir+"/"+LOG_I).c_str());
|
|
ULOGGER_DEBUG("Log file (float)=%s", (_wDir+"/"+LOG_F).c_str());
|
|
}
|
|
else
|
|
{
|
|
if(_statisticLogged)
|
|
{
|
|
UWARN("Working directory is not set, log disabled!");
|
|
}
|
|
UDEBUG("Log disabled!");
|
|
}
|
|
}
|
|
|
|
void Rtabmap::flushStatisticLogs()
|
|
{
|
|
if(_foutFloat && _bufferedLogsF.size())
|
|
{
|
|
UDEBUG("_bufferedLogsF.size=%d", _bufferedLogsF.size());
|
|
for(std::list<std::string>::iterator iter = _bufferedLogsF.begin(); iter!=_bufferedLogsF.end(); ++iter)
|
|
{
|
|
fprintf(_foutFloat, "%s", iter->c_str());
|
|
}
|
|
_bufferedLogsF.clear();
|
|
}
|
|
if(_foutInt && _bufferedLogsI.size())
|
|
{
|
|
UDEBUG("_bufferedLogsI.size=%d", _bufferedLogsI.size());
|
|
for(std::list<std::string>::iterator iter = _bufferedLogsI.begin(); iter!=_bufferedLogsI.end(); ++iter)
|
|
{
|
|
fprintf(_foutInt, "%s", iter->c_str());
|
|
}
|
|
_bufferedLogsI.clear();
|
|
}
|
|
}
|
|
|
|
void Rtabmap::init(const ParametersMap & parameters, const std::string & databasePath, bool loadDatabaseParameters)
|
|
{
|
|
UDEBUG("path=%s", databasePath.c_str());
|
|
_databasePath = databasePath;
|
|
if(!_databasePath.empty())
|
|
{
|
|
UASSERT(UFile::getExtension(_databasePath).compare("db") == 0);
|
|
UINFO("Using database \"%s\".", _databasePath.c_str());
|
|
}
|
|
else
|
|
{
|
|
UWARN("Using empty database. Mapping session will not be saved unless it is closed with an output database path.");
|
|
}
|
|
|
|
bool newDatabase = _databasePath.empty() || !UFile::exists(_databasePath);
|
|
|
|
ParametersMap allParameters;
|
|
if(!newDatabase && loadDatabaseParameters)
|
|
{
|
|
DBDriver * driver = DBDriver::create();
|
|
if(driver->openConnection(_databasePath, false))
|
|
{
|
|
allParameters = driver->getLastParameters();
|
|
// ignore working directory (we may be on a different computer)
|
|
allParameters.erase(Parameters::kRtabmapWorkingDirectory());
|
|
}
|
|
delete driver;
|
|
}
|
|
|
|
uInsert(allParameters, parameters);
|
|
ParametersMap::const_iterator iter;
|
|
if((iter=allParameters.find(Parameters::kRtabmapWorkingDirectory())) != allParameters.end())
|
|
{
|
|
this->setWorkingDirectory(iter->second.c_str());
|
|
}
|
|
|
|
// If doesn't exist, create a memory
|
|
if(!_memory)
|
|
{
|
|
_memory = new Memory(allParameters);
|
|
_memory->init(_databasePath, false, allParameters, true);
|
|
}
|
|
|
|
_optimizedPoses.clear();
|
|
_constraints.clear();
|
|
_globalScanMap.clear();
|
|
_globalScanMapPoses.clear();
|
|
_odomCachePoses.clear();
|
|
_odomCacheConstraints.clear();
|
|
_nodesToRepublish.clear();
|
|
|
|
// Parse all parameters
|
|
this->parseParameters(allParameters);
|
|
|
|
Transform lastPose;
|
|
_optimizedPoses = _memory->loadOptimizedPoses(&lastPose);
|
|
if(!_memory->isIncremental())
|
|
{
|
|
if(_optimizedPoses.empty() &&
|
|
_memory->getWorkingMem().size()>1 &&
|
|
_memory->getWorkingMem().lower_bound(1)!=_memory->getWorkingMem().end())
|
|
{
|
|
cv::Mat cov;
|
|
this->optimizeCurrentMap(
|
|
_memory->getLastWorkingSignature(true)->id(),
|
|
false, _optimizedPoses, cov, &_constraints);
|
|
}
|
|
if(_optimizedPoses.lower_bound(1) != _optimizedPoses.end())
|
|
{
|
|
if(_restartAtOrigin)
|
|
{
|
|
UWARN("last localization pose is ignored (%s=true), assuming we start at the first node of the map.", Parameters::kRGBDStartAtOrigin().c_str());
|
|
lastPose = _optimizedPoses.lower_bound(1)->second;
|
|
}
|
|
_lastLocalizationPose = lastPose;
|
|
|
|
UINFO("Loaded optimizedPoses=%d firstPose %d=%s lastLocalizationPose=%s",
|
|
_optimizedPoses.size(),
|
|
_optimizedPoses.lower_bound(1)->first,
|
|
_optimizedPoses.lower_bound(1)->second.prettyPrint().c_str(),
|
|
_lastLocalizationPose.prettyPrint().c_str());
|
|
|
|
if(_constraints.empty())
|
|
{
|
|
std::map<int, Transform> tmp;
|
|
// Get just the links
|
|
_memory->getMetricConstraints(uKeysSet(_optimizedPoses), tmp, _constraints, false, true);
|
|
}
|
|
|
|
// Initialize Bayes' prediction matrix
|
|
UTimer time;
|
|
std::map<int, float> likelihood;
|
|
likelihood.insert(std::make_pair(Memory::kIdVirtual, 1));
|
|
for(std::map<int, Transform>::iterator iter=_optimizedPoses.lower_bound(1); iter!=_optimizedPoses.end(); ++iter)
|
|
{
|
|
if(_memory->getSignature(iter->first))
|
|
{
|
|
likelihood.insert(std::make_pair(iter->first, 0));
|
|
}
|
|
}
|
|
_bayesFilter->computePosterior(_memory, likelihood);
|
|
UINFO("Time initializing Bayes' prediction with %ld nodes: %fs", _optimizedPoses.size(), time.ticks());
|
|
|
|
if(_createGlobalScanMap)
|
|
createGlobalScanMap();
|
|
}
|
|
else
|
|
{
|
|
UINFO("Loaded optimizedPoses=0, last localization pose is ignored!");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_lastLocalizationPose = lastPose;
|
|
if(!_optimizedPoses.empty())
|
|
{
|
|
std::map<int, Transform> tmp;
|
|
// Get just the links
|
|
_memory->getMetricConstraints(uKeysSet(_optimizedPoses), tmp, _constraints, false, true);
|
|
}
|
|
}
|
|
|
|
if(_databasePath.empty())
|
|
{
|
|
_statisticLogged = false;
|
|
}
|
|
setupLogFiles(newDatabase);
|
|
}
|
|
|
|
void Rtabmap::init(const std::string & configFile, const std::string & databasePath, bool loadDatabaseParameters)
|
|
{
|
|
// fill ctrl struct with values from the configuration file
|
|
ParametersMap param;// = Parameters::defaultParameters;
|
|
|
|
if(!configFile.empty())
|
|
{
|
|
ULOGGER_DEBUG("Read parameters from = %s", configFile.c_str());
|
|
Parameters::readINI(configFile, param);
|
|
}
|
|
|
|
this->init(param, databasePath, loadDatabaseParameters);
|
|
}
|
|
|
|
void Rtabmap::close(bool databaseSaved, const std::string & ouputDatabasePath)
|
|
{
|
|
UINFO("databaseSaved=%d", databaseSaved?1:0);
|
|
_highestHypothesis = std::make_pair(0,0.0f);
|
|
_loopClosureHypothesis = std::make_pair(0,0.0f);
|
|
_lastProcessTime = 0.0;
|
|
_someNodesHaveBeenTransferred = false;
|
|
_constraints.clear();
|
|
_mapCorrection.setIdentity();
|
|
_mapCorrectionBackup.setNull();
|
|
|
|
_localizationCovariance = cv::Mat();
|
|
_lastLocalizationNodeId = 0;
|
|
_odomCachePoses.clear();
|
|
_odomCacheConstraints.clear();
|
|
_distanceTravelled = 0.0f;
|
|
_distanceTravelledSinceLastLocalization = 0.0f;
|
|
_optimizeFromGraphEndChanged = false;
|
|
this->clearPath(0);
|
|
_gpsGeocentricCache.clear();
|
|
_currentSessionHasGPS = false;
|
|
|
|
_globalScanMap.clear();
|
|
_globalScanMapPoses.clear();
|
|
|
|
_nodesToRepublish.clear();
|
|
|
|
flushStatisticLogs();
|
|
if(_foutFloat)
|
|
{
|
|
fclose(_foutFloat);
|
|
_foutFloat = 0;
|
|
}
|
|
if(_foutInt)
|
|
{
|
|
fclose(_foutInt);
|
|
_foutInt = 0;
|
|
}
|
|
|
|
if(_epipolarGeometry)
|
|
{
|
|
delete _epipolarGeometry;
|
|
_epipolarGeometry = 0;
|
|
}
|
|
if(_memory)
|
|
{
|
|
if(_memory->isReadOnly() && databaseSaved)
|
|
{
|
|
UWARN("Database is read-only, latest optimized poses, latest localization pose and latest state of the memory are not saved.");
|
|
databaseSaved = false;
|
|
}
|
|
if(databaseSaved)
|
|
{
|
|
if(_memory->isGraphReduced() && _memory->isIncremental())
|
|
{
|
|
// Force reducing graph, then remove filtered nodes from the optimized poses
|
|
std::map<int, int> reducedIds;
|
|
_memory->incrementMapId(&reducedIds);
|
|
for(std::map<int, int>::iterator iter=reducedIds.begin(); iter!=reducedIds.end(); ++iter)
|
|
{
|
|
_optimizedPoses.erase(iter->first);
|
|
}
|
|
}
|
|
_memory->saveOptimizedPoses(_optimizedPoses, _lastLocalizationPose);
|
|
}
|
|
_memory->close(databaseSaved, true, ouputDatabasePath);
|
|
delete _memory;
|
|
_memory = 0;
|
|
}
|
|
_optimizedPoses.clear();
|
|
_lastLocalizationPose.setNull();
|
|
|
|
if(_bayesFilter)
|
|
{
|
|
delete _bayesFilter;
|
|
_bayesFilter = 0;
|
|
}
|
|
if(_graphOptimizer)
|
|
{
|
|
delete _graphOptimizer;
|
|
_graphOptimizer = 0;
|
|
}
|
|
_databasePath.clear();
|
|
parseParameters(Parameters::getDefaultParameters()); // reset to default parameters
|
|
_parameters.clear();
|
|
}
|
|
|
|
void Rtabmap::parseParameters(const ParametersMap & parameters)
|
|
{
|
|
uInsert(_parameters, parameters);
|
|
|
|
// place this before changing working directory
|
|
Parameters::parse(parameters, Parameters::kRtabmapStatisticLogsBufferedInRAM(), _statisticLogsBufferedInRAM);
|
|
Parameters::parse(parameters, Parameters::kRtabmapStatisticLogged(), _statisticLogged);
|
|
Parameters::parse(parameters, Parameters::kRtabmapStatisticLoggedHeaders(), _statisticLoggedHeaders);
|
|
|
|
ULOGGER_DEBUG("");
|
|
ParametersMap::const_iterator iter;
|
|
if((iter=parameters.find(Parameters::kRtabmapWorkingDirectory())) != parameters.end())
|
|
{
|
|
this->setWorkingDirectory(iter->second.c_str());
|
|
}
|
|
|
|
Parameters::parse(parameters, Parameters::kRtabmapPublishStats(), _publishStats);
|
|
Parameters::parse(parameters, Parameters::kRtabmapPublishLastSignature(), _publishLastSignatureData);
|
|
Parameters::parse(parameters, Parameters::kRtabmapPublishPdf(), _publishPdf);
|
|
Parameters::parse(parameters, Parameters::kRtabmapPublishLikelihood(), _publishLikelihood);
|
|
Parameters::parse(parameters, Parameters::kRtabmapPublishRAMUsage(), _publishRAMUsage);
|
|
Parameters::parse(parameters, Parameters::kRtabmapComputeRMSE(), _computeRMSE);
|
|
Parameters::parse(parameters, Parameters::kRtabmapSaveWMState(), _saveWMState);
|
|
Parameters::parse(parameters, Parameters::kRtabmapTimeThr(), _maxTimeAllowed);
|
|
Parameters::parse(parameters, Parameters::kRtabmapMemoryThr(), _maxMemoryAllowed);
|
|
Parameters::parse(parameters, Parameters::kRtabmapLoopThr(), _loopThr);
|
|
Parameters::parse(parameters, Parameters::kRtabmapLoopRatio(), _loopRatio);
|
|
Parameters::parse(parameters, Parameters::kRGBDAggressiveLoopThr(), _aggressiveLoopThr);
|
|
Parameters::parse(parameters, Parameters::kRtabmapVirtualPlaceLikelihoodRatio(), _virtualPlaceLikelihoodRatio);
|
|
|
|
Parameters::parse(parameters, Parameters::kRGBDMaxLoopClosureDistance(), _maxLoopClosureDistance);
|
|
Parameters::parse(parameters, Parameters::kVhEpEnabled(), _verifyLoopClosureHypothesis);
|
|
Parameters::parse(parameters, Parameters::kRtabmapMaxRetrieved(), _maxRetrieved);
|
|
Parameters::parse(parameters, Parameters::kRGBDMaxLocalRetrieved(), _maxLocalRetrieved);
|
|
Parameters::parse(parameters, Parameters::kRtabmapMaxRepublished(), _maxRepublished);
|
|
if(_maxRepublished == 0 || !_publishLastSignatureData)
|
|
{
|
|
_nodesToRepublish.clear();
|
|
}
|
|
Parameters::parse(parameters, Parameters::kMemImageKept(), _rawDataKept);
|
|
Parameters::parse(parameters, Parameters::kRGBDEnabled(), _rgbdSlamMode);
|
|
Parameters::parse(parameters, Parameters::kRGBDLinearUpdate(), _rgbdLinearUpdate);
|
|
Parameters::parse(parameters, Parameters::kRGBDAngularUpdate(), _rgbdAngularUpdate);
|
|
Parameters::parse(parameters, Parameters::kRGBDLinearSpeedUpdate(), _rgbdLinearSpeedUpdate);
|
|
Parameters::parse(parameters, Parameters::kRGBDAngularSpeedUpdate(), _rgbdAngularSpeedUpdate);
|
|
Parameters::parse(parameters, Parameters::kRGBDNewMapOdomChangeDistance(), _newMapOdomChangeDistance);
|
|
Parameters::parse(parameters, Parameters::kRGBDNeighborLinkRefining(), _neighborLinkRefining);
|
|
Parameters::parse(parameters, Parameters::kRGBDProximityByTime(), _proximityByTime);
|
|
Parameters::parse(parameters, Parameters::kRGBDProximityBySpace(), _proximityBySpace);
|
|
Parameters::parse(parameters, Parameters::kRGBDScanMatchingIdsSavedInLinks(), _scanMatchingIdsSavedInLinks);
|
|
Parameters::parse(parameters, Parameters::kRGBDLoopClosureIdentityGuess(), _loopClosureIdentityGuess);
|
|
Parameters::parse(parameters, Parameters::kRGBDLocalRadius(), _localRadius);
|
|
Parameters::parse(parameters, Parameters::kRGBDLocalImmunizationRatio(), _localImmunizationRatio);
|
|
Parameters::parse(parameters, Parameters::kRGBDProximityMaxGraphDepth(), _proximityMaxGraphDepth);
|
|
Parameters::parse(parameters, Parameters::kRGBDProximityMaxPaths(), _proximityMaxPaths);
|
|
Parameters::parse(parameters, Parameters::kRGBDProximityPathMaxNeighbors(), _proximityMaxNeighbors);
|
|
Parameters::parse(parameters, Parameters::kRGBDProximityPathFilteringRadius(), _proximityFilteringRadius);
|
|
Parameters::parse(parameters, Parameters::kRGBDProximityPathRawPosesUsed(), _proximityRawPosesUsed);
|
|
if(Parameters::parse(parameters, Parameters::kRGBDProximityAngle(), _proximityAngle))
|
|
{
|
|
_proximityAngle *= M_PI/180.0f;
|
|
}
|
|
Parameters::parse(parameters, Parameters::kRGBDProximityOdomGuess(), _proximityOdomGuess);
|
|
Parameters::parse(parameters, Parameters::kRGBDProximityMergedScanCovFactor(), _proximityMergedScanCovFactor);
|
|
UASSERT(_proximityMergedScanCovFactor>0.0);
|
|
|
|
bool optimizeFromGraphEndPrevious = _optimizeFromGraphEnd;
|
|
Parameters::parse(parameters, Parameters::kRGBDOptimizeFromGraphEnd(), _optimizeFromGraphEnd);
|
|
if(optimizeFromGraphEndPrevious != _optimizeFromGraphEnd && !_optimizedPoses.empty())
|
|
{
|
|
_optimizeFromGraphEndChanged = true;
|
|
}
|
|
Parameters::parse(parameters, Parameters::kRGBDOptimizeMaxError(), _optimizationMaxError);
|
|
Parameters::parse(parameters, Parameters::kRGBDOptimizeMaxErrorRepairRadius(), _optimizationMaxErrorRepairRadius);
|
|
Parameters::parse(parameters, Parameters::kRtabmapStartNewMapOnLoopClosure(), _startNewMapOnLoopClosure);
|
|
Parameters::parse(parameters, Parameters::kRtabmapStartNewMapOnGoodSignature(), _startNewMapOnGoodSignature);
|
|
Parameters::parse(parameters, Parameters::kRGBDGoalReachedRadius(), _goalReachedRadius);
|
|
Parameters::parse(parameters, Parameters::kRGBDGoalsSavedInUserData(), _goalsSavedInUserData);
|
|
Parameters::parse(parameters, Parameters::kRGBDPlanStuckIterations(), _pathStuckIterations);
|
|
Parameters::parse(parameters, Parameters::kRGBDPlanLinearVelocity(), _pathLinearVelocity);
|
|
Parameters::parse(parameters, Parameters::kRGBDPlanAngularVelocity(), _pathAngularVelocity);
|
|
Parameters::parse(parameters, Parameters::kRGBDForceOdom3DoF(), _forceOdom3doF);
|
|
Parameters::parse(parameters, Parameters::kRGBDStartAtOrigin(), _restartAtOrigin);
|
|
Parameters::parse(parameters, Parameters::kRGBDLoopCovLimited(), _loopCovLimited);
|
|
Parameters::parse(parameters, Parameters::kRtabmapLoopGPS(), _loopGPS);
|
|
Parameters::parse(parameters, Parameters::kRGBDMaxOdomCacheSize(), _maxOdomCacheSize);
|
|
Parameters::parse(parameters, Parameters::kRGBDLocalizationSmoothing(), _localizationSmoothing);
|
|
double localizationPriorError = Parameters::defaultRGBDLocalizationPriorError();
|
|
Parameters::parse(parameters, Parameters::kRGBDLocalizationPriorError(), localizationPriorError);
|
|
UASSERT(localizationPriorError>0.0);
|
|
_localizationPriorInf = 1.0/(localizationPriorError*localizationPriorError);
|
|
Parameters::parse(parameters, Parameters::kRGBDLocalizationSecondTryWithoutProximityLinks(), _localizationSecondTryWithoutProximityLinks);
|
|
Parameters::parse(parameters, Parameters::kRGBDProximityGlobalScanMap(), _createGlobalScanMap);
|
|
|
|
Parameters::parse(parameters, Parameters::kMarkerPriorsVarianceLinear(), _markerPriorsLinearVariance);
|
|
UASSERT(_markerPriorsLinearVariance>0.0f);
|
|
Parameters::parse(parameters, Parameters::kMarkerPriorsVarianceAngular(), _markerPriorsAngularVariance);
|
|
UASSERT(_markerPriorsAngularVariance>0.0f);
|
|
std::string markerPriorsStr;
|
|
if(Parameters::parse(parameters, Parameters::kMarkerPriors(), markerPriorsStr))
|
|
{
|
|
_markerPriors.clear();
|
|
std::list<std::string> strList = uSplit(markerPriorsStr, '|');
|
|
for(std::list<std::string>::iterator iter=strList.begin(); iter!=strList.end(); ++iter)
|
|
{
|
|
std::string markerStr = *iter;
|
|
while(!markerStr.empty() && !uIsDigit(markerStr[0]))
|
|
{
|
|
markerStr.erase(markerStr.begin());
|
|
}
|
|
if(!markerStr.empty())
|
|
{
|
|
std::string idStr = uSplitNumChar(markerStr).front();
|
|
int id = uStr2Int(idStr);
|
|
Transform prior = Transform::fromString(markerStr.substr(idStr.size()));
|
|
if(!prior.isNull() && id>0)
|
|
{
|
|
_markerPriors.insert(std::make_pair(-id, prior));
|
|
UDEBUG("Added landmark prior %d: %s", id, prior.prettyPrint().c_str());
|
|
}
|
|
else
|
|
{
|
|
UERROR("Failed to parse element \"%s\" in parameter %s", markerStr.c_str(), Parameters::kMarkerPriors().c_str());
|
|
}
|
|
}
|
|
else if(!iter->empty())
|
|
{
|
|
UERROR("Failed to parse parameter %s, value=\"%s\"", Parameters::kMarkerPriors().c_str(), iter->c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
UASSERT(_rgbdLinearUpdate >= 0.0f);
|
|
UASSERT(_rgbdAngularUpdate >= 0.0f);
|
|
UASSERT(_rgbdLinearSpeedUpdate >= 0.0f);
|
|
UASSERT(_rgbdAngularSpeedUpdate >= 0.0f);
|
|
UASSERT(_maxOdomCacheSize >= 0);
|
|
|
|
// By default, we create our strategies if they are not already created.
|
|
// If they already exists, we check the parameters if a change is requested
|
|
|
|
// Graph optimizer
|
|
Optimizer::Type optimizerType = Optimizer::kTypeUndef;
|
|
if((iter=parameters.find(Parameters::kOptimizerStrategy())) != parameters.end())
|
|
{
|
|
optimizerType = (Optimizer::Type)std::atoi((*iter).second.c_str());
|
|
}
|
|
if(optimizerType!=Optimizer::kTypeUndef)
|
|
{
|
|
UDEBUG("new detector strategy %d", int(optimizerType));
|
|
if(_graphOptimizer)
|
|
{
|
|
delete _graphOptimizer;
|
|
_graphOptimizer = 0;
|
|
}
|
|
|
|
_graphOptimizer = Optimizer::create(optimizerType, _parameters);
|
|
}
|
|
else if(_graphOptimizer)
|
|
{
|
|
_graphOptimizer->parseParameters(parameters);
|
|
}
|
|
else
|
|
{
|
|
optimizerType = (Optimizer::Type)Parameters::defaultOptimizerStrategy();
|
|
_graphOptimizer = Optimizer::create(optimizerType, parameters);
|
|
}
|
|
|
|
if(!_createGlobalScanMap)
|
|
{
|
|
_globalScanMap.clear();
|
|
_globalScanMapPoses.clear();
|
|
}
|
|
|
|
if(_memory)
|
|
{
|
|
bool isMemIncremental = _memory->isIncremental();
|
|
if(Parameters::parse(parameters, Parameters::kMemIncrementalMemory(), isMemIncremental) &&
|
|
isMemIncremental != _memory->isIncremental())
|
|
{
|
|
// Mode has changed from Mapping to Localization, cleanup the local graph
|
|
if(_memory->isIncremental())
|
|
{
|
|
if(_memory->isGraphReduced())
|
|
{
|
|
// Force reducing graph, then remove filtered nodes from the optimized poses
|
|
std::map<int, int> reducedIds;
|
|
_memory->incrementMapId(&reducedIds);
|
|
for(std::map<int, int>::iterator iter=reducedIds.begin(); iter!=reducedIds.end(); ++iter)
|
|
{
|
|
_optimizedPoses.erase(iter->first);
|
|
}
|
|
}
|
|
_odomCachePoses.clear();
|
|
_odomCacheConstraints.clear();
|
|
}
|
|
|
|
// In both cases, we save the latest optimized graph and latest localization pose
|
|
_memory->saveOptimizedPoses(_optimizedPoses, _lastLocalizationPose);
|
|
}
|
|
|
|
_memory->parseParameters(parameters);
|
|
if(_memory->isIncremental() && !_globalScanMap.empty())
|
|
{
|
|
UWARN("Map is now incremental, clearing global scan map...");
|
|
_globalScanMap.clear();
|
|
_globalScanMapPoses.clear();
|
|
}
|
|
|
|
if(_createGlobalScanMap && !_memory->isIncremental() && _globalScanMap.empty() && !_optimizedPoses.empty())
|
|
{
|
|
this->createGlobalScanMap();
|
|
}
|
|
}
|
|
|
|
if(!_epipolarGeometry)
|
|
{
|
|
_epipolarGeometry = new EpipolarGeometry(_parameters);
|
|
}
|
|
else
|
|
{
|
|
_epipolarGeometry->parseParameters(parameters);
|
|
}
|
|
|
|
// Bayes filter, create one if not exists
|
|
if(!_bayesFilter)
|
|
{
|
|
_bayesFilter = new BayesFilter(_parameters);
|
|
}
|
|
else
|
|
{
|
|
_bayesFilter->parseParameters(parameters);
|
|
}
|
|
}
|
|
|
|
int Rtabmap::getLastLocationId() const
|
|
{
|
|
int id = 0;
|
|
if(_memory)
|
|
{
|
|
id = _memory->getLastSignatureId();
|
|
}
|
|
return id;
|
|
}
|
|
|
|
std::list<int> Rtabmap::getWM() const
|
|
{
|
|
std::list<int> mem;
|
|
if(_memory)
|
|
{
|
|
mem = uKeysList(_memory->getWorkingMem());
|
|
mem.remove(-1);// Ignore the virtual signature (if here)
|
|
}
|
|
return mem;
|
|
}
|
|
|
|
int Rtabmap::getWMSize() const
|
|
{
|
|
if(_memory)
|
|
{
|
|
return (int)_memory->getWorkingMem().size()-1; // remove virtual place
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
std::map<int, int> Rtabmap::getWeights() const
|
|
{
|
|
std::map<int, int> weights;
|
|
if(_memory)
|
|
{
|
|
weights = _memory->getWeights();
|
|
weights.erase(-1);// Ignore the virtual signature (if here)
|
|
}
|
|
return weights;
|
|
}
|
|
|
|
std::set<int> Rtabmap::getSTM() const
|
|
{
|
|
if(_memory)
|
|
{
|
|
return _memory->getStMem();
|
|
}
|
|
return std::set<int>();
|
|
}
|
|
|
|
int Rtabmap::getSTMSize() const
|
|
{
|
|
if(_memory)
|
|
{
|
|
return (int)_memory->getStMem().size();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int Rtabmap::getTotalMemSize() const
|
|
{
|
|
if(_memory)
|
|
{
|
|
const Signature * s =_memory->getLastWorkingSignature(false);
|
|
if(s)
|
|
{
|
|
return s->id();
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool Rtabmap::isInSTM(int locationId) const
|
|
{
|
|
if(_memory)
|
|
{
|
|
return _memory->isInSTM(locationId);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Rtabmap::isIDsGenerated() const
|
|
{
|
|
if(_memory)
|
|
{
|
|
return _memory->isIDsGenerated();
|
|
}
|
|
return Parameters::defaultMemGenerateIds();
|
|
}
|
|
|
|
const Statistics & Rtabmap::getStatistics() const
|
|
{
|
|
return statistics_;
|
|
}
|
|
|
|
Transform Rtabmap::getPose(int locationId) const
|
|
{
|
|
return uValue(_optimizedPoses, locationId, Transform());
|
|
}
|
|
|
|
void Rtabmap::setInitialPose(const Transform & initialPose)
|
|
{
|
|
if(_memory)
|
|
{
|
|
if(!_memory->isIncremental())
|
|
{
|
|
_lastLocalizationPose = initialPose;
|
|
_localizationCovariance = cv::Mat();
|
|
_lastLocalizationNodeId = 0;
|
|
_odomCachePoses.clear();
|
|
_odomCacheConstraints.clear();
|
|
_mapCorrection.setIdentity();
|
|
_mapCorrectionBackup.setNull();
|
|
|
|
if(_memory->getLastWorkingSignature(true)->id() &&
|
|
_optimizedPoses.empty())
|
|
{
|
|
cv::Mat covariance;
|
|
this->optimizeCurrentMap(_memory->getLastWorkingSignature(true)->id(), false, _optimizedPoses, covariance, &_constraints);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UWARN("Initial pose can only be set in localization mode (%s=false), ignoring it...", Parameters::kMemIncrementalMemory().c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
int Rtabmap::triggerNewMap()
|
|
{
|
|
int mapId = -1;
|
|
if(_memory)
|
|
{
|
|
_localizationCovariance = cv::Mat();
|
|
_lastLocalizationNodeId = 0;
|
|
_odomCachePoses.clear();
|
|
_odomCacheConstraints.clear();
|
|
_distanceTravelled = 0.0f;
|
|
_distanceTravelledSinceLastLocalization = 0.0f;
|
|
|
|
if(!_memory->isIncremental())
|
|
{
|
|
_mapCorrection.setIdentity();
|
|
if(_restartAtOrigin)
|
|
{
|
|
_lastLocalizationPose.setIdentity();
|
|
}
|
|
return mapId;
|
|
}
|
|
std::map<int, int> reducedIds;
|
|
mapId = _memory->incrementMapId(&reducedIds);
|
|
UINFO("New map triggered, new map = %d", mapId);
|
|
_optimizedPoses.clear();
|
|
_constraints.clear();
|
|
_lastRejectedLoopClosureIds = std::make_pair(0,0);
|
|
|
|
if(_bayesFilter)
|
|
{
|
|
_bayesFilter->reset();
|
|
}
|
|
|
|
//Verify if there are nodes that were merged through graph reduction
|
|
if(reducedIds.size() && _path.size())
|
|
{
|
|
for(unsigned int i=0; i<_path.size(); ++i)
|
|
{
|
|
std::map<int, int>::const_iterator iter = reducedIds.find(_path[i].first);
|
|
if(iter!= reducedIds.end())
|
|
{
|
|
// change path ID to loop closure ID
|
|
_path[i].first = iter->second;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return mapId;
|
|
}
|
|
|
|
bool Rtabmap::labelLocation(int id, const std::string & label)
|
|
{
|
|
if(_memory)
|
|
{
|
|
if(id > 0)
|
|
{
|
|
return _memory->labelSignature(id, label);
|
|
}
|
|
else if(_memory->isIncremental() && _memory->getLastWorkingSignature(true))
|
|
{
|
|
return _memory->labelSignature(_memory->getLastWorkingSignature(true)->id(), label);
|
|
}
|
|
else if(!_memory->isIncremental() && !_lastLocalizationPose.isNull() && !_lastLocalizationPose.isIdentity())
|
|
{
|
|
std::map<int, Transform> nearestNodes = getNodesInRadius(_lastLocalizationPose, _localRadius, 1);
|
|
if(!nearestNodes.empty())
|
|
{
|
|
return _memory->labelSignature(nearestNodes.begin()->first, label);
|
|
}
|
|
else
|
|
{
|
|
UERROR("No nodes found inside %s=%fm of the current pose (%s). Cannot set label \"%s\"",
|
|
Parameters::kRGBDLocalRadius().c_str(),
|
|
_localRadius,
|
|
_lastLocalizationPose.prettyPrint().c_str(),
|
|
label.c_str());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UERROR("Last signature is null! Cannot set label \"%s\"", label.c_str());
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Rtabmap::setUserData(int id, const cv::Mat & data)
|
|
{
|
|
if(_memory)
|
|
{
|
|
if(id > 0)
|
|
{
|
|
return _memory->setUserData(id, data);
|
|
}
|
|
else if(_memory->getLastWorkingSignature(true))
|
|
{
|
|
return _memory->setUserData(_memory->getLastWorkingSignature(true)->id(), data);
|
|
}
|
|
else
|
|
{
|
|
UERROR("Last signature is null! Cannot set user data!");
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Rtabmap::generateDOTGraph(const std::string & path, int id, int margin)
|
|
{
|
|
if(_memory)
|
|
{
|
|
_memory->joinTrashThread(); // make sure the trash is flushed
|
|
|
|
if(id > 0)
|
|
{
|
|
std::map<int, int> ids = _memory->getNeighborsId(id, margin, -1, false);
|
|
|
|
if(ids.size() > 0)
|
|
{
|
|
ids.insert(std::pair<int,int>(id, 0));
|
|
std::set<int> idsSet;
|
|
for(std::map<int, int>::iterator iter = ids.begin(); iter!=ids.end(); ++iter)
|
|
{
|
|
idsSet.insert(idsSet.end(), iter->first);
|
|
}
|
|
_memory->generateGraph(path, idsSet);
|
|
}
|
|
else
|
|
{
|
|
UERROR("No neighbors found for signature %d.", id);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_memory->generateGraph(path);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Rtabmap::exportPoses(const std::string & path, bool optimized, bool global, int format)
|
|
{
|
|
if(_memory && _memory->getLastWorkingSignature(!global))
|
|
{
|
|
std::map<int, Transform> poses;
|
|
std::multimap<int, Link> constraints;
|
|
|
|
if(optimized)
|
|
{
|
|
cv::Mat covariance;
|
|
this->optimizeCurrentMap(_memory->getLastWorkingSignature(!global)->id(), global, poses, covariance, &constraints);
|
|
}
|
|
else
|
|
{
|
|
std::map<int, int> ids = _memory->getNeighborsId(_memory->getLastWorkingSignature(!global)->id(), 0, global?-1:0, true);
|
|
_memory->getMetricConstraints(uKeysSet(ids), poses, constraints, global);
|
|
}
|
|
|
|
std::map<int, double> stamps;
|
|
if(format == 1 || format == 10 || format == 11)
|
|
{
|
|
for(std::map<int, Transform>::iterator iter=poses.begin(); iter!=poses.end(); ++iter)
|
|
{
|
|
Transform o,g;
|
|
int m, w;
|
|
std::string l;
|
|
double stamp = 0.0;
|
|
std::vector<float> v;
|
|
GPS gps;
|
|
EnvSensors sensors;
|
|
_memory->getNodeInfo(iter->first, o, m, w, l, stamp, g, v, gps, sensors, true);
|
|
stamps.insert(std::make_pair(iter->first, stamp));
|
|
}
|
|
}
|
|
|
|
graph::exportPoses(path, format, poses, constraints, stamps, _parameters);
|
|
}
|
|
}
|
|
|
|
void Rtabmap::resetMemory()
|
|
{
|
|
UDEBUG("");
|
|
_highestHypothesis = std::make_pair(0,0.0f);
|
|
_loopClosureHypothesis = std::make_pair(0,0.0f);
|
|
_lastProcessTime = 0.0;
|
|
_someNodesHaveBeenTransferred = false;
|
|
_optimizedPoses.clear();
|
|
_constraints.clear();
|
|
_mapCorrection.setIdentity();
|
|
_mapCorrectionBackup.setNull();
|
|
_lastLocalizationPose.setNull();
|
|
_localizationCovariance = cv::Mat();
|
|
_lastLocalizationNodeId = 0;
|
|
_odomCachePoses.clear();
|
|
_odomCacheConstraints.clear();
|
|
_distanceTravelled = 0.0f;
|
|
_distanceTravelledSinceLastLocalization = 0.0f;
|
|
_optimizeFromGraphEndChanged = false;
|
|
_globalScanMap.clear();
|
|
_globalScanMapPoses.clear();
|
|
_nodesToRepublish.clear();
|
|
_lastRejectedLoopClosureIds = std::make_pair(0,0);
|
|
this->clearPath(0);
|
|
|
|
if(_memory)
|
|
{
|
|
if(_memory->isReadOnly())
|
|
{
|
|
UWARN("Memory is reset but the database won't be cleared because read-only mode is enabled.");
|
|
}
|
|
_memory->init(_databasePath, true, _parameters, true);
|
|
if(_memory->getLastWorkingSignature(true))
|
|
{
|
|
cv::Mat covariance;
|
|
optimizeCurrentMap(_memory->getLastWorkingSignature(true)->id(), false, _optimizedPoses, covariance, &_constraints);
|
|
}
|
|
if(_bayesFilter)
|
|
{
|
|
_bayesFilter->reset();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UERROR("RTAB-Map is not initialized. No memory to reset...");
|
|
}
|
|
|
|
if(_graphOptimizer)
|
|
{
|
|
delete _graphOptimizer;
|
|
_graphOptimizer = Optimizer::create(_parameters);
|
|
}
|
|
|
|
this->setupLogFiles(true);
|
|
}
|
|
|
|
class NearestPathKey
|
|
{
|
|
public:
|
|
NearestPathKey(float l, int i, float d) :
|
|
likelihood(l),
|
|
id(i),
|
|
distance(d){}
|
|
bool operator<(const NearestPathKey & k) const
|
|
{
|
|
if(likelihood < k.likelihood)
|
|
{
|
|
return true;
|
|
}
|
|
else if(likelihood == k.likelihood)
|
|
{
|
|
if(distance > k.distance)
|
|
{
|
|
return true;
|
|
}
|
|
else if(distance == k.distance && id < k.id)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
float likelihood;
|
|
int id;
|
|
float distance;
|
|
};
|
|
|
|
//============================================================
|
|
// MAIN LOOP
|
|
//============================================================
|
|
bool Rtabmap::process(
|
|
const cv::Mat & image,
|
|
int id,
|
|
const std::map<std::string, float> & externalStats)
|
|
{
|
|
return this->process(SensorData(image, id), Transform());
|
|
}
|
|
bool Rtabmap::process(
|
|
const SensorData & data,
|
|
Transform odomPose,
|
|
float odomLinearVariance,
|
|
float odomAngularVariance,
|
|
const std::vector<float> & odomVelocity,
|
|
const std::map<std::string, float> & externalStats)
|
|
{
|
|
if(!odomPose.isNull())
|
|
{
|
|
UASSERT(odomLinearVariance>0.0f);
|
|
UASSERT(odomAngularVariance>0.0f);
|
|
}
|
|
cv::Mat covariance = cv::Mat::eye(6,6,CV_64FC1);
|
|
covariance.at<double>(0,0) = odomLinearVariance;
|
|
covariance.at<double>(1,1) = odomLinearVariance;
|
|
covariance.at<double>(2,2) = odomLinearVariance;
|
|
covariance.at<double>(3,3) = odomAngularVariance;
|
|
covariance.at<double>(4,4) = odomAngularVariance;
|
|
covariance.at<double>(5,5) = odomAngularVariance;
|
|
return process(data, odomPose, covariance, odomVelocity, externalStats);
|
|
}
|
|
bool Rtabmap::process(
|
|
const SensorData & data,
|
|
Transform odomPose,
|
|
const cv::Mat & odomCovariance,
|
|
const std::vector<float> & odomVelocity,
|
|
const std::map<std::string, float> & externalStats)
|
|
{
|
|
UDEBUG("");
|
|
|
|
//============================================================
|
|
// Initialization
|
|
//============================================================
|
|
UTimer timer;
|
|
UTimer timerTotal;
|
|
double timeMemoryUpdate = 0;
|
|
double timeNeighborLinkRefining = 0;
|
|
double timeProximityByTimeDetection = 0;
|
|
double timeProximityBySpaceSearch = 0;
|
|
double timeProximityBySpaceVisualDetection = 0;
|
|
double timeProximityBySpaceDetection = 0;
|
|
double timeCleaningNeighbors = 0;
|
|
double timeReactivations = 0;
|
|
double timeAddLoopClosureLink = 0;
|
|
double timeMapOptimization = 0;
|
|
double timeRetrievalDbAccess = 0;
|
|
double timeLikelihoodCalculation = 0;
|
|
double timePosteriorCalculation = 0;
|
|
double timeHypothesesCreation = 0;
|
|
double timeHypothesesValidation = 0;
|
|
double timeRealTimeLimitReachedProcess = 0;
|
|
double timeMemoryCleanup = 0;
|
|
double timeEmptyingTrash = 0;
|
|
double timeFinalizingStatistics = 0;
|
|
double timeJoiningTrash = 0;
|
|
double timeStatsCreation = 0;
|
|
|
|
float hypothesisRatio = 0.0f; // Only used for statistics
|
|
bool rejectedLoopClosure = false;
|
|
|
|
std::map<int, float> rawLikelihood;
|
|
std::map<int, float> adjustedLikelihood;
|
|
std::map<int, float> likelihood;
|
|
std::map<int, int> weights;
|
|
std::map<int, float> posterior;
|
|
std::list<std::pair<int, float> > reactivateHypotheses;
|
|
|
|
std::map<int, int> childCount;
|
|
std::set<int> signaturesRetrieved;
|
|
int proximityDetectionsInTimeFound = 0;
|
|
|
|
const Signature * signature = 0;
|
|
const Signature * sLoop = 0;
|
|
|
|
_loopClosureHypothesis = std::make_pair(0,0.0f);
|
|
std::pair<int, float> lastHighestHypothesis = _highestHypothesis;
|
|
_highestHypothesis = std::make_pair(0,0.0f);
|
|
|
|
std::set<int> immunizedLocations;
|
|
|
|
statistics_ = Statistics(); // reset
|
|
for(std::map<std::string, float>::const_iterator iter=externalStats.begin(); iter!=externalStats.end(); ++iter)
|
|
{
|
|
statistics_.addStatistic(iter->first, iter->second);
|
|
}
|
|
|
|
//============================================================
|
|
// Wait for an image...
|
|
//============================================================
|
|
ULOGGER_INFO("getting data...");
|
|
|
|
timer.start();
|
|
timerTotal.start();
|
|
|
|
UASSERT_MSG(_memory, "RTAB-Map is not initialized!");
|
|
UASSERT_MSG(_bayesFilter, "RTAB-Map is not initialized!");
|
|
UASSERT_MSG(_graphOptimizer, "RTAB-Map is not initialized!");
|
|
|
|
//============================================================
|
|
// If RGBD SLAM is enabled, a pose must be set.
|
|
//============================================================
|
|
bool fakeOdom = false;
|
|
if(_rgbdSlamMode)
|
|
{
|
|
if(!odomPose.isNull())
|
|
{
|
|
// If we are doing 2D mapping, make sure the pose is 3DoF so that landmark logic works.
|
|
if(_forceOdom3doF && _graphOptimizer->isSlam2d() && !odomPose.is3DoF())
|
|
{
|
|
odomPose = odomPose.to3DoF();
|
|
}
|
|
|
|
// this will make sure that all inverse operations will work!
|
|
if(!odomPose.isInvertible())
|
|
{
|
|
UWARN("Input odometry is not invertible! pose = %s\n"
|
|
"[%f %f %f %f;\n"
|
|
" %f %f %f %f;\n"
|
|
" %f %f %f %f;\n"
|
|
" 0 0 0 1]\n"
|
|
"Trying to normalize rotation to see if it makes it invertible...",
|
|
odomPose.prettyPrint().c_str(),
|
|
odomPose.r11(), odomPose.r12(), odomPose.r13(), odomPose.o14(),
|
|
odomPose.r21(), odomPose.r22(), odomPose.r23(), odomPose.o24(),
|
|
odomPose.r31(), odomPose.r32(), odomPose.r33(), odomPose.o34());
|
|
odomPose.normalizeRotation();
|
|
UASSERT_MSG(odomPose.isInvertible(), uFormat("Odometry pose is not invertible!\n"
|
|
"[%f %f %f %f;\n"
|
|
" %f %f %f %f;\n"
|
|
" %f %f %f %f;\n"
|
|
" 0 0 0 1]", odomPose.prettyPrint().c_str(),
|
|
odomPose.r11(), odomPose.r12(), odomPose.r13(), odomPose.o14(),
|
|
odomPose.r21(), odomPose.r22(), odomPose.r23(), odomPose.o24(),
|
|
odomPose.r31(), odomPose.r32(), odomPose.r33(), odomPose.o34()).c_str());
|
|
UWARN("Normalizing rotation succeeded! fixed pose = %s\n"
|
|
"[%f %f %f %f;\n"
|
|
" %f %f %f %f;\n"
|
|
" %f %f %f %f;\n"
|
|
" 0 0 0 1]\n"
|
|
"If the resulting rotation is very different from original one, try to fix the odometry or TF.",
|
|
odomPose.prettyPrint().c_str(),
|
|
odomPose.r11(), odomPose.r12(), odomPose.r13(), odomPose.o14(),
|
|
odomPose.r21(), odomPose.r22(), odomPose.r23(), odomPose.o24(),
|
|
odomPose.r31(), odomPose.r32(), odomPose.r33(), odomPose.o34());
|
|
}
|
|
}
|
|
|
|
UDEBUG("incremental=%d odomPose=%s optimizedPoses=%d mapCorrection=%s lastLocalizationPose=%s lastLocalizationNodeId=%d",
|
|
_memory->isIncremental()?1:0,
|
|
odomPose.prettyPrint().c_str(),
|
|
(int)_optimizedPoses.size(),
|
|
_mapCorrection.prettyPrint().c_str(),
|
|
_lastLocalizationPose.prettyPrint().c_str(),
|
|
_lastLocalizationNodeId);
|
|
|
|
if(!_memory->isIncremental() &&
|
|
!odomPose.isNull() &&
|
|
_optimizedPoses.size() &&
|
|
_mapCorrection.isIdentity() &&
|
|
!_lastLocalizationPose.isNull() &&
|
|
_lastLocalizationNodeId == 0)
|
|
{
|
|
// Localization mode
|
|
if(!_optimizeFromGraphEnd)
|
|
{
|
|
//set map->odom so that odom is moved back to last saved localization
|
|
if(_graphOptimizer->isSlam2d())
|
|
{
|
|
_mapCorrection = _lastLocalizationPose.to3DoF() * odomPose.to3DoF().inverse();
|
|
}
|
|
else if((!data.imu().empty() || _memory->isOdomGravityUsed()) && _graphOptimizer->gravitySigma()>0.0f)
|
|
{
|
|
_mapCorrection = _lastLocalizationPose.to4DoF() * odomPose.to4DoF().inverse();
|
|
}
|
|
else
|
|
{
|
|
_mapCorrection = _lastLocalizationPose * odomPose.inverse();
|
|
}
|
|
std::map<int, Transform> nodesOnly(_optimizedPoses.lower_bound(1), _optimizedPoses.end());
|
|
_lastLocalizationNodeId = graph::findNearestNode(nodesOnly, _lastLocalizationPose);
|
|
UWARN("Update map correction based on last localization saved in database! correction = %s, nearest id = %d of last pose = %s, odom = %s",
|
|
_mapCorrection.prettyPrint().c_str(),
|
|
_lastLocalizationNodeId,
|
|
_lastLocalizationPose.prettyPrint().c_str(),
|
|
odomPose.prettyPrint().c_str());
|
|
}
|
|
else
|
|
{
|
|
//move optimized poses accordingly to last saved localization
|
|
Transform mapCorrectionInv;
|
|
if(_graphOptimizer->isSlam2d())
|
|
{
|
|
mapCorrectionInv = odomPose.to3DoF() * _lastLocalizationPose.to3DoF().inverse();
|
|
}
|
|
else if((!data.imu().empty() || _memory->isOdomGravityUsed()) && _graphOptimizer->gravitySigma()>0.0f)
|
|
{
|
|
mapCorrectionInv = odomPose.to4DoF() * _lastLocalizationPose.to4DoF().inverse();
|
|
}
|
|
else
|
|
{
|
|
mapCorrectionInv = odomPose * _lastLocalizationPose.inverse();
|
|
}
|
|
for(std::map<int, Transform>::iterator iter=_optimizedPoses.begin(); iter!=_optimizedPoses.end(); ++iter)
|
|
{
|
|
iter->second = mapCorrectionInv * iter->second;
|
|
}
|
|
|
|
std::map<int, Transform> nodesOnly(_optimizedPoses.lower_bound(1), _optimizedPoses.end());
|
|
_lastLocalizationNodeId = graph::findNearestNode(nodesOnly, _lastLocalizationPose);
|
|
UWARN("Transformed map accordingly to last localization pose saved in database (%s=true)! nearest id = %d of last pose = %s",
|
|
Parameters::kRGBDOptimizeFromGraphEnd().c_str(),
|
|
_lastLocalizationNodeId,
|
|
_lastLocalizationPose.prettyPrint().c_str());
|
|
}
|
|
}
|
|
|
|
if(odomPose.isNull())
|
|
{
|
|
if(_memory->isIncremental())
|
|
{
|
|
UERROR("RGB-D SLAM mode is enabled, memory is incremental but no odometry is provided. "
|
|
"Image %d is ignored!", data.id());
|
|
return false;
|
|
}
|
|
else // fake localization
|
|
{
|
|
if(!_mapCorrectionBackup.isNull())
|
|
{
|
|
_mapCorrection = _mapCorrectionBackup;
|
|
_mapCorrectionBackup.setNull();
|
|
}
|
|
if(_lastLocalizationPose.isNull())
|
|
{
|
|
_lastLocalizationPose = Transform::getIdentity();
|
|
}
|
|
fakeOdom = true;
|
|
odomPose = _mapCorrection.inverse() * _lastLocalizationPose;
|
|
UDEBUG("Map correction = %s", _mapCorrection.prettyPrint().c_str());
|
|
UDEBUG("Last localization pose: %s", _lastLocalizationPose.prettyPrint().c_str());
|
|
UDEBUG("Fake odom: %s", odomPose.prettyPrint().c_str());
|
|
}
|
|
}
|
|
else if(_memory->isIncremental()) // only in mapping mode
|
|
{
|
|
// Detect if the odometry is reset. If yes, trigger a new map.
|
|
if(_memory->getLastWorkingSignature(false))
|
|
{
|
|
const Transform & lastPose = _memory->getLastWorkingSignature(false)->getPose(); // use raw odometry
|
|
|
|
// look for identity
|
|
if(!lastPose.isIdentity() && odomPose.isIdentity())
|
|
{
|
|
int mapId = triggerNewMap();
|
|
UWARN("Odometry is reset (identity pose detected). Increment map id to %d!", mapId);
|
|
}
|
|
else if(_newMapOdomChangeDistance > 0.0)
|
|
{
|
|
// look for large change
|
|
Transform lastPoseToNewPose = lastPose.inverse() * odomPose;
|
|
float x,y,z, roll,pitch,yaw;
|
|
lastPoseToNewPose.getTranslationAndEulerAngles(x,y,z, roll,pitch,yaw);
|
|
if((x*x + y*y + z*z) > _newMapOdomChangeDistance*_newMapOdomChangeDistance)
|
|
{
|
|
int mapId = triggerNewMap();
|
|
UWARN("Odometry is reset (large odometry change detected > %f). A new map (%d) is created! Last pose = %s, new pose = %s",
|
|
_newMapOdomChangeDistance,
|
|
mapId,
|
|
lastPose.prettyPrint().c_str(),
|
|
odomPose.prettyPrint().c_str());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//============================================================
|
|
// Memory Update : Location creation + Add to STM + Weight Update (Rehearsal)
|
|
//============================================================
|
|
ULOGGER_INFO("Updating memory...");
|
|
if(_rgbdSlamMode)
|
|
{
|
|
if(!_memory->update(data, odomPose, odomCovariance, odomVelocity, &statistics_))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(!_memory->update(data, Transform(), cv::Mat(), std::vector<float>(), &statistics_))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
signature = _memory->getLastWorkingSignature(false);
|
|
_currentSessionHasGPS = _currentSessionHasGPS || signature->sensorData().gps().stamp() > 0.0;
|
|
if(!signature)
|
|
{
|
|
UFATAL("Not supposed to be here...last signature is null?!?");
|
|
}
|
|
|
|
ULOGGER_INFO("Processing signature %d (%f) w=%d map=%d", signature->id(), signature->getStamp(), signature->getWeight(), signature->mapId());
|
|
timeMemoryUpdate = timer.ticks();
|
|
ULOGGER_INFO("timeMemoryUpdate=%fs", timeMemoryUpdate);
|
|
|
|
//============================================================
|
|
// Metric
|
|
//============================================================
|
|
bool smallDisplacement = false;
|
|
bool tooFastMovement = false;
|
|
std::list<int> signaturesRemoved;
|
|
bool neighborLinkRefined = false;
|
|
bool addedNewLandmark = false;
|
|
float distanceToClosestNodeInTheGraph = 0;
|
|
float angleToClosestNodeInTheGraph = 0;
|
|
if(_rgbdSlamMode)
|
|
{
|
|
double linVar = odomCovariance.empty()?0.0f:uMax3(odomCovariance.at<double>(0,0), odomCovariance.at<double>(1,1)>=9999?0:odomCovariance.at<double>(1,1), odomCovariance.at<double>(2,2)>=9999?0:odomCovariance.at<double>(2,2));
|
|
double angVar = odomCovariance.empty()?0.0f:uMax3(odomCovariance.at<double>(3,3)>=9999?0:odomCovariance.at<double>(3,3), odomCovariance.at<double>(4,4)>=9999?0:odomCovariance.at<double>(4,4), odomCovariance.at<double>(5,5));
|
|
statistics_.addStatistic(Statistics::kMemoryOdometry_variance_lin(), (float)linVar);
|
|
statistics_.addStatistic(Statistics::kMemoryOdometry_variance_ang(), (float)angVar);
|
|
|
|
//Verify if there was a rehearsal
|
|
int rehearsedId = (int)uValue(statistics_.data(), Statistics::kMemoryRehearsal_merged(), 0.0f);
|
|
if(rehearsedId > 0)
|
|
{
|
|
_optimizedPoses.erase(rehearsedId);
|
|
}
|
|
else
|
|
{
|
|
bool linkedToIntermediateNode = false;
|
|
Transform t;
|
|
if(_memory->isIncremental())
|
|
{
|
|
// Check small motion if current node is not an intermediate node already
|
|
if(signature->getWeight() >= 0)
|
|
{
|
|
// It should contain only the query and its first (non-intermediate) neighbor (smaller id)
|
|
std::map<int, int> neighbors = _memory->getNeighborsId(signature->id(), 2, 0, true, true, true, true);
|
|
if(neighbors.size() == 2)
|
|
{
|
|
int nid = neighbors.begin()->first;
|
|
const std::multimap<int, Link> & links = signature->getLinks();
|
|
if(links.find(nid) != links.end())
|
|
{
|
|
// direct neighbor
|
|
t = links.find(nid)->second.transform();
|
|
}
|
|
else
|
|
{
|
|
// Use optimized poses to check how far it is from the latest non-intermediate node
|
|
std::map<int, Transform>::iterator niter = _optimizedPoses.find(nid);
|
|
if(niter != _optimizedPoses.end())
|
|
{
|
|
t = niter->second.inverse() * _mapCorrection * signature->getPose();
|
|
}
|
|
// not direct link, it means there are intermediate nodes
|
|
linkedToIntermediateNode = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if(!_odomCachePoses.empty())
|
|
{
|
|
t = _odomCachePoses.rbegin()->second.inverse() * signature->getPose();
|
|
}
|
|
|
|
if(_rgbdLinearUpdate > 0.0f || _rgbdAngularUpdate > 0.0f)
|
|
{
|
|
//============================================================
|
|
// Minimum displacement required to add to Memory
|
|
//============================================================
|
|
if(!t.isNull())
|
|
{
|
|
float x,y,z, roll,pitch,yaw;
|
|
t.getTranslationAndEulerAngles(x,y,z, roll,pitch,yaw);
|
|
bool isMoving = (_rgbdLinearUpdate > 0.0f && (
|
|
fabs(x) > _rgbdLinearUpdate ||
|
|
fabs(y) > _rgbdLinearUpdate ||
|
|
fabs(z) > _rgbdLinearUpdate)) ||
|
|
(_rgbdAngularUpdate>0.0f && (
|
|
fabs(roll) > _rgbdAngularUpdate ||
|
|
fabs(pitch) > _rgbdAngularUpdate ||
|
|
fabs(yaw) > _rgbdAngularUpdate));
|
|
if(!isMoving)
|
|
{
|
|
// This will disable global loop closure detection, only retrieval will be done.
|
|
// The location will also be deleted at the end.
|
|
smallDisplacement = true;
|
|
UDEBUG("smallDisplacement: %f %f %f %f %f %f", x,y,z, roll,pitch,yaw);
|
|
}
|
|
}
|
|
}
|
|
if(odomVelocity.size() == 6 && signature->getWeight() != -1)
|
|
{
|
|
// This will disable global loop closure detection, only retrieval will be done.
|
|
// The location will also be deleted at the end.
|
|
tooFastMovement =
|
|
(_rgbdLinearSpeedUpdate>0.0f && uMax3(fabs(odomVelocity[0]), fabs(odomVelocity[1]), fabs(odomVelocity[2])) > _rgbdLinearSpeedUpdate) ||
|
|
(_rgbdAngularSpeedUpdate>0.0f && uMax3(fabs(odomVelocity[3]), fabs(odomVelocity[4]), fabs(odomVelocity[5])) > _rgbdAngularSpeedUpdate);
|
|
}
|
|
if(linkedToIntermediateNode && (smallDisplacement || tooFastMovement))
|
|
{
|
|
_memory->convertToIntermediate(signature->id());
|
|
}
|
|
}
|
|
|
|
// Update optimizedPoses with the newly added node
|
|
Transform newPose;
|
|
bool intermediateNodeRefining = false;
|
|
if(_neighborLinkRefining &&
|
|
signature->getLinks().size() &&
|
|
signature->getLinks().begin()->second.type() == Link::kNeighbor &&
|
|
_memory->isIncremental() && // ignore pose matching in localization mode
|
|
rehearsedId == 0 && // don't do it if rehearsal happened
|
|
!tooFastMovement) // ignore if too fast movement has been detected
|
|
{
|
|
int oldId = signature->getLinks().begin()->first;
|
|
const Signature * oldS = _memory->getSignature(oldId);
|
|
UASSERT(oldS != 0);
|
|
|
|
if(signature->getWeight() >= 0 && oldS->getWeight()>=0) // ignore intermediate nodes
|
|
{
|
|
Transform guess = signature->getLinks().begin()->second.transform().inverse();
|
|
|
|
if(smallDisplacement)
|
|
{
|
|
if(signature->getLinks().begin()->second.transVariance() == 1)
|
|
{
|
|
// set small variance
|
|
UDEBUG("Set small variance. The robot is not moving.");
|
|
_memory->updateLink(Link(oldId, signature->id(), signature->getLinks().begin()->second.type(), guess, cv::Mat::eye(6,6,CV_64FC1)*1000));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//============================================================
|
|
// Refine neighbor links
|
|
//============================================================
|
|
UINFO("Odometry refining: guess = %s", guess.prettyPrint().c_str());
|
|
RegistrationInfo info;
|
|
Transform t = _memory->computeTransform(oldId, signature->id(), guess, &info);
|
|
if(!t.isNull())
|
|
{
|
|
UASSERT(!info.covariance.empty() && info.covariance.at<double>(0,0) > 0.0 && info.covariance.at<double>(5,5) > 0.0);
|
|
UINFO("Odometry refining: update neighbor link (%d->%d, variance:lin=%f, ang=%f) from %s to %s",
|
|
oldId,
|
|
signature->id(),
|
|
info.covariance.at<double>(0,0),
|
|
info.covariance.at<double>(5,5),
|
|
guess.prettyPrint().c_str(),
|
|
t.prettyPrint().c_str());
|
|
_memory->updateLink(Link(oldId, signature->id(), signature->getLinks().begin()->second.type(), t, info.covariance.inv()));
|
|
|
|
if(_optimizeFromGraphEnd)
|
|
{
|
|
// update all previous nodes
|
|
// Normally _mapCorrection should be identity, but if _optimizeFromGraphEnd
|
|
// parameters just changed state, we should put back all poses without map correction.
|
|
Transform u = guess * t.inverse();
|
|
std::map<int, Transform>::iterator jter = _optimizedPoses.find(oldId);
|
|
UASSERT(jter!=_optimizedPoses.end());
|
|
Transform up = jter->second * u * jter->second.inverse();
|
|
Transform mapCorrectionInv = _mapCorrection.inverse();
|
|
for(std::map<int, Transform>::iterator iter=_optimizedPoses.begin(); iter!=_optimizedPoses.end(); ++iter)
|
|
{
|
|
iter->second = mapCorrectionInv * up * iter->second;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UINFO("Odometry refining rejected: %s", info.rejectedMsg.c_str());
|
|
if(!info.covariance.empty() && info.covariance.at<double>(0,0) > 0.0 && info.covariance.at<double>(0,0) != 1.0 && info.covariance.at<double>(5,5) > 0.0 && info.covariance.at<double>(5,5) != 1.0)
|
|
{
|
|
if(ULogger::level() <= ULogger::kInfo)
|
|
{
|
|
std::cout << info.covariance << std::endl;
|
|
}
|
|
_memory->updateLink(Link(oldId, signature->id(), signature->getLinks().begin()->second.type(), guess, (info.covariance*100.0).inv()));
|
|
}
|
|
}
|
|
neighborLinkRefined = !t.isNull();
|
|
statistics_.addStatistic(Statistics::kNeighborLinkRefiningAccepted(),neighborLinkRefined?1.0f:0);
|
|
statistics_.addStatistic(Statistics::kNeighborLinkRefiningInliers(), info.inliers);
|
|
statistics_.addStatistic(Statistics::kNeighborLinkRefiningICP_inliers_ratio(), info.icpInliersRatio);
|
|
statistics_.addStatistic(Statistics::kNeighborLinkRefiningICP_rotation(), info.icpRotation);
|
|
statistics_.addStatistic(Statistics::kNeighborLinkRefiningICP_translation(), info.icpTranslation);
|
|
statistics_.addStatistic(Statistics::kNeighborLinkRefiningICP_complexity(), info.icpStructuralComplexity);
|
|
statistics_.addStatistic(Statistics::kNeighborLinkRefiningPts(), signature->sensorData().laserScanRaw().size());
|
|
}
|
|
timeNeighborLinkRefining = timer.ticks();
|
|
ULOGGER_INFO("timeOdometryRefining=%fs", timeNeighborLinkRefining);
|
|
|
|
UASSERT(oldS->hasLink(signature->id()));
|
|
UASSERT(uContains(_optimizedPoses, oldId));
|
|
|
|
statistics_.addStatistic(Statistics::kNeighborLinkRefiningVariance(), oldS->getLinks().find(signature->id())->second.transVariance());
|
|
|
|
newPose = _optimizedPoses.at(oldId) * oldS->getLinks().find(signature->id())->second.transform();
|
|
_mapCorrection = newPose * signature->getPose().inverse();
|
|
if(_mapCorrection.getNormSquared() > 0.001f && _optimizeFromGraphEnd)
|
|
{
|
|
UERROR("Map correction should be identity when optimizing from the last node. T=%s NewPose=%s OldPose=%s",
|
|
_mapCorrection.prettyPrint().c_str(),
|
|
newPose.prettyPrint().c_str(),
|
|
signature->getPose().prettyPrint().c_str());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
newPose = _mapCorrection * signature->getPose();
|
|
intermediateNodeRefining = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
newPose = _mapCorrection * signature->getPose();
|
|
}
|
|
|
|
// Get statistics about the closest node in the graph
|
|
if(!_memory->isIncremental())
|
|
{
|
|
int closestNode = 0;
|
|
float sqrdDistance = 0.0f;
|
|
if(_optimizedPoses.begin()->first < 0)
|
|
{
|
|
std::map<int, Transform> poses(_optimizedPoses.lower_bound(1), _optimizedPoses.end());
|
|
closestNode = graph::findNearestNode(poses, newPose, &sqrdDistance);
|
|
}
|
|
else
|
|
{
|
|
closestNode = graph::findNearestNode(_optimizedPoses, newPose, &sqrdDistance);
|
|
}
|
|
if(closestNode>0 && sqrdDistance>0.0f)
|
|
{
|
|
distanceToClosestNodeInTheGraph = sqrt(sqrdDistance);
|
|
UDEBUG("Last localization pose = %s, closest node=%d (%f m)", newPose.prettyPrint().c_str(), closestNode, distanceToClosestNodeInTheGraph);
|
|
angleToClosestNodeInTheGraph = newPose.getAngle(_optimizedPoses.at(closestNode));
|
|
}
|
|
}
|
|
|
|
UDEBUG("Added pose %s (odom=%s)", newPose.prettyPrint().c_str(), signature->getPose().prettyPrint().c_str());
|
|
// Update Poses and Constraints
|
|
_optimizedPoses.insert(std::make_pair(signature->id(), newPose));
|
|
if(_memory->isIncremental() && signature->getWeight() >= 0)
|
|
{
|
|
for(std::map<int, Link>::const_iterator iter = signature->getLandmarks().begin(); iter!=signature->getLandmarks().end(); ++iter)
|
|
{
|
|
if(_optimizedPoses.find(iter->first) == _optimizedPoses.end())
|
|
{
|
|
_optimizedPoses.insert(std::make_pair(iter->first, newPose*iter->second.transform()));
|
|
UDEBUG("Added landmark %d : %s", iter->first, (newPose*iter->second.transform()).prettyPrint().c_str());
|
|
addedNewLandmark = true;
|
|
}
|
|
_constraints.insert(std::make_pair(iter->first, iter->second.inverse()));
|
|
}
|
|
}
|
|
|
|
float distanceTravelledOld = _distanceTravelled;
|
|
|
|
// only in mapping mode we add a neighbor link
|
|
if(signature->getLinks().size() &&
|
|
signature->getLinks().begin()->second.type() == Link::kNeighbor)
|
|
{
|
|
// link should be old to new
|
|
UASSERT_MSG(signature->id() > signature->getLinks().begin()->second.to(),
|
|
"Only forward links should be added.");
|
|
|
|
Link tmp = signature->getLinks().begin()->second.inverse();
|
|
|
|
if(!smallDisplacement)
|
|
{
|
|
_distanceTravelled += tmp.transform().getNorm();
|
|
}
|
|
|
|
// if the previous node is an intermediate node, remove it from the local graph
|
|
if(_constraints.size() &&
|
|
_constraints.rbegin()->second.to() == signature->getLinks().begin()->second.to())
|
|
{
|
|
const Signature * s = _memory->getSignature(signature->getLinks().begin()->second.to());
|
|
UASSERT(s!=0);
|
|
if(s->getWeight() == -1)
|
|
{
|
|
tmp = _constraints.rbegin()->second.merge(tmp, tmp.type());
|
|
_optimizedPoses.erase(s->id());
|
|
_constraints.erase(--_constraints.end());
|
|
}
|
|
}
|
|
|
|
_constraints.insert(std::make_pair(tmp.from(), tmp));
|
|
}
|
|
// Localization mode stuff
|
|
if( signature->getWeight() >= 0 &&
|
|
!smallDisplacement &&
|
|
odomCovariance.cols == 6 &&
|
|
odomCovariance.rows == 6 &&
|
|
odomCovariance.type() == CV_64FC1 &&
|
|
odomCovariance.at<double>(0,0) < 1)
|
|
{
|
|
if( _memory->isIncremental() && _localizationCovariance.empty())
|
|
{
|
|
_localizationCovariance = cv::Mat::zeros(6,6,CV_64FC1);
|
|
}
|
|
if(_localizationCovariance.total() == 36)
|
|
{
|
|
#ifdef RTABMAP_MRPT
|
|
// Transform odometry covariance (which in base frame) into global frame
|
|
// "odometry error propagation law"
|
|
Eigen::Quaterniond rotation = _lastLocalizationPose.getQuaterniond();
|
|
mrpt::poses::CPose3D pose = mrpt::poses::CPose3D::FromQuaternion(mrpt::math::CQuaternionDouble(rotation.w(), rotation.x(), rotation.y(), rotation.z()));
|
|
mrpt::math::CMatrixDouble66 gaussian;
|
|
gaussian.loadFromRawPointer((const double*)odomCovariance.data);
|
|
mrpt::poses::CPose3DPDFGaussian gaussianTransformed(mrpt::poses::CPose3D(), gaussian);
|
|
gaussianTransformed.changeCoordinatesReference(pose);
|
|
_localizationCovariance += cv::Mat(6,6,CV_64FC1, gaussianTransformed.cov.data());
|
|
#else
|
|
// Assuming diagonal uniform covariance matrix!
|
|
// If variance is different for each axis,
|
|
// build rtabmap with MRPT to use approach above.
|
|
_localizationCovariance += odomCovariance;
|
|
#endif
|
|
}
|
|
}
|
|
_lastLocalizationPose = newPose; // keep in cache the latest corrected pose
|
|
if(signature->getWeight() >= 0)
|
|
{
|
|
UDEBUG("Update odometry localization cache (size=%d/%d)", (int)_odomCachePoses.size(), _maxOdomCacheSize);
|
|
if(!_odomCachePoses.empty())
|
|
{
|
|
float odomDistance = (_odomCachePoses.rbegin()->second.inverse() * signature->getPose()).getNorm();
|
|
if(!smallDisplacement)
|
|
{
|
|
_distanceTravelled += odomDistance;
|
|
}
|
|
|
|
while(!_odomCachePoses.empty() && (int)_odomCachePoses.size() > _maxOdomCacheSize)
|
|
{
|
|
_odomCacheConstraints.erase(_odomCachePoses.begin()->first);
|
|
_odomCachePoses.erase(_odomCachePoses.begin());
|
|
}
|
|
if(!_odomCachePoses.empty())
|
|
{
|
|
Link odomLink(_odomCachePoses.rbegin()->first,
|
|
signature->id(),
|
|
Link::kNeighbor,
|
|
_odomCachePoses.rbegin()->second.inverse() * signature->getPose(),
|
|
odomCovariance.inv());
|
|
_odomCacheConstraints.insert(std::make_pair(_odomCachePoses.rbegin()->first, odomLink));
|
|
UDEBUG("Added odom cov = %f %f", odomLink.transVariance(), odomLink.rotVariance());
|
|
}
|
|
}
|
|
|
|
_odomCachePoses.insert(std::make_pair(signature->id(), signature->getPose()));
|
|
}
|
|
_distanceTravelledSinceLastLocalization += _distanceTravelled - distanceTravelledOld;
|
|
|
|
//============================================================
|
|
// Reduced graph
|
|
//============================================================
|
|
//Verify if there are nodes that were merged through graph reduction
|
|
if(statistics_.reducedIds().size())
|
|
{
|
|
for(unsigned int i=0; i<_path.size(); ++i)
|
|
{
|
|
std::map<int, int>::const_iterator iter = statistics_.reducedIds().find(_path[i].first);
|
|
if(iter!= statistics_.reducedIds().end())
|
|
{
|
|
// change path ID to loop closure ID
|
|
_path[i].first = iter->second;
|
|
}
|
|
}
|
|
|
|
for(std::map<int, int>::const_iterator iter=statistics_.reducedIds().begin();
|
|
iter!=statistics_.reducedIds().end();
|
|
++iter)
|
|
{
|
|
int erased = (int)_optimizedPoses.erase(iter->first);
|
|
if(erased)
|
|
{
|
|
for(std::multimap<int, Link>::iterator jter = _constraints.begin(); jter!=_constraints.end();)
|
|
{
|
|
if(jter->second.from() == iter->first || jter->second.to() == iter->first)
|
|
{
|
|
_constraints.erase(jter++);
|
|
}
|
|
else
|
|
{
|
|
++jter;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//============================================================
|
|
// Local loop closure in TIME
|
|
//============================================================
|
|
if((_proximityByTime || intermediateNodeRefining) &&
|
|
rehearsedId == 0 && // don't do it if rehearsal happened
|
|
_memory->isIncremental() && // don't do it in localization mode
|
|
signature->getWeight()>=0)
|
|
{
|
|
const std::set<int> & stm = _memory->getStMem();
|
|
for(std::set<int>::const_reverse_iterator iter = stm.rbegin(); iter!=stm.rend(); ++iter)
|
|
{
|
|
if(*iter != signature->id() &&
|
|
signature->getLinks().find(*iter) == signature->getLinks().end() &&
|
|
_memory->getSignature(*iter)->mapId() == signature->mapId() &&
|
|
_memory->getSignature(*iter)->getWeight()>=0)
|
|
{
|
|
std::string rejectedMsg;
|
|
UDEBUG("Check local transform between %d and %d", signature->id(), *iter);
|
|
RegistrationInfo info;
|
|
Transform guess;
|
|
if(_optimizedPoses.find(*iter) != _optimizedPoses.end())
|
|
{
|
|
guess = _optimizedPoses.at(*iter).inverse() * newPose;
|
|
}
|
|
|
|
// For proximity by time, correspondences should be already enough precise, so don't recompute them
|
|
Transform transform = _memory->computeTransform(*iter, signature->id(), guess, &info, true);
|
|
|
|
if(!transform.isNull())
|
|
{
|
|
transform = transform.inverse();
|
|
UDEBUG("Add local loop closure in TIME (%d->%d) %s",
|
|
signature->id(),
|
|
*iter,
|
|
transform.prettyPrint().c_str());
|
|
// Add a loop constraint
|
|
UASSERT(!info.covariance.empty() && info.covariance.at<double>(0,0) > 0.0 && info.covariance.at<double>(5,5) > 0.0);
|
|
if(_memory->addLink(Link(signature->id(), *iter, Link::kLocalTimeClosure, transform, getInformation(info.covariance))))
|
|
{
|
|
++proximityDetectionsInTimeFound;
|
|
UINFO("Local loop closure found between %d and %d with t=%s",
|
|
*iter, signature->id(), transform.prettyPrint().c_str());
|
|
}
|
|
else
|
|
{
|
|
UWARN("Cannot add local loop closure between %d and %d ?!?",
|
|
*iter, signature->id());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UINFO("Local loop closure (time) between %d and %d rejected: %s",
|
|
*iter, signature->id(), rejectedMsg.c_str());
|
|
}
|
|
|
|
if(!_proximityByTime && intermediateNodeRefining)
|
|
{
|
|
// Do it only with the latest non-intermediate node
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
timeProximityByTimeDetection = timer.ticks();
|
|
UINFO("timeProximityByTimeDetection=%fs", timeProximityByTimeDetection);
|
|
|
|
//============================================================
|
|
// Bayes filter update
|
|
//============================================================
|
|
bool localizationOnPreviousUpdate = false;
|
|
if(_memory->isIncremental())
|
|
{
|
|
localizationOnPreviousUpdate =
|
|
signature->getLinks().size() &&
|
|
signature->getLinks().begin()->first!=signature->id() &&
|
|
_memory->getLoopClosureLinks(signature->getLinks().begin()->first, false).size() != 0;
|
|
}
|
|
else
|
|
{
|
|
// localization mode
|
|
|
|
// Count how many localization links are in the constraints
|
|
int localizationLinks = 0;
|
|
int previousIdWithLocalizationLink = 0;
|
|
for(std::multimap<int, Link>::iterator iter=_odomCacheConstraints.begin();
|
|
iter!=_odomCacheConstraints.end(); ++iter)
|
|
{
|
|
if(previousIdWithLocalizationLink == iter->first)
|
|
{
|
|
// ignore links with node already counted
|
|
continue;
|
|
}
|
|
if(iter->second.type() == Link::kGlobalClosure ||
|
|
iter->second.type() == Link::kLocalSpaceClosure ||
|
|
iter->second.type() == Link::kLocalTimeClosure ||
|
|
iter->second.type() == Link::kUserClosure ||
|
|
iter->second.type() == Link::kNeighborMerged ||
|
|
iter->second.type() == Link::kLandmark)
|
|
{
|
|
++localizationLinks;
|
|
previousIdWithLocalizationLink = iter->first;
|
|
}
|
|
}
|
|
|
|
localizationOnPreviousUpdate = localizationLinks > 1; // need two links in case we have delayed localization
|
|
}
|
|
|
|
// Not a bad signature, not an intermediate node, not a small displacement unless the previous signature didn't have a loop closure, not too fast movement
|
|
if(!signature->isBadSignature() && signature->getWeight()>=0 && (!smallDisplacement || !localizationOnPreviousUpdate) && !tooFastMovement)
|
|
{
|
|
// If the working memory is empty, don't do the detection. It happens when it
|
|
// is the first time the detector is started (there needs some images to
|
|
// fill the short-time memory before a signature is added to the working memory).
|
|
if(_memory->getWorkingMem().size())
|
|
{
|
|
//============================================================
|
|
// Likelihood computation
|
|
// Get the likelihood of the new signature
|
|
// with all images contained in the working memory + reactivated.
|
|
//============================================================
|
|
ULOGGER_INFO("computing likelihood...");
|
|
|
|
std::list<int> signaturesToCompare;
|
|
GPS originGPS;
|
|
Transform originOffsetENU = Transform::getIdentity();
|
|
if(_loopGPS)
|
|
{
|
|
originGPS = signature->sensorData().gps();
|
|
if(originGPS.stamp() == 0.0 && _currentSessionHasGPS)
|
|
{
|
|
UTimer tmpT;
|
|
if(_optimizedPoses.size() && _memory->isIncremental())
|
|
{
|
|
//Search for latest node having GPS linked to current signature not too far.
|
|
std::map<int, float> nearestIds = graph::findNearestNodes(signature->id(), _optimizedPoses, _localRadius);
|
|
for(std::map<int, float>::reverse_iterator iter=nearestIds.rbegin(); iter!=nearestIds.rend() && iter->first>0; ++iter)
|
|
{
|
|
const Signature * s = _memory->getSignature(iter->first);
|
|
UASSERT(s!=0);
|
|
if(s->sensorData().gps().stamp() > 0.0)
|
|
{
|
|
originGPS = s->sensorData().gps();
|
|
const Transform & sPose = _optimizedPoses.at(s->id());
|
|
Transform localToENU(0,0,(float)((-(originGPS.bearing()-90))*M_PI/180.0) - sPose.theta());
|
|
originOffsetENU = localToENU * (sPose.rotation()*(sPose.inverse()*_optimizedPoses.at(signature->id())));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
//else if(!_memory->isIncremental()) // TODO, how can we estimate current GPS position in localization?
|
|
//{
|
|
//}
|
|
}
|
|
if(originGPS.stamp() > 0.0)
|
|
{
|
|
// no need to save it if it is in localization mode
|
|
_gpsGeocentricCache.insert(std::make_pair(signature->id(), std::make_pair(originGPS.toGeodeticCoords().toGeocentric_WGS84(), originOffsetENU)));
|
|
}
|
|
}
|
|
|
|
for(std::map<int, double>::const_iterator iter=_memory->getWorkingMem().begin();
|
|
iter!=_memory->getWorkingMem().end();
|
|
++iter)
|
|
{
|
|
if(iter->first > 0)
|
|
{
|
|
const Signature * s = _memory->getSignature(iter->first);
|
|
UASSERT(s!=0);
|
|
if(s->getWeight() != -1) // ignore intermediate nodes
|
|
{
|
|
bool accept = true;
|
|
if(originGPS.stamp()>0.0)
|
|
{
|
|
std::map<int, std::pair<cv::Point3d, Transform> >::iterator cacheIter = _gpsGeocentricCache.find(s->id());
|
|
if(cacheIter == _gpsGeocentricCache.end())
|
|
{
|
|
GPS gps = s->sensorData().gps();
|
|
Transform offsetENU = Transform::getIdentity();
|
|
if(gps.stamp()==0.0)
|
|
{
|
|
_memory->getGPS(s->id(), gps, offsetENU, false);
|
|
}
|
|
if(gps.stamp() > 0.0)
|
|
{
|
|
cacheIter = _gpsGeocentricCache.insert(
|
|
std::make_pair(s->id(),
|
|
std::make_pair(gps.toGeodeticCoords().toGeocentric_WGS84(), offsetENU))).first;
|
|
}
|
|
}
|
|
|
|
|
|
if(cacheIter != _gpsGeocentricCache.end())
|
|
{
|
|
std::map<int, std::pair<cv::Point3d, Transform> >::iterator originIter = _gpsGeocentricCache.find(signature->id());
|
|
UASSERT(originIter != _gpsGeocentricCache.end());
|
|
cv::Point3d relativePose = GeodeticCoords::Geocentric_WGS84ToENU_WGS84(cacheIter->second.first, originIter->second.first, originGPS.toGeodeticCoords());
|
|
const double & error = originGPS.error();
|
|
const Transform & offsetENU = cacheIter->second.second;
|
|
relativePose.x += offsetENU.x() - originOffsetENU.x();
|
|
relativePose.y += offsetENU.y() - originOffsetENU.y();
|
|
relativePose.z += offsetENU.z() - originOffsetENU.z();
|
|
// ignore altitude if difference is under GPS error
|
|
if(relativePose.z>error)
|
|
{
|
|
relativePose.z -= error;
|
|
}
|
|
else if(relativePose.z < -error)
|
|
{
|
|
relativePose.z += error;
|
|
}
|
|
else
|
|
{
|
|
relativePose.z = 0;
|
|
}
|
|
accept = uNormSquared(relativePose.x, relativePose.y, relativePose.z) < _localRadius*_localRadius;
|
|
}
|
|
}
|
|
|
|
if(accept)
|
|
{
|
|
signaturesToCompare.push_back(iter->first);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// virtual signature should be added
|
|
signaturesToCompare.push_back(iter->first);
|
|
}
|
|
}
|
|
|
|
rawLikelihood = _memory->computeLikelihood(signature, signaturesToCompare);
|
|
|
|
// Adjust the likelihood (with mean and std dev)
|
|
likelihood = rawLikelihood;
|
|
this->adjustLikelihood(likelihood);
|
|
|
|
timeLikelihoodCalculation = timer.ticks();
|
|
ULOGGER_INFO("timeLikelihoodCalculation=%fs",timeLikelihoodCalculation);
|
|
|
|
//============================================================
|
|
// Apply the Bayes filter
|
|
// Posterior = Likelihood x Prior
|
|
//============================================================
|
|
ULOGGER_INFO("getting posterior...");
|
|
|
|
// Compute the posterior
|
|
posterior = _bayesFilter->computePosterior(_memory, likelihood);
|
|
timePosteriorCalculation = timer.ticks();
|
|
ULOGGER_INFO("timePosteriorCalculation=%fs",timePosteriorCalculation);
|
|
|
|
// For statistics, copy weights
|
|
if(_publishStats && (_publishLikelihood || _publishPdf))
|
|
{
|
|
weights = _memory->getWeights();
|
|
}
|
|
|
|
//============================================================
|
|
// Select the highest hypothesis
|
|
//============================================================
|
|
ULOGGER_INFO("creating hypotheses...");
|
|
if(posterior.size())
|
|
{
|
|
for(std::map<int, float>::const_reverse_iterator iter = posterior.rbegin(); iter != posterior.rend(); ++iter)
|
|
{
|
|
if(iter->first > 0 && iter->second > _highestHypothesis.second)
|
|
{
|
|
_highestHypothesis = *iter;
|
|
}
|
|
}
|
|
// With the virtual place, use sum of LC probabilities (1 - virtual place hypothesis).
|
|
_highestHypothesis.second = 1-posterior.begin()->second;
|
|
}
|
|
timeHypothesesCreation = timer.ticks();
|
|
ULOGGER_INFO("Highest hypothesis=%d, value=%f, timeHypothesesCreation=%fs", _highestHypothesis.first, _highestHypothesis.second, timeHypothesesCreation);
|
|
|
|
if(_highestHypothesis.first > 0)
|
|
{
|
|
float loopThr = _loopThr;
|
|
bool hasLoopClosureConstraints = false;
|
|
for(std::multimap<int, Link>::iterator iter=_odomCacheConstraints.begin(); iter!=_odomCacheConstraints.end() && !hasLoopClosureConstraints; ++iter)
|
|
{
|
|
hasLoopClosureConstraints =
|
|
iter->second.type() == Link::kGlobalClosure ||
|
|
iter->second.type() == Link::kLocalSpaceClosure ||
|
|
iter->second.type() == Link::kLandmark;
|
|
}
|
|
if( (( _memory->isIncremental() && !uContains(_optimizedPoses, _highestHypothesis.first)) || // not linked to previous map of that hypothesis
|
|
(!_memory->isIncremental() && !hasLoopClosureConstraints)) && // not yet localized to any previous sessions
|
|
_memory->getWorkingMem().size()>1 && // should have an old map (beside virtual signature)
|
|
_rgbdSlamMode &&
|
|
loopThr > _aggressiveLoopThr)
|
|
{
|
|
// use the best hypothesis directly.
|
|
UDEBUG("Using %s=%f", Parameters::kRGBDAggressiveLoopThr().c_str(), _aggressiveLoopThr);
|
|
loopThr = _aggressiveLoopThr;
|
|
}
|
|
|
|
// Loop closure Threshold
|
|
if(_highestHypothesis.second >= loopThr)
|
|
{
|
|
rejectedLoopClosure = true;
|
|
if(posterior.size() <= 2 && loopThr>0.0f)
|
|
{
|
|
// Ignore loop closure if there is only one loop closure hypothesis
|
|
UDEBUG("rejected hypothesis: single hypothesis");
|
|
}
|
|
else if(_verifyLoopClosureHypothesis && !_epipolarGeometry->check(signature, _memory->getSignature(_highestHypothesis.first)))
|
|
{
|
|
UWARN("rejected hypothesis: by epipolar geometry");
|
|
}
|
|
else if(_loopRatio > 0.0f && lastHighestHypothesis.second && _highestHypothesis.second < _loopRatio*lastHighestHypothesis.second)
|
|
{
|
|
UWARN("rejected hypothesis: not satisfying hypothesis ratio (%f < %f * %f)",
|
|
_highestHypothesis.second, _loopRatio, lastHighestHypothesis.second);
|
|
}
|
|
else if(_loopRatio > 0.0f && lastHighestHypothesis.second == 0)
|
|
{
|
|
UWARN("rejected hypothesis: last closure hypothesis is null (loop ratio is on)");
|
|
}
|
|
else
|
|
{
|
|
_loopClosureHypothesis = _highestHypothesis;
|
|
rejectedLoopClosure = false;
|
|
}
|
|
|
|
timeHypothesesValidation = timer.ticks();
|
|
ULOGGER_INFO("timeHypothesesValidation=%fs",timeHypothesesValidation);
|
|
}
|
|
else if(_highestHypothesis.second < _loopRatio*lastHighestHypothesis.second)
|
|
{
|
|
// Used for Precision-Recall computation.
|
|
// When analyzing logs, it's convenient to know
|
|
// if the hypothesis would be rejected if T_loop would be lower.
|
|
rejectedLoopClosure = true;
|
|
UDEBUG("rejected hypothesis: under loop ratio %f < %f", _highestHypothesis.second, _loopRatio*lastHighestHypothesis.second);
|
|
}
|
|
|
|
//for statistic...
|
|
hypothesisRatio = _loopClosureHypothesis.second>0?_highestHypothesis.second/_loopClosureHypothesis.second:0;
|
|
}
|
|
} // if(_memory->getWorkingMemSize())
|
|
}// !isBadSignature
|
|
else if(!signature->isBadSignature() && signature->getWeight()>=0 && (smallDisplacement || tooFastMovement))
|
|
{
|
|
_highestHypothesis = lastHighestHypothesis;
|
|
UDEBUG("smallDisplacement=%d tooFastMovement=%d", smallDisplacement?1:0, tooFastMovement?1:0);
|
|
}
|
|
else
|
|
{
|
|
UDEBUG("Ignoring likelihood and loop closure hypotheses as current signature doesn't have enough visual features.");
|
|
}
|
|
|
|
//============================================================
|
|
// Before retrieval, make sure the trash has finished
|
|
//============================================================
|
|
_memory->joinTrashThread();
|
|
timeEmptyingTrash = _memory->getDbSavingTime();
|
|
timeJoiningTrash = timer.ticks();
|
|
ULOGGER_INFO("Time emptying memory trash = %fs, joining (actual overhead) = %fs", timeEmptyingTrash, timeJoiningTrash);
|
|
|
|
//============================================================
|
|
// RETRIEVAL 1/3 : Loop closure neighbors reactivation
|
|
//============================================================
|
|
int retrievalId = _highestHypothesis.first;
|
|
std::list<int> reactivatedIds;
|
|
double timeGetNeighborsTimeDb = 0.0;
|
|
double timeGetNeighborsSpaceDb = 0.0;
|
|
int immunizedGlobally = 0;
|
|
int immunizedLocally = 0;
|
|
int maxLocalLocationsImmunized = 0;
|
|
if(_maxTimeAllowed != 0 || _maxMemoryAllowed != 0)
|
|
{
|
|
// with memory management, we have to immunize some nodes
|
|
maxLocalLocationsImmunized = _localImmunizationRatio * float(_memory->getWorkingMem().size());
|
|
}
|
|
// no need to do retrieval or immunization of locations if memory management
|
|
// is disabled and all nodes are in WM.
|
|
// Also skip memory mangement on intermediate nodes
|
|
if(!(_memory->allNodesInWM() && maxLocalLocationsImmunized == 0) && signature->getWeight()>=0)
|
|
{
|
|
if(retrievalId > 0)
|
|
{
|
|
//Load neighbors
|
|
ULOGGER_INFO("Retrieving locations... around id=%d", retrievalId);
|
|
int neighborhoodSize = (int)_bayesFilter->getPredictionLC().size()-1;
|
|
UASSERT(neighborhoodSize >= 0);
|
|
ULOGGER_DEBUG("margin=%d maxRetieved=%d", neighborhoodSize, _maxRetrieved);
|
|
|
|
UTimer timeGetN;
|
|
unsigned int nbLoadedFromDb = 0;
|
|
std::set<int> reactivatedIdsSet;
|
|
std::map<int, int> neighbors;
|
|
int nbDirectNeighborsInDb = 0;
|
|
|
|
// priority in time
|
|
// Direct neighbors TIME
|
|
ULOGGER_DEBUG("In TIME");
|
|
neighbors = _memory->getNeighborsId(retrievalId,
|
|
neighborhoodSize,
|
|
_maxRetrieved,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
std::set<int>(),
|
|
&timeGetNeighborsTimeDb);
|
|
ULOGGER_DEBUG("neighbors of %d in time = %d", retrievalId, (int)neighbors.size());
|
|
//Priority to locations near in time (direct neighbor) then by space (loop closure)
|
|
bool firstPassDone = false; // just to avoid checking to STM after the first pass
|
|
int m = 0;
|
|
while(m < neighborhoodSize)
|
|
{
|
|
std::set<int> idsSorted;
|
|
for(std::map<int, int>::iterator iter=neighbors.begin(); iter!=neighbors.end();)
|
|
{
|
|
if(!firstPassDone && _memory->isInSTM(iter->first))
|
|
{
|
|
neighbors.erase(iter++);
|
|
}
|
|
else if(iter->second == m)
|
|
{
|
|
if(reactivatedIdsSet.find(iter->first) == reactivatedIdsSet.end())
|
|
{
|
|
idsSorted.insert(iter->first);
|
|
reactivatedIdsSet.insert(iter->first);
|
|
|
|
if(m == 1 && _memory->getSignature(iter->first) == 0)
|
|
{
|
|
++nbDirectNeighborsInDb;
|
|
}
|
|
|
|
//immunized locations in the neighborhood from being transferred
|
|
if(immunizedLocations.insert(iter->first).second)
|
|
{
|
|
++immunizedGlobally;
|
|
}
|
|
|
|
//UDEBUG("nt=%d m=%d immunized=1", iter->first, iter->second);
|
|
}
|
|
neighbors.erase(iter++);
|
|
}
|
|
else
|
|
{
|
|
++iter;
|
|
}
|
|
}
|
|
firstPassDone = true;
|
|
reactivatedIds.insert(reactivatedIds.end(), idsSorted.rbegin(), idsSorted.rend());
|
|
++m;
|
|
}
|
|
|
|
// neighbors SPACE, already added direct neighbors will be ignored
|
|
ULOGGER_DEBUG("In SPACE");
|
|
neighbors = _memory->getNeighborsId(retrievalId,
|
|
neighborhoodSize,
|
|
_maxRetrieved,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
std::set<int>(),
|
|
&timeGetNeighborsSpaceDb);
|
|
ULOGGER_DEBUG("neighbors of %d in space = %d", retrievalId, (int)neighbors.size());
|
|
firstPassDone = false;
|
|
m = 0;
|
|
while(m < neighborhoodSize)
|
|
{
|
|
std::set<int> idsSorted;
|
|
for(std::map<int, int>::iterator iter=neighbors.begin(); iter!=neighbors.end();)
|
|
{
|
|
if(!firstPassDone && _memory->isInSTM(iter->first))
|
|
{
|
|
neighbors.erase(iter++);
|
|
}
|
|
else if(iter->second == m)
|
|
{
|
|
if(reactivatedIdsSet.find(iter->first) == reactivatedIdsSet.end())
|
|
{
|
|
idsSorted.insert(iter->first);
|
|
reactivatedIdsSet.insert(iter->first);
|
|
|
|
if(m == 1 && _memory->getSignature(iter->first) == 0)
|
|
{
|
|
++nbDirectNeighborsInDb;
|
|
}
|
|
//UDEBUG("nt=%d m=%d", iter->first, iter->second);
|
|
}
|
|
neighbors.erase(iter++);
|
|
}
|
|
else
|
|
{
|
|
++iter;
|
|
}
|
|
}
|
|
firstPassDone = true;
|
|
reactivatedIds.insert(reactivatedIds.end(), idsSorted.rbegin(), idsSorted.rend());
|
|
++m;
|
|
}
|
|
ULOGGER_INFO("neighborhoodSize=%d, "
|
|
"reactivatedIds.size=%d, "
|
|
"nbLoadedFromDb=%d, "
|
|
"nbDirectNeighborsInDb=%d, "
|
|
"time=%fs (%fs %fs)",
|
|
neighborhoodSize,
|
|
reactivatedIds.size(),
|
|
(int)nbLoadedFromDb,
|
|
nbDirectNeighborsInDb,
|
|
timeGetN.ticks(),
|
|
timeGetNeighborsTimeDb,
|
|
timeGetNeighborsSpaceDb);
|
|
|
|
}
|
|
}
|
|
|
|
//============================================================
|
|
// RETRIEVAL 2/3 : Update planned path and get next nodes to retrieve
|
|
//============================================================
|
|
std::list<int> retrievalLocalIds;
|
|
if(_rgbdSlamMode && signature->getWeight()>=0)
|
|
{
|
|
// Priority on locations on the planned path
|
|
if(_path.size())
|
|
{
|
|
// Note: retrieval on path with intermediate nodes is not supported. Note that the planned path would
|
|
// eventually fail anyway because intermediate nodes are not in _optimizedPoses.
|
|
updateGoalIndex();
|
|
|
|
float distanceSoFar = 0.0f;
|
|
// immunize all nodes after current node and
|
|
// retrieve nodes after current node in the maximum radius from the current node
|
|
for(unsigned int i=_pathCurrentIndex; i<_path.size(); ++i)
|
|
{
|
|
if(_localRadius > 0.0f && i != _pathCurrentIndex)
|
|
{
|
|
distanceSoFar += _path[i-1].second.getDistance(_path[i].second);
|
|
}
|
|
|
|
if(_memory->getSignature(_path[i].first) != 0)
|
|
{
|
|
if(immunizedLocations.insert(_path[i].first).second)
|
|
{
|
|
++immunizedLocally;
|
|
}
|
|
UDEBUG("Path immunization: node %d (dist=%fm)", _path[i].first, distanceSoFar);
|
|
}
|
|
else if(retrievalLocalIds.size() < _maxLocalRetrieved)
|
|
{
|
|
UINFO("retrieval of node %d on path (dist=%fm)", _path[i].first, distanceSoFar);
|
|
retrievalLocalIds.push_back(_path[i].first);
|
|
// retrieved locations are automatically immunized
|
|
}
|
|
|
|
if(distanceSoFar > _localRadius)
|
|
{
|
|
UDEBUG("Stop on node %d (dist=%fm > %fm)",
|
|
_path[i].first, distanceSoFar, _localRadius);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!(_memory->allNodesInWM() && maxLocalLocationsImmunized == 0))
|
|
{
|
|
// immunize the path from the nearest local location to the current location
|
|
if(immunizedLocally < maxLocalLocationsImmunized &&
|
|
_memory->isIncremental()) // Can only work in mapping mode
|
|
{
|
|
std::map<int ,Transform> poses;
|
|
// remove poses from STM
|
|
for(std::map<int, Transform>::iterator iter=_optimizedPoses.begin(); iter!=_optimizedPoses.end(); ++iter)
|
|
{
|
|
if(iter->first > 0 && !_memory->isInSTM(iter->first))
|
|
{
|
|
poses.insert(*iter);
|
|
}
|
|
}
|
|
int nearestId = graph::findNearestNode(poses, _optimizedPoses.at(signature->id()));
|
|
|
|
if(nearestId > 0 &&
|
|
(_localRadius==0 ||
|
|
_optimizedPoses.at(signature->id()).getDistance(_optimizedPoses.at(nearestId)) < _localRadius))
|
|
{
|
|
std::multimap<int, int> links;
|
|
for(std::multimap<int, Link>::iterator iter=_constraints.begin(); iter!=_constraints.end(); ++iter)
|
|
{
|
|
if(uContains(_optimizedPoses, iter->second.from()) && uContains(_optimizedPoses, iter->second.to()))
|
|
{
|
|
links.insert(std::make_pair(iter->second.from(), iter->second.to()));
|
|
links.insert(std::make_pair(iter->second.to(), iter->second.from())); // <->
|
|
}
|
|
}
|
|
|
|
std::list<std::pair<int, Transform> > path = graph::computePath(_optimizedPoses, links, nearestId, signature->id());
|
|
if(path.size() == 0)
|
|
{
|
|
UWARN("Could not compute a path between %d and %d", nearestId, signature->id());
|
|
}
|
|
else
|
|
{
|
|
for(std::list<std::pair<int, Transform> >::iterator iter=path.begin();
|
|
iter!=path.end();
|
|
++iter)
|
|
{
|
|
if(iter->first>0)
|
|
{
|
|
if(immunizedLocally >= maxLocalLocationsImmunized)
|
|
{
|
|
// set 20 to avoid this warning when starting mapping
|
|
if(maxLocalLocationsImmunized > 20 && _someNodesHaveBeenTransferred)
|
|
{
|
|
UWARN("Could not immunize the whole local path (%d) between "
|
|
"%d and %d (max location immunized=%d). You may want "
|
|
"to increase RGBD/LocalImmunizationRatio (current=%f (%d of WM=%d)) "
|
|
"to be able to immunize longer paths.",
|
|
(int)path.size(),
|
|
nearestId,
|
|
signature->id(),
|
|
maxLocalLocationsImmunized,
|
|
_localImmunizationRatio,
|
|
maxLocalLocationsImmunized,
|
|
(int)_memory->getWorkingMem().size());
|
|
}
|
|
break;
|
|
}
|
|
else if(!_memory->isInSTM(iter->first))
|
|
{
|
|
if(immunizedLocations.insert(iter->first).second)
|
|
{
|
|
++immunizedLocally;
|
|
}
|
|
//UDEBUG("local node %d on path immunized=1", iter->first);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// retrieval based on the nodes close the the nearest pose in WM
|
|
// immunize closest nodes
|
|
std::map<int, float> nearNodes = graph::findNearestNodes(signature->id(), _optimizedPoses, _localRadius);
|
|
// sort by distance
|
|
std::multimap<float, int> nearNodesByDist;
|
|
for(std::map<int, float>::iterator iter=nearNodes.lower_bound(1); iter!=nearNodes.end(); ++iter)
|
|
{
|
|
nearNodesByDist.insert(std::make_pair(iter->second, iter->first));
|
|
}
|
|
UINFO("near nodes=%d, max local immunized=%d (immunized by path so far=%d), ratio=%f WM=%d",
|
|
(int)nearNodesByDist.size(),
|
|
maxLocalLocationsImmunized,
|
|
immunizedLocally,
|
|
_localImmunizationRatio,
|
|
(int)_memory->getWorkingMem().size());
|
|
std::list<int> retrievalLocalIdsIntermediate;
|
|
for(std::multimap<float, int>::iterator iter=nearNodesByDist.begin();
|
|
iter!=nearNodesByDist.end() && (retrievalLocalIds.size() < _maxLocalRetrieved || immunizedLocally < maxLocalLocationsImmunized);
|
|
++iter)
|
|
{
|
|
const Signature * s = _memory->getSignature(iter->second);
|
|
if(s!=0)
|
|
{
|
|
if(s->getWeight() != -1 && retrievalLocalIds.size() < _maxLocalRetrieved)
|
|
{
|
|
// If there is a change of direction, better to be retrieving
|
|
// all nearest signatures than only newest neighbors.
|
|
// Use getNeighborsId instead of direct links to support intermediate nodes.
|
|
std::map<int, int> ids = _memory->getNeighborsId(s->id(), 2, _maxLocalRetrieved-retrievalLocalIds.size(), true, false, false);
|
|
for(std::map<int, int>::const_reverse_iterator jter=ids.rbegin();
|
|
jter!=ids.rend() && (retrievalLocalIds.size() < _maxLocalRetrieved || jter->second == 0);
|
|
++jter)
|
|
{
|
|
if(_memory->getSignature(jter->first) == 0)
|
|
{
|
|
if(jter->second == 0)
|
|
{
|
|
UINFO("retrieval of intermediate node %d (margin=%d) on local map (from=%d)", jter->first, jter->second, s->id());
|
|
retrievalLocalIdsIntermediate.push_back(jter->first);
|
|
}
|
|
else
|
|
{
|
|
UINFO("retrieval of node %d (margin=%d) on local map (from=%d)", jter->first, jter->second, s->id());
|
|
retrievalLocalIds.push_back(jter->first);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(!_memory->isInSTM(s->id()) && immunizedLocally < maxLocalLocationsImmunized)
|
|
{
|
|
if(immunizedLocations.insert(s->id()).second)
|
|
{
|
|
++immunizedLocally;
|
|
}
|
|
//UDEBUG("local node %d (%f m) immunized=1", iter->second, iter->first);
|
|
}
|
|
}
|
|
}
|
|
// well, if the maximum retrieved is not reached, look for neighbors in database
|
|
if(retrievalLocalIds.size() < _maxLocalRetrieved)
|
|
{
|
|
std::set<int> retrievalLocalIdsSet(retrievalLocalIds.begin(), retrievalLocalIds.end());
|
|
retrievalLocalIdsSet.insert(retrievalLocalIdsIntermediate.begin(), retrievalLocalIdsIntermediate.end());
|
|
for(std::list<int>::iterator iter=retrievalLocalIds.begin();
|
|
iter!=retrievalLocalIds.end() && retrievalLocalIds.size() < _maxLocalRetrieved;
|
|
++iter)
|
|
{
|
|
std::map<int, int> ids = _memory->getNeighborsId(*iter, 2, _maxLocalRetrieved - (unsigned int)retrievalLocalIds.size() + 1, true, false, false);
|
|
for(std::map<int, int>::reverse_iterator jter=ids.rbegin();
|
|
jter!=ids.rend() && (retrievalLocalIds.size() < _maxLocalRetrieved || jter->second == 0);
|
|
++jter)
|
|
{
|
|
if(_memory->getSignature(jter->first) == 0 &&
|
|
retrievalLocalIdsSet.find(jter->first) == retrievalLocalIdsSet.end())
|
|
{
|
|
if(jter->second == 0)
|
|
{
|
|
UINFO("retrieval of intermediate node %d (margin=%d) on local map (from=%d)", jter->first, jter->second, *iter);
|
|
retrievalLocalIdsIntermediate.push_back(jter->first);
|
|
}
|
|
else
|
|
{
|
|
UINFO("retrieval of node %d (margin=%d) on local map (from=%d)", jter->first, jter->second, *iter);
|
|
retrievalLocalIds.push_back(jter->first);
|
|
}
|
|
retrievalLocalIdsSet.insert(jter->first);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// update Age of the close signatures (oldest the farthest)
|
|
for(std::multimap<float, int>::reverse_iterator iter=nearNodesByDist.rbegin(); iter!=nearNodesByDist.rend(); ++iter)
|
|
{
|
|
_memory->updateAge(iter->second);
|
|
}
|
|
|
|
// insert them first to make sure they are loaded.
|
|
reactivatedIds.insert(reactivatedIds.begin(), retrievalLocalIdsIntermediate.begin(), retrievalLocalIdsIntermediate.end());
|
|
reactivatedIds.insert(reactivatedIds.begin(), retrievalLocalIds.begin(), retrievalLocalIds.end());
|
|
}
|
|
}
|
|
|
|
//============================================================
|
|
// RETRIEVAL 3/3 : Load signatures from the database
|
|
//============================================================
|
|
if(reactivatedIds.size())
|
|
{
|
|
// Not important if the loop closure hypothesis don't have all its neighbors loaded,
|
|
// only a loop closure link is added...
|
|
signaturesRetrieved = _memory->reactivateSignatures(
|
|
reactivatedIds,
|
|
_maxRetrieved+(unsigned int)retrievalLocalIds.size(), // add path retrieved
|
|
timeRetrievalDbAccess);
|
|
|
|
ULOGGER_INFO("retrieval of %d (db time = %fs)", (int)signaturesRetrieved.size(), timeRetrievalDbAccess);
|
|
|
|
timeRetrievalDbAccess += timeGetNeighborsTimeDb + timeGetNeighborsSpaceDb;
|
|
UINFO("total timeRetrievalDbAccess=%fs", timeRetrievalDbAccess);
|
|
|
|
// Immunize just retrieved signatures
|
|
immunizedLocations.insert(signaturesRetrieved.begin(), signaturesRetrieved.end());
|
|
|
|
if(!signaturesRetrieved.empty() && !_globalScanMap.empty())
|
|
{
|
|
UWARN("Some signatures have been retrieved from memory management, clearing global scan map...");
|
|
_globalScanMap.clear();
|
|
_globalScanMapPoses.clear();
|
|
}
|
|
}
|
|
timeReactivations = timer.ticks();
|
|
ULOGGER_INFO("timeReactivations=%fs", timeReactivations);
|
|
|
|
//============================================================
|
|
// Proximity detections
|
|
//============================================================
|
|
std::list<std::pair<int, int> > loopClosureLinksAdded;
|
|
int loopClosureVisualInliers = 0; // for statistics
|
|
float loopClosureVisualInliersRatio = 0.0f;
|
|
int loopClosureVisualMatches = 0;
|
|
float loopClosureVisualVariance = 0.0f;
|
|
float loopClosureLinearVariance = 0.0f;
|
|
float loopClosureAngularVariance = 0.0f;
|
|
float loopClosureVisualInliersMeanDist = 0;
|
|
float loopClosureVisualInliersDistribution = 0;
|
|
|
|
int proximityDetectionsAddedVisually = 0;
|
|
int proximityDetectionsAddedByICPMulti = 0;
|
|
int proximityDetectionsAddedByICPGlobal = 0;
|
|
int lastProximitySpaceClosureId = 0;
|
|
int proximitySpacePaths = 0;
|
|
int localVisualPathsChecked = 0;
|
|
int localScanPathsChecked = 0;
|
|
int loopIdSuppressedByProximity = 0;
|
|
|
|
if(_proximityBySpace &&
|
|
_localRadius > 0 &&
|
|
_rgbdSlamMode &&
|
|
signature->getWeight() >= 0) // not an intermediate node
|
|
{
|
|
if(_startNewMapOnLoopClosure &&
|
|
_memory->getWorkingMem().size()>=2 && // must have an old map (+1 virtual place)
|
|
_localizationCovariance.empty() && // if we didn't localize yet
|
|
graph::filterLinks(signature->getLinks(), Link::kSelfRefLink).size() == 0) // alone in new session
|
|
{
|
|
UINFO("Proximity detection by space disabled as if we force to have a global loop "
|
|
"closure with previous map before doing proximity detections (%s=true).",
|
|
Parameters::kRtabmapStartNewMapOnLoopClosure().c_str());
|
|
}
|
|
else if(_graphOptimizer->iterations() == 0)
|
|
{
|
|
UWARN("Cannot do local loop closure detection in space if graph optimization is disabled!");
|
|
}
|
|
else
|
|
{
|
|
// In localization mode, no need to check local loop
|
|
// closures if we are already localized by a landmark.
|
|
|
|
// don't do it if it is a small displacement unless the previous signature didn't have a loop closure
|
|
// don't do it if there is a too fast movement
|
|
if((!smallDisplacement || !localizationOnPreviousUpdate) && !tooFastMovement)
|
|
{
|
|
|
|
//============================================================
|
|
// LOCAL LOOP CLOSURE SPACE
|
|
//============================================================
|
|
|
|
//
|
|
// 1) compare visually with nearest locations
|
|
//
|
|
UDEBUG("Proximity detection (local loop closure in SPACE using matching images, local radius=%fm)", _localRadius);
|
|
std::map<int, float> nearestIds = graph::findNearestNodes(signature->id(), _optimizedPoses, _localRadius);
|
|
UDEBUG("nearestIds=%d/%d", (int)nearestIds.size(), (int)_optimizedPoses.size());
|
|
std::map<int, Transform> nearestPoses;
|
|
std::map<int, Transform> optimizedPosesWithOdomCache;
|
|
std::multimap<int, int> links;
|
|
std::map<int, Transform> * refPoses = &_optimizedPoses;
|
|
if(_memory->isIncremental() && _proximityMaxGraphDepth>0)
|
|
{
|
|
// get bidirectional links
|
|
for(std::multimap<int, Link>::iterator iter=_constraints.begin(); iter!=_constraints.end(); ++iter)
|
|
{
|
|
if(uContains(_optimizedPoses, iter->second.from()) && uContains(_optimizedPoses, iter->second.to()))
|
|
{
|
|
links.insert(std::make_pair(iter->second.from(), iter->second.to()));
|
|
links.insert(std::make_pair(iter->second.to(), iter->second.from())); // <->
|
|
}
|
|
}
|
|
if(_odomCachePoses.size() > 1)
|
|
{
|
|
// Add odometry cache if it contains a loop closure
|
|
// That could happen when we just switched from localization mode to
|
|
// mapping mode while being localized on the previous session.
|
|
optimizedPosesWithOdomCache = _optimizedPoses;
|
|
optimizedPosesWithOdomCache.insert(_odomCachePoses.begin(), _odomCachePoses.end());
|
|
refPoses = &optimizedPosesWithOdomCache;
|
|
for(std::multimap<int, Link>::iterator iter=_odomCacheConstraints.begin(); iter!=_odomCacheConstraints.end(); ++iter)
|
|
{
|
|
if(uContains(optimizedPosesWithOdomCache, iter->second.from()) &&
|
|
uContains(optimizedPosesWithOdomCache, iter->second.to()) &&
|
|
iter->second.from() != iter->second.to())
|
|
{
|
|
links.insert(std::make_pair(iter->second.from(), iter->second.to()));
|
|
links.insert(std::make_pair(iter->second.to(), iter->second.from())); // <->
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for(std::map<int, float>::iterator iter=nearestIds.lower_bound(1); iter!=nearestIds.end(); ++iter)
|
|
{
|
|
if(_memory->getStMem().find(iter->first) == _memory->getStMem().end())
|
|
{
|
|
if(_memory->isIncremental() && _proximityMaxGraphDepth > 0)
|
|
{
|
|
std::list<std::pair<int, Transform> > path = graph::computePath(*refPoses, links, signature->id(), iter->first);
|
|
UDEBUG("Graph depth to %d = %ld", iter->first, path.size());
|
|
if(!path.empty() && (int)path.size() <= _proximityMaxGraphDepth)
|
|
{
|
|
nearestPoses.insert(std::make_pair(iter->first, _optimizedPoses.at(iter->first)));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nearestPoses.insert(std::make_pair(iter->first, _optimizedPoses.at(iter->first)));
|
|
}
|
|
}
|
|
}
|
|
UDEBUG("nearestPoses=%d", (int)nearestPoses.size());
|
|
|
|
// segment poses by paths, only one detection per path, landmarks are ignored
|
|
std::map<int, std::map<int, Transform> > nearestPathsNotSorted = getPaths(nearestPoses, _optimizedPoses.at(signature->id()), _proximityMaxGraphDepth);
|
|
UDEBUG("got %d paths", (int)nearestPathsNotSorted.size());
|
|
// sort nearest paths by highest likelihood (if two have same likelihood, sort by id)
|
|
std::map<NearestPathKey, std::map<int, Transform> > nearestPaths;
|
|
Transform currentPoseInv = _optimizedPoses.at(signature->id()).inverse();
|
|
for(std::map<int, std::map<int, Transform> >::const_iterator iter=nearestPathsNotSorted.begin();iter!=nearestPathsNotSorted.end(); ++iter)
|
|
{
|
|
const std::map<int, Transform> & path = iter->second;
|
|
float highestLikelihood = 0.0f;
|
|
int highestLikelihoodId = iter->first;
|
|
float smallestDistanceSqr = -1;
|
|
for(std::map<int, Transform>::const_iterator jter=path.begin(); jter!=path.end(); ++jter)
|
|
{
|
|
float v = uValue(likelihood, jter->first, 0.0f);
|
|
float distance = (currentPoseInv * jter->second).getNormSquared();
|
|
if(v > highestLikelihood || (v == highestLikelihood && (smallestDistanceSqr < 0 || distance < smallestDistanceSqr)))
|
|
{
|
|
highestLikelihood = v;
|
|
highestLikelihoodId = jter->first;
|
|
smallestDistanceSqr = distance;
|
|
}
|
|
}
|
|
nearestPaths.insert(std::make_pair(NearestPathKey(highestLikelihood, highestLikelihoodId, smallestDistanceSqr), path));
|
|
}
|
|
UDEBUG("nearestPaths=%d proximityMaxPaths=%d", (int)nearestPaths.size(), _proximityMaxPaths);
|
|
|
|
timeProximityBySpaceSearch = timer.ticks();
|
|
ULOGGER_INFO("timeProximityBySpaceSearch=%fs", timeProximityBySpaceSearch);
|
|
|
|
float proximityFilteringRadius = _proximityFilteringRadius;
|
|
if(_maxLoopClosureDistance>0.0f && (proximityFilteringRadius <= 0.0f || _maxLoopClosureDistance<proximityFilteringRadius))
|
|
{
|
|
proximityFilteringRadius = _maxLoopClosureDistance;
|
|
}
|
|
for(std::map<NearestPathKey, std::map<int, Transform> >::const_reverse_iterator iter=nearestPaths.rbegin();
|
|
iter!=nearestPaths.rend() &&
|
|
(_proximityMaxPaths <= 0 || localVisualPathsChecked < _proximityMaxPaths);
|
|
++iter)
|
|
{
|
|
std::map<int, Transform> path = iter->second;
|
|
UASSERT(path.size());
|
|
|
|
//find the nearest pose on the path looking in the same direction
|
|
path.insert(std::make_pair(signature->id(), _optimizedPoses.at(signature->id())));
|
|
path = graph::findNearestPoses(signature->id(), path, _localRadius, _proximityAngle);
|
|
//take the one with highest likelihood if not null
|
|
int nearestId = 0;
|
|
if(iter->first.likelihood > 0.0f &&
|
|
path.find(iter->first.id)!=path.end())
|
|
{
|
|
nearestId = iter->first.id;
|
|
}
|
|
else
|
|
{
|
|
nearestId = rtabmap::graph::findNearestNode(path, _optimizedPoses.at(signature->id()));
|
|
}
|
|
|
|
if(nearestId > 0)
|
|
{
|
|
// nearest pose must not be linked to current location and enough close
|
|
if(!signature->hasLink(nearestId) &&
|
|
(proximityFilteringRadius <= 0.0f ||
|
|
_optimizedPoses.at(signature->id()).getDistanceSquared(_optimizedPoses.at(nearestId)) < proximityFilteringRadius*proximityFilteringRadius))
|
|
{
|
|
++localVisualPathsChecked;
|
|
RegistrationInfo info;
|
|
Transform guess;
|
|
if(_proximityOdomGuess)
|
|
{
|
|
// Use odometry as guess so that correspondences can be computed by projection
|
|
guess = _optimizedPoses.at(nearestId).inverse()*_optimizedPoses.at(signature->id());
|
|
} //else: guess is null to make sure visual correspondences are globally computed
|
|
Transform transform = _memory->computeTransform(nearestId, signature->id(), guess, &info);
|
|
if(!transform.isNull())
|
|
{
|
|
transform = transform.inverse();
|
|
if(proximityFilteringRadius <= 0 || transform.getNormSquared() <= proximityFilteringRadius*proximityFilteringRadius)
|
|
{
|
|
UINFO("[Visual] Add local loop closure in SPACE (%d->%d) %s",
|
|
signature->id(),
|
|
nearestId,
|
|
transform.prettyPrint().c_str());
|
|
UASSERT(!info.covariance.empty() && info.covariance.at<double>(0,0) > 0.0 && info.covariance.at<double>(5,5) > 0.0);
|
|
|
|
//for statistics
|
|
loopClosureVisualInliersMeanDist = info.inliersMeanDistance;
|
|
loopClosureVisualInliersDistribution = info.inliersDistribution;
|
|
|
|
++proximityDetectionsAddedVisually;
|
|
lastProximitySpaceClosureId = nearestId;
|
|
|
|
loopClosureVisualInliers = info.inliers;
|
|
loopClosureVisualInliersRatio = info.inliersRatio;
|
|
loopClosureVisualMatches = info.matches;
|
|
loopClosureVisualVariance = info.variance;
|
|
|
|
cv::Mat information = getInformation(info.covariance);
|
|
loopClosureLinearVariance = 1.0/information.at<double>(0,0);
|
|
loopClosureAngularVariance = 1.0/information.at<double>(5,5);
|
|
|
|
Link::Type type = Link::kLocalSpaceClosure;
|
|
if(_loopClosureHypothesis.first>0 &&
|
|
nearestIds.find(_loopClosureHypothesis.first)!=nearestIds.end())
|
|
{
|
|
// Avoid transform computation on the global loop closure if a visual proximity
|
|
// one has been detected close (inside proximity radius) to that hypothesis.
|
|
UDEBUG("Proximity detection on %d is close to loop closure %d, ignoring loop closure transform estimation...",
|
|
nearestId, _loopClosureHypothesis.first);
|
|
|
|
if(nearestId == _loopClosureHypothesis.first)
|
|
{
|
|
type = Link::kGlobalClosure;
|
|
loopIdSuppressedByProximity = nearestId;
|
|
}
|
|
else if(loopIdSuppressedByProximity == 0)
|
|
{
|
|
loopIdSuppressedByProximity = nearestId;
|
|
}
|
|
}
|
|
|
|
_memory->addLink(Link(signature->id(), nearestId, type, transform, information));
|
|
loopClosureLinksAdded.push_back(std::make_pair(signature->id(), nearestId));
|
|
}
|
|
else
|
|
{
|
|
UWARN("Ignoring local loop closure with %d because resulting "
|
|
"transform is too large!? (%fm > %fm)",
|
|
nearestId, transform.getNorm(), proximityFilteringRadius);
|
|
}
|
|
}
|
|
}
|
|
else if(!signature->hasLink(nearestId) && proximityFilteringRadius>0.0f)
|
|
{
|
|
UDEBUG("Skipping path %d as most likely ID %d is too far %f > %f (%s)",
|
|
iter->first.id,
|
|
nearestId,
|
|
_optimizedPoses.at(signature->id()).getDistance(_optimizedPoses.at(nearestId)),
|
|
proximityFilteringRadius,
|
|
Parameters::kRGBDProximityPathFilteringRadius().c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
timeProximityBySpaceVisualDetection = timer.ticks();
|
|
ULOGGER_INFO("timeProximityBySpaceVisualDetection=%fs", timeProximityBySpaceVisualDetection);
|
|
|
|
//
|
|
// 2) compare locally with nearest locations by scan matching
|
|
//
|
|
UDEBUG("Proximity detection (local loop closure in SPACE with scan matching)");
|
|
if( _proximityMaxNeighbors <= 0)
|
|
{
|
|
UDEBUG("Proximity by scan matching is disabled (%s=%d).", Parameters::kRGBDProximityPathMaxNeighbors().c_str(), _proximityMaxNeighbors);
|
|
}
|
|
else if(!signature->sensorData().laserScanCompressed().isEmpty())
|
|
{
|
|
proximitySpacePaths = (int)nearestPaths.size();
|
|
for(std::map<NearestPathKey, std::map<int, Transform> >::const_reverse_iterator iter=nearestPaths.rbegin();
|
|
iter!=nearestPaths.rend() &&
|
|
(_proximityMaxPaths <= 0 || localScanPathsChecked < _proximityMaxPaths);
|
|
++iter)
|
|
{
|
|
std::map<int, Transform> path = iter->second; // should contain only nodes (no landmarks)
|
|
UASSERT(path.size());
|
|
UASSERT(path.begin()->first > 0);
|
|
|
|
//find the nearest pose on the path
|
|
int nearestId = rtabmap::graph::findNearestNode(path, _optimizedPoses.at(signature->id()));
|
|
UASSERT(nearestId > 0);
|
|
//UDEBUG("Path %d (size=%d) distance=%fm", nearestId, (int)path.size(), _optimizedPoses.at(signature->id()).getDistance(_optimizedPoses.at(nearestId)));
|
|
|
|
// nearest pose must be close and not linked to current location
|
|
if(!signature->hasLink(nearestId))
|
|
{
|
|
if(_proximityMaxNeighbors < _proximityMaxGraphDepth || _proximityMaxGraphDepth == 0)
|
|
{
|
|
std::map<int, Transform> filteredPath;
|
|
int i=0;
|
|
std::map<int, Transform>::iterator nearestIdIter = path.find(nearestId);
|
|
// "_proximityMaxNeighbors-1" means that if _proximityMaxNeighbors=1,
|
|
// only nearest node on the path is taken (no scan merging). Useful to find
|
|
// proximity detection between only 2 nodes with 360x360 lidar scans.
|
|
for(std::map<int, Transform>::iterator iter=nearestIdIter; iter!=path.end() && i<=_proximityMaxNeighbors-1; ++iter, ++i)
|
|
{
|
|
filteredPath.insert(*iter);
|
|
}
|
|
i=1;
|
|
for(std::map<int, Transform>::reverse_iterator iter(nearestIdIter); iter!=path.rend() && i<=_proximityMaxNeighbors-1; ++iter, ++i)
|
|
{
|
|
filteredPath.insert(*iter);
|
|
}
|
|
path = filteredPath;
|
|
}
|
|
|
|
// Assemble scans in the path and do ICP only
|
|
std::map<int, Transform> optimizedLocalPath;
|
|
if(_globalScanMap.empty() && _proximityRawPosesUsed)
|
|
{
|
|
//optimize the path's poses locally
|
|
cv::Mat covariance;
|
|
path = optimizeGraph(nearestId, uKeysSet(path), std::map<int, Transform>(), false, covariance);
|
|
// transform local poses in optimized graph referential
|
|
if(!uContains(path, nearestId))
|
|
{
|
|
UERROR("Proximity path not containing nearest ID ?! Skipping this path.");
|
|
continue;
|
|
}
|
|
Transform t = _optimizedPoses.at(nearestId) * path.at(nearestId).inverse();
|
|
|
|
for(std::map<int, Transform>::iterator jter=path.lower_bound(1); jter!=path.end(); ++jter)
|
|
{
|
|
optimizedLocalPath.insert(std::make_pair(jter->first, t * jter->second));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
optimizedLocalPath = path;
|
|
}
|
|
|
|
std::map<int, Transform> filteredPath;
|
|
if(_globalScanMap.empty() && optimizedLocalPath.size() > 2 && proximityFilteringRadius > 0.0f)
|
|
{
|
|
// path filtering
|
|
filteredPath = graph::radiusPosesFiltering(optimizedLocalPath, proximityFilteringRadius, 0, true);
|
|
// make sure the current pose is still here
|
|
filteredPath.insert(*optimizedLocalPath.find(nearestId));
|
|
}
|
|
else
|
|
{
|
|
filteredPath = optimizedLocalPath;
|
|
}
|
|
|
|
if(filteredPath.size() > 0)
|
|
{
|
|
// add current node to poses
|
|
filteredPath.insert(std::make_pair(signature->id(), _optimizedPoses.at(signature->id())));
|
|
//The nearest will be the reference for a loop closure transform
|
|
if(signature->getLinks().find(nearestId) == signature->getLinks().end())
|
|
{
|
|
++localScanPathsChecked;
|
|
RegistrationInfo info;
|
|
Transform transform;
|
|
bool icpMulti = true;
|
|
if(_globalScanMap.empty())
|
|
{
|
|
transform = _memory->computeIcpTransformMulti(signature->id(), nearestId, filteredPath, &info);
|
|
}
|
|
else
|
|
{
|
|
UASSERT_MSG(_globalScanMapPoses.find(nearestId) != _globalScanMapPoses.end(), uFormat("Pose of %d not found in global scan poses", nearestId).c_str());
|
|
icpMulti = false;
|
|
// use pre-assembled scan map
|
|
SensorData assembledData;
|
|
assembledData.setId(nearestId);
|
|
assembledData.setLaserScan(
|
|
LaserScan(_globalScanMap,
|
|
signature->sensorData().laserScanCompressed().maxPoints(),
|
|
signature->sensorData().laserScanCompressed().rangeMax(),
|
|
_globalScanMapPoses.at(nearestId).inverse() * (signature->sensorData().laserScanCompressed().is2d()?Transform(0,0,signature->sensorData().laserScanCompressed().localTransform().z(),0,0,0):Transform::getIdentity())));
|
|
Signature nearestNode(assembledData);
|
|
Transform guess = filteredPath.at(nearestId).inverse() * filteredPath.at(signature->id());
|
|
transform = _memory->computeIcpTransform(nearestNode, *signature, guess, &info);
|
|
if(!transform.isNull())
|
|
{
|
|
transform = transform.inverse();
|
|
}
|
|
}
|
|
|
|
if(!transform.isNull())
|
|
{
|
|
UINFO("[Scan matching] Add local loop closure in SPACE (%d->%d) %s",
|
|
signature->id(),
|
|
nearestId,
|
|
transform.prettyPrint().c_str());
|
|
|
|
cv::Mat scanMatchingIds;
|
|
if(_scanMatchingIdsSavedInLinks)
|
|
{
|
|
std::stringstream stream;
|
|
stream << "SCANS:";
|
|
for(std::map<int, Transform>::iterator iter=optimizedLocalPath.begin(); iter!=optimizedLocalPath.end(); ++iter)
|
|
{
|
|
if(iter != optimizedLocalPath.begin())
|
|
{
|
|
stream << ";";
|
|
}
|
|
stream << uNumber2Str(iter->first);
|
|
}
|
|
std::string scansStr = stream.str();
|
|
scanMatchingIds = cv::Mat(1, int(scansStr.size()+1), CV_8SC1, (void *)scansStr.c_str());
|
|
scanMatchingIds = compressData2(scanMatchingIds); // compressed
|
|
}
|
|
|
|
// set Identify covariance for laser scan matching only
|
|
UASSERT(!info.covariance.empty() && info.covariance.at<double>(0,0) > 0.0 && info.covariance.at<double>(5,5) > 0.0);
|
|
_memory->addLink(Link(signature->id(), nearestId, Link::kLocalSpaceClosure, transform, getInformation(info.covariance)/_proximityMergedScanCovFactor, scanMatchingIds));
|
|
loopClosureLinksAdded.push_back(std::make_pair(signature->id(), nearestId));
|
|
|
|
if(icpMulti)
|
|
{
|
|
++proximityDetectionsAddedByICPMulti;
|
|
}
|
|
else
|
|
{
|
|
++proximityDetectionsAddedByICPGlobal;
|
|
}
|
|
|
|
// no local loop closure added visually
|
|
if(proximityDetectionsAddedVisually == 0)
|
|
{
|
|
lastProximitySpaceClosureId = nearestId;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UINFO("Local scan matching rejected: %s", info.rejectedMsg.c_str());
|
|
}
|
|
if(!_globalScanMap.empty())
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//UDEBUG("Path %d ignored", nearestId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
timeProximityBySpaceDetection = timer.ticks();
|
|
ULOGGER_INFO("timeProximityBySpaceDetection=%fs", timeProximityBySpaceDetection);
|
|
|
|
//=============================================================
|
|
// Global loop closure detection
|
|
// (updated: place this after retrieval to be sure that neighbors of the loop closure are in RAM)
|
|
//=============================================================
|
|
if(_loopClosureHypothesis.first>0)
|
|
{
|
|
if(loopIdSuppressedByProximity==0)
|
|
{
|
|
//Compute transform if metric data are present
|
|
Transform transform;
|
|
RegistrationInfo info;
|
|
info.covariance = cv::Mat::eye(6,6,CV_64FC1);
|
|
if(_rgbdSlamMode)
|
|
{
|
|
transform = _memory->computeTransform(
|
|
_loopClosureHypothesis.first,
|
|
signature->id(),
|
|
_loopClosureIdentityGuess?Transform::getIdentity():Transform(),
|
|
&info);
|
|
|
|
loopClosureVisualInliersMeanDist = info.inliersMeanDistance;
|
|
loopClosureVisualInliersDistribution = info.inliersDistribution;
|
|
|
|
loopClosureVisualInliers = info.inliers;
|
|
loopClosureVisualInliersRatio = info.inliersRatio;
|
|
loopClosureVisualMatches = info.matches;
|
|
loopClosureVisualVariance = info.variance;
|
|
rejectedLoopClosure = transform.isNull();
|
|
if(rejectedLoopClosure)
|
|
{
|
|
UWARN("Rejected loop closure %d -> %d: %s",
|
|
_loopClosureHypothesis.first, signature->id(), info.rejectedMsg.c_str());
|
|
}
|
|
else if(_maxLoopClosureDistance>0.0f && transform.getNorm() > _maxLoopClosureDistance)
|
|
{
|
|
rejectedLoopClosure = true;
|
|
UWARN("Rejected localization %d -> %d because distance to map (%fm) is over %s=%fm.",
|
|
_loopClosureHypothesis.first, signature->id(), transform.getNorm(), Parameters::kRGBDMaxLoopClosureDistance().c_str(), _maxLoopClosureDistance);
|
|
}
|
|
else
|
|
{
|
|
transform = transform.inverse();
|
|
}
|
|
}
|
|
if(!rejectedLoopClosure)
|
|
{
|
|
// Make the new one the parent of the old one
|
|
UASSERT(!info.covariance.empty() && info.covariance.at<double>(0,0) > 0.0 && info.covariance.at<double>(5,5) > 0.0);
|
|
|
|
loopClosureLinearVariance = uMax3(info.covariance.at<double>(0,0), info.covariance.at<double>(1,1)>=9999?0:info.covariance.at<double>(1,1), info.covariance.at<double>(2,2)>=9999?0:info.covariance.at<double>(2,2));
|
|
loopClosureAngularVariance = uMax3(info.covariance.at<double>(3,3)>=9999?0:info.covariance.at<double>(3,3), info.covariance.at<double>(4,4)>=9999?0:info.covariance.at<double>(4,4), info.covariance.at<double>(5,5));
|
|
cv::Mat information = getInformation(info.covariance);
|
|
rejectedLoopClosure = !_memory->addLink(Link(signature->id(), _loopClosureHypothesis.first, Link::kGlobalClosure, transform, information));
|
|
if(!rejectedLoopClosure)
|
|
{
|
|
loopClosureLinksAdded.push_back(std::make_pair(signature->id(), _loopClosureHypothesis.first));
|
|
}
|
|
}
|
|
|
|
if(rejectedLoopClosure)
|
|
{
|
|
_loopClosureHypothesis.first = 0;
|
|
}
|
|
}
|
|
else if(loopIdSuppressedByProximity != _loopClosureHypothesis.first)
|
|
{
|
|
_loopClosureHypothesis.first = 0;
|
|
}
|
|
}
|
|
|
|
timeAddLoopClosureLink = timer.ticks();
|
|
ULOGGER_INFO("timeAddLoopClosureLink=%fs", timeAddLoopClosureLink);
|
|
|
|
//============================================================
|
|
// Landmark
|
|
//============================================================
|
|
std::map<int, std::set<int> > landmarksDetected; // <Landmark ID, list of nodes that saw this landmark>
|
|
if(!signature->getLandmarks().empty() && !_graphOptimizer->landmarksIgnored() && signature->getWeight()!=-1)
|
|
{
|
|
bool hasGlobalLoopClosuresInOdomCache = !graph::filterLinks(_odomCacheConstraints, Link::kGlobalClosure, true).empty() || _loopClosureHypothesis.first != 0;
|
|
UDEBUG("hasGlobalLoopClosuresInOdomCache=%d", hasGlobalLoopClosuresInOdomCache?1:0);
|
|
for(std::map<int, Link>::const_iterator iter=signature->getLandmarks().begin(); iter!=signature->getLandmarks().end(); ++iter)
|
|
{
|
|
if(uContains(_memory->getLandmarksIndex(), iter->first) &&
|
|
_memory->getLandmarksIndex().find(iter->first)->second.size()>1)
|
|
{
|
|
if(!_memory->isIncremental() && // In localization mode
|
|
!hasGlobalLoopClosuresInOdomCache && // If there are global loop closures in odom cache, we can keep far landmarks
|
|
_localRadius>0.0 &&
|
|
iter->second.transform().getNormSquared() > _localRadius*_localRadius)
|
|
{
|
|
// Ignore landmark detections over local radius
|
|
UWARN("Ignoring landmark %d for localization as it is too far (%fm > %s=%f) "
|
|
"and odom cache doesn't contain global loop closure(s).",
|
|
iter->first,
|
|
iter->second.transform().getNorm(),
|
|
Parameters::kRGBDLocalRadius().c_str(),
|
|
_localRadius);
|
|
}
|
|
else
|
|
{
|
|
UINFO("Landmark %d observed again! Seen the first time by node %d.", -iter->first, *_memory->getLandmarksIndex().find(iter->first)->second.begin());
|
|
landmarksDetected.insert(std::make_pair(iter->first, _memory->getLandmarksIndex().find(iter->first)->second));
|
|
rejectedLoopClosure = false; // If it was true, it will be set back to false if landmarks are rejected on graph optimization
|
|
loopClosureLinksAdded.push_back(std::make_pair(signature->id(), iter->first));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//============================================================
|
|
// Add virtual links if a path is activated
|
|
//============================================================
|
|
if(_path.size())
|
|
{
|
|
// Add a virtual loop closure link to keep the path linked to local map
|
|
if( signature->id() != _path[_pathCurrentIndex].first &&
|
|
!signature->hasLink(_path[_pathCurrentIndex].first))
|
|
{
|
|
UASSERT(uContains(_optimizedPoses, signature->id()));
|
|
UASSERT_MSG(uContains(_optimizedPoses, _path[_pathCurrentIndex].first), uFormat("id=%d", _path[_pathCurrentIndex].first).c_str());
|
|
Transform virtualLoop = _optimizedPoses.at(signature->id()).inverse() * _optimizedPoses.at(_path[_pathCurrentIndex].first);
|
|
_memory->addLink(Link(signature->id(), _path[_pathCurrentIndex].first, Link::kVirtualClosure, virtualLoop, cv::Mat::eye(6,6,CV_64FC1)*0.01)); // set high variance
|
|
}
|
|
}
|
|
|
|
//============================================================
|
|
// Optimize map graph
|
|
//============================================================
|
|
graph::MaxGraphErrors maxGraphErrors;
|
|
std::pair<int, int> maxGraphErrorsLinearIds(0,0);
|
|
std::pair<int, int> maxGraphErrorsAngularIds(0,0);
|
|
std::pair<int, int> maxGraphErrorsRemovedIds(0,0);
|
|
int maxGraphErrorsRemovedCount = 0;
|
|
double optimizationError = 0.0;
|
|
int optimizationIterations = 0;
|
|
Transform previousMapCorrection;
|
|
bool delayedLocalization = false;
|
|
int odomCacheProximityLinksCleared = 0;
|
|
UDEBUG("RGB-D SLAM mode: %d", _rgbdSlamMode?1:0);
|
|
UDEBUG("Incremental: %d", _memory->isIncremental());
|
|
UDEBUG("Loop hyp: %d", _loopClosureHypothesis.first);
|
|
UDEBUG("Last prox: %d", lastProximitySpaceClosureId);
|
|
UDEBUG("Reduced ids: %d", (int)statistics_.reducedIds().size());
|
|
UDEBUG("Has prior: %d (prior ignored=%d)", signature->hasLink(signature->id(), Link::kPosePrior)?1:0, _graphOptimizer->priorsIgnored()?1:0);
|
|
UDEBUG("Has gravity: %d (sigma=%f, odomGravity=%d, refined=%d)", signature->hasLink(signature->id(), Link::kGravity)?1:0, _graphOptimizer->gravitySigma(), _memory->isOdomGravityUsed()?1:0, neighborLinkRefined?1:0);
|
|
UDEBUG("Has virtual link: %d", (int)graph::filterLinks(signature->getLinks(), Link::kVirtualClosure, true).size());
|
|
UDEBUG("Prox Time: %d", proximityDetectionsInTimeFound);
|
|
UDEBUG("Landmarks: %d", (int)landmarksDetected.size());
|
|
UDEBUG("Retrieved: %d", (int)signaturesRetrieved.size());
|
|
UDEBUG("Not self ref links: %d", (int)graph::filterLinks(signature->getLinks(), Link::kSelfRefLink).size());
|
|
|
|
if(_rgbdSlamMode
|
|
&&
|
|
signature->getWeight() != -1 // Ignore graph optimization on intermediate nodes
|
|
&&
|
|
(_loopClosureHypothesis.first>0 ||
|
|
lastProximitySpaceClosureId>0 || // can be different map of the current one
|
|
statistics_.reducedIds().size() ||
|
|
(signature->hasLink(signature->id(), Link::kPosePrior) && !_graphOptimizer->priorsIgnored()) || // prior edge
|
|
(signature->hasLink(signature->id(), Link::kGravity) && _graphOptimizer->gravitySigma()>0.0f && (!_memory->isOdomGravityUsed() || neighborLinkRefined)) || // gravity edge
|
|
proximityDetectionsInTimeFound>0 ||
|
|
!landmarksDetected.empty() ||
|
|
signaturesRetrieved.size()) // can be different map of the current one
|
|
&&
|
|
(_memory->isIncremental() ||
|
|
// In localization mode, the new node should be linked to another node or a landmark already in the working memory
|
|
graph::filterLinks(graph::filterLinks(signature->getLinks(), Link::kVirtualClosure), Link::kSelfRefLink).size() ||
|
|
!landmarksDetected.empty()))
|
|
{
|
|
UASSERT(uContains(_optimizedPoses, signature->id()));
|
|
|
|
//used in localization mode: filter virtual links
|
|
std::multimap<int, Link> localizationLinks = graph::filterLinks(signature->getLinks(), Link::kVirtualClosure);
|
|
localizationLinks = graph::filterLinks(localizationLinks, Link::kSelfRefLink);
|
|
if(!landmarksDetected.empty() && !_memory->isIncremental())
|
|
{
|
|
for(std::map<int, std::set<int> >::iterator iter=landmarksDetected.begin(); iter!=landmarksDetected.end(); ++iter)
|
|
{
|
|
if(_optimizedPoses.find(iter->first)!=_optimizedPoses.end())
|
|
{
|
|
UASSERT(uContains(signature->getLandmarks(), iter->first));
|
|
localizationLinks.insert(std::make_pair(iter->first, signature->getLandmarks().at(iter->first)));
|
|
}
|
|
}
|
|
}
|
|
|
|
bool allLocalizationLinksInGraph = !localizationLinks.empty();
|
|
for(std::multimap<int, Link>::iterator iter=localizationLinks.begin(); iter!=localizationLinks.end(); ++iter)
|
|
{
|
|
if(!uContains(_optimizedPoses, iter->first))
|
|
{
|
|
allLocalizationLinksInGraph = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Note that in localization mode, we don't re-optimize the graph
|
|
// if:
|
|
// 1- there are no signatures retrieved,
|
|
// 2- we are relocalizing on a node already in the optimized graph
|
|
if(!_memory->isIncremental() &&
|
|
signaturesRetrieved.empty() &&
|
|
!localizationLinks.empty() &&
|
|
allLocalizationLinksInGraph)
|
|
{
|
|
bool rejectLocalization = _odomCachePoses.empty();
|
|
if(!_odomCachePoses.empty())
|
|
{
|
|
// Verify if the new localization is valid by checking if there is
|
|
// not too much deformation using current odometry poses
|
|
// This will also refine localization links
|
|
|
|
std::map<int, Transform> poses = _odomCachePoses;
|
|
std::multimap<int, Link> constraints = _odomCacheConstraints;
|
|
// add self referring links (e.g., gravity)
|
|
std::multimap<int, Link> selfLinks = graph::filterLinks(signature->getLinks(), Link::kSelfRefLink, true);
|
|
if(_graphOptimizer->priorsIgnored())
|
|
{
|
|
selfLinks = graph::filterLinks(selfLinks, Link::kPosePrior);
|
|
}
|
|
constraints.insert(selfLinks.begin(), selfLinks.end());
|
|
for(std::multimap<int, Link>::iterator iter=localizationLinks.begin(); iter!=localizationLinks.end(); ++iter)
|
|
{
|
|
constraints.insert(std::make_pair(iter->second.from(), iter->second));
|
|
}
|
|
|
|
cv::Mat priorInfMat = cv::Mat::eye(6,6, CV_64FC1)*_localizationPriorInf;
|
|
for(std::multimap<int, Link>::iterator iter=constraints.begin(); iter!=constraints.end(); ++iter)
|
|
{
|
|
std::map<int, Transform>::iterator iterPose = _optimizedPoses.find(iter->second.to());
|
|
if(iterPose != _optimizedPoses.end() && poses.find(iterPose->first) == poses.end())
|
|
{
|
|
poses.insert(*iterPose);
|
|
|
|
// make the poses in the map fixed
|
|
constraints.insert(std::make_pair(iterPose->first, Link(iterPose->first, iterPose->first, Link::kPosePrior, iterPose->second, priorInfMat)));
|
|
UDEBUG("Constraint %d->%d: %s (type=%s, var=%f)", iterPose->first, iterPose->first, iterPose->second.prettyPrint().c_str(), Link::typeName(Link::kPosePrior).c_str(), 1./_localizationPriorInf);
|
|
}
|
|
UDEBUG("Constraint %d->%d: %s (type=%s, var = %f %f)", iter->second.from(), iter->second.to(), iter->second.transform().prettyPrint().c_str(), iter->second.typeName().c_str(), iter->second.transVariance(), iter->second.rotVariance());
|
|
}
|
|
|
|
std::map<int, Transform> posesOut;
|
|
std::multimap<int, Link> edgeConstraintsOut;
|
|
|
|
bool priorsIgnored = _graphOptimizer->priorsIgnored();
|
|
UDEBUG("priorsIgnored was %s", priorsIgnored?"true":"false");
|
|
_graphOptimizer->setPriorsIgnored(false); //temporary set false to use priors above to fix nodes of the map
|
|
|
|
// If slam2d: get connected graph while keeping original roll,pitch,z values.
|
|
_graphOptimizer->getConnectedGraph(signature->id(), poses, constraints, posesOut, edgeConstraintsOut);
|
|
|
|
if(ULogger::level() == ULogger::kDebug)
|
|
{
|
|
for(std::map<int, Transform>::iterator iter=posesOut.begin(); iter!=posesOut.end(); ++iter)
|
|
{
|
|
UDEBUG("Pose %d %s", iter->first, iter->second.prettyPrint().c_str());
|
|
}
|
|
}
|
|
cv::Mat locOptCovariance;
|
|
std::map<int, Transform> optPoses;
|
|
if(!posesOut.empty() &&
|
|
posesOut.begin()->first < _odomCachePoses.begin()->first)
|
|
{
|
|
optPoses = _graphOptimizer->optimize(posesOut.begin()->first, posesOut, edgeConstraintsOut, locOptCovariance, 0, &optimizationError, &optimizationIterations);
|
|
}
|
|
else
|
|
{
|
|
UERROR("Invalid localization constraints");
|
|
}
|
|
_graphOptimizer->setPriorsIgnored(priorsIgnored); // set back
|
|
for(std::map<int, Transform>::iterator iter=optPoses.begin(); iter!=optPoses.end(); ++iter)
|
|
{
|
|
UDEBUG("Opt %d %s", iter->first, iter->second.prettyPrint().c_str());
|
|
}
|
|
|
|
std::list<std::pair<int, int> > removedLinks;
|
|
if(optPoses.empty())
|
|
{
|
|
UWARN("Optimization failed, rejecting localization!");
|
|
rejectLocalization = true;
|
|
}
|
|
else
|
|
{
|
|
UINFO("Compute max graph errors...");
|
|
maxGraphErrors = graph::computeMaxGraphErrors(
|
|
optPoses,
|
|
edgeConstraintsOut,
|
|
_graphOptimizer->isSlam2d());
|
|
if(!maxGraphErrors.linearLink.isValid() && !maxGraphErrors.angularLink.isValid())
|
|
{
|
|
UWARN("Could not compute graph errors! Rejecting localization!");
|
|
rejectLocalization = true;
|
|
}
|
|
|
|
if(maxGraphErrors.linearLink.isValid())
|
|
{
|
|
maxGraphErrorsLinearIds = std::make_pair(maxGraphErrors.linearLink.from(), maxGraphErrors.linearLink.to());
|
|
UINFO("Max optimization linear error = %f m (link %d->%d, var=%f, ratio error/std=%f, thr=%f)",
|
|
maxGraphErrors.linear,
|
|
maxGraphErrors.linearLink.from(),
|
|
maxGraphErrors.linearLink.to(),
|
|
maxGraphErrors.linearLink.transVariance(),
|
|
maxGraphErrors.linear/sqrt(maxGraphErrors.linearLink.transVariance()),
|
|
_optimizationMaxError);
|
|
if(_optimizationMaxError > 0.0f && maxGraphErrors.linearRatio > _optimizationMaxError)
|
|
{
|
|
if( _optimizationMaxErrorRepairRadius > 0.0 &&
|
|
maxGraphErrorsLinearIds == _lastRejectedLoopClosureIds &&
|
|
graph::findLink(edgeConstraintsOut, maxGraphErrorsLinearIds.first, maxGraphErrorsLinearIds.second) != edgeConstraintsOut.end())
|
|
{
|
|
UWARN("We detected 2 consecutive loop closure rejections because of the same loop closure link (%d->%d), trying optimization again without that link...",
|
|
maxGraphErrorsLinearIds.first, maxGraphErrorsLinearIds.second);
|
|
|
|
UDEBUG("priorsIgnored was %s", priorsIgnored?"true":"false");
|
|
_graphOptimizer->setPriorsIgnored(false); //temporary set false to use priors above to fix nodes of the map
|
|
removedLinks = repairGraph(
|
|
maxGraphErrors,
|
|
optPoses,
|
|
edgeConstraintsOut,
|
|
optimizationError,
|
|
optimizationIterations,
|
|
locOptCovariance);
|
|
_graphOptimizer->setPriorsIgnored(priorsIgnored); // set back
|
|
|
|
if(removedLinks.empty())
|
|
{
|
|
UWARN("Optimization failed when trying to repair the graph.");
|
|
rejectLocalization = true;
|
|
}
|
|
}
|
|
else {
|
|
rejectLocalization = true;
|
|
}
|
|
|
|
if(rejectLocalization)
|
|
{
|
|
UWARN("Rejecting localization (%d <-> %d) in this "
|
|
"iteration because a wrong loop closure has been "
|
|
"detected after graph optimization, resulting in "
|
|
"a maximum graph error ratio of %f (edge %d->%d, type=%d, abs error=%f m, stddev=%f). The "
|
|
"maximum error ratio parameter \"%s\" is %f of std deviation.",
|
|
localizationLinks.rbegin()->second.from(),
|
|
localizationLinks.rbegin()->second.to(),
|
|
maxGraphErrors.linearRatio,
|
|
maxGraphErrors.linearLink.from(),
|
|
maxGraphErrors.linearLink.to(),
|
|
maxGraphErrors.linearLink.type(),
|
|
maxGraphErrors.linear,
|
|
sqrt(maxGraphErrors.linearLink.transVariance()),
|
|
Parameters::kRGBDOptimizeMaxError().c_str(),
|
|
_optimizationMaxError);
|
|
|
|
if(maxGraphErrors.linearLink.type() != Link::kNeighbor)
|
|
{
|
|
_lastRejectedLoopClosureIds = maxGraphErrorsLinearIds;
|
|
}
|
|
}
|
|
}
|
|
else if(_optimizationMaxError == 0.0f && maxGraphErrors.linearRatio>100 && !_graphOptimizer->isRobust())
|
|
{
|
|
UERROR("Huge optimization error detected!"
|
|
"Linear error ratio of %f (edge %d->%d, type=%d, abs error=%f m, stddev=%f). You may consider "
|
|
"enabling \"%s\" to reject those bad optimizations by setting it to a non null value!",
|
|
maxGraphErrors.linearRatio,
|
|
maxGraphErrors.linearLink.from(),
|
|
maxGraphErrors.linearLink.to(),
|
|
maxGraphErrors.linearLink.type(),
|
|
maxGraphErrors.linear,
|
|
sqrt(maxGraphErrors.linearLink.transVariance()),
|
|
Parameters::kRGBDOptimizeMaxError().c_str());
|
|
}
|
|
}
|
|
if(maxGraphErrors.angularLink.isValid())
|
|
{
|
|
maxGraphErrorsAngularIds = std::make_pair(maxGraphErrors.angularLink.from(), maxGraphErrors.angularLink.to());
|
|
UINFO("Max optimization angular error = %f deg (link %d->%d, var=%f, ratio error/std=%f, thr=%f)",
|
|
maxGraphErrors.angular*180.0f/CV_PI,
|
|
maxGraphErrors.angularLink.from(),
|
|
maxGraphErrors.angularLink.to(),
|
|
maxGraphErrors.angularLink.rotVariance(),
|
|
maxGraphErrors.angular/sqrt(maxGraphErrors.angularLink.rotVariance()),
|
|
_optimizationMaxError);
|
|
if(_optimizationMaxError > 0.0f && maxGraphErrors.angularRatio > _optimizationMaxError)
|
|
{
|
|
UWARN("Rejecting localization (%d <-> %d) in this "
|
|
"iteration because a wrong loop closure has been "
|
|
"detected after graph optimization, resulting in "
|
|
"a maximum graph error ratio of %f (edge %d->%d, type=%d, abs error=%f deg, stddev=%f). The "
|
|
"maximum error ratio parameter \"%s\" is %f of std deviation.",
|
|
localizationLinks.rbegin()->second.from(),
|
|
localizationLinks.rbegin()->second.to(),
|
|
maxGraphErrors.angularRatio,
|
|
maxGraphErrors.angularLink.from(),
|
|
maxGraphErrors.angularLink.to(),
|
|
maxGraphErrors.angularLink.type(),
|
|
maxGraphErrors.angular*180.0f/CV_PI,
|
|
sqrt(maxGraphErrors.angularLink.rotVariance()),
|
|
Parameters::kRGBDOptimizeMaxError().c_str(),
|
|
_optimizationMaxError);
|
|
rejectLocalization = true;
|
|
}
|
|
else if(_optimizationMaxError == 0.0f && maxGraphErrors.angularRatio>100 && !_graphOptimizer->isRobust())
|
|
{
|
|
UERROR("Huge optimization error detected!"
|
|
"Angular error ratio of %f (edge %d->%d, type=%d, abs error=%f m, stddev=%f). You may consider "
|
|
"enabling \"%s\" to reject those bad optimizations by setting it to a non null value!",
|
|
maxGraphErrors.angularRatio,
|
|
maxGraphErrors.angularLink.from(),
|
|
maxGraphErrors.angularLink.to(),
|
|
maxGraphErrors.angularLink.type(),
|
|
maxGraphErrors.angular*180.0f/CV_PI,
|
|
sqrt(maxGraphErrors.angularLink.rotVariance()),
|
|
Parameters::kRGBDOptimizeMaxError().c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
bool hasGlobalLoopClosuresOrLandmarks = false;
|
|
if(rejectLocalization &&
|
|
(_localizationSecondTryWithoutProximityLinks && !graph::filterLinks(constraints, Link::kLocalSpaceClosure, true).empty()))
|
|
{
|
|
// Let's try again without local loop closures
|
|
localizationLinks = graph::filterLinks(localizationLinks, Link::kLocalSpaceClosure);
|
|
constraints = graph::filterLinks(constraints, Link::kLocalSpaceClosure);
|
|
for(std::multimap<int, Link>::iterator iter=constraints.begin(); iter!=constraints.end() && !hasGlobalLoopClosuresOrLandmarks; ++iter)
|
|
{
|
|
hasGlobalLoopClosuresOrLandmarks =
|
|
iter->second.type() == Link::kGlobalClosure ||
|
|
iter->second.type() == Link::kLandmark;
|
|
}
|
|
if(hasGlobalLoopClosuresOrLandmarks && !localizationLinks.empty())
|
|
{
|
|
rejectLocalization = false;
|
|
UWARN("Global and loop closures seem not tallying together, try again to optimize without local loop closures...");
|
|
UDEBUG("priorsIgnored was %s", priorsIgnored?"true":"false");
|
|
_graphOptimizer->setPriorsIgnored(false); //temporary set false to use priors above to fix nodes of the map
|
|
// If slam2d: get connected graph while keeping original roll,pitch,z values.
|
|
_graphOptimizer->getConnectedGraph(signature->id(), poses, constraints, posesOut, edgeConstraintsOut);
|
|
optPoses.clear();
|
|
if(!posesOut.empty() &&
|
|
posesOut.begin()->first < _odomCachePoses.begin()->first)
|
|
{
|
|
optPoses = _graphOptimizer->optimize(posesOut.begin()->first, posesOut, edgeConstraintsOut, locOptCovariance, 0, &optimizationError, &optimizationIterations);
|
|
}
|
|
else
|
|
{
|
|
UERROR("Invalid localization constraints");
|
|
}
|
|
_graphOptimizer->setPriorsIgnored(priorsIgnored); // set back
|
|
for(std::map<int, Transform>::iterator iter=optPoses.begin(); iter!=optPoses.end(); ++iter)
|
|
{
|
|
UDEBUG("Opt2 %d %s", iter->first, iter->second.prettyPrint().c_str());
|
|
}
|
|
|
|
if(optPoses.empty())
|
|
{
|
|
UWARN("Optimization failed, rejecting localization!");
|
|
rejectLocalization = true;
|
|
}
|
|
else
|
|
{
|
|
UINFO("Compute max graph errors...");
|
|
maxGraphErrors = graph::computeMaxGraphErrors(
|
|
optPoses,
|
|
edgeConstraintsOut,
|
|
_graphOptimizer->isSlam2d());
|
|
if(!maxGraphErrors.linearLink.isValid() && !maxGraphErrors.angularLink.isValid())
|
|
{
|
|
UWARN("Could not compute graph errors! Rejecting localization!");
|
|
rejectLocalization = true;
|
|
}
|
|
|
|
if(maxGraphErrors.linearLink.isValid())
|
|
{
|
|
maxGraphErrorsLinearIds = std::make_pair(maxGraphErrors.linearLink.from(), maxGraphErrors.linearLink.to());
|
|
UINFO("Max optimization linear error = %f m (link %d->%d, var=%f, ratio error/std=%f, thr=%f)",
|
|
maxGraphErrors.linear,
|
|
maxGraphErrors.linearLink.from(),
|
|
maxGraphErrors.linearLink.to(),
|
|
maxGraphErrors.linearLink.transVariance(),
|
|
maxGraphErrors.linear/sqrt(maxGraphErrors.linearLink.transVariance()),
|
|
_optimizationMaxError);
|
|
if(_optimizationMaxError > 0.0f && maxGraphErrors.linearRatio > _optimizationMaxError)
|
|
{
|
|
UWARN("Rejecting localization (%d <-> %d) in this "
|
|
"iteration because a wrong loop closure has been "
|
|
"detected after graph optimization, resulting in "
|
|
"a maximum graph error ratio of %f (edge %d->%d, type=%d, abs error=%f m, stddev=%f). The "
|
|
"maximum error ratio parameter \"%s\" is %f of std deviation.",
|
|
localizationLinks.rbegin()->second.from(),
|
|
localizationLinks.rbegin()->second.to(),
|
|
maxGraphErrors.linearRatio,
|
|
maxGraphErrors.linearLink.from(),
|
|
maxGraphErrors.linearLink.to(),
|
|
maxGraphErrors.linearLink.type(),
|
|
maxGraphErrors.linear,
|
|
sqrt(maxGraphErrors.linearLink.transVariance()),
|
|
Parameters::kRGBDOptimizeMaxError().c_str(),
|
|
_optimizationMaxError);
|
|
rejectLocalization = true;
|
|
}
|
|
else if(_optimizationMaxError == 0.0f && maxGraphErrors.linearRatio>100 && !_graphOptimizer->isRobust())
|
|
{
|
|
UERROR("Huge optimization error detected!"
|
|
"Linear error ratio of %f (edge %d->%d, type=%d, abs error=%f m, stddev=%f). You may consider "
|
|
"enabling \"%s\" to reject those bad optimizations by setting it to a non null value!",
|
|
maxGraphErrors.linearRatio,
|
|
maxGraphErrors.linearLink.from(),
|
|
maxGraphErrors.linearLink.to(),
|
|
maxGraphErrors.linearLink.type(),
|
|
maxGraphErrors.linear,
|
|
sqrt(maxGraphErrors.linearLink.transVariance()),
|
|
Parameters::kRGBDOptimizeMaxError().c_str());
|
|
}
|
|
}
|
|
if(maxGraphErrors.angularLink.isValid())
|
|
{
|
|
maxGraphErrorsAngularIds = std::make_pair(maxGraphErrors.angularLink.from(), maxGraphErrors.angularLink.to());
|
|
UINFO("Max optimization angular error = %f deg (link %d->%d, var=%f, ratio error/std=%f, thr=%f)",
|
|
maxGraphErrors.angular*180.0f/CV_PI,
|
|
maxGraphErrors.angularLink.from(),
|
|
maxGraphErrors.angularLink.to(),
|
|
maxGraphErrors.angularLink.rotVariance(),
|
|
maxGraphErrors.angular/sqrt(maxGraphErrors.angularLink.rotVariance()),
|
|
_optimizationMaxError);
|
|
if(_optimizationMaxError > 0.0f && maxGraphErrors.angularRatio > _optimizationMaxError)
|
|
{
|
|
UWARN("Rejecting localization (%d <-> %d) in this "
|
|
"iteration because a wrong loop closure has been "
|
|
"detected after graph optimization, resulting in "
|
|
"a maximum graph error ratio of %f (edge %d->%d, type=%d, abs error=%f deg, stddev=%f). The "
|
|
"maximum error ratio parameter \"%s\" is %f of std deviation.",
|
|
localizationLinks.rbegin()->second.from(),
|
|
localizationLinks.rbegin()->second.to(),
|
|
maxGraphErrors.angularRatio,
|
|
maxGraphErrors.angularLink.from(),
|
|
maxGraphErrors.angularLink.to(),
|
|
maxGraphErrors.angularLink.type(),
|
|
maxGraphErrors.angular*180.0f/CV_PI,
|
|
sqrt(maxGraphErrors.angularLink.rotVariance()),
|
|
Parameters::kRGBDOptimizeMaxError().c_str(),
|
|
_optimizationMaxError);
|
|
rejectLocalization = true;
|
|
}
|
|
else if(_optimizationMaxError == 0.0f && maxGraphErrors.angularRatio>100 && !_graphOptimizer->isRobust())
|
|
{
|
|
UERROR("Huge optimization error detected!"
|
|
"Angular error ratio of %f (edge %d->%d, type=%d, abs error=%f m, stddev=%f). You may consider "
|
|
"enabling \"%s\" to reject those bad optimizations by setting it to a non null value!",
|
|
maxGraphErrors.angularRatio,
|
|
maxGraphErrors.angularLink.from(),
|
|
maxGraphErrors.angularLink.to(),
|
|
maxGraphErrors.angularLink.type(),
|
|
maxGraphErrors.angular*180.0f/CV_PI,
|
|
sqrt(maxGraphErrors.angularLink.rotVariance()),
|
|
Parameters::kRGBDOptimizeMaxError().c_str());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!rejectLocalization)
|
|
{
|
|
if(hasGlobalLoopClosuresOrLandmarks)
|
|
{
|
|
// We successfully optimize the graph without local loop closures,
|
|
// clear them as some of them may be wrong.
|
|
size_t before = _odomCacheConstraints.size();
|
|
_odomCacheConstraints = graph::filterLinks(_odomCacheConstraints, Link::kLocalSpaceClosure);
|
|
if(before != _odomCacheConstraints.size())
|
|
{
|
|
UWARN("Successfully optimized without local loop closures! Clearing them from local odometry cache. %ld/%ld have been removed.",
|
|
before - _odomCacheConstraints.size(), before);
|
|
}
|
|
else
|
|
{
|
|
UWARN("Successfully optimized without local loop closures!");
|
|
}
|
|
odomCacheProximityLinksCleared = before - _odomCacheConstraints.size();
|
|
}
|
|
else if(!removedLinks.empty())
|
|
{
|
|
// If we removed links (repaired the graph)
|
|
for(auto link: removedLinks)
|
|
{
|
|
UWARN("Removing link %d->%d from odometry cache", link.first, link.second);
|
|
auto iter = graph::findLink(_odomCacheConstraints, link.first, link.second);
|
|
if(iter!=_odomCacheConstraints.end()) {
|
|
_odomCacheConstraints.erase(iter);
|
|
}
|
|
}
|
|
UWARN("Successfully repaired the graph.");
|
|
maxGraphErrorsRemovedIds = removedLinks.front();
|
|
maxGraphErrorsRemovedCount = removedLinks.size();
|
|
}
|
|
|
|
// Count how many localization links are in the constraints
|
|
bool hadAlreadyLocalizationLinks = false;
|
|
for(std::multimap<int, Link>::iterator iter=_odomCacheConstraints.begin();
|
|
iter!=_odomCacheConstraints.end(); ++iter)
|
|
{
|
|
if(iter->second.type() == Link::kGlobalClosure ||
|
|
iter->second.type() == Link::kLocalSpaceClosure ||
|
|
iter->second.type() == Link::kLocalTimeClosure ||
|
|
iter->second.type() == Link::kUserClosure ||
|
|
iter->second.type() == Link::kNeighborMerged ||
|
|
iter->second.type() == Link::kLandmark)
|
|
{
|
|
hadAlreadyLocalizationLinks = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// update localization links
|
|
UASSERT(uContains(optPoses, signature->id()));
|
|
Transform newOptPoseInv = optPoses.at(signature->id()).inverse();
|
|
for(std::multimap<int, Link>::iterator iter=localizationLinks.begin(); iter!=localizationLinks.end(); ++iter)
|
|
{
|
|
if(!_localizationSmoothing)
|
|
{
|
|
// Add original link without optimization
|
|
UDEBUG("Adding new odom cache constraint %d->%d (%s)",
|
|
iter->second.from(), iter->second.to(), iter->second.transform().prettyPrint().c_str());
|
|
}
|
|
else
|
|
{
|
|
// Adjust with optimized poses, this will smooth the localization
|
|
UASSERT(uContains(optPoses, iter->first));
|
|
Transform newT = newOptPoseInv * optPoses.at(iter->first);
|
|
UDEBUG("Adjusted localization link %d->%d after optimization", iter->second.from(), iter->second.to());
|
|
UDEBUG("from %s", iter->second.transform().prettyPrint().c_str());
|
|
UDEBUG(" to %s", newT.prettyPrint().c_str());
|
|
iter->second.setTransform(newT);
|
|
|
|
// Update link in the referred signatures
|
|
if(iter->first > 0)
|
|
_memory->updateLink(iter->second, false);
|
|
}
|
|
|
|
_odomCacheConstraints.insert(std::make_pair(signature->id(), iter->second));
|
|
}
|
|
|
|
_odomCacheConstraints.insert(selfLinks.begin(), selfLinks.end());
|
|
|
|
// At least 2 localizations at 2 different time required
|
|
if(hadAlreadyLocalizationLinks || _maxOdomCacheSize == 0)
|
|
{
|
|
UINFO("Update localization");
|
|
|
|
// update odomCachePoses with optimized poses (but make sure to put them back in odom frame)
|
|
Transform mapToOdomCache = signature->getPose() * newOptPoseInv;
|
|
for(std::map<int, Transform>::iterator iter = _odomCachePoses.begin(); iter!=_odomCachePoses.end(); ++iter)
|
|
{
|
|
iter->second = mapToOdomCache * optPoses.at(iter->first);
|
|
}
|
|
|
|
if(_optimizeFromGraphEnd)
|
|
{
|
|
// update all previous nodes
|
|
// Normally _mapCorrection should be identity, but if _optimizeFromGraphEnd
|
|
// parameters just changed state, we should put back all poses without map correction.
|
|
Transform oldPose = _optimizedPoses.at(localizationLinks.rbegin()->first);
|
|
Transform mapCorrectionInv = _mapCorrection.inverse();
|
|
Transform u = signature->getPose() * localizationLinks.rbegin()->second.transform();
|
|
if(_graphOptimizer->gravitySigma() > 0)
|
|
{
|
|
// Adjust transform with gravity
|
|
Transform transform = localizationLinks.rbegin()->second.transform();
|
|
int loopId = localizationLinks.rbegin()->first;
|
|
int landmarkId = 0;
|
|
if(loopId<0)
|
|
{
|
|
//For landmarks, use transform against other node looking the landmark
|
|
// (because we don't assume that landmarks are aligned with gravity)
|
|
landmarkId = loopId;
|
|
UASSERT(landmarksDetected.find(landmarkId) != landmarksDetected.end() &&
|
|
!landmarksDetected.at(landmarkId).empty());
|
|
loopId = *landmarksDetected.at(landmarkId).begin();
|
|
}
|
|
|
|
const Signature * loopS = _memory->getSignature(loopId);
|
|
UASSERT(loopS !=0);
|
|
std::multimap<int, Link>::const_iterator iterGravityLoop = graph::findLink(loopS->getLinks(), loopS->id(), loopS->id(), false, Link::kGravity);
|
|
std::multimap<int, Link>::const_iterator iterGravitySign = graph::findLink(signature->getLinks(), signature->id(), signature->id(), false, Link::kGravity);
|
|
if(iterGravityLoop!=loopS->getLinks().end() &&
|
|
iterGravitySign!=signature->getLinks().end())
|
|
{
|
|
float roll,pitch,yaw;
|
|
if(landmarkId < 0)
|
|
{
|
|
iterGravityLoop->second.transform().getEulerAngles(roll, pitch, yaw);
|
|
Transform gravityCorr = Transform(_optimizedPoses.at(loopS->id()).x(),
|
|
_optimizedPoses.at(loopS->id()).y(),
|
|
_optimizedPoses.at(loopS->id()).z(),
|
|
roll, pitch, _optimizedPoses.at(loopS->id()).theta()) * _optimizedPoses.at(loopS->id()).inverse();
|
|
(gravityCorr * _optimizedPoses.at(landmarkId)).getEulerAngles(roll,pitch,yaw);
|
|
}
|
|
else
|
|
{
|
|
iterGravityLoop->second.transform().getEulerAngles(roll, pitch, yaw);
|
|
}
|
|
Transform targetRotation = iterGravitySign->second.transform().rotation()*transform.rotation();
|
|
targetRotation = Transform(0,0,0,roll,pitch,targetRotation.theta());
|
|
Transform error = transform.rotation().inverse() * iterGravitySign->second.transform().rotation().inverse() * targetRotation;
|
|
transform *= error;
|
|
u = signature->getPose() * transform;
|
|
}
|
|
else if(iterGravityLoop!=loopS->getLinks().end() ||
|
|
iterGravitySign!=signature->getLinks().end())
|
|
{
|
|
UWARN("Gravity link not found for %d or %d, localization won't be corrected with gravity.", loopId, signature->id());
|
|
}
|
|
}
|
|
Transform up = u * oldPose.inverse();
|
|
if(_graphOptimizer->isSlam2d())
|
|
{
|
|
// in case of 3d landmarks, transform constraint to 2D
|
|
up.to3DoF();
|
|
}
|
|
for(std::map<int, Transform>::iterator iter=_optimizedPoses.begin(); iter!=_optimizedPoses.end(); ++iter)
|
|
{
|
|
iter->second = mapCorrectionInv * up * iter->second;
|
|
}
|
|
_optimizedPoses.at(signature->id()) = signature->getPose();
|
|
}
|
|
else
|
|
{
|
|
Transform newPose = _optimizedPoses.at(localizationLinks.rbegin()->first) * localizationLinks.rbegin()->second.transform().inverse();
|
|
UDEBUG("newPose=%s", newPose.prettyPrint().c_str());
|
|
if(_graphOptimizer->isSlam2d() && signature->getPose().is3DoF())
|
|
{
|
|
// in case of 3d landmarks, transform constraint to 2D
|
|
newPose = newPose.to3DoF();
|
|
UDEBUG("newPose 2D=%s", newPose.prettyPrint().c_str());
|
|
}
|
|
else if(_graphOptimizer->gravitySigma() > 0)
|
|
{
|
|
// Adjust transform with gravity
|
|
std::multimap<int, Link>::const_iterator iterGravitySign = graph::findLink(signature->getLinks(), signature->id(), signature->id(), false, Link::kGravity);
|
|
if(iterGravitySign!=signature->getLinks().end())
|
|
{
|
|
Transform transform = localizationLinks.rbegin()->second.transform();
|
|
float roll,pitch,yaw;
|
|
float tmp1,tmp2;
|
|
UDEBUG("Gravity link = %s", iterGravitySign->second.transform().prettyPrint().c_str());
|
|
_optimizedPoses.at(localizationLinks.rbegin()->first).getEulerAngles(roll, pitch, yaw);
|
|
Transform targetRotation = iterGravitySign->second.transform().rotation()*transform.rotation();
|
|
targetRotation = Transform(0,0,0,roll,pitch,targetRotation.theta());
|
|
Transform error = transform.rotation().inverse() * iterGravitySign->second.transform().rotation().inverse() * targetRotation;
|
|
transform *= error;
|
|
newPose = _optimizedPoses.at(localizationLinks.rbegin()->first) * transform.inverse();
|
|
iterGravitySign->second.transform().getEulerAngles(roll, pitch, tmp1);
|
|
newPose.getEulerAngles(tmp1, tmp2, yaw);
|
|
newPose = Transform(newPose.x(), newPose.y(), newPose.z(), roll, pitch, yaw);
|
|
UDEBUG("newPose gravity=%s", newPose.prettyPrint().c_str());
|
|
}
|
|
else if(iterGravitySign!=signature->getLinks().end())
|
|
{
|
|
UWARN("Gravity link not found for %d, localization won't be corrected with gravity.", signature->id());
|
|
}
|
|
}
|
|
_optimizedPoses.at(signature->id()) = newPose;
|
|
}
|
|
_localizationCovariance = locOptCovariance.empty()?localizationLinks.rbegin()->second.infMatrix().inv():locOptCovariance;
|
|
}
|
|
else //delayed localization (wait for more than 1 link)
|
|
{
|
|
UWARN("Localization was good, but waiting for another one to be more accurate (%s>0)", Parameters::kRGBDMaxOdomCacheSize().c_str());
|
|
delayedLocalization = true;
|
|
rejectLocalization = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(rejectLocalization)
|
|
{
|
|
_loopClosureHypothesis.first = 0;
|
|
lastProximitySpaceClosureId = 0;
|
|
rejectedLoopClosure = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UINFO("Update map correction");
|
|
std::map<int, Transform> poses = _optimizedPoses;
|
|
|
|
// if _optimizeFromGraphEnd parameter just changed state, don't use optimized poses as guess
|
|
if(_optimizeFromGraphEndChanged)
|
|
{
|
|
UWARN("Optimization: clearing guess poses as %s has changed state, now %s",
|
|
Parameters::kRGBDOptimizeFromGraphEnd().c_str(), _optimizeFromGraphEnd?"true":"false");
|
|
poses.clear();
|
|
_optimizeFromGraphEndChanged = false;
|
|
}
|
|
|
|
std::multimap<int, Link> constraints;
|
|
cv::Mat covariance;
|
|
optimizeCurrentMap(signature->id(), false, poses, covariance, &constraints, &optimizationError, &optimizationIterations);
|
|
|
|
// Check added loop closures have broken the graph
|
|
// (in case of wrong loop closures).
|
|
bool updateConstraints = true;
|
|
if(poses.empty())
|
|
{
|
|
UWARN("Graph optimization failed! Rejecting last loop closures added.");
|
|
for(std::list<std::pair<int, int> >::iterator iter=loopClosureLinksAdded.begin(); iter!=loopClosureLinksAdded.end(); ++iter)
|
|
{
|
|
_memory->removeLink(iter->first, iter->second);
|
|
UWARN("Loop closure %d->%d rejected!", iter->first, iter->second);
|
|
}
|
|
updateConstraints = false;
|
|
_loopClosureHypothesis.first = 0;
|
|
lastProximitySpaceClosureId = 0;
|
|
rejectedLoopClosure = true;
|
|
}
|
|
else if(_memory->isIncremental() &&
|
|
loopClosureLinksAdded.size() &&
|
|
optimizationIterations > 0 &&
|
|
constraints.size())
|
|
{
|
|
UINFO("Compute max graph errors...");
|
|
maxGraphErrors = graph::computeMaxGraphErrors(
|
|
poses,
|
|
constraints);
|
|
if(!maxGraphErrors.linearLink.isValid() && !maxGraphErrors.angularLink.isValid())
|
|
{
|
|
UWARN("Could not compute graph errors! Wrong loop closures could be accepted!");
|
|
}
|
|
|
|
bool reject = false;
|
|
if(maxGraphErrors.linearLink.isValid())
|
|
{
|
|
maxGraphErrorsLinearIds = std::make_pair(maxGraphErrors.linearLink.from(), maxGraphErrors.linearLink.to());
|
|
UINFO("Max optimization linear error = %f m (link %d->%d, var=%f, ratio error/std=%f)", maxGraphErrors.linear, maxGraphErrors.linearLink.from(), maxGraphErrors.linearLink.to(), maxGraphErrors.linearLink.transVariance(), maxGraphErrors.linear/sqrt(maxGraphErrors.linearLink.transVariance()));
|
|
if(_optimizationMaxError > 0.0f && maxGraphErrors.linearRatio > _optimizationMaxError)
|
|
{
|
|
if( _optimizationMaxErrorRepairRadius > 0.0 &&
|
|
maxGraphErrorsLinearIds == _lastRejectedLoopClosureIds &&
|
|
graph::findLink(constraints, maxGraphErrorsLinearIds.first, maxGraphErrorsLinearIds.second) != constraints.end())
|
|
{
|
|
UWARN("We detected 2 consecutive loop closure rejections because of the same loop closure link (%d->%d), trying optimization again without that link...",
|
|
maxGraphErrorsLinearIds.first, maxGraphErrorsLinearIds.second);
|
|
|
|
std::list<std::pair<int, int> > removedLinks = repairGraph(
|
|
maxGraphErrors,
|
|
poses,
|
|
constraints,
|
|
optimizationError,
|
|
optimizationIterations,
|
|
covariance);
|
|
|
|
if(removedLinks.empty())
|
|
{
|
|
UWARN("Optimization failed when trying to repair the graph.");
|
|
reject = true;
|
|
}
|
|
else
|
|
{
|
|
for(auto link: removedLinks)
|
|
{
|
|
UWARN("Removing link %d->%d from memory", link.first, link.second);
|
|
_memory->removeLink(link.first, link.second);
|
|
}
|
|
UWARN("Successfully repaired the graph.");
|
|
maxGraphErrorsRemovedIds = removedLinks.front();
|
|
maxGraphErrorsRemovedCount = removedLinks.size();
|
|
}
|
|
}
|
|
else {
|
|
reject = true;
|
|
}
|
|
|
|
if(reject)
|
|
{
|
|
UWARN("Rejecting all added loop closures (%d, first is %d <-> %d) in this "
|
|
"iteration because a wrong loop closure has been "
|
|
"detected after graph optimization, resulting in "
|
|
"a maximum graph error ratio of %f (edge %d->%d, type=%d, abs error=%f m, stddev=%f). The "
|
|
"maximum error ratio parameter \"%s\" is %f of std deviation.",
|
|
(int)loopClosureLinksAdded.size(),
|
|
loopClosureLinksAdded.front().first,
|
|
loopClosureLinksAdded.front().second,
|
|
maxGraphErrors.linearRatio,
|
|
maxGraphErrors.linearLink.from(),
|
|
maxGraphErrors.linearLink.to(),
|
|
maxGraphErrors.linearLink.type(),
|
|
maxGraphErrors.linear,
|
|
sqrt(maxGraphErrors.linearLink.transVariance()),
|
|
Parameters::kRGBDOptimizeMaxError().c_str(),
|
|
_optimizationMaxError);
|
|
|
|
if(maxGraphErrors.linearLink.type() != Link::kNeighbor)
|
|
{
|
|
_lastRejectedLoopClosureIds = maxGraphErrorsLinearIds;
|
|
}
|
|
}
|
|
}
|
|
else if(_optimizationMaxError == 0.0f && maxGraphErrors.linearRatio>100 && !_graphOptimizer->isRobust())
|
|
{
|
|
UERROR("Huge optimization error detected!"
|
|
"Linear error ratio of %f (edge %d->%d, type=%d, abs error=%f m, stddev=%f). You may consider "
|
|
"enabling \"%s\" to reject those bad optimizations by setting it to a non null value!",
|
|
maxGraphErrors.linearRatio,
|
|
maxGraphErrors.linearLink.from(),
|
|
maxGraphErrors.linearLink.to(),
|
|
maxGraphErrors.linearLink.type(),
|
|
maxGraphErrors.linear,
|
|
sqrt(maxGraphErrors.linearLink.transVariance()),
|
|
Parameters::kRGBDOptimizeMaxError().c_str());
|
|
}
|
|
}
|
|
|
|
if(maxGraphErrors.angularLink.isValid())
|
|
{
|
|
maxGraphErrorsAngularIds = std::make_pair(maxGraphErrors.angularLink.from(), maxGraphErrors.angularLink.to());
|
|
UINFO("Max optimization angular error = %f deg (link %d->%d, var=%f, ratio error/std=%f)", maxGraphErrors.angular*180.0f/CV_PI, maxGraphErrors.angularLink.from(), maxGraphErrors.angularLink.to(), maxGraphErrors.angularLink.rotVariance(), maxGraphErrors.angular/sqrt(maxGraphErrors.angularLink.rotVariance()));
|
|
if(_optimizationMaxError > 0.0f && maxGraphErrors.angularRatio > _optimizationMaxError)
|
|
{
|
|
UWARN("Rejecting all added loop closures (%d, first is %d <-> %d) in this "
|
|
"iteration because a wrong loop closure has been "
|
|
"detected after graph optimization, resulting in "
|
|
"a maximum graph error ratio of %f (edge %d->%d, type=%d, abs error=%f deg, stddev=%f). The "
|
|
"maximum error ratio parameter \"%s\" is %f of std deviation.",
|
|
(int)loopClosureLinksAdded.size(),
|
|
loopClosureLinksAdded.front().first,
|
|
loopClosureLinksAdded.front().second,
|
|
maxGraphErrors.angularRatio,
|
|
maxGraphErrors.angularLink.from(),
|
|
maxGraphErrors.angularLink.to(),
|
|
maxGraphErrors.angularLink.type(),
|
|
maxGraphErrors.angular*180.0f/CV_PI,
|
|
sqrt(maxGraphErrors.angularLink.rotVariance()),
|
|
Parameters::kRGBDOptimizeMaxError().c_str(),
|
|
_optimizationMaxError);
|
|
reject = true;
|
|
}
|
|
else if(_optimizationMaxError == 0.0f && maxGraphErrors.angularRatio>100 && !_graphOptimizer->isRobust())
|
|
{
|
|
UERROR("Huge optimization error detected!"
|
|
"Angular error ratio of %f (edge %d->%d, type=%d, abs error=%f m, stddev=%f). You may consider "
|
|
"enabling \"%s\" to reject those bad optimizations by setting it to a non null value!",
|
|
maxGraphErrors.angularRatio,
|
|
maxGraphErrors.angularLink.from(),
|
|
maxGraphErrors.angularLink.to(),
|
|
maxGraphErrors.angularLink.type(),
|
|
maxGraphErrors.angular*180.0f/CV_PI,
|
|
sqrt(maxGraphErrors.angularLink.rotVariance()),
|
|
Parameters::kRGBDOptimizeMaxError().c_str());
|
|
}
|
|
}
|
|
|
|
if(reject)
|
|
{
|
|
for(std::list<std::pair<int, int> >::iterator iter=loopClosureLinksAdded.begin(); iter!=loopClosureLinksAdded.end(); ++iter)
|
|
{
|
|
_memory->removeLink(iter->first, iter->second);
|
|
UWARN("Loop closure %d->%d rejected!", iter->first, iter->second);
|
|
}
|
|
updateConstraints = false;
|
|
_loopClosureHypothesis.first = 0;
|
|
lastProximitySpaceClosureId = 0;
|
|
rejectedLoopClosure = true;
|
|
}
|
|
}
|
|
|
|
if(updateConstraints)
|
|
{
|
|
UINFO("Updated local map (old size=%d, new size=%d)", (int)_optimizedPoses.size(), (int)poses.size());
|
|
_optimizedPoses = poses;
|
|
_constraints = constraints;
|
|
_localizationCovariance = covariance;
|
|
}
|
|
}
|
|
|
|
// Update map correction, it should be identify when optimizing from the last node
|
|
UASSERT(_optimizedPoses.find(signature->id()) != _optimizedPoses.end());
|
|
if(fakeOdom && _mapCorrectionBackup.isNull())
|
|
{
|
|
_mapCorrectionBackup = _mapCorrection;
|
|
}
|
|
previousMapCorrection = _mapCorrection;
|
|
_mapCorrection = _optimizedPoses.at(signature->id()) * signature->getPose().inverse();
|
|
// Update statistics about the closest node in the graph using the actual loop closure
|
|
if(!_memory->isIncremental() && !_lastLocalizationPose.isNull())
|
|
{
|
|
int closestNode = _loopClosureHypothesis.first>0?_loopClosureHypothesis.first:lastProximitySpaceClosureId;
|
|
if(closestNode>0)
|
|
{
|
|
distanceToClosestNodeInTheGraph = _lastLocalizationPose.getDistance(_optimizedPoses.at(closestNode));
|
|
UDEBUG("Last localization pose = %s, updated closest node=%d (%f m)", _lastLocalizationPose.prettyPrint().c_str(), closestNode, distanceToClosestNodeInTheGraph);
|
|
angleToClosestNodeInTheGraph = _lastLocalizationPose.getAngle(_optimizedPoses.at(closestNode));
|
|
}
|
|
}
|
|
_lastLocalizationPose = _optimizedPoses.at(signature->id()); // update
|
|
if(_mapCorrection.getNormSquared() > 0.1f && _optimizeFromGraphEnd)
|
|
{
|
|
bool hasPrior = signature->hasLink(signature->id());
|
|
if(!_graphOptimizer->priorsIgnored())
|
|
{
|
|
for(std::multimap<int, Link>::reverse_iterator iter=_constraints.rbegin(); !hasPrior && iter!=_constraints.rend(); ++iter)
|
|
{
|
|
if(iter->second.type() == Link::kPosePrior)
|
|
{
|
|
hasPrior = true;
|
|
}
|
|
}
|
|
}
|
|
if((!hasPrior || _graphOptimizer->priorsIgnored()) && _graphOptimizer->gravitySigma()==0.0f)
|
|
{
|
|
UERROR("Map correction should be identity when optimizing from the last node. T=%s", _mapCorrection.prettyPrint().c_str());
|
|
}
|
|
}
|
|
}
|
|
int newLocId = _loopClosureHypothesis.first>0?_loopClosureHypothesis.first:lastProximitySpaceClosureId>0?lastProximitySpaceClosureId:0;
|
|
_lastLocalizationNodeId = newLocId!=0?newLocId:_lastLocalizationNodeId;
|
|
if(newLocId==0 && !landmarksDetected.empty())
|
|
{
|
|
std::map<int, std::set<int> >::const_iterator iter = _memory->getLandmarksIndex().find(landmarksDetected.begin()->first);
|
|
if(iter!=_memory->getLandmarksIndex().end())
|
|
{
|
|
if(iter->second.size() && *iter->second.begin()!=signature->id())
|
|
{
|
|
_lastLocalizationNodeId = *iter->second.begin();
|
|
}
|
|
}
|
|
}
|
|
|
|
timeMapOptimization = timer.ticks();
|
|
ULOGGER_INFO("timeMapOptimization=%fs", timeMapOptimization);
|
|
|
|
//============================================================
|
|
// Prepare statistics
|
|
//============================================================
|
|
// Data used for the statistics event and for the log files
|
|
int dictionarySize = 0;
|
|
int refWordsCount = 0;
|
|
int refUniqueWordsCount = 0;
|
|
int lcHypothesisReactivated = 0;
|
|
float rehearsalValue = uValue(statistics_.data(), Statistics::kMemoryRehearsal_sim(), 0.0f);
|
|
int rehearsalMaxId = (int)uValue(statistics_.data(), Statistics::kMemoryRehearsal_merged(), 0.0f);
|
|
sLoop = _memory->getSignature(_loopClosureHypothesis.first?_loopClosureHypothesis.first:lastProximitySpaceClosureId?lastProximitySpaceClosureId:_highestHypothesis.first);
|
|
if(sLoop)
|
|
{
|
|
lcHypothesisReactivated = sLoop->isSaved()?1.0f:0.0f;
|
|
}
|
|
dictionarySize = (int)_memory->getVWDictionary()->getVisualWords().size();
|
|
refWordsCount = (int)signature->getWords().size();
|
|
refUniqueWordsCount = (int)uUniqueKeys(signature->getWords()).size();
|
|
|
|
if(_graphOptimizer->isSlam2d() && _localizationCovariance.total() == 36)
|
|
{
|
|
// set very small
|
|
_localizationCovariance.at<double>(2,2) = Registration::COVARIANCE_LINEAR_EPSILON;
|
|
_localizationCovariance.at<double>(3,3) = Registration::COVARIANCE_ANGULAR_EPSILON;
|
|
_localizationCovariance.at<double>(4,4) = Registration::COVARIANCE_ANGULAR_EPSILON;
|
|
}
|
|
|
|
// Posterior is empty if a bad signature is detected
|
|
float vpHypothesis = posterior.size()?posterior.at(Memory::kIdVirtual):0.0f;
|
|
int loopId = _loopClosureHypothesis.first>0?_loopClosureHypothesis.first:lastProximitySpaceClosureId;
|
|
|
|
// prepare statistics
|
|
if(_loopClosureHypothesis.first || _publishStats)
|
|
{
|
|
ULOGGER_INFO("sending stats...");
|
|
statistics_.setRefImageId(_memory->getLastSignatureId()); // Use last id from Memory (in case of rehearsal)
|
|
statistics_.setRefImageMapId(signature->mapId());
|
|
statistics_.setStamp(data.stamp());
|
|
if(_loopClosureHypothesis.first != Memory::kIdInvalid)
|
|
{
|
|
statistics_.setLoopClosureId(_loopClosureHypothesis.first);
|
|
statistics_.setLoopClosureMapId(_memory->getMapId(_loopClosureHypothesis.first));
|
|
ULOGGER_INFO("Loop closure detected! With id=%d", _loopClosureHypothesis.first);
|
|
}
|
|
|
|
if(_publishStats)
|
|
{
|
|
ULOGGER_INFO("send all stats...");
|
|
statistics_.setExtended(1);
|
|
|
|
statistics_.addStatistic(Statistics::kLoopAccepted_hypothesis_id(), _loopClosureHypothesis.first);
|
|
statistics_.addStatistic(Statistics::kLoopSuppressed_hypothesis_id(), loopIdSuppressedByProximity);
|
|
statistics_.addStatistic(Statistics::kLoopHighest_hypothesis_id(), _highestHypothesis.first);
|
|
statistics_.addStatistic(Statistics::kLoopHighest_hypothesis_value(), _highestHypothesis.second);
|
|
statistics_.addStatistic(Statistics::kLoopHypothesis_reactivated(), lcHypothesisReactivated);
|
|
statistics_.addStatistic(Statistics::kLoopVp_hypothesis(), vpHypothesis);
|
|
statistics_.addStatistic(Statistics::kLoopReactivate_id(), retrievalId);
|
|
statistics_.addStatistic(Statistics::kLoopHypothesis_ratio(), hypothesisRatio);
|
|
statistics_.addStatistic(Statistics::kLoopVisual_inliers(), loopClosureVisualInliers);
|
|
statistics_.addStatistic(Statistics::kLoopVisual_inliers_ratio(), loopClosureVisualInliersRatio);
|
|
statistics_.addStatistic(Statistics::kLoopVisual_matches(), loopClosureVisualMatches);
|
|
statistics_.addStatistic(Statistics::kLoopVisual_variance(), loopClosureVisualVariance);
|
|
statistics_.addStatistic(Statistics::kLoopLinear_variance(), loopClosureLinearVariance);
|
|
statistics_.addStatistic(Statistics::kLoopAngular_variance(), loopClosureAngularVariance);
|
|
statistics_.addStatistic(Statistics::kLoopLast_id(), _memory->getLastGlobalLoopClosureId());
|
|
if(maxGraphErrors.linear>=0 || maxGraphErrors.angular>=0)
|
|
{
|
|
statistics_.addStatistic(Statistics::kLoopOptimization_max_error(), maxGraphErrors.linear);
|
|
statistics_.addStatistic(Statistics::kLoopOptimization_max_error_ratio(), maxGraphErrors.linearRatio);
|
|
statistics_.addStatistic(Statistics::kLoopOptimization_max_ang_error(), maxGraphErrors.angular*180.0f/M_PI);
|
|
statistics_.addStatistic(Statistics::kLoopOptimization_max_ang_error_ratio(), maxGraphErrors.angularRatio);
|
|
statistics_.addStatistic(Statistics::kLoopOptimization_max_error_from_id(), maxGraphErrorsLinearIds.first);
|
|
statistics_.addStatistic(Statistics::kLoopOptimization_max_error_to_id(), maxGraphErrorsLinearIds.second);
|
|
statistics_.addStatistic(Statistics::kLoopOptimization_max_ang_error_from_id(), maxGraphErrorsAngularIds.first);
|
|
statistics_.addStatistic(Statistics::kLoopOptimization_max_ang_error_to_id(), maxGraphErrorsAngularIds.second);
|
|
|
|
if(_optimizationMaxErrorRepairRadius > 0)
|
|
{
|
|
statistics_.addStatistic(Statistics::kLoopOptimization_max_error_removed_from_id(), maxGraphErrorsRemovedIds.first);
|
|
statistics_.addStatistic(Statistics::kLoopOptimization_max_error_removed_to_id(), maxGraphErrorsRemovedIds.second);
|
|
statistics_.addStatistic(Statistics::kLoopOptimization_max_error_removed_count(), maxGraphErrorsRemovedCount);
|
|
}
|
|
}
|
|
statistics_.addStatistic(Statistics::kLoopOptimization_error(), optimizationError);
|
|
statistics_.addStatistic(Statistics::kLoopOptimization_iterations(), optimizationIterations);
|
|
statistics_.addStatistic(Statistics::kLoopLandmark_detected(), landmarksDetected.empty()?0:-landmarksDetected.begin()->first);
|
|
statistics_.addStatistic(Statistics::kLoopLandmark_detected_node_ref(), landmarksDetected.empty() || landmarksDetected.begin()->second.empty()?0:*landmarksDetected.begin()->second.begin());
|
|
statistics_.addStatistic(Statistics::kLoopVisual_inliers_mean_dist(), loopClosureVisualInliersMeanDist);
|
|
statistics_.addStatistic(Statistics::kLoopVisual_inliers_distribution(), loopClosureVisualInliersDistribution);
|
|
|
|
statistics_.addStatistic(Statistics::kProximityTime_detections(), proximityDetectionsInTimeFound);
|
|
statistics_.addStatistic(Statistics::kProximitySpace_detections_added_visually(), proximityDetectionsAddedVisually);
|
|
statistics_.addStatistic(Statistics::kProximitySpace_detections_added_icp_multi(), proximityDetectionsAddedByICPMulti);
|
|
statistics_.addStatistic(Statistics::kProximitySpace_detections_added_icp_global(), proximityDetectionsAddedByICPGlobal);
|
|
statistics_.addStatistic(Statistics::kProximitySpace_paths(), proximitySpacePaths);
|
|
statistics_.addStatistic(Statistics::kProximitySpace_visual_paths_checked(), localVisualPathsChecked);
|
|
statistics_.addStatistic(Statistics::kProximitySpace_scan_paths_checked(), localScanPathsChecked);
|
|
statistics_.addStatistic(Statistics::kProximitySpace_last_detection_id(), lastProximitySpaceClosureId);
|
|
statistics_.setProximityDetectionId(lastProximitySpaceClosureId);
|
|
statistics_.setProximityDetectionMapId(_memory->getMapId(lastProximitySpaceClosureId));
|
|
|
|
statistics_.addStatistic(Statistics::kLoopId(), loopId);
|
|
statistics_.addStatistic(Statistics::kLoopMap_id(), (loopId>0 && sLoop)?sLoop->mapId():-1);
|
|
|
|
statistics_.addStatistic(Statistics::kLoopDistance_since_last_loc(), _distanceTravelledSinceLastLocalization);
|
|
|
|
float x,y,z,roll,pitch,yaw;
|
|
if(_loopClosureHypothesis.first || lastProximitySpaceClosureId || (!rejectedLoopClosure && !landmarksDetected.empty()))
|
|
{
|
|
if(_loopClosureHypothesis.first || lastProximitySpaceClosureId)
|
|
{
|
|
// Loop closure transform
|
|
UASSERT(sLoop);
|
|
std::multimap<int, Link>::const_iterator loopIter = sLoop->getLinks().find(signature->id());
|
|
UASSERT(loopIter!=sLoop->getLinks().end());
|
|
UINFO("Set loop closure transform = %s", loopIter->second.transform().prettyPrint().c_str());
|
|
statistics_.setLoopClosureTransform(loopIter->second.transform());
|
|
|
|
statistics_.addStatistic(Statistics::kLoopVisual_words(), sLoop->getWords().size());
|
|
|
|
// if ground truth exists, compute localization error
|
|
if(!sLoop->getGroundTruthPose().isNull() && !signature->getGroundTruthPose().isNull())
|
|
{
|
|
Transform transformGT = sLoop->getGroundTruthPose().inverse() * signature->getGroundTruthPose();
|
|
statistics_.addStatistic(Statistics::kGtLocalization_linear_error(), loopIter->second.transform().getDistance(transformGT));
|
|
statistics_.addStatistic(Statistics::kGtLocalization_angular_error(), loopIter->second.transform().getAngle(transformGT)*180/M_PI);
|
|
}
|
|
}
|
|
|
|
_distanceTravelledSinceLastLocalization = 0.0f;
|
|
|
|
statistics_.addStatistic(Statistics::kLoopMapToOdom_norm(), _mapCorrection.getNorm());
|
|
statistics_.addStatistic(Statistics::kLoopMapToOdom_angle(), _mapCorrection.getAngle(Transform::getIdentity())*180.0f/M_PI);
|
|
_mapCorrection.getTranslationAndEulerAngles(x, y, z, roll, pitch, yaw);
|
|
statistics_.addStatistic(Statistics::kLoopMapToOdom_x(), x);
|
|
statistics_.addStatistic(Statistics::kLoopMapToOdom_y(), y);
|
|
statistics_.addStatistic(Statistics::kLoopMapToOdom_z(), z);
|
|
statistics_.addStatistic(Statistics::kLoopMapToOdom_roll(), roll*180.0f/M_PI);
|
|
statistics_.addStatistic(Statistics::kLoopMapToOdom_pitch(), pitch*180.0f/M_PI);
|
|
statistics_.addStatistic(Statistics::kLoopMapToOdom_yaw(), yaw*180.0f/M_PI);
|
|
|
|
// Odom correction (actual odometry pose change), ignore correction from first localization
|
|
if(!odomPose.isNull() && !previousMapCorrection.isNull() && !previousMapCorrection.isIdentity())
|
|
{
|
|
Transform odomCorrection = (previousMapCorrection*odomPose).inverse()*_mapCorrection*odomPose;
|
|
statistics_.addStatistic(Statistics::kLoopOdom_correction_norm(), odomCorrection.getNorm());
|
|
statistics_.addStatistic(Statistics::kLoopOdom_correction_angle(), odomCorrection.getAngle(Transform::getIdentity())*180.0f/M_PI);
|
|
odomCorrection.getTranslationAndEulerAngles(x, y, z, roll, pitch, yaw);
|
|
statistics_.addStatistic(Statistics::kLoopOdom_correction_x(), x);
|
|
statistics_.addStatistic(Statistics::kLoopOdom_correction_y(), y);
|
|
statistics_.addStatistic(Statistics::kLoopOdom_correction_z(), z);
|
|
statistics_.addStatistic(Statistics::kLoopOdom_correction_roll(), roll*180.0f/M_PI);
|
|
statistics_.addStatistic(Statistics::kLoopOdom_correction_pitch(), pitch*180.0f/M_PI);
|
|
statistics_.addStatistic(Statistics::kLoopOdom_correction_yaw(), yaw*180.0f/M_PI);
|
|
}
|
|
}
|
|
if(!_lastLocalizationPose.isNull() && !_lastLocalizationPose.isIdentity())
|
|
{
|
|
_lastLocalizationPose.getTranslationAndEulerAngles(x, y, z, roll, pitch, yaw);
|
|
statistics_.addStatistic(Statistics::kLoopMapToBase_x(), x);
|
|
statistics_.addStatistic(Statistics::kLoopMapToBase_y(), y);
|
|
statistics_.addStatistic(Statistics::kLoopMapToBase_z(), z);
|
|
statistics_.addStatistic(Statistics::kLoopMapToBase_roll(), roll*180.0f/M_PI);
|
|
statistics_.addStatistic(Statistics::kLoopMapToBase_pitch(), pitch*180.0f/M_PI);
|
|
statistics_.addStatistic(Statistics::kLoopMapToBase_yaw(), yaw*180.0f/M_PI);
|
|
UINFO("Localization pose = %s", _lastLocalizationPose.prettyPrint().c_str());
|
|
|
|
if(_localizationSecondTryWithoutProximityLinks) {
|
|
statistics_.addStatistic(Statistics::kLoopProximity_links_cleared(), (float)odomCacheProximityLinksCleared);
|
|
}
|
|
|
|
if(_localizationCovariance.total()==36)
|
|
{
|
|
double varLin = _graphOptimizer->isSlam2d()?
|
|
std::max(_localizationCovariance.at<double>(0,0), _localizationCovariance.at<double>(1,1)):
|
|
uMax3(_localizationCovariance.at<double>(0,0), _localizationCovariance.at<double>(1,1), _localizationCovariance.at<double>(2,2));
|
|
|
|
statistics_.addStatistic(Statistics::kLoopMapToBase_lin_std(), sqrt(varLin));
|
|
statistics_.addStatistic(Statistics::kLoopMapToBase_lin_var(), varLin);
|
|
}
|
|
}
|
|
|
|
statistics_.setMapCorrection(_mapCorrection);
|
|
UINFO("Set map correction = %s", _mapCorrection.prettyPrint().c_str());
|
|
statistics_.setLocalizationCovariance(_localizationCovariance);
|
|
|
|
// timings...
|
|
statistics_.addStatistic(Statistics::kTimingMemory_update(), timeMemoryUpdate*1000);
|
|
statistics_.addStatistic(Statistics::kTimingNeighbor_link_refining(), timeNeighborLinkRefining*1000);
|
|
statistics_.addStatistic(Statistics::kTimingProximity_by_time(), timeProximityByTimeDetection*1000);
|
|
statistics_.addStatistic(Statistics::kTimingProximity_by_space_search(), timeProximityBySpaceSearch*1000);
|
|
statistics_.addStatistic(Statistics::kTimingProximity_by_space_visual(), timeProximityBySpaceVisualDetection*1000);
|
|
statistics_.addStatistic(Statistics::kTimingProximity_by_space(), timeProximityBySpaceDetection*1000);
|
|
statistics_.addStatistic(Statistics::kTimingReactivation(), timeReactivations*1000);
|
|
statistics_.addStatistic(Statistics::kTimingAdd_loop_closure_link(), timeAddLoopClosureLink*1000);
|
|
statistics_.addStatistic(Statistics::kTimingMap_optimization(), timeMapOptimization*1000);
|
|
statistics_.addStatistic(Statistics::kTimingLikelihood_computation(), timeLikelihoodCalculation*1000);
|
|
statistics_.addStatistic(Statistics::kTimingPosterior_computation(), timePosteriorCalculation*1000);
|
|
statistics_.addStatistic(Statistics::kTimingHypotheses_creation(), timeHypothesesCreation*1000);
|
|
statistics_.addStatistic(Statistics::kTimingHypotheses_validation(), timeHypothesesValidation*1000);
|
|
statistics_.addStatistic(Statistics::kTimingCleaning_neighbors(), timeCleaningNeighbors*1000);
|
|
|
|
// retrieval
|
|
statistics_.addStatistic(Statistics::kMemorySignatures_retrieved(), (float)signaturesRetrieved.size());
|
|
|
|
// Feature specific parameters
|
|
statistics_.addStatistic(Statistics::kKeypointDictionary_size(), dictionarySize);
|
|
statistics_.addStatistic(Statistics::kKeypointCurrent_frame(), refWordsCount);
|
|
statistics_.addStatistic(Statistics::kKeypointIndexed_words(), _memory->getVWDictionary()->getIndexedWordsCount());
|
|
statistics_.addStatistic(Statistics::kKeypointIndex_memory_usage(), _memory->getVWDictionary()->getIndexMemoryUsed());
|
|
|
|
//Epipolar geometry constraint
|
|
statistics_.addStatistic(Statistics::kLoopRejectedHypothesis(), rejectedLoopClosure?1.0f:0);
|
|
|
|
statistics_.addStatistic(Statistics::kMemorySmall_movement(), smallDisplacement?1.0f:0);
|
|
statistics_.addStatistic(Statistics::kMemoryDistance_travelled(), _distanceTravelled);
|
|
statistics_.addStatistic(Statistics::kMemoryFast_movement(), tooFastMovement?1.0f:0);
|
|
statistics_.addStatistic(Statistics::kMemoryNew_landmark(), addedNewLandmark?1.0f:0);
|
|
|
|
if(distanceToClosestNodeInTheGraph>0.0)
|
|
{
|
|
statistics_.addStatistic(Statistics::kMemoryClosest_node_distance(), distanceToClosestNodeInTheGraph);
|
|
statistics_.addStatistic(Statistics::kMemoryClosest_node_angle(), angleToClosestNodeInTheGraph);
|
|
}
|
|
|
|
if(_publishRAMUsage)
|
|
{
|
|
UTimer ramTimer;
|
|
statistics_.addStatistic(Statistics::kMemoryRAM_usage(), UProcessInfo::getMemoryUsage()/(1024*1024));
|
|
long estimatedMemoryUsage = sizeof(Rtabmap);
|
|
estimatedMemoryUsage += _optimizedPoses.size() * (sizeof(int) + sizeof(Transform) + 12 * sizeof(float) + sizeof(std::map<int, Transform>::iterator)) + sizeof(std::map<int, Transform>);
|
|
estimatedMemoryUsage += _constraints.size() * (sizeof(int) + sizeof(Transform) + 12 * sizeof(float) + sizeof(cv::Mat) + 36 * sizeof(double) + sizeof(std::map<int, Link>::iterator)) + sizeof(std::map<int, Link>);
|
|
estimatedMemoryUsage += _memory->getMemoryUsed();
|
|
estimatedMemoryUsage += _bayesFilter->getMemoryUsed();
|
|
estimatedMemoryUsage += _parameters.size()*(sizeof(std::string)*2+sizeof(ParametersMap::iterator)) + sizeof(ParametersMap);
|
|
statistics_.addStatistic(Statistics::kMemoryRAM_estimated(), (float)(estimatedMemoryUsage/(1024*1024)));//MB
|
|
statistics_.addStatistic(Statistics::kTimingRAM_estimation(), ramTimer.ticks()*1000);
|
|
}
|
|
|
|
if(_publishLikelihood || _publishPdf)
|
|
{
|
|
// Child count by parent signature on the root of the memory ... for statistics
|
|
statistics_.setWeights(weights);
|
|
if(_publishPdf)
|
|
{
|
|
statistics_.setPosterior(posterior);
|
|
}
|
|
if(_publishLikelihood)
|
|
{
|
|
statistics_.setLikelihood(likelihood);
|
|
statistics_.setRawLikelihood(rawLikelihood);
|
|
}
|
|
}
|
|
|
|
statistics_.setLabels(_memory->getAllLabels());
|
|
|
|
// Path
|
|
if(_path.size())
|
|
{
|
|
statistics_.setLocalPath(this->getPathNextNodes());
|
|
statistics_.setCurrentGoalId(this->getPathCurrentGoalId());
|
|
}
|
|
}
|
|
|
|
timeStatsCreation = timer.ticks();
|
|
ULOGGER_INFO("Time creating stats = %f...", timeStatsCreation);
|
|
}
|
|
|
|
Signature lastSignatureData = *signature;
|
|
Transform lastSignatureLocalizedPose;
|
|
if(_optimizedPoses.find(signature->id()) != _optimizedPoses.end())
|
|
{
|
|
lastSignatureLocalizedPose = _optimizedPoses.at(signature->id());
|
|
}
|
|
if(!_publishLastSignatureData)
|
|
{
|
|
lastSignatureData.sensorData().clearCompressedData(true, true, true, false);
|
|
lastSignatureData.sensorData().clearRawData();
|
|
}
|
|
if(!_rawDataKept)
|
|
{
|
|
_memory->removeRawData(signature->id(), true, !_neighborLinkRefining && !_proximityBySpace, true, false);
|
|
}
|
|
|
|
// Localization mode and saving localization data: save odometry covariance in a prior link
|
|
// so that DBReader can republish the covariance of localization data
|
|
if(!_memory->isIncremental() && _memory->isLocalizationDataSaved() && !odomCovariance.empty())
|
|
{
|
|
_memory->addLink(Link(signature->id(), signature->id(), Link::kPosePrior, odomPose, odomCovariance.inv()));
|
|
}
|
|
bool lastSignatureWasIntermediateNode = signature->getWeight() == -1;
|
|
|
|
// remove last signature if the memory is not incremental or is a bad signature (if bad signatures are ignored)
|
|
int signatureRemoved = _memory->cleanup();
|
|
if(signatureRemoved)
|
|
{
|
|
signaturesRemoved.push_back(signatureRemoved);
|
|
}
|
|
|
|
// If this option activated, add new nodes only if there are linked with a previous map.
|
|
// Used when rtabmap is first started, it will wait a
|
|
// global loop closure detection before starting the new map,
|
|
// otherwise it deletes the current node.
|
|
if(signatureRemoved != lastSignatureData.id())
|
|
{
|
|
if(_startNewMapOnLoopClosure &&
|
|
_memory->isIncremental() && // only in mapping mode
|
|
graph::filterLinks(signature->getLinks(), Link::kSelfRefLink).size() == 0 && // alone in the current map
|
|
(landmarksDetected.empty() || rejectedLoopClosure) && // if we re not seeing a landmark from a previous map
|
|
_memory->getWorkingMem().size()>=2) // The working memory should not be empty (beside virtual signature)
|
|
{
|
|
UWARN("Ignoring location %d because a global loop closure is required before starting a new map!",
|
|
signature->id());
|
|
signaturesRemoved.push_back(signature->id());
|
|
_memory->deleteLocation(signature->id());
|
|
}
|
|
else if(_startNewMapOnGoodSignature &&
|
|
(signature->getLandmarks().empty() && signature->isBadSignature()) &&
|
|
graph::filterLinks(signature->getLinks(), Link::kSelfRefLink).size() == 0) // alone in the current map
|
|
{
|
|
UWARN("Ignoring location %d because a good signature (with enough features or with a landmark detected) is required before starting a new map!",
|
|
signature->id());
|
|
signaturesRemoved.push_back(signature->id());
|
|
_memory->deleteLocation(signature->id());
|
|
}
|
|
else if((!_memory->isIncremental() || signature->getWeight()>=0) &&
|
|
(smallDisplacement || tooFastMovement) &&
|
|
_loopClosureHypothesis.first == 0 &&
|
|
lastProximitySpaceClosureId == 0 &&
|
|
(rejectedLoopClosure || landmarksDetected.empty()) &&
|
|
!addedNewLandmark)
|
|
{
|
|
// Don't delete the location if a loop closure is detected
|
|
UINFO("Ignoring location %d because the displacement is too small! (d=%f a=%f)",
|
|
signature->id(), _rgbdLinearUpdate, _rgbdAngularUpdate);
|
|
// If there is a too small displacement, remove the node
|
|
signaturesRemoved.push_back(signature->id());
|
|
_memory->deleteLocation(signature->id());
|
|
|
|
// Update odom cache (if we just switched from mapping mode to localization mode)
|
|
_odomCachePoses.erase(signature->id());
|
|
for(std::multimap<int, Link>::iterator iter=_odomCacheConstraints.begin(); iter!=_odomCacheConstraints.end();)
|
|
{
|
|
if(iter->second.from() == signature->id() || iter->second.to() == signature->id())
|
|
{
|
|
_odomCacheConstraints.erase(iter++);
|
|
}
|
|
else
|
|
{
|
|
++iter;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_memory->saveLocationData(signature->id());
|
|
}
|
|
}
|
|
else if(!_memory->isIncremental() &&
|
|
(smallDisplacement || tooFastMovement) &&
|
|
_loopClosureHypothesis.first == 0 &&
|
|
lastProximitySpaceClosureId == 0 &&
|
|
!delayedLocalization)
|
|
{
|
|
_odomCachePoses.erase(signatureRemoved);
|
|
for(std::multimap<int, Link>::iterator iter=_odomCacheConstraints.begin(); iter!=_odomCacheConstraints.end();)
|
|
{
|
|
if(iter->second.from() == signatureRemoved || iter->second.to() == signatureRemoved)
|
|
{
|
|
_odomCacheConstraints.erase(iter++);
|
|
}
|
|
else
|
|
{
|
|
++iter;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Pass this point signature should not be used, since it could have been transferred...
|
|
signature = 0;
|
|
|
|
timeMemoryCleanup = timer.ticks();
|
|
ULOGGER_INFO("timeMemoryCleanup = %fs... %d signatures removed", timeMemoryCleanup, (int)signaturesRemoved.size());
|
|
|
|
|
|
|
|
//============================================================
|
|
// TRANSFER
|
|
//============================================================
|
|
// If time allowed for the detection exceeds the limit of
|
|
// real-time, move the oldest signature with less frequency
|
|
// entry (from X oldest) from the short term memory to the
|
|
// long term memory.
|
|
//============================================================
|
|
double totalTime = timerTotal.ticks();
|
|
ULOGGER_INFO("Total time processing = %fs...", totalTime);
|
|
if(!lastSignatureWasIntermediateNode && // skip memory management on intermediate nodes
|
|
((_maxTimeAllowed != 0 && totalTime*1000>_maxTimeAllowed) ||
|
|
(_maxMemoryAllowed != 0 && _memory->getWorkingMem().size() > _maxMemoryAllowed)))
|
|
{
|
|
if(_maxTimeAllowed!=0 && totalTime*1000>_maxTimeAllowed)
|
|
{
|
|
ULOGGER_INFO("Removing old signatures because time limit is reached %f ms > %f ms...",
|
|
totalTime*1000, _maxTimeAllowed);
|
|
}
|
|
if(_maxMemoryAllowed != 0 && _memory->getWorkingMem().size() > _maxMemoryAllowed)
|
|
{
|
|
ULOGGER_INFO("Removing old signatures because memory limit is reached %d > %d...",
|
|
_memory->getWorkingMem().size(), _maxMemoryAllowed);
|
|
}
|
|
immunizedLocations.insert(_lastLocalizationNodeId); // keep the latest localization in working memory
|
|
std::list<int> transferred = _memory->forget(immunizedLocations);
|
|
signaturesRemoved.insert(signaturesRemoved.end(), transferred.begin(), transferred.end());
|
|
if(!_someNodesHaveBeenTransferred && transferred.size())
|
|
{
|
|
_someNodesHaveBeenTransferred = true; // only used to hide a warning on close nodes immunization
|
|
}
|
|
}
|
|
_lastProcessTime = totalTime;
|
|
|
|
// cleanup cached gps values
|
|
for(std::list<int>::iterator iter=signaturesRemoved.begin(); iter!=signaturesRemoved.end() && _gpsGeocentricCache.size(); ++iter)
|
|
{
|
|
_gpsGeocentricCache.erase(*iter);
|
|
}
|
|
|
|
//Remove optimized poses from signatures transferred
|
|
if(signaturesRemoved.size() && (_optimizedPoses.size() || _constraints.size()))
|
|
{
|
|
//refresh the local map because some transferred nodes may have broken the tree
|
|
int id = 0;
|
|
if(!_memory->isIncremental() && (_lastLocalizationNodeId > 0 || _path.size()))
|
|
{
|
|
if(_path.size())
|
|
{
|
|
// priority on node on the path
|
|
UASSERT(_pathCurrentIndex < _path.size());
|
|
UASSERT_MSG(uContains(_optimizedPoses, _path.at(_pathCurrentIndex).first), uFormat("id=%d", _path.at(_pathCurrentIndex).first).c_str());
|
|
id = _path.at(_pathCurrentIndex).first;
|
|
UDEBUG("Refresh local map from %d", id);
|
|
}
|
|
else
|
|
{
|
|
if(uContains(_optimizedPoses, _lastLocalizationNodeId))
|
|
{
|
|
id = _lastLocalizationNodeId;
|
|
UDEBUG("Refresh local map from %d", id);
|
|
}
|
|
else
|
|
{
|
|
UDEBUG("Clearing _lastLocalizationNodeId(%d)", _lastLocalizationNodeId);
|
|
_lastLocalizationNodeId = 0;
|
|
}
|
|
}
|
|
}
|
|
else if(_memory->isIncremental() &&
|
|
_optimizedPoses.size() &&
|
|
_memory->getLastWorkingSignature(true))
|
|
{
|
|
id = _memory->getLastWorkingSignature(true)->id();
|
|
UDEBUG("Refresh local map from %d", id);
|
|
}
|
|
UDEBUG("id=%d _optimizedPoses=%d", id, (int)_optimizedPoses.size());
|
|
if(id > 0)
|
|
{
|
|
if(_lastLocalizationNodeId != 0)
|
|
{
|
|
_lastLocalizationNodeId = id;
|
|
}
|
|
UASSERT_MSG(_memory->getSignature(id) != 0, uFormat("id=%d", id).c_str());
|
|
|
|
if(signaturesRemoved.size() == 1 && signaturesRemoved.front() == lastSignatureData.id())
|
|
{
|
|
int lastId = signaturesRemoved.front();
|
|
UDEBUG("Detected that only last signature has been removed (lastId=%d)", lastId);
|
|
_optimizedPoses.erase(lastId);
|
|
for(std::multimap<int, Link>::iterator iter=_constraints.find(lastId); iter!=_constraints.end() && iter->first==lastId;++iter)
|
|
{
|
|
if(iter->second.to() != iter->second.from())
|
|
{
|
|
std::multimap<int, Link>::iterator jter = graph::findLink(_constraints, iter->second.to(), iter->second.from(), false);
|
|
if(jter != _constraints.end())
|
|
{
|
|
_constraints.erase(jter);
|
|
}
|
|
}
|
|
}
|
|
_constraints.erase(lastId);
|
|
}
|
|
else
|
|
{
|
|
|
|
std::map<int, int> ids = _memory->getNeighborsId(id, 0, 0, true);
|
|
for(std::map<int, Transform>::iterator iter=_optimizedPoses.begin(); iter!=_optimizedPoses.end();)
|
|
{
|
|
if(iter->first > 0 && !uContains(ids, iter->first))
|
|
{
|
|
UDEBUG("Removed %d from local map", iter->first);
|
|
UASSERT(iter->first != _lastLocalizationNodeId);
|
|
_optimizedPoses.erase(iter++);
|
|
|
|
if(!_globalScanMap.empty())
|
|
{
|
|
UWARN("optimized poses have been modified, clearing global scan map...");
|
|
_globalScanMap.clear();
|
|
_globalScanMapPoses.clear();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
++iter;
|
|
}
|
|
}
|
|
for(std::multimap<int, Link>::iterator iter=_constraints.begin(); iter!=_constraints.end();)
|
|
{
|
|
if(iter->first > 0 && (!uContains(ids, iter->second.from()) || !uContains(ids, iter->second.to())))
|
|
{
|
|
_constraints.erase(iter++);
|
|
}
|
|
else
|
|
{
|
|
++iter;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(!_optimizedPoses.empty())
|
|
UDEBUG("Optimized poses cleared!");
|
|
_optimizedPoses.clear();
|
|
_constraints.clear();
|
|
}
|
|
}
|
|
// just some verifications to make sure that planning path is still in the local map!
|
|
if(_path.size())
|
|
{
|
|
UASSERT(_pathCurrentIndex < _path.size());
|
|
UASSERT(_pathGoalIndex < _path.size());
|
|
UASSERT_MSG(uContains(_optimizedPoses, _path.at(_pathCurrentIndex).first), uFormat("local map size=%d, id=%d", (int)_optimizedPoses.size(), _path.at(_pathCurrentIndex).first).c_str());
|
|
UASSERT_MSG(uContains(_optimizedPoses, _path.at(_pathGoalIndex).first), uFormat("local map size=%d, id=%d", (int)_optimizedPoses.size(), _path.at(_pathGoalIndex).first).c_str());
|
|
}
|
|
|
|
|
|
timeRealTimeLimitReachedProcess = timer.ticks();
|
|
ULOGGER_INFO("Time limit reached processing = %f...", timeRealTimeLimitReachedProcess);
|
|
|
|
//==============================================================
|
|
// Finalize statistics and log files
|
|
//==============================================================
|
|
int localGraphSize = 0;
|
|
if(_publishStats)
|
|
{
|
|
statistics_.addStatistic(Statistics::kTimingStatistics_creation(), timeStatsCreation*1000);
|
|
statistics_.addStatistic(Statistics::kTimingTotal(), totalTime*1000);
|
|
statistics_.addStatistic(Statistics::kTimingForgetting(), timeRealTimeLimitReachedProcess*1000);
|
|
statistics_.addStatistic(Statistics::kTimingJoining_trash(), timeJoiningTrash*1000);
|
|
statistics_.addStatistic(Statistics::kTimingEmptying_trash(), timeEmptyingTrash*1000);
|
|
statistics_.addStatistic(Statistics::kTimingMemory_cleanup(), timeMemoryCleanup*1000);
|
|
|
|
// Transfer
|
|
statistics_.addStatistic(Statistics::kMemorySignatures_removed(), signaturesRemoved.size());
|
|
statistics_.addStatistic(Statistics::kMemoryImmunized_globally(), immunizedGlobally);
|
|
statistics_.addStatistic(Statistics::kMemoryImmunized_locally(), immunizedLocally);
|
|
statistics_.addStatistic(Statistics::kMemoryImmunized_locally_max(), maxLocalLocationsImmunized);
|
|
|
|
// place after transfer because the memory/local graph may have changed
|
|
statistics_.addStatistic(Statistics::kMemoryWorking_memory_size(), _memory->getWorkingMem().size());
|
|
statistics_.addStatistic(Statistics::kMemoryShort_time_memory_size(), _memory->getStMem().size());
|
|
statistics_.addStatistic(Statistics::kMemoryDatabase_memory_used(), _memory->getDatabaseMemoryUsed());
|
|
|
|
// Set local graph
|
|
std::map<int, Transform> poses;
|
|
std::multimap<int, Link> constraints;
|
|
if(!_rgbdSlamMode)
|
|
{
|
|
UDEBUG("");
|
|
// no optimization on appearance-only mode, create a local graph
|
|
std::map<int, int> ids = _memory->getNeighborsId(lastSignatureData.id(), 0, 0, true);
|
|
_memory->getMetricConstraints(uKeysSet(ids), poses, constraints, false);
|
|
}
|
|
else // RGBD-SLAM mode
|
|
{
|
|
poses = _optimizedPoses;
|
|
constraints = _constraints;
|
|
}
|
|
UINFO("Adding data %d [%d] (rgb/left=%d depth/right=%d)", lastSignatureData.id(), lastSignatureData.mapId(), lastSignatureData.sensorData().imageRaw().empty()?0:1, lastSignatureData.sensorData().depthOrRightRaw().empty()?0:1);
|
|
statistics_.addSignatureData(lastSignatureData);
|
|
|
|
if(_nodesToRepublish.size())
|
|
{
|
|
std::multimap<int, int> missingIds;
|
|
|
|
// priority to loopId
|
|
int tmpId = loopId>0?loopId:_highestHypothesis.first;
|
|
if(tmpId>0 && _nodesToRepublish.find(tmpId) != _nodesToRepublish.end())
|
|
{
|
|
missingIds.insert(std::make_pair(-1, tmpId));
|
|
}
|
|
|
|
if(!_lastLocalizationPose.isNull())
|
|
{
|
|
// Republish data from closest nodes of the current localization
|
|
std::map<int, Transform> nodesOnly(_optimizedPoses.lower_bound(1), _optimizedPoses.end());
|
|
int id = rtabmap::graph::findNearestNode(nodesOnly, _lastLocalizationPose);
|
|
if(id>0)
|
|
{
|
|
std::map<int, int> ids = _memory->getNeighborsId(id, 0, 0, true, false, true);
|
|
for(std::map<int, int>::iterator iter=ids.begin(); iter!=ids.end(); ++iter)
|
|
{
|
|
if(iter->first != loopId &&
|
|
_nodesToRepublish.find(iter->first) != _nodesToRepublish.end())
|
|
{
|
|
missingIds.insert(std::make_pair(iter->second, iter->first));
|
|
}
|
|
}
|
|
|
|
if(_nodesToRepublish.size() != missingIds.size())
|
|
{
|
|
// remove requested nodes not anymore in the graph
|
|
for(std::set<int>::iterator iter=_nodesToRepublish.begin(); iter!=_nodesToRepublish.end();)
|
|
{
|
|
if(ids.find(*iter) == ids.end())
|
|
{
|
|
iter = _nodesToRepublish.erase(iter);
|
|
}
|
|
else
|
|
{
|
|
++iter;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int loaded = 0;
|
|
std::stringstream stream;
|
|
for(std::multimap<int, int>::iterator iter=missingIds.begin(); iter!=missingIds.end() && loaded<(int)_maxRepublished; ++iter)
|
|
{
|
|
statistics_.addSignatureData(getSignatureCopy(iter->second, true, true, true, true, true, true));
|
|
_nodesToRepublish.erase(iter->second);
|
|
++loaded;
|
|
stream << iter->second << " ";
|
|
}
|
|
if(loaded)
|
|
{
|
|
UWARN("Republishing data of requested node(s) %s(%s=%d)",
|
|
stream.str().c_str(),
|
|
Parameters::kRtabmapMaxRepublished().c_str(),
|
|
_maxRepublished);
|
|
}
|
|
}
|
|
|
|
UDEBUG("");
|
|
localGraphSize = (int)poses.size();
|
|
if(!lastSignatureLocalizedPose.isNull())
|
|
{
|
|
poses.insert(std::make_pair(lastSignatureData.id(), lastSignatureLocalizedPose)); // in case we are in localization
|
|
}
|
|
statistics_.setPoses(poses);
|
|
statistics_.setConstraints(constraints);
|
|
|
|
statistics_.addStatistic(Statistics::kMemoryLocal_graph_size(), poses.size());
|
|
|
|
statistics_.setOdomCachePoses(_odomCachePoses);
|
|
statistics_.setOdomCacheConstraints(_odomCacheConstraints);
|
|
statistics_.addStatistic(Statistics::kMemoryOdom_cache_poses(), _odomCachePoses.size());
|
|
statistics_.addStatistic(Statistics::kMemoryOdom_cache_links(), _odomCacheConstraints.size());
|
|
|
|
if(_computeRMSE && _memory->getGroundTruths().size())
|
|
{
|
|
UDEBUG("Computing RMSE...");
|
|
float translational_rmse = 0.0f;
|
|
float translational_mean = 0.0f;
|
|
float translational_median = 0.0f;
|
|
float translational_std = 0.0f;
|
|
float translational_min = 0.0f;
|
|
float translational_max = 0.0f;
|
|
float rotational_rmse = 0.0f;
|
|
float rotational_mean = 0.0f;
|
|
float rotational_median = 0.0f;
|
|
float rotational_std = 0.0f;
|
|
float rotational_min = 0.0f;
|
|
float rotational_max = 0.0f;
|
|
|
|
graph::calcRMSE(
|
|
_memory->getGroundTruths(),
|
|
poses,
|
|
translational_rmse,
|
|
translational_mean,
|
|
translational_median,
|
|
translational_std,
|
|
translational_min,
|
|
translational_max,
|
|
rotational_rmse,
|
|
rotational_mean,
|
|
rotational_median,
|
|
rotational_std,
|
|
rotational_min,
|
|
rotational_max);
|
|
|
|
statistics_.addStatistic(Statistics::kGtTranslational_rmse(), translational_rmse);
|
|
statistics_.addStatistic(Statistics::kGtTranslational_mean(), translational_mean);
|
|
statistics_.addStatistic(Statistics::kGtTranslational_median(), translational_median);
|
|
statistics_.addStatistic(Statistics::kGtTranslational_std(), translational_std);
|
|
statistics_.addStatistic(Statistics::kGtTranslational_min(), translational_min);
|
|
statistics_.addStatistic(Statistics::kGtTranslational_max(), translational_max);
|
|
statistics_.addStatistic(Statistics::kGtRotational_rmse(), rotational_rmse);
|
|
statistics_.addStatistic(Statistics::kGtRotational_mean(), rotational_mean);
|
|
statistics_.addStatistic(Statistics::kGtRotational_median(), rotational_median);
|
|
statistics_.addStatistic(Statistics::kGtRotational_std(), rotational_std);
|
|
statistics_.addStatistic(Statistics::kGtRotational_min(), rotational_min);
|
|
statistics_.addStatistic(Statistics::kGtRotational_max(), rotational_max);
|
|
UDEBUG("Computing RMSE...done!");
|
|
}
|
|
|
|
std::vector<int> ids;
|
|
ids.reserve(_memory->getWorkingMem().size() + _memory->getStMem().size());
|
|
for(std::set<int>::const_iterator iter=_memory->getStMem().begin(); iter!=_memory->getStMem().end(); ++iter)
|
|
{
|
|
ids.push_back(*iter);
|
|
}
|
|
for(std::map<int, double>::const_iterator iter=_memory->getWorkingMem().lower_bound(0); iter!=_memory->getWorkingMem().end(); ++iter)
|
|
{
|
|
ids.push_back(iter->first);
|
|
}
|
|
statistics_.setWmState(ids);
|
|
UDEBUG("wmState=%d", (int)ids.size());
|
|
}
|
|
|
|
//Save statistics to database
|
|
if(_memory->isIncremental() || _memory->isLocalizationDataSaved())
|
|
{
|
|
_memory->saveStatistics(statistics_, _saveWMState);
|
|
}
|
|
|
|
//Start trashing
|
|
UDEBUG("Empty trash...");
|
|
_memory->emptyTrash();
|
|
|
|
// Log info...
|
|
// TODO : use a specific class which will handle the RtabmapEvent
|
|
if(_foutFloat && _foutInt)
|
|
{
|
|
UDEBUG("Logging...");
|
|
std::string logF = uFormat("%f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f\n",
|
|
totalTime,
|
|
timeMemoryUpdate,
|
|
timeReactivations,
|
|
timeLikelihoodCalculation,
|
|
timePosteriorCalculation,
|
|
timeHypothesesCreation,
|
|
timeHypothesesValidation,
|
|
timeRealTimeLimitReachedProcess,
|
|
timeStatsCreation,
|
|
_highestHypothesis.second,
|
|
0.0f,
|
|
0.0f,
|
|
0.0f,
|
|
0.0f,
|
|
0.0f,
|
|
vpHypothesis,
|
|
timeJoiningTrash,
|
|
rehearsalValue,
|
|
timeEmptyingTrash,
|
|
timeRetrievalDbAccess,
|
|
timeAddLoopClosureLink,
|
|
timeMemoryCleanup,
|
|
timeNeighborLinkRefining,
|
|
timeProximityByTimeDetection,
|
|
timeProximityBySpaceDetection,
|
|
timeMapOptimization);
|
|
std::string logI = uFormat("%d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d\n",
|
|
_loopClosureHypothesis.first,
|
|
_highestHypothesis.first,
|
|
(int)signaturesRemoved.size(),
|
|
0,
|
|
refWordsCount,
|
|
dictionarySize,
|
|
int(_memory->getWorkingMem().size()),
|
|
rejectedLoopClosure?1:0,
|
|
0,
|
|
0,
|
|
int(signaturesRetrieved.size()),
|
|
lcHypothesisReactivated,
|
|
refUniqueWordsCount,
|
|
retrievalId,
|
|
0,
|
|
rehearsalMaxId,
|
|
rehearsalMaxId>0?1:0,
|
|
localGraphSize,
|
|
data.id(),
|
|
_memory->getVWDictionary()->getIndexedWordsCount(),
|
|
_memory->getVWDictionary()->getIndexMemoryUsed());
|
|
if(_statisticLogsBufferedInRAM)
|
|
{
|
|
_bufferedLogsF.push_back(logF);
|
|
_bufferedLogsI.push_back(logI);
|
|
}
|
|
else
|
|
{
|
|
if(_foutFloat)
|
|
{
|
|
fprintf(_foutFloat, "%s", logF.c_str());
|
|
}
|
|
if(_foutInt)
|
|
{
|
|
fprintf(_foutInt, "%s", logI.c_str());
|
|
}
|
|
}
|
|
UINFO("Time logging = %f...", timer.ticks());
|
|
//ULogger::flush();
|
|
}
|
|
timeFinalizingStatistics = timer.ticks();
|
|
UDEBUG("End process, timeFinalizingStatistics=%fs", timeFinalizingStatistics);
|
|
if(_publishStats)
|
|
{
|
|
statistics_.addStatistic(Statistics::kTimingFinalizing_statistics(), timeFinalizingStatistics*1000);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// SETTERS
|
|
void Rtabmap::setTimeThreshold(float maxTimeAllowed)
|
|
{
|
|
//must be positive, 0 mean inf time allowed (no time limit)
|
|
_maxTimeAllowed = maxTimeAllowed;
|
|
if(_maxTimeAllowed < 0)
|
|
{
|
|
ULOGGER_WARN("maxTimeAllowed < 0, then setting it to 0 (inf).");
|
|
_maxTimeAllowed = 0;
|
|
}
|
|
else if(_maxTimeAllowed > 0.0f && _maxTimeAllowed < 1.0f)
|
|
{
|
|
ULOGGER_WARN("Time threshold set to %fms, it is not in seconds!", _maxTimeAllowed);
|
|
}
|
|
}
|
|
void Rtabmap::setMemoryThreshold(int maxMemoryAllowed)
|
|
{
|
|
//must be positive, 0 mean inf memory allowed (no memory limit)
|
|
_maxMemoryAllowed = maxMemoryAllowed;
|
|
if(maxMemoryAllowed < 0)
|
|
{
|
|
ULOGGER_WARN("maxMemoryAllowed < 0, then setting it to 0 (inf).");
|
|
_maxMemoryAllowed = 0;
|
|
}
|
|
}
|
|
|
|
void Rtabmap::setWorkingDirectory(std::string path)
|
|
{
|
|
path = uReplaceChar(path, '~', UDirectory::homeDir());
|
|
if(!path.empty() && UDirectory::exists(path))
|
|
{
|
|
ULOGGER_DEBUG("Comparing new working directory path \"%s\" with \"%s\"", path.c_str(), _wDir.c_str());
|
|
if(path.compare(_wDir) != 0)
|
|
{
|
|
if (_foutFloat || _foutInt)
|
|
{
|
|
UWARN("Working directory has been changed from \"%s\" with \"%s\", new log files will be created.",
|
|
path.c_str(), _wDir.c_str());
|
|
}
|
|
_wDir = path;
|
|
setupLogFiles();
|
|
}
|
|
}
|
|
else if(path.empty())
|
|
{
|
|
_wDir.clear();
|
|
setupLogFiles();
|
|
}
|
|
else
|
|
{
|
|
ULOGGER_ERROR("Directory \"%s\" doesn't exist!", path.c_str());
|
|
}
|
|
}
|
|
|
|
void Rtabmap::rejectLastLoopClosure()
|
|
{
|
|
if(!_memory)
|
|
{
|
|
return;
|
|
}
|
|
const Signature * lastS = _memory->getLastWorkingSignature(true); // last non-intermediate
|
|
if(lastS && _memory->getStMem().find(lastS->id())!=_memory->getStMem().end())
|
|
{
|
|
std::multimap<int, Link> links = _memory->getLinks(lastS->id(), false);
|
|
bool linksRemoved = false;
|
|
for(std::multimap<int, Link>::iterator iter = links.begin(); iter!=links.end(); ++iter)
|
|
{
|
|
if(iter->second.type() == Link::kGlobalClosure ||
|
|
iter->second.type() == Link::kLocalSpaceClosure ||
|
|
iter->second.type() == Link::kLocalTimeClosure ||
|
|
iter->second.type() == Link::kUserClosure)
|
|
{
|
|
_memory->removeLink(iter->second.from(), iter->second.to());
|
|
std::multimap<int, Link>::iterator jter = graph::findLink(_constraints, iter->second.from(), iter->second.to(), true);
|
|
if(jter!=_constraints.end())
|
|
{
|
|
_constraints.erase(jter);
|
|
// second time if link is also inverted
|
|
jter = graph::findLink(_constraints, iter->second.from(), iter->second.to(), true);
|
|
if(jter!=_constraints.end())
|
|
{
|
|
_constraints.erase(jter);
|
|
}
|
|
}
|
|
linksRemoved = true;
|
|
}
|
|
}
|
|
|
|
if(linksRemoved)
|
|
{
|
|
_loopClosureHypothesis.first = 0;
|
|
|
|
// we have to re-optimize the graph without the rejected links
|
|
if(_memory->isIncremental() && _optimizedPoses.size())
|
|
{
|
|
UINFO("Update graph");
|
|
std::map<int, Transform> poses = _optimizedPoses;
|
|
std::multimap<int, Link> constraints;
|
|
cv::Mat covariance;
|
|
optimizeCurrentMap(lastS->id(), false, poses, covariance, &constraints);
|
|
|
|
if(poses.empty())
|
|
{
|
|
UWARN("Graph optimization failed after removing loop closure links from last location!");
|
|
}
|
|
else
|
|
{
|
|
UINFO("Updated local map (old size=%d, new size=%d)", (int)_optimizedPoses.size(), (int)poses.size());
|
|
_optimizedPoses = poses;
|
|
_constraints = constraints;
|
|
_mapCorrection = _optimizedPoses.at(lastS->id()) * lastS->getPose().inverse();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Rtabmap::deleteLastLocation()
|
|
{
|
|
if(_memory && _memory->getStMem().size())
|
|
{
|
|
int lastId = *_memory->getStMem().rbegin();
|
|
const Signature * s = _memory->getSignature(lastId);
|
|
UASSERT(s);
|
|
if(s->getWeight() == -1)
|
|
{
|
|
UERROR("Deleting last location with inermediate nodes is not supported. Aborting.");
|
|
return;
|
|
}
|
|
_memory->deleteLocation(lastId);
|
|
// we have to re-optimize the graph without the deleted location
|
|
if(_memory->isIncremental() && _optimizedPoses.size())
|
|
{
|
|
UINFO("Update graph");
|
|
_optimizedPoses.erase(lastId);
|
|
std::map<int, Transform> poses = _optimizedPoses;
|
|
//remove all constraints with last localization id
|
|
for(std::multimap<int, Link>::iterator iter=_constraints.begin(); iter!=_constraints.end();)
|
|
{
|
|
if(iter->second.from() == lastId || iter->second.to() == lastId)
|
|
{
|
|
_constraints.erase(iter++);
|
|
}
|
|
else
|
|
{
|
|
++iter;
|
|
}
|
|
}
|
|
|
|
if(poses.empty())
|
|
{
|
|
_mapCorrection.setIdentity();
|
|
}
|
|
else
|
|
{
|
|
std::multimap<int, Link> constraints;
|
|
cv::Mat covariance;
|
|
optimizeCurrentMap(_memory->getLastWorkingSignature(true)->id(), false, poses, covariance, &constraints);
|
|
|
|
if(poses.empty())
|
|
{
|
|
UWARN("Graph optimization failed after deleting the last location!");
|
|
}
|
|
else
|
|
{
|
|
_optimizedPoses = poses;
|
|
_constraints = constraints;
|
|
_mapCorrection = _optimizedPoses.at(_memory->getLastWorkingSignature(true)->id()) * _memory->getLastWorkingSignature(true)->getPose().inverse();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Rtabmap::setOptimizedPoses(const std::map<int, Transform> & poses, const std::multimap<int, Link> & constraints)
|
|
{
|
|
_optimizedPoses = poses;
|
|
_constraints = constraints;
|
|
}
|
|
|
|
void Rtabmap::dumpData() const
|
|
{
|
|
UDEBUG("");
|
|
if(_memory)
|
|
{
|
|
if(this->getWorkingDir().empty())
|
|
{
|
|
UERROR("Working directory not set.");
|
|
}
|
|
else
|
|
{
|
|
_memory->dumpMemory(this->getWorkingDir());
|
|
}
|
|
}
|
|
}
|
|
|
|
// fromId must be in _memory and in _optimizedPoses
|
|
// Get poses in front of the robot, return optimized poses
|
|
std::map<int, Transform> Rtabmap::getForwardWMPoses(
|
|
int fromId,
|
|
int maxNearestNeighbors,
|
|
float radius,
|
|
int maxGraphDepth // 0 means ignore
|
|
) const
|
|
{
|
|
std::map<int, Transform> poses;
|
|
if(_memory && fromId > 0)
|
|
{
|
|
UDEBUG("");
|
|
const Signature * fromS = _memory->getSignature(fromId);
|
|
UASSERT(fromS != 0);
|
|
UASSERT(_optimizedPoses.find(fromId) != _optimizedPoses.end());
|
|
|
|
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
|
|
cloud->resize(_optimizedPoses.size());
|
|
std::vector<int> ids(_optimizedPoses.size());
|
|
int oi = 0;
|
|
const std::set<int> & stm = _memory->getStMem();
|
|
//get distances
|
|
std::map<int, float> foundIds;
|
|
if(_memory->isIncremental())
|
|
{
|
|
foundIds = _memory->getNeighborsIdRadius(fromId, radius, _optimizedPoses, maxGraphDepth);
|
|
}
|
|
else
|
|
{
|
|
foundIds = graph::findNearestNodes(fromId, _optimizedPoses, radius);
|
|
}
|
|
|
|
float radiusSqrd = radius * radius;
|
|
for(std::map<int, Transform>::const_iterator iter = _optimizedPoses.begin(); iter!=_optimizedPoses.end(); ++iter)
|
|
{
|
|
if(iter->first != fromId)
|
|
{
|
|
if(stm.find(iter->first) == stm.end() &&
|
|
uContains(foundIds, iter->first) &&
|
|
(radiusSqrd==0 || foundIds.at(iter->first) <= radiusSqrd))
|
|
{
|
|
(*cloud)[oi] = pcl::PointXYZ(iter->second.x(), iter->second.y(), iter->second.z());
|
|
ids[oi++] = iter->first;
|
|
}
|
|
}
|
|
}
|
|
|
|
cloud->resize(oi);
|
|
ids.resize(oi);
|
|
|
|
Transform fromT = _optimizedPoses.at(fromId);
|
|
|
|
if(cloud->size())
|
|
{
|
|
//if(cloud->size())
|
|
//{
|
|
// pcl::io::savePCDFile("radiusPoses.pcd", *cloud);
|
|
// UWARN("Saved radiusPoses.pcd");
|
|
//}
|
|
|
|
//filter poses in front of the fromId
|
|
float x,y,z, roll,pitch,yaw;
|
|
fromT.getTranslationAndEulerAngles(x,y,z, roll,pitch,yaw);
|
|
|
|
pcl::CropBox<pcl::PointXYZ> cropbox;
|
|
cropbox.setInputCloud(cloud);
|
|
cropbox.setMin(Eigen::Vector4f(-1, -radius, -999999, 0));
|
|
cropbox.setMax(Eigen::Vector4f(radius, radius, 999999, 0));
|
|
cropbox.setRotation(Eigen::Vector3f(roll, pitch, yaw));
|
|
cropbox.setTranslation(Eigen::Vector3f(x, y, z));
|
|
cropbox.setRotation(Eigen::Vector3f(roll,pitch,yaw));
|
|
pcl::IndicesPtr indices(new std::vector<int>());
|
|
cropbox.filter(*indices);
|
|
|
|
//if(indices->size())
|
|
//{
|
|
// pcl::io::savePCDFile("radiusCrop.pcd", *cloud, *indices);
|
|
// UWARN("Saved radiusCrop.pcd");
|
|
//}
|
|
|
|
if(indices->size())
|
|
{
|
|
pcl::search::KdTree<pcl::PointXYZ>::Ptr kdTree(new pcl::search::KdTree<pcl::PointXYZ>);
|
|
kdTree->setInputCloud(cloud, indices);
|
|
std::vector<int> ind;
|
|
std::vector<float> dist;
|
|
pcl::PointXYZ pt(fromT.x(), fromT.y(), fromT.z());
|
|
kdTree->radiusSearch(pt, radius, ind, dist, maxNearestNeighbors);
|
|
//pcl::PointCloud<pcl::PointXYZ> inliers;
|
|
for(unsigned int i=0; i<ind.size(); ++i)
|
|
{
|
|
if(ind[i] >=0)
|
|
{
|
|
Transform tmp = _optimizedPoses.find(ids[ind[i]])->second;
|
|
//inliers.push_back(pcl::PointXYZ(tmp.x(), tmp.y(), tmp.z()));
|
|
UDEBUG("Inlier %d: %s", ids[ind[i]], tmp.prettyPrint().c_str());
|
|
poses.insert(std::make_pair(ids[ind[i]], tmp));
|
|
}
|
|
}
|
|
|
|
//if(inliers.size())
|
|
//{
|
|
// pcl::io::savePCDFile("radiusInliers.pcd", inliers);
|
|
//}
|
|
//if(nearestId >0)
|
|
//{
|
|
// pcl::PointCloud<pcl::PointXYZ> c;
|
|
// Transform ct = _optimizedPoses.find(nearestId)->second;
|
|
// c.push_back(pcl::PointXYZ(ct.x(), ct.y(), ct.z()));
|
|
// pcl::io::savePCDFile("radiusNearestPt.pcd", c);
|
|
//}
|
|
}
|
|
}
|
|
}
|
|
return poses;
|
|
}
|
|
|
|
std::map<int, std::map<int, Transform> > Rtabmap::getPaths(const std::map<int, Transform> & posesIn, const Transform & target, int maxGraphDepth) const
|
|
{
|
|
std::map<int, std::map<int, Transform> > paths;
|
|
std::set<int> nodesSet;
|
|
std::map<int, Transform> poses;
|
|
for(std::map<int, Transform>::const_iterator iter=posesIn.lower_bound(1); iter!=posesIn.end(); ++iter)
|
|
{
|
|
nodesSet.insert(iter->first);
|
|
poses.insert(*iter);
|
|
}
|
|
if(_memory && nodesSet.size() && !target.isNull())
|
|
{
|
|
double e0=0,e1=0,e2=0,e3=0,e4=0;
|
|
UTimer t;
|
|
e0 = t.ticks();
|
|
// Segment poses connected only by neighbor links
|
|
while(poses.size())
|
|
{
|
|
std::map<int, Transform> path;
|
|
// select nearest pose and iterate neighbors from there
|
|
int nearestId = rtabmap::graph::findNearestNode(poses, target);
|
|
|
|
e1+=t.ticks();
|
|
|
|
if(nearestId == 0)
|
|
{
|
|
UWARN("Nearest id of %s in %d poses is 0 !? Returning empty path.", target.prettyPrint().c_str(), (int)poses.size());
|
|
break;
|
|
}
|
|
std::map<int, int> ids = _memory->getNeighborsId(nearestId, maxGraphDepth, 0, true, true, true, true, nodesSet);
|
|
|
|
e2+=t.ticks();
|
|
|
|
for(std::map<int, int>::iterator iter=ids.begin(); iter!=ids.end(); ++iter)
|
|
{
|
|
std::map<int, Transform>::iterator jter = poses.find(iter->first);
|
|
if(jter != poses.end())
|
|
{
|
|
bool valid = path.empty();
|
|
if(!valid)
|
|
{
|
|
// make sure it has a neighbor added to path
|
|
std::multimap<int, Link> links = _memory->getNeighborLinks(iter->first);
|
|
for(std::multimap<int, Link>::iterator kter=links.begin(); kter!=links.end() && !valid; ++kter)
|
|
{
|
|
valid = path.find(kter->first) != path.end();
|
|
}
|
|
}
|
|
|
|
if(valid)
|
|
{
|
|
//UDEBUG("%d <- %d", nearestId, jter->first);
|
|
path.insert(*jter);
|
|
poses.erase(jter);
|
|
}
|
|
}
|
|
}
|
|
|
|
e3+=t.ticks();
|
|
|
|
if (path.size())
|
|
{
|
|
if (maxGraphDepth > 0 && !_memory->isGraphReduced() && (int)path.size() > maxGraphDepth * 2 + 1)
|
|
{
|
|
UWARN("%s=Off but path(%d) > maxGraphDepth(%d)*2+1, nearestId=%d ids=%d. Is reduce graph activated before?",
|
|
Parameters::kMemReduceGraph().c_str(), (int)path.size(), maxGraphDepth, nearestId, (int)ids.size());
|
|
}
|
|
paths.insert(std::make_pair(nearestId, path));
|
|
}
|
|
else
|
|
{
|
|
UWARN("path.size()=0!? nearestId=%d ids=%d, aborting...", nearestId, (int)ids.size());
|
|
break;
|
|
}
|
|
|
|
e4+=t.ticks();
|
|
}
|
|
UDEBUG("e0=%fs e1=%fs e2=%fs e3=%fs e4=%fs", e0, e1, e2, e3, e4);
|
|
}
|
|
return paths;
|
|
}
|
|
|
|
void Rtabmap::optimizeCurrentMap(
|
|
int id,
|
|
bool lookInDatabase,
|
|
std::map<int, Transform> & optimizedPoses,
|
|
cv::Mat & covariance,
|
|
std::multimap<int, Link> * constraints,
|
|
double * error,
|
|
int * iterationsDone) const
|
|
{
|
|
//Optimize the map
|
|
UINFO("Optimize map: around location %d (lookInDatabase=%s)", id, lookInDatabase?"true":"false");
|
|
if(_memory && id > 0)
|
|
{
|
|
if(!lookInDatabase && (!_memory->getSignature(id) || _memory->getSignature(id)->getWeight() == -1))
|
|
{
|
|
UERROR("When doing a local optimization, the root id (%d) must exist and not be an intermediate node! Aborting...", id);
|
|
optimizedPoses.clear();
|
|
if(constraints)
|
|
{
|
|
constraints->clear();
|
|
}
|
|
return;
|
|
}
|
|
|
|
UTimer timer;
|
|
std::map<int, int> ids = _memory->getNeighborsId(id, 0, lookInDatabase?-1:0, true, false);
|
|
if(!_optimizeFromGraphEnd && ids.size() > 1)
|
|
{
|
|
if(lookInDatabase)
|
|
{
|
|
id = ids.begin()->first;
|
|
}
|
|
else
|
|
{
|
|
// Find first node that is not intermediate
|
|
for(auto pair: ids)
|
|
{
|
|
// Make sure fromId is not an intermediate node
|
|
const Signature * s = _memory->getSignature(pair.first);
|
|
if(s && s->getWeight() != -1)
|
|
{
|
|
id = pair.first;
|
|
break;
|
|
}
|
|
else if(!s)
|
|
{
|
|
UWARN("Not found node %d in memory?!", pair.first);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
UINFO("get %d ids time %f s", (int)ids.size(), timer.ticks());
|
|
|
|
std::map<int, Transform> poses = Rtabmap::optimizeGraph(id, uKeysSet(ids), optimizedPoses, lookInDatabase, covariance, constraints, error, iterationsDone);
|
|
UINFO("optimize time %f s", timer.ticks());
|
|
|
|
if(poses.size())
|
|
{
|
|
optimizedPoses = poses;
|
|
|
|
if(_memory->getSignature(id) && uContains(optimizedPoses, id))
|
|
{
|
|
Transform t = optimizedPoses.at(id) * _memory->getSignature(id)->getPose().inverse();
|
|
UINFO("Correction (from node %d) %s", id, t.prettyPrint().c_str());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UWARN("Failed to optimize the graph! returning empty optimized poses...");
|
|
optimizedPoses.clear();
|
|
if(constraints)
|
|
{
|
|
constraints->clear();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::map<int, Transform> Rtabmap::optimizeGraph(
|
|
int fromId,
|
|
const std::set<int> & ids,
|
|
const std::map<int, Transform> & guessPoses,
|
|
bool lookInDatabase,
|
|
cv::Mat & covariance,
|
|
std::multimap<int, Link> * constraints,
|
|
double * error,
|
|
int * iterationsDone) const
|
|
{
|
|
UTimer timer;
|
|
std::map<int, Transform> optimizedPoses;
|
|
std::map<int, Transform> poses;
|
|
std::multimap<int, Link> edgeConstraints;
|
|
UDEBUG("ids=%d", (int)ids.size());
|
|
_memory->getMetricConstraints(ids, poses, edgeConstraints, lookInDatabase, !_graphOptimizer->landmarksIgnored());
|
|
UINFO("get constraints (ids=%d, %d poses, %d edges) time %f s", (int)ids.size(), (int)poses.size(), (int)edgeConstraints.size(), timer.ticks());
|
|
|
|
// add landmark priors if there are some
|
|
for(std::map<int, Transform>::iterator iter=poses.begin(); iter!=poses.end() && iter->first < 0; ++iter)
|
|
{
|
|
if(_markerPriors.find(iter->first) != _markerPriors.end())
|
|
{
|
|
cv::Mat infMatrix = cv::Mat::eye(6, 6, CV_64FC1);
|
|
infMatrix(cv::Range(0,3), cv::Range(0,3)) /= _markerPriorsLinearVariance;
|
|
infMatrix(cv::Range(3,6), cv::Range(3,6)) /= _markerPriorsAngularVariance;
|
|
edgeConstraints.insert(std::make_pair(iter->first, Link(iter->first, iter->first, Link::kPosePrior, _markerPriors.at(iter->first), infMatrix)));
|
|
UDEBUG("Added prior %d : %s (variance: lin=%f ang=%f)", iter->first, _markerPriors.at(iter->first).prettyPrint().c_str(),
|
|
_markerPriorsLinearVariance, _markerPriorsAngularVariance);
|
|
}
|
|
}
|
|
|
|
if(_graphOptimizer->iterations() > 0)
|
|
{
|
|
for(std::map<int, Transform>::iterator iter=poses.begin(); iter!=poses.end(); ++iter)
|
|
{
|
|
// Apply guess poses (if some), ignore for rootid to avoid origin drifting
|
|
std::map<int, Transform>::const_iterator foundGuess = guessPoses.find(iter->first);
|
|
if(foundGuess!=guessPoses.end() && iter->first != fromId)
|
|
{
|
|
iter->second = foundGuess->second;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
UASSERT(_graphOptimizer!=0);
|
|
if(_graphOptimizer->iterations() == 0)
|
|
{
|
|
// Optimization disabled! Return not optimized poses.
|
|
optimizedPoses = poses;
|
|
if(constraints)
|
|
{
|
|
*constraints = edgeConstraints;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bool hasLandmarks = !edgeConstraints.empty() && edgeConstraints.begin()->first < 0;
|
|
if(poses.size() != guessPoses.size() || hasLandmarks)
|
|
{
|
|
UDEBUG("recompute poses using only links (robust to multi-session)");
|
|
std::map<int, Transform> posesOut;
|
|
std::multimap<int, Link> edgeConstraintsOut;
|
|
_graphOptimizer->getConnectedGraph(fromId, poses, edgeConstraints, posesOut, edgeConstraintsOut);
|
|
optimizedPoses = _graphOptimizer->optimize(fromId, posesOut, edgeConstraintsOut, covariance, 0, error, iterationsDone);
|
|
if(constraints)
|
|
{
|
|
*constraints = edgeConstraintsOut;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UDEBUG("use input guess poses");
|
|
optimizedPoses = _graphOptimizer->optimize(fromId, poses, edgeConstraints, covariance, 0, error, iterationsDone);
|
|
if(constraints)
|
|
{
|
|
*constraints = edgeConstraints;
|
|
}
|
|
}
|
|
|
|
if(!poses.empty() && optimizedPoses.empty())
|
|
{
|
|
UWARN("Optimization has failed (poses=%d, guess=%d, links=%d)...",
|
|
(int)poses.size(), (int)guessPoses.size(), (int)edgeConstraints.size());
|
|
}
|
|
}
|
|
|
|
UINFO("Optimization time %f s", timer.ticks());
|
|
|
|
return optimizedPoses;
|
|
}
|
|
|
|
// If repairing works, all input arguments are updated accordingly to new graph
|
|
// Returns IDs of the links removed from constraints
|
|
std::list<std::pair<int, int> > Rtabmap::repairGraph(
|
|
graph::MaxGraphErrors & maxGraphErrors,
|
|
std::map<int, Transform> & poses,
|
|
std::multimap<int, Link> & constraints,
|
|
double & optimizationError,
|
|
int & optimizationIterations,
|
|
cv::Mat & optimizationCovariance)
|
|
{
|
|
UASSERT(maxGraphErrors.linearLink.isValid());
|
|
UASSERT(graph::findLink(constraints, maxGraphErrors.linearLink.from(), maxGraphErrors.linearLink.to()) != constraints.end());
|
|
|
|
int originalMaxErrorLinkFrom = maxGraphErrors.linearLink.from();
|
|
int originalMaxErrorLinkTo = maxGraphErrors.linearLink.to();
|
|
|
|
graph::MaxGraphErrors subMaxGraphErrors = maxGraphErrors;
|
|
std::list<std::pair<int, int> > removedLinks;
|
|
std::map<int, Transform> subPoses = poses;
|
|
std::multimap<int, Link> subConstraints = constraints;
|
|
while(subMaxGraphErrors.linearLink.isValid() && subMaxGraphErrors.linearRatio > _optimizationMaxError)
|
|
{
|
|
removedLinks.push_back(std::make_pair(subMaxGraphErrors.linearLink.from(), subMaxGraphErrors.linearLink.to()));
|
|
subConstraints.erase(graph::findLink(subConstraints, subMaxGraphErrors.linearLink.from(), subMaxGraphErrors.linearLink.to()));
|
|
subMaxGraphErrors.linearLink = Link();
|
|
|
|
// Get connected graph just to check if a node got disconnected (localization mode)
|
|
std::map<int, Transform> posesOut;
|
|
std::multimap<int, Link> subConstraintsOut;
|
|
_graphOptimizer->getConnectedGraph(subPoses.rbegin()->first, subPoses, subConstraints, posesOut, subConstraintsOut);
|
|
if(subPoses.size() - posesOut.size() > 1)
|
|
{
|
|
UWARN("More than one pose filtered in one iteration when trying to repair graph. Aborting repairing.");
|
|
break;
|
|
}
|
|
else if(subPoses.size() != posesOut.size())
|
|
{
|
|
for(std::map<int, Transform>::iterator pter=subPoses.begin(); pter!=subPoses.end(); ++pter)
|
|
{
|
|
if(posesOut.find(pter->first) == posesOut.end())
|
|
{
|
|
subPoses.erase(pter);
|
|
break; // should be only one if different, break now
|
|
}
|
|
}
|
|
}
|
|
cv::Mat subOptimizationCovariance;
|
|
double subOptimizationError = 0.0;
|
|
int subOptimizationIterations = 0;
|
|
|
|
int fromId = subPoses.rbegin()->first;
|
|
if(!_optimizeFromGraphEnd)
|
|
{
|
|
// Find first node that is not intermediate
|
|
for(std::map<int, Transform>::iterator iter = subPoses.lower_bound(1); iter!=subPoses.end(); ++iter)
|
|
{
|
|
// Make sure fromId is not an intermediate node
|
|
const Signature * s = _memory->getSignature(iter->first);
|
|
if(s && s->getWeight() != -1)
|
|
{
|
|
fromId = iter->first;
|
|
break;
|
|
}
|
|
else if(!s)
|
|
{
|
|
UWARN("Not found node %d in memory?!", iter->first);
|
|
}
|
|
}
|
|
}
|
|
|
|
subPoses = _graphOptimizer->optimize(
|
|
fromId,
|
|
subPoses,
|
|
subConstraintsOut,
|
|
subOptimizationCovariance,
|
|
0,
|
|
&subOptimizationError,
|
|
&subOptimizationIterations);
|
|
|
|
if(subPoses.empty())
|
|
{
|
|
UWARN("Optimization failed when trying to repair graph.");
|
|
}
|
|
else
|
|
{
|
|
subMaxGraphErrors = graph::computeMaxGraphErrors(
|
|
subPoses,
|
|
subConstraintsOut);
|
|
if(!subMaxGraphErrors.linearLink.isValid())
|
|
{
|
|
UWARN("Could not compute graph errors! Wrong loop closures could be accepted!");
|
|
}
|
|
else if(subMaxGraphErrors.linearRatio > _optimizationMaxError)
|
|
{
|
|
float distance = poses.at(originalMaxErrorLinkFrom).getDistance(poses.at(subMaxGraphErrors.linearLink.from()));
|
|
if(subMaxGraphErrors.linearLink.type() != Link::kNeighbor && distance < _optimizationMaxErrorRepairRadius)
|
|
{
|
|
UWARN("Optimization error is still high (%f, on link %d->%d type=%d) after removing the loop closure with the highest error. "
|
|
"As it is close (%f m < %s=%f m) to original loop closure with high error (%d->%d), we will reject again this one to see if it helps.",
|
|
subMaxGraphErrors.linearRatio, subMaxGraphErrors.linearLink.from(), subMaxGraphErrors.linearLink.to(), subMaxGraphErrors.linearLink.type(),
|
|
distance, Parameters::kRGBDOptimizeMaxErrorRepairRadius().c_str(), _optimizationMaxErrorRepairRadius,
|
|
originalMaxErrorLinkFrom, originalMaxErrorLinkTo);
|
|
}
|
|
else
|
|
{
|
|
UWARN("Optimization error is still high (%f, on link %d->%d) after removing loop closure with highest error.",
|
|
subMaxGraphErrors.linearRatio, subMaxGraphErrors.linearLink.from(), subMaxGraphErrors.linearLink.to());
|
|
subMaxGraphErrors.linearLink = Link();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UWARN("Optimization error is lower (%f, on link %d->%d) after removing loop "
|
|
"closure with highest error. We will remove the old link (%d->%d collaterals=%ld) and accept the new one.",
|
|
subMaxGraphErrors.linearRatio, subMaxGraphErrors.linearLink.from(), subMaxGraphErrors.linearLink.to(),
|
|
removedLinks.front().first, removedLinks.front().second,
|
|
removedLinks.size()-1);
|
|
|
|
maxGraphErrors = subMaxGraphErrors;
|
|
|
|
// Make sure the link pointers in maxGraphErrors point on same constraints
|
|
std::swap(poses, subPoses);
|
|
std::swap(constraints, subConstraintsOut);
|
|
|
|
optimizationCovariance = subOptimizationCovariance;
|
|
optimizationError = subOptimizationError;
|
|
optimizationIterations = subOptimizationIterations;
|
|
|
|
return removedLinks;
|
|
}
|
|
}
|
|
}
|
|
return std::list<std::pair<int, int> >();
|
|
}
|
|
|
|
void Rtabmap::adjustLikelihood(std::map<int, float> & likelihood) const
|
|
{
|
|
ULOGGER_DEBUG("likelihood.size()=%d", likelihood.size());
|
|
UTimer timer;
|
|
timer.start();
|
|
if(likelihood.size()==0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Use only non-null values (ignore virtual place)
|
|
std::list<float> values;
|
|
bool likelihoodNullValuesIgnored = true;
|
|
for(std::map<int, float>::iterator iter = ++likelihood.begin(); iter!=likelihood.end(); ++iter)
|
|
{
|
|
if((iter->second >= 0 && !likelihoodNullValuesIgnored) ||
|
|
(iter->second > 0 && likelihoodNullValuesIgnored))
|
|
{
|
|
values.push_back(iter->second);
|
|
}
|
|
}
|
|
UDEBUG("values.size=%d", values.size());
|
|
|
|
float mean = uMean(values);
|
|
float stdDev = std::sqrt(uVariance(values, mean));
|
|
|
|
|
|
//Adjust likelihood with mean and standard deviation (see Angeli phd)
|
|
float epsilon = 0.0001;
|
|
float max = 0.0f;
|
|
int maxId = 0;
|
|
for(std::map<int, float>::iterator iter=++likelihood.begin(); iter!= likelihood.end(); ++iter)
|
|
{
|
|
float value = iter->second;
|
|
iter->second = 1.0f;
|
|
if(value > mean+stdDev)
|
|
{
|
|
if(_virtualPlaceLikelihoodRatio==0 && mean)
|
|
{
|
|
iter->second = (value-(stdDev-epsilon))/mean;
|
|
}
|
|
else if(_virtualPlaceLikelihoodRatio!=0 && stdDev)
|
|
{
|
|
iter->second = (value-mean)/stdDev;
|
|
}
|
|
}
|
|
|
|
if(value > max)
|
|
{
|
|
max = value;
|
|
maxId = iter->first;
|
|
}
|
|
}
|
|
|
|
if(_virtualPlaceLikelihoodRatio==0 && stdDev > epsilon && max)
|
|
{
|
|
likelihood.begin()->second = mean/stdDev + 1.0f;
|
|
}
|
|
else if(_virtualPlaceLikelihoodRatio!=0 && max > mean)
|
|
{
|
|
likelihood.begin()->second = stdDev/(max-mean) + 1.0f;
|
|
}
|
|
else
|
|
{
|
|
likelihood.begin()->second = 2.0f; //2 * std dev
|
|
}
|
|
|
|
double time = timer.ticks();
|
|
UDEBUG("mean=%f, stdDev=%f, max=%f, maxId=%d, time=%fs", mean, stdDev, max, maxId, time);
|
|
}
|
|
|
|
void Rtabmap::dumpPrediction() const
|
|
{
|
|
if(_memory && _bayesFilter)
|
|
{
|
|
if(this->getWorkingDir().empty())
|
|
{
|
|
UERROR("Working directory not set.");
|
|
return;
|
|
}
|
|
std::list<int> signaturesToCompare;
|
|
for(std::map<int, double>::const_iterator iter=_memory->getWorkingMem().begin();
|
|
iter!=_memory->getWorkingMem().end();
|
|
++iter)
|
|
{
|
|
if(iter->first > 0)
|
|
{
|
|
const Signature * s = _memory->getSignature(iter->first);
|
|
UASSERT(s!=0);
|
|
if(s->getWeight() != -1) // ignore intermediate nodes
|
|
{
|
|
signaturesToCompare.push_back(iter->first);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// virtual signature should be added
|
|
signaturesToCompare.push_back(iter->first);
|
|
}
|
|
}
|
|
cv::Mat prediction = _bayesFilter->generatePrediction(_memory, uListToVector(signaturesToCompare));
|
|
|
|
FILE* fout = 0;
|
|
std::string fileName = this->getWorkingDir() + "/DumpPrediction.txt";
|
|
#ifdef _MSC_VER
|
|
fopen_s(&fout, fileName.c_str(), "w");
|
|
#else
|
|
fout = fopen(fileName.c_str(), "w");
|
|
#endif
|
|
|
|
if(fout)
|
|
{
|
|
for(int i=0; i<prediction.rows; ++i)
|
|
{
|
|
for(int j=0; j<prediction.cols; ++j)
|
|
{
|
|
fprintf(fout, "%f ",((float*)prediction.data)[j + i*prediction.cols]);
|
|
}
|
|
fprintf(fout, "\n");
|
|
}
|
|
fclose(fout);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UWARN("Memory and/or the Bayes filter are not created");
|
|
}
|
|
}
|
|
|
|
Signature Rtabmap::getSignatureCopy(int id, bool images, bool scan, bool userData, bool occupancyGrid, bool withWords, bool withGlobalDescriptors) const
|
|
{
|
|
Signature s;
|
|
if(_memory)
|
|
{
|
|
Transform odomPoseLocal;
|
|
int weight = -1;
|
|
int mapId = -1;
|
|
std::string label;
|
|
double stamp = 0;
|
|
Transform groundTruth;
|
|
std::vector<float> velocity;
|
|
GPS gps;
|
|
EnvSensors sensors;
|
|
_memory->getNodeInfo(id, odomPoseLocal, mapId, weight, label, stamp, groundTruth, velocity, gps, sensors, true);
|
|
SensorData data;
|
|
data.setId(id);
|
|
if(images || scan || userData || occupancyGrid)
|
|
{
|
|
data = _memory->getNodeData(id, images, scan, userData, occupancyGrid);
|
|
}
|
|
if(!images && withWords)
|
|
{
|
|
std::vector<CameraModel> models;
|
|
std::vector<StereoCameraModel> stereoModels;
|
|
_memory->getNodeCalibration(id, models, stereoModels);
|
|
data.setCameraModels(models);
|
|
data.setStereoCameraModels(stereoModels);
|
|
}
|
|
|
|
s=Signature(id,
|
|
mapId,
|
|
weight,
|
|
stamp,
|
|
label,
|
|
odomPoseLocal,
|
|
groundTruth,
|
|
data);
|
|
|
|
std::multimap<int, Link> links = _memory->getLinks(id, true, true);
|
|
for(std::multimap<int, Link>::iterator iter=links.begin(); iter!=links.end(); ++iter)
|
|
{
|
|
if(iter->second.type() == Link::kLandmark)
|
|
{
|
|
s.addLandmark(iter->second);
|
|
}
|
|
else
|
|
{
|
|
s.addLink(iter->second);
|
|
}
|
|
}
|
|
|
|
if(withWords || withGlobalDescriptors)
|
|
{
|
|
std::multimap<int, int> words;
|
|
std::vector<cv::KeyPoint> wordsKpts;
|
|
std::vector<cv::Point3f> words3;
|
|
cv::Mat wordsDescriptors;
|
|
std::vector<rtabmap::GlobalDescriptor> globalDescriptors;
|
|
_memory->getNodeWordsAndGlobalDescriptors(id, words, wordsKpts, words3, wordsDescriptors, globalDescriptors);
|
|
if(withWords)
|
|
{
|
|
s.setWords(words, wordsKpts, words3, wordsDescriptors);
|
|
}
|
|
if(withGlobalDescriptors)
|
|
{
|
|
s.sensorData().setGlobalDescriptors(globalDescriptors);
|
|
}
|
|
}
|
|
if(velocity.size()==6)
|
|
{
|
|
s.setVelocity(velocity[0], velocity[1], velocity[2], velocity[3], velocity[4], velocity[5]);
|
|
}
|
|
s.sensorData().setGPS(gps);
|
|
s.sensorData().setEnvSensors(sensors);
|
|
}
|
|
return s;
|
|
}
|
|
|
|
void Rtabmap::get3DMap(
|
|
std::map<int, Signature> & signatures,
|
|
std::map<int, Transform> & poses,
|
|
std::multimap<int, Link> & constraints,
|
|
bool optimized,
|
|
bool global) const
|
|
{
|
|
UDEBUG("");
|
|
return getGraph(poses, constraints, optimized, global, &signatures, true, true, true, true);
|
|
}
|
|
|
|
void Rtabmap::getGraph(
|
|
std::map<int, Transform> & poses,
|
|
std::multimap<int, Link> & constraints,
|
|
bool optimized,
|
|
bool global,
|
|
std::map<int, Signature> * signatures,
|
|
bool withImages,
|
|
bool withScan,
|
|
bool withUserData,
|
|
bool withGrid,
|
|
bool withWords,
|
|
bool withGlobalDescriptors) const
|
|
{
|
|
if(_memory && _memory->getLastWorkingSignature(!global))
|
|
{
|
|
if(_rgbdSlamMode)
|
|
{
|
|
if(optimized)
|
|
{
|
|
poses = _optimizedPoses; // guess
|
|
cv::Mat covariance;
|
|
this->optimizeCurrentMap(_memory->getLastWorkingSignature(!global)->id(), global, poses, covariance, &constraints);
|
|
if(!global && !_optimizedPoses.empty())
|
|
{
|
|
// We send directly the already optimized poses if they are set
|
|
UDEBUG("_optimizedPoses=%ld poses=%ld", _optimizedPoses.size(), poses.size());
|
|
poses = _optimizedPoses;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
std::map<int, int> ids = _memory->getNeighborsId(_memory->getLastWorkingSignature(!global)->id(), 0, global?-1:0, true);
|
|
_memory->getMetricConstraints(uKeysSet(ids), poses, constraints, global);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// no optimization on appearance-only mode
|
|
std::map<int, int> ids = _memory->getNeighborsId(_memory->getLastWorkingSignature(!global)->id(), 0, global?-1:0, true);
|
|
_memory->getMetricConstraints(uKeysSet(ids), poses, constraints, global);
|
|
}
|
|
|
|
if(signatures)
|
|
{
|
|
// Get data
|
|
std::set<int> ids = uKeysSet(_memory->getWorkingMem()); // WM
|
|
|
|
//remove virtual signature
|
|
ids.erase(Memory::kIdVirtual);
|
|
|
|
ids.insert(_memory->getStMem().begin(), _memory->getStMem().end()); // STM + WM
|
|
if(global)
|
|
{
|
|
ids = _memory->getAllSignatureIds(); // STM + WM + LTM, ignoreChildren=true
|
|
}
|
|
|
|
for(std::set<int>::iterator iter = ids.begin(); iter!=ids.end(); ++iter)
|
|
{
|
|
signatures->insert(std::make_pair(*iter, getSignatureCopy(*iter, withImages, withScan, withUserData, withGrid, withWords, withGlobalDescriptors)));
|
|
}
|
|
}
|
|
}
|
|
else if(_memory && (_memory->getStMem().size() || _memory->getWorkingMem().size() > 1))
|
|
{
|
|
UERROR("Last working signature is null!?");
|
|
}
|
|
else if(_memory == 0)
|
|
{
|
|
UWARN("Memory not initialized...");
|
|
}
|
|
}
|
|
|
|
std::map<int, Transform> Rtabmap::getNodesInRadius(const Transform & pose, float radius, int k, std::map<int, float> * distsSqr)
|
|
{
|
|
std::map<int, float> nearestNodesTmp;
|
|
std::map<int, float> * nearestNodesPtr = distsSqr == 0? &nearestNodesTmp : distsSqr;
|
|
*nearestNodesPtr = graph::findNearestNodes(pose, _optimizedPoses, radius<=0?_localRadius:radius, 0, k);
|
|
std::map<int, Transform> nearestPoses;
|
|
for(std::map<int, float>::iterator iter=nearestNodesPtr->begin(); iter!=nearestNodesPtr->end(); ++iter)
|
|
{
|
|
nearestPoses.insert(*_optimizedPoses.find(iter->first));
|
|
}
|
|
return nearestPoses;
|
|
}
|
|
|
|
std::map<int, Transform> Rtabmap::getNodesInRadius(int nodeId, float radius, int k, std::map<int, float> * distsSqr)
|
|
{
|
|
UDEBUG("nodeId=%d, radius=%f", nodeId, radius);
|
|
std::map<int, float> nearestNodesTmp;
|
|
std::map<int, float> * nearestNodesPtr = distsSqr == 0? &nearestNodesTmp : distsSqr;
|
|
if(nodeId==0 && !_lastLocalizationPose.isNull() && !_lastLocalizationPose.isIdentity())
|
|
{
|
|
*nearestNodesPtr = graph::findNearestNodes(_lastLocalizationPose, _optimizedPoses, radius<=0?_localRadius:radius, 0, k);
|
|
}
|
|
else
|
|
{
|
|
if(nodeId==0 && !_optimizedPoses.empty())
|
|
{
|
|
nodeId = _optimizedPoses.rbegin()->first;
|
|
}
|
|
|
|
if(_optimizedPoses.find(nodeId) != _optimizedPoses.end())
|
|
{
|
|
*nearestNodesPtr = graph::findNearestNodes(nodeId, _optimizedPoses, radius<=0?_localRadius:radius, 0, k);
|
|
}
|
|
}
|
|
|
|
std::map<int, Transform> nearestPoses;
|
|
for(std::map<int, float>::iterator iter=nearestNodesPtr->begin(); iter!=nearestNodesPtr->end(); ++iter)
|
|
{
|
|
nearestPoses.insert(*_optimizedPoses.find(iter->first));
|
|
}
|
|
|
|
return nearestPoses;
|
|
}
|
|
|
|
int Rtabmap::detectMoreLoopClosures(
|
|
float clusterRadiusMax,
|
|
float clusterAngle,
|
|
int iterations,
|
|
bool intraSession,
|
|
bool interSession,
|
|
const ProgressState * processState,
|
|
float clusterRadiusMin,
|
|
int toFromMapId)
|
|
{
|
|
UDEBUG("");
|
|
UASSERT(iterations>0);
|
|
|
|
if(_graphOptimizer->iterations() <= 0)
|
|
{
|
|
UERROR("Cannot detect more loop closures if graph optimization iterations = 0");
|
|
return -1;
|
|
}
|
|
if(!_rgbdSlamMode)
|
|
{
|
|
UERROR("Detecting more loop closures can be done only in RGBD-SLAM mode.");
|
|
return -1;
|
|
}
|
|
if(!intraSession && !interSession)
|
|
{
|
|
UERROR("Intra and/or inter session argument should be true.");
|
|
return -1;
|
|
}
|
|
|
|
std::list<Link> loopClosuresAdded;
|
|
std::multimap<int, int> checkedLoopClosures;
|
|
|
|
std::map<int, Transform> posesToCheckLoopClosures;
|
|
std::map<int, Transform> poses;
|
|
std::multimap<int, Link> links;
|
|
this->getGraph(poses, links, true, true);
|
|
|
|
std::map<int, int> mapIds;
|
|
UDEBUG("remove all invalid or intermediate nodes, fill mapIds");
|
|
for(std::map<int, Transform>::iterator iter=poses.upper_bound(0); iter!=poses.end();++iter)
|
|
{
|
|
Transform odom, gt;
|
|
int mapId, weight;
|
|
std::string l;
|
|
double s;
|
|
std::vector<float> v;
|
|
GPS gps;
|
|
EnvSensors srs;
|
|
if(_memory->getNodeInfo(iter->first, odom, mapId, weight, l, s, gt, v, gps, srs, true) && weight >= 0)
|
|
{
|
|
posesToCheckLoopClosures.insert(*iter);
|
|
mapIds.insert(std::make_pair(iter->first, mapId));
|
|
}
|
|
}
|
|
|
|
for(int n=0; n<iterations; ++n)
|
|
{
|
|
UINFO("Looking for more loop closures, clustering poses... (iteration=%d/%d, radius=%f m angle=%f rad)",
|
|
n+1, iterations, clusterRadiusMax, clusterAngle);
|
|
|
|
std::multimap<int, int> clusters = graph::radiusPosesClustering(
|
|
posesToCheckLoopClosures,
|
|
clusterRadiusMax,
|
|
clusterAngle);
|
|
|
|
UINFO("Looking for more loop closures: clustering poses... found %ld clusters.", clusters.size());
|
|
|
|
if(toFromMapId >=0)
|
|
{
|
|
size_t clustersBefore = clusters.size();
|
|
for(std::multimap<int, int>::iterator iter=clusters.begin(); iter!=clusters.end();)
|
|
{
|
|
int mapId = uValue(mapIds, iter->first, 0);
|
|
if(mapId != toFromMapId)
|
|
{
|
|
iter = clusters.erase(iter);
|
|
}
|
|
else {
|
|
++iter;
|
|
}
|
|
}
|
|
UINFO("Looking for more loop closures: filtered %ld/%ld clusters for map session %d.", clustersBefore-clusters.size(), clustersBefore, toFromMapId);
|
|
if(clusters.empty())
|
|
{
|
|
UERROR("No clusters belong to mapId %d, aborting.", toFromMapId);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(_memory->getMaxStMemSize() > 1)
|
|
{
|
|
size_t clustersBefore = clusters.size();
|
|
for(std::multimap<int, int>::iterator iter=clusters.begin(); iter!=clusters.end();)
|
|
{
|
|
if(abs(iter->first - iter->second) < _memory->getMaxStMemSize())
|
|
{
|
|
iter = clusters.erase(iter);
|
|
}
|
|
else
|
|
{
|
|
// compute path to know how far we are in terms of graph length
|
|
std::map<int, int> ids = _memory->getNeighborsId(iter->first, _memory->getMaxStMemSize(), -1, true, true, true);
|
|
if(ids.find(iter->second) != ids.end())
|
|
{
|
|
iter = clusters.erase(iter);
|
|
}
|
|
else
|
|
{
|
|
++iter;
|
|
}
|
|
}
|
|
}
|
|
UINFO("Looking for more loop closures: filtered %ld/%ld clusters for too close nodes (below %s=%d).",
|
|
clustersBefore-clusters.size(), clustersBefore, Parameters::kMemSTMSize().c_str(), _memory->getMaxStMemSize());
|
|
}
|
|
|
|
int i=0;
|
|
std::set<int> addedLinks;
|
|
for(std::multimap<int, int>::iterator iter=clusters.begin(); iter!= clusters.end(); ++iter, ++i)
|
|
{
|
|
if(processState && processState->isCanceled())
|
|
{
|
|
return -1;
|
|
break;
|
|
}
|
|
|
|
int from = iter->first;
|
|
int to = iter->second;
|
|
if(from > to)
|
|
{
|
|
from = iter->second;
|
|
to = iter->first;
|
|
}
|
|
|
|
int mapIdFrom = uValue(mapIds, from, 0);
|
|
int mapIdTo = uValue(mapIds, to, 0);
|
|
|
|
if((interSession && mapIdFrom != mapIdTo) ||
|
|
(intraSession && mapIdFrom == mapIdTo))
|
|
{
|
|
|
|
bool alreadyChecked = false;
|
|
for(std::multimap<int, int>::iterator jter = checkedLoopClosures.lower_bound(from);
|
|
!alreadyChecked && jter!=checkedLoopClosures.end() && jter->first == from;
|
|
++jter)
|
|
{
|
|
if(to == jter->second)
|
|
{
|
|
alreadyChecked = true;
|
|
}
|
|
}
|
|
|
|
if(!alreadyChecked)
|
|
{
|
|
// only add new links and one per cluster per iteration
|
|
if(addedLinks.find(from) == addedLinks.end() &&
|
|
addedLinks.find(to) == addedLinks.end() &&
|
|
rtabmap::graph::findLink(links, from, to) == links.end())
|
|
{
|
|
// Reverify if in the bounds with the current optimized graph
|
|
Transform delta = poses.at(from).inverse() * poses.at(to);
|
|
if(delta.getNorm() < clusterRadiusMax &&
|
|
delta.getNorm() >= clusterRadiusMin)
|
|
{
|
|
checkedLoopClosures.insert(std::make_pair(from, to));
|
|
|
|
Signature fromS = getSignatureCopy(from, false, true, false, false, true, false);
|
|
Signature toS = getSignatureCopy(to, false, true, false, false, true, false);
|
|
UASSERT(fromS.getWeight()>=0);
|
|
UASSERT(toS.getWeight()>=0);
|
|
|
|
Transform guess;
|
|
if(_proximityBySpace && uContains(poses, from) && uContains(poses, to))
|
|
{
|
|
guess = poses.at(from).inverse() * poses.at(to);
|
|
}
|
|
|
|
RegistrationInfo info;
|
|
// use signatures instead of IDs because some signatures may not be in WM
|
|
Transform t = _memory->computeTransform(fromS, toS, guess, &info);
|
|
|
|
if(!t.isNull())
|
|
{
|
|
bool updateConstraints = true;
|
|
|
|
//optimize the graph to see if the new constraint is globally valid
|
|
|
|
int fromId = from;
|
|
int mapId = fromS.mapId();
|
|
// use first node of the map containing from
|
|
for(std::map<int, Transform>::iterator ster=posesToCheckLoopClosures.begin(); ster!=posesToCheckLoopClosures.end(); ++ster)
|
|
{
|
|
if(uValue(mapIds, ster->first, 0) == mapId)
|
|
{
|
|
fromId = ster->first;
|
|
break;
|
|
}
|
|
}
|
|
std::multimap<int, Link> linksIn = links;
|
|
linksIn.insert(std::make_pair(from, Link(from, to, Link::kUserClosure, t, getInformation(info.covariance))));
|
|
graph::MaxGraphErrors maxGraphErrors;
|
|
std::map<int, Transform> optimizedPoses;
|
|
std::multimap<int, Link> linksOut;
|
|
UASSERT(poses.find(fromId) != poses.end());
|
|
UASSERT_MSG(poses.find(from) != poses.end(), uFormat("id=%d poses=%d links=%d", from, (int)poses.size(), (int)links.size()).c_str());
|
|
UASSERT_MSG(poses.find(to) != poses.end(), uFormat("id=%d poses=%d links=%d", to, (int)poses.size(), (int)links.size()).c_str());
|
|
_graphOptimizer->getConnectedGraph(fromId, poses, linksIn, optimizedPoses, linksOut);
|
|
UASSERT(optimizedPoses.find(fromId) != optimizedPoses.end());
|
|
UASSERT_MSG(optimizedPoses.find(from) != optimizedPoses.end(), uFormat("id=%d poses=%d links=%d", from, (int)optimizedPoses.size(), (int)linksOut.size()).c_str());
|
|
UASSERT_MSG(optimizedPoses.find(to) != optimizedPoses.end(), uFormat("id=%d poses=%d links=%d", to, (int)optimizedPoses.size(), (int)linksOut.size()).c_str());
|
|
UASSERT(graph::findLink(linksOut, from, to) != linksOut.end());
|
|
optimizedPoses = _graphOptimizer->optimize(fromId, optimizedPoses, linksOut);
|
|
std::string msg;
|
|
if(optimizedPoses.size())
|
|
{
|
|
maxGraphErrors = graph::computeMaxGraphErrors(
|
|
optimizedPoses,
|
|
linksOut);
|
|
if(maxGraphErrors.linearLink.isValid())
|
|
{
|
|
UINFO("Max optimization linear error = %f m (link %d->%d)", maxGraphErrors.linear, maxGraphErrors.linearLink.from(), maxGraphErrors.linearLink.to());
|
|
if(_optimizationMaxError > 0.0f && maxGraphErrors.linearRatio > _optimizationMaxError)
|
|
{
|
|
msg = uFormat("Rejecting edge %d->%d because "
|
|
"graph error is too large after optimization (%f m for edge %d->%d with ratio %f > std=%f m). "
|
|
"\"%s\" is %f.",
|
|
from,
|
|
to,
|
|
maxGraphErrors.linear,
|
|
maxGraphErrors.linearLink.from(),
|
|
maxGraphErrors.linearLink.to(),
|
|
maxGraphErrors.linearRatio,
|
|
sqrt(maxGraphErrors.linearLink.transVariance()),
|
|
Parameters::kRGBDOptimizeMaxError().c_str(),
|
|
_optimizationMaxError);
|
|
}
|
|
else if(_optimizationMaxError == 0.0f && maxGraphErrors.linearRatio>100 && !_graphOptimizer->isRobust())
|
|
{
|
|
UERROR("Huge optimization error detected!"
|
|
"Linear error ratio of %f (edge %d->%d, type=%d, abs error=%f m, stddev=%f). You may consider "
|
|
"enabling \"%s\" to reject those bad optimizations by setting it to a non null value!",
|
|
maxGraphErrors.linearRatio,
|
|
maxGraphErrors.linearLink.from(),
|
|
maxGraphErrors.linearLink.to(),
|
|
maxGraphErrors.linearLink.type(),
|
|
maxGraphErrors.linear,
|
|
sqrt(maxGraphErrors.linearLink.transVariance()),
|
|
Parameters::kRGBDOptimizeMaxError().c_str());
|
|
}
|
|
}
|
|
else if(maxGraphErrors.angularLink.isValid())
|
|
{
|
|
UINFO("Max optimization angular error = %f deg (link %d->%d)", maxGraphErrors.angular*180.0f/M_PI, maxGraphErrors.angularLink.from(), maxGraphErrors.angularLink.to());
|
|
if(_optimizationMaxError > 0.0f && maxGraphErrors.angularRatio > _optimizationMaxError)
|
|
{
|
|
msg = uFormat("Rejecting edge %d->%d because "
|
|
"graph error is too large after optimization (%f deg for edge %d->%d with ratio %f > std=%f deg). "
|
|
"\"%s\" is %f m.",
|
|
from,
|
|
to,
|
|
maxGraphErrors.angular*180.0f/M_PI,
|
|
maxGraphErrors.angularLink.from(),
|
|
maxGraphErrors.angularLink.to(),
|
|
maxGraphErrors.angularRatio,
|
|
sqrt(maxGraphErrors.angularLink.rotVariance()),
|
|
Parameters::kRGBDOptimizeMaxError().c_str(),
|
|
_optimizationMaxError);
|
|
}
|
|
else if(_optimizationMaxError == 0.0f && maxGraphErrors.angularRatio>100 && !_graphOptimizer->isRobust())
|
|
{
|
|
UERROR("Huge optimization error detected!"
|
|
"Angular error ratio of %f (edge %d->%d, type=%d, abs error=%f m, stddev=%f). You may consider "
|
|
"enabling \"%s\" to reject those bad optimizations by setting it to a non null value!",
|
|
maxGraphErrors.angularRatio,
|
|
maxGraphErrors.angularLink.from(),
|
|
maxGraphErrors.angularLink.to(),
|
|
maxGraphErrors.angularLink.type(),
|
|
maxGraphErrors.angular*180.0f/CV_PI,
|
|
sqrt(maxGraphErrors.angularLink.rotVariance()),
|
|
Parameters::kRGBDOptimizeMaxError().c_str());
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
msg = uFormat("Rejecting edge %d->%d because graph optimization has failed!",
|
|
from,
|
|
to);
|
|
}
|
|
if(!msg.empty())
|
|
{
|
|
UWARN("%s", msg.c_str());
|
|
updateConstraints = false;
|
|
}
|
|
else
|
|
{
|
|
poses = optimizedPoses;
|
|
}
|
|
|
|
if(updateConstraints)
|
|
{
|
|
addedLinks.insert(from);
|
|
addedLinks.insert(to);
|
|
cv::Mat inf = getInformation(info.covariance);
|
|
links.insert(std::make_pair(from, Link(from, to, Link::kUserClosure, t, inf)));
|
|
loopClosuresAdded.push_back(Link(from, to, Link::kUserClosure, t, inf));
|
|
std::string msg = uFormat("Iteration %d/%d: Added loop closure %d->%d! (%d/%d)", n+1, iterations, from, to, i+1, (int)clusters.size());
|
|
UINFO(msg.c_str());
|
|
|
|
if(processState)
|
|
{
|
|
UINFO(msg.c_str());
|
|
if(!processState->callback(msg))
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(processState)
|
|
{
|
|
std::string msg = uFormat("Iteration %d/%d: Detected %d total loop closures!", n+1, iterations, (int)addedLinks.size()/2);
|
|
UINFO(msg.c_str());
|
|
if(!processState->callback(msg))
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UINFO("Iteration %d/%d: Detected %d total loop closures!", n+1, iterations, (int)addedLinks.size()/2);
|
|
}
|
|
|
|
if(addedLinks.size() == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
UINFO("Optimizing graph with new links (%d nodes, %d constraints)...",
|
|
(int)poses.size(), (int)links.size());
|
|
int fromId = _optimizeFromGraphEnd?poses.rbegin()->first:poses.begin()->first;
|
|
poses = _graphOptimizer->optimize(fromId, poses, links, 0);
|
|
if(poses.size() == 0)
|
|
{
|
|
UERROR("Optimization failed! Rejecting all loop closures...");
|
|
loopClosuresAdded.clear();
|
|
return -1;
|
|
}
|
|
UINFO("Optimizing graph with new links... done!");
|
|
}
|
|
UINFO("Total added %d loop closures.", (int)loopClosuresAdded.size());
|
|
|
|
if(loopClosuresAdded.size())
|
|
{
|
|
for(std::list<Link>::iterator iter=loopClosuresAdded.begin(); iter!=loopClosuresAdded.end(); ++iter)
|
|
{
|
|
_memory->addLink(*iter, true);
|
|
}
|
|
// Update optimized poses
|
|
for(std::map<int, Transform>::iterator iter=_optimizedPoses.begin(); iter!=_optimizedPoses.end(); ++iter)
|
|
{
|
|
std::map<int, Transform>::iterator jter = poses.find(iter->first);
|
|
if(jter != poses.end())
|
|
{
|
|
iter->second = jter->second;
|
|
}
|
|
}
|
|
std::map<int, Transform> tmp;
|
|
// Update also the links if some have been added in WM
|
|
_memory->getMetricConstraints(uKeysSet(_optimizedPoses), tmp, _constraints, false);
|
|
// This will force rtabmap_ros to regenerate the global occupancy grid if there was one
|
|
_memory->save2DMap(cv::Mat(), 0, 0, 0);
|
|
}
|
|
return (int)loopClosuresAdded.size();
|
|
}
|
|
|
|
bool Rtabmap::globalBundleAdjustment(
|
|
int optimizerType,
|
|
bool rematchFeatures,
|
|
int iterations,
|
|
float pixelVariance)
|
|
{
|
|
if(!_optimizedPoses.empty() && !_constraints.empty())
|
|
{
|
|
int iterations = Parameters::defaultOptimizerIterations();
|
|
float pixelVariance = Parameters::defaultg2oPixelVariance();
|
|
ParametersMap params = _parameters;
|
|
Parameters::parse(params, Parameters::kOptimizerIterations(), iterations);
|
|
Parameters::parse(params, Parameters::kg2oPixelVariance(), pixelVariance);
|
|
if(iterations > 0)
|
|
{
|
|
uInsert(params, ParametersPair(Parameters::kOptimizerIterations(), uNumber2Str(iterations)));
|
|
}
|
|
if(pixelVariance > 0.0f)
|
|
{
|
|
uInsert(params, ParametersPair(Parameters::kg2oPixelVariance(), uNumber2Str(pixelVariance)));
|
|
}
|
|
|
|
std::map<int, Signature> signatures;
|
|
for(std::map<int, Transform>::iterator iter=_optimizedPoses.lower_bound(1); iter!=_optimizedPoses.end(); ++iter)
|
|
{
|
|
if(_memory->getSignature(iter->first))
|
|
{
|
|
signatures.insert(std::make_pair(iter->first, *_memory->getSignature(iter->first)));
|
|
}
|
|
}
|
|
|
|
Optimizer * optimizer = Optimizer::create((Optimizer::Type)optimizerType, params);
|
|
std::map<int, Transform> poses = optimizer->optimizeBA(
|
|
_optimizeFromGraphEnd?_optimizedPoses.lower_bound(1)->first:_optimizedPoses.rbegin()->first,
|
|
_optimizedPoses,
|
|
_constraints,
|
|
signatures,
|
|
rematchFeatures);
|
|
delete optimizer;
|
|
|
|
if(poses.empty())
|
|
{
|
|
UERROR("Optimization failed!");
|
|
}
|
|
else
|
|
{
|
|
_optimizedPoses = poses;
|
|
// This will force rtabmap_ros to regenerate the global occupancy grid if there was one
|
|
_memory->save2DMap(cv::Mat(), 0, 0, 0);
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UERROR("Optimized poses (%ld) or constraints (%ld) are empty!", _optimizedPoses.size(), _constraints.size());
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int Rtabmap::cleanupLocalGrids(
|
|
const std::map<int, Transform> & poses,
|
|
const cv::Mat & map,
|
|
float xMin,
|
|
float yMin,
|
|
float cellSize,
|
|
int cropRadius,
|
|
bool filterScans)
|
|
{
|
|
if(_memory)
|
|
{
|
|
return _memory->cleanupLocalGrids(
|
|
poses,
|
|
map,
|
|
xMin,
|
|
yMin,
|
|
cellSize,
|
|
cropRadius,
|
|
filterScans);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int Rtabmap::refineLinks()
|
|
{
|
|
if(!_rgbdSlamMode)
|
|
{
|
|
UERROR("Refining links can be done only in RGBD-SLAM mode.");
|
|
return -1;
|
|
}
|
|
|
|
std::list<Link> linksRefined;
|
|
|
|
std::map<int, Transform> poses;
|
|
std::multimap<int, Link> links;
|
|
std::map<int, Signature> signatures;
|
|
this->getGraph(poses, links, false, true, &signatures);
|
|
|
|
int i=0;
|
|
for(std::multimap<int, Link>::iterator iter=links.lower_bound(1); iter!= links.end(); ++iter)
|
|
{
|
|
int from = iter->second.from();
|
|
int to = iter->second.to();
|
|
|
|
UASSERT(signatures.find(from) != signatures.end());
|
|
UASSERT(signatures.find(to) != signatures.end());
|
|
|
|
RegistrationInfo info;
|
|
// use signatures instead of IDs because some signatures may not be in WM
|
|
Transform t = _memory->computeTransform(signatures.at(from), signatures.at(to), iter->second.transform(), &info);
|
|
|
|
if(!t.isNull())
|
|
{
|
|
linksRefined.push_back(Link(from, to, iter->second.type(), t, info.covariance.inv()));
|
|
UINFO("Refined link %d->%d! (%d/%d)", from, to, ++i, (int)links.size());
|
|
}
|
|
}
|
|
UINFO("Total refined %d links.", (int)linksRefined.size());
|
|
|
|
if(linksRefined.size())
|
|
{
|
|
for(std::list<Link>::iterator iter=linksRefined.begin(); iter!=linksRefined.end(); ++iter)
|
|
{
|
|
_memory->updateLink(*iter, true);
|
|
}
|
|
}
|
|
return (int)linksRefined.size();
|
|
}
|
|
|
|
bool Rtabmap::addLink(const Link & link)
|
|
{
|
|
const Transform & t = link.transform();
|
|
if(!_rgbdSlamMode)
|
|
{
|
|
UERROR("Adding new link can be done only in RGBD-SLAM mode.");
|
|
return false;
|
|
}
|
|
if(!_memory)
|
|
{
|
|
UERROR("Memory is not initialized.");
|
|
return false;
|
|
}
|
|
if(t.isNull())
|
|
{
|
|
UERROR("Link's transform is null! (%d->%d type=%s)", link.from(), link.to(), link.typeName().c_str());
|
|
return false;
|
|
}
|
|
if(_memory->isIncremental())
|
|
{
|
|
if(_memory->getSignature(link.from()) == 0)
|
|
{
|
|
UERROR("Link's \"from id\" %d is not in working memory", link.from());
|
|
return false;
|
|
}
|
|
if(_memory->getSignature(link.to()) == 0)
|
|
{
|
|
UERROR("Link's \"to id\" %d is not in working memory", link.to());
|
|
return false;
|
|
}
|
|
|
|
if(_optimizedPoses.find(link.from()) == _optimizedPoses.end() &&
|
|
_optimizedPoses.find(link.to()) == _optimizedPoses.end())
|
|
{
|
|
UERROR("Neither nodes %d or %d are in the local graph (size=%d). One of the 2 nodes should be in the local graph.", (int)_optimizedPoses.size(), link.from(), link.to());
|
|
return false;
|
|
}
|
|
|
|
// add temporary the link
|
|
if(!_memory->addLink(link))
|
|
{
|
|
UERROR("Cannot add new link %d->%d to memory", link.from(), link.to());
|
|
return false;
|
|
}
|
|
|
|
// optimize with new link
|
|
std::map<int, Transform> poses = _optimizedPoses;
|
|
std::multimap<int, Link> links;
|
|
cv::Mat covariance;
|
|
optimizeCurrentMap(_memory->getLastWorkingSignature(true)->id(), false, poses, covariance, &links);
|
|
|
|
if(poses.find(link.from()) == poses.end())
|
|
{
|
|
UERROR("Link's \"from id\" %d is not in the graph (size=%d)", link.from(), (int)poses.size());
|
|
_memory->removeLink(link.from(), link.to());
|
|
return false;
|
|
}
|
|
if(poses.find(link.to()) == poses.end())
|
|
{
|
|
UERROR("Link's \"to id\" %d is not in the graph (size=%d)", link.to(), (int)poses.size());
|
|
_memory->removeLink(link.from(), link.to());
|
|
return false;
|
|
}
|
|
|
|
std::string msg;
|
|
if(poses.empty())
|
|
{
|
|
msg = uFormat("Rejecting edge %d->%d because graph optimization has failed!", link.from(), link.to());
|
|
}
|
|
else
|
|
{
|
|
graph::MaxGraphErrors maxGraphErrors;
|
|
|
|
maxGraphErrors = graph::computeMaxGraphErrors(
|
|
poses,
|
|
links);
|
|
if(maxGraphErrors.linearLink.isValid())
|
|
{
|
|
UINFO("Max optimization linear error = %f m (link %d->%d)", maxGraphErrors.linear, maxGraphErrors.linearLink.from(), maxGraphErrors.linearLink.to());
|
|
if(_optimizationMaxError > 0.0f && maxGraphErrors.linearRatio > _optimizationMaxError)
|
|
{
|
|
msg = uFormat("Rejecting edge %d->%d because "
|
|
"graph error is too large after optimization (%f m for edge %d->%d with ratio %f > std=%f m). "
|
|
"\"%s\" is %f.",
|
|
link.from(),
|
|
link.to(),
|
|
maxGraphErrors.linear,
|
|
maxGraphErrors.linearLink.from(),
|
|
maxGraphErrors.linearLink.to(),
|
|
maxGraphErrors.linearRatio,
|
|
sqrt(maxGraphErrors.linearLink.transVariance()),
|
|
Parameters::kRGBDOptimizeMaxError().c_str(),
|
|
_optimizationMaxError);
|
|
}
|
|
else if(_optimizationMaxError == 0.0f && maxGraphErrors.linearRatio>100 && !_graphOptimizer->isRobust())
|
|
{
|
|
UERROR("Huge optimization error detected!"
|
|
"Linear error ratio of %f (edge %d->%d, type=%d, abs error=%f m, stddev=%f). You may consider "
|
|
"enabling \"%s\" to reject those bad optimizations by setting it to a non null value!",
|
|
maxGraphErrors.linearRatio,
|
|
maxGraphErrors.linearLink.from(),
|
|
maxGraphErrors.linearLink.to(),
|
|
maxGraphErrors.linearLink.type(),
|
|
maxGraphErrors.linear,
|
|
sqrt(maxGraphErrors.linearLink.transVariance()),
|
|
Parameters::kRGBDOptimizeMaxError().c_str());
|
|
}
|
|
}
|
|
else if(maxGraphErrors.angularLink.isValid())
|
|
{
|
|
UINFO("Max optimization angular error = %f deg (link %d->%d)", maxGraphErrors.angular*180.0f/M_PI, maxGraphErrors.angularLink.from(), maxGraphErrors.angularLink.to());
|
|
if(_optimizationMaxError > 0.0f && maxGraphErrors.angularRatio > _optimizationMaxError)
|
|
{
|
|
msg = uFormat("Rejecting edge %d->%d because "
|
|
"graph error is too large after optimization (%f deg for edge %d->%d with ratio %f > std=%f deg). "
|
|
"\"%s\" is %f m.",
|
|
link.from(),
|
|
link.to(),
|
|
maxGraphErrors.angular*180.0f/M_PI,
|
|
maxGraphErrors.angularLink.from(),
|
|
maxGraphErrors.angularLink.to(),
|
|
maxGraphErrors.angularRatio,
|
|
sqrt(maxGraphErrors.angularLink.rotVariance()),
|
|
Parameters::kRGBDOptimizeMaxError().c_str(),
|
|
_optimizationMaxError);
|
|
}
|
|
else if(_optimizationMaxError == 0.0f && maxGraphErrors.angularRatio>100 && !_graphOptimizer->isRobust())
|
|
{
|
|
UERROR("Huge optimization error detected!"
|
|
"Angular error ratio of %f (edge %d->%d, type=%d, abs error=%f m, stddev=%f). You may consider "
|
|
"enabling \"%s\" to reject those bad optimizations by setting it to a non null value!",
|
|
maxGraphErrors.angularRatio,
|
|
maxGraphErrors.angularLink.from(),
|
|
maxGraphErrors.angularLink.to(),
|
|
maxGraphErrors.angularLink.type(),
|
|
maxGraphErrors.angular*180.0f/CV_PI,
|
|
sqrt(maxGraphErrors.angularLink.rotVariance()),
|
|
Parameters::kRGBDOptimizeMaxError().c_str());
|
|
}
|
|
}
|
|
}
|
|
if(!msg.empty())
|
|
{
|
|
UERROR("%s", msg.c_str());
|
|
_memory->removeLink(link.from(), link.to());
|
|
return false;
|
|
}
|
|
|
|
// Update optimized poses
|
|
for(std::map<int, Transform>::iterator iter=_optimizedPoses.begin(); iter!=_optimizedPoses.end(); ++iter)
|
|
{
|
|
std::map<int, Transform>::iterator jter = poses.find(iter->first);
|
|
if(jter != poses.end())
|
|
{
|
|
iter->second = jter->second;
|
|
}
|
|
}
|
|
if(!_optimizeFromGraphEnd)
|
|
{
|
|
_mapCorrection = _optimizedPoses.rbegin()->second * _memory->getSignature(_optimizedPoses.rbegin()->first)->getPose().inverse();
|
|
}
|
|
|
|
std::map<int, Transform> tmp;
|
|
// Update also the links if some have been added in WM
|
|
_memory->getMetricConstraints(uKeysSet(_optimizedPoses), tmp, _constraints, false);
|
|
// This will force rtabmap_ros to regenerate the global occupancy grid if there was one
|
|
_memory->save2DMap(cv::Mat(), 0, 0, 0);
|
|
|
|
return true;
|
|
}
|
|
else // localization mode
|
|
{
|
|
int oldestId = link.from()>link.to()?link.to():link.from();
|
|
int newestId = link.from()<link.to()?link.to():link.from();
|
|
|
|
if(_memory->getSignature(oldestId) == 0)
|
|
{
|
|
UERROR("Link's id %d is not in working memory", oldestId);
|
|
return false;
|
|
}
|
|
if(_optimizedPoses.find(oldestId) == _optimizedPoses.end())
|
|
{
|
|
UERROR("Link's id %d is not in the optimized graph (_optimizedPoses=%d)", oldestId, (int)_optimizedPoses.size());
|
|
return false;
|
|
}
|
|
if(_optimizeFromGraphEnd)
|
|
{
|
|
UERROR("Adding link with %s=true in localization mode is not supported.", Parameters::kRGBDOptimizeFromGraphEnd().c_str());
|
|
return false;
|
|
}
|
|
if(_odomCachePoses.find(newestId) == _odomCachePoses.end())
|
|
{
|
|
if(!_odomCachePoses.empty())
|
|
{
|
|
UERROR("Link's id %d is not in the odometry cache (oldest=%d, newest=%d, %s=%d)",
|
|
newestId,
|
|
_odomCachePoses.begin()->first,
|
|
_odomCachePoses.rbegin()->first,
|
|
Parameters::kRGBDMaxOdomCacheSize().c_str(),
|
|
_maxOdomCacheSize);
|
|
}
|
|
else
|
|
{
|
|
UERROR("Link's id %d is not in the odometry cache (%s=%d).",
|
|
newestId,
|
|
Parameters::kRGBDMaxOdomCacheSize().c_str(),
|
|
_maxOdomCacheSize);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Verify if the new localization is valid by checking if there is
|
|
// not too much deformation using current odometry poses
|
|
// This will also refine localization links
|
|
|
|
std::map<int, Transform> poses = _odomCachePoses;
|
|
std::multimap<int, Link> constraints = _odomCacheConstraints;
|
|
constraints.insert(std::make_pair(link.from(), link));
|
|
cv::Mat priorInfMat = cv::Mat::eye(6,6, CV_64FC1)*_localizationPriorInf;
|
|
for(std::multimap<int, Link>::iterator iter=constraints.begin(); iter!=constraints.end(); ++iter)
|
|
{
|
|
std::map<int, Transform>::iterator iterPose = _optimizedPoses.find(iter->second.to());
|
|
if(iterPose != _optimizedPoses.end() && poses.find(iterPose->first) == poses.end())
|
|
{
|
|
poses.insert(*iterPose);
|
|
// make the poses in the map fixed
|
|
constraints.insert(std::make_pair(iterPose->first, Link(iterPose->first, iterPose->first, Link::kPosePrior, iterPose->second, priorInfMat)));
|
|
}
|
|
}
|
|
|
|
std::map<int, Transform> posesOut;
|
|
std::multimap<int, Link> edgeConstraintsOut;
|
|
bool priorsIgnored = _graphOptimizer->priorsIgnored();
|
|
_graphOptimizer->setPriorsIgnored(false); //temporary set false to use priors above to fix nodes of the map
|
|
_graphOptimizer->getConnectedGraph(newestId, poses, constraints, posesOut, edgeConstraintsOut);
|
|
std::map<int, Transform> optPoses = _graphOptimizer->optimize(poses.begin()->first, posesOut, edgeConstraintsOut);
|
|
_graphOptimizer->setPriorsIgnored(priorsIgnored); // set back
|
|
|
|
bool rejectLocalization = false;
|
|
if(optPoses.empty())
|
|
{
|
|
UWARN("Optimization failed, rejecting localization!");
|
|
rejectLocalization = true;
|
|
}
|
|
else
|
|
{
|
|
UINFO("Compute max graph errors...");
|
|
graph::MaxGraphErrors maxGraphErrors = graph::computeMaxGraphErrors(
|
|
optPoses,
|
|
edgeConstraintsOut,
|
|
_graphOptimizer->isSlam2d());
|
|
if(!maxGraphErrors.linearLink.isValid() && !maxGraphErrors.angularLink.isValid())
|
|
{
|
|
UWARN("Could not compute graph errors! Wrong loop closures could be accepted!");
|
|
}
|
|
|
|
if(maxGraphErrors.linearLink.isValid())
|
|
{
|
|
UINFO("Max optimization linear error = %f m (link %d->%d, var=%f, ratio error/std=%f)", maxGraphErrors.linear, maxGraphErrors.linearLink.from(), maxGraphErrors.linearLink.to(), maxGraphErrors.linearLink.transVariance(), maxGraphErrors.linear/sqrt(maxGraphErrors.linearLink.transVariance()));
|
|
if(_optimizationMaxError > 0.0f && maxGraphErrors.linearRatio > _optimizationMaxError)
|
|
{
|
|
UWARN("Rejecting localization (%d <-> %d) in this "
|
|
"iteration because a wrong loop closure has been "
|
|
"detected after graph optimization, resulting in "
|
|
"a maximum graph error ratio of %f (edge %d->%d, type=%d, abs error=%f m, stddev=%f). The "
|
|
"maximum error ratio parameter \"%s\" is %f of std deviation.",
|
|
link.from(),
|
|
link.to(),
|
|
maxGraphErrors.linearRatio,
|
|
maxGraphErrors.linearLink.from(),
|
|
maxGraphErrors.linearLink.to(),
|
|
maxGraphErrors.linearLink.type(),
|
|
maxGraphErrors.linear,
|
|
sqrt(maxGraphErrors.linearLink.transVariance()),
|
|
Parameters::kRGBDOptimizeMaxError().c_str(),
|
|
_optimizationMaxError);
|
|
rejectLocalization = true;
|
|
}
|
|
else if(_optimizationMaxError == 0.0f && maxGraphErrors.linearRatio>100 && !_graphOptimizer->isRobust())
|
|
{
|
|
UERROR("Huge optimization error detected!"
|
|
"Linear error ratio of %f (edge %d->%d, type=%d, abs error=%f m, stddev=%f). You may consider "
|
|
"enabling \"%s\" to reject those bad optimizations by setting it to a non null value!",
|
|
maxGraphErrors.linearRatio,
|
|
maxGraphErrors.linearLink.from(),
|
|
maxGraphErrors.linearLink.to(),
|
|
maxGraphErrors.linearLink.type(),
|
|
maxGraphErrors.linear,
|
|
sqrt(maxGraphErrors.linearLink.transVariance()),
|
|
Parameters::kRGBDOptimizeMaxError().c_str());
|
|
}
|
|
}
|
|
if(maxGraphErrors.angularLink.isValid())
|
|
{
|
|
UINFO("Max optimization angular error = %f deg (link %d->%d, var=%f, ratio error/std=%f)", maxGraphErrors.angular*180.0f/CV_PI, maxGraphErrors.angularLink.from(), maxGraphErrors.angularLink.to(), maxGraphErrors.angularLink.rotVariance(), maxGraphErrors.angular/sqrt(maxGraphErrors.angularLink.rotVariance()));
|
|
if(_optimizationMaxError > 0.0f && maxGraphErrors.angularRatio > _optimizationMaxError)
|
|
{
|
|
UWARN("Rejecting localization (%d <-> %d) in this "
|
|
"iteration because a wrong loop closure has been "
|
|
"detected after graph optimization, resulting in "
|
|
"a maximum graph error ratio of %f (edge %d->%d, type=%d, abs error=%f deg, stddev=%f). The "
|
|
"maximum error ratio parameter \"%s\" is %f of std deviation.",
|
|
link.from(),
|
|
link.to(),
|
|
maxGraphErrors.angularRatio,
|
|
maxGraphErrors.angularLink.from(),
|
|
maxGraphErrors.angularLink.to(),
|
|
maxGraphErrors.angularLink.type(),
|
|
maxGraphErrors.angular*180.0f/CV_PI,
|
|
sqrt(maxGraphErrors.angularLink.rotVariance()),
|
|
Parameters::kRGBDOptimizeMaxError().c_str(),
|
|
_optimizationMaxError);
|
|
rejectLocalization = true;
|
|
}
|
|
else if(_optimizationMaxError == 0.0f && maxGraphErrors.angularRatio>100 && !_graphOptimizer->isRobust())
|
|
{
|
|
UERROR("Huge optimization error detected!"
|
|
"Angular error ratio of %f (edge %d->%d, type=%d, abs error=%f m, stddev=%f). You may consider "
|
|
"enabling \"%s\" to reject those bad optimizations by setting it to a non null value!",
|
|
maxGraphErrors.angularRatio,
|
|
maxGraphErrors.angularLink.from(),
|
|
maxGraphErrors.angularLink.to(),
|
|
maxGraphErrors.angularLink.type(),
|
|
maxGraphErrors.angular*180.0f/CV_PI,
|
|
sqrt(maxGraphErrors.angularLink.rotVariance()),
|
|
Parameters::kRGBDOptimizeMaxError().c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!rejectLocalization)
|
|
{
|
|
Transform newOptPoseInv = optPoses.at(link.from()).inverse();
|
|
Transform newT = newOptPoseInv * optPoses.at(link.to());
|
|
Link linkTmp = link;
|
|
linkTmp.setTransform(newT);
|
|
if(oldestId == link.from())
|
|
{
|
|
_lastLocalizationPose = _optimizedPoses.at(link.from()) * linkTmp.transform();
|
|
_odomCacheConstraints.insert(std::make_pair(linkTmp.to(), linkTmp.inverse()));
|
|
}
|
|
else
|
|
{
|
|
_lastLocalizationPose = _optimizedPoses.at(link.to()) * linkTmp.transform().inverse();
|
|
_odomCacheConstraints.insert(std::make_pair(linkTmp.from(), linkTmp));
|
|
}
|
|
UINFO("Set _lastLocalizationPose=%s", _lastLocalizationPose.prettyPrint().c_str());
|
|
if(_graphOptimizer->isSlam2d())
|
|
{
|
|
// transform constraint to 2D
|
|
_lastLocalizationPose = _lastLocalizationPose.to3DoF();
|
|
}
|
|
Transform odomPose = _odomCachePoses.find(newestId)->second;
|
|
_mapCorrection = _lastLocalizationPose * odomPose.inverse();
|
|
_lastLocalizationNodeId = oldestId;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
cv::Mat Rtabmap::getInformation(const cv::Mat & covariance) const
|
|
{
|
|
cv::Mat information = covariance.inv();
|
|
if(_loopCovLimited)
|
|
{
|
|
const std::vector<double> & odomMaxInf = _memory->getOdomMaxInf();
|
|
if(odomMaxInf.size() == 6)
|
|
{
|
|
for(int i=0; i<6; ++i)
|
|
{
|
|
if(information.at<double>(i,i) > odomMaxInf[i])
|
|
{
|
|
information.at<double>(i,i) = odomMaxInf[i];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return information;
|
|
}
|
|
|
|
void Rtabmap::addNodesToRepublish(const std::vector<int> & ids)
|
|
{
|
|
if(ids.empty())
|
|
{
|
|
_nodesToRepublish.clear();
|
|
}
|
|
else if(_maxRepublished > 0 && _publishLastSignatureData)
|
|
{
|
|
_nodesToRepublish.insert(ids.begin(), ids.end());
|
|
}
|
|
else if(_maxRepublished == 0)
|
|
{
|
|
UWARN("%s=0, so cannot republish the %d requested nodes.", Parameters::kRtabmapMaxRepublished().c_str(), (int)ids.size());
|
|
}
|
|
else //_publishLastSignatureData=false
|
|
{
|
|
UWARN("%s=false, so cannot republish the %d requested nodes.", Parameters::kRtabmapPublishLastSignature().c_str(), (int)ids.size());
|
|
}
|
|
}
|
|
|
|
void Rtabmap::clearPath(int status)
|
|
{
|
|
UINFO("status=%d", status);
|
|
_pathStatus = status;
|
|
_path.clear();
|
|
_pathCurrentIndex=0;
|
|
_pathGoalIndex = 0;
|
|
_pathTransformToGoal.setIdentity();
|
|
_pathUnreachableNodes.clear();
|
|
_pathStuckCount = 0;
|
|
_pathStuckDistance = 0.0f;
|
|
if(_memory)
|
|
{
|
|
_memory->removeAllVirtualLinks();
|
|
}
|
|
}
|
|
|
|
// return true if path is updated
|
|
bool Rtabmap::computePath(int targetNode, bool global)
|
|
{
|
|
this->clearPath(0);
|
|
|
|
if(targetNode>0)
|
|
{
|
|
UINFO("Planning a path to node %d (global=%d)", targetNode, global?1:0);
|
|
}
|
|
else
|
|
{
|
|
UINFO("Planning a path to landmark %d (global=%d)", -targetNode, global?1:0);
|
|
}
|
|
|
|
if(!_rgbdSlamMode)
|
|
{
|
|
UWARN("A path can only be computed in RGBD-SLAM mode");
|
|
return false;
|
|
}
|
|
|
|
UTimer totalTimer;
|
|
UTimer timer;
|
|
Transform transformToLandmark = Transform::getIdentity();
|
|
|
|
// No need to optimize the graph
|
|
if(_memory)
|
|
{
|
|
int currentNode = 0;
|
|
if(_memory->isIncremental())
|
|
{
|
|
if(!_memory->getLastWorkingSignature(true))
|
|
{
|
|
UWARN("Working memory is empty... cannot compute a path");
|
|
return false;
|
|
}
|
|
currentNode = _memory->getLastWorkingSignature(true)->id();
|
|
}
|
|
else
|
|
{
|
|
if(_lastLocalizationPose.isNull() || _optimizedPoses.empty())
|
|
{
|
|
UWARN("Last localization pose is null or optimized graph is empty... cannot compute a path");
|
|
return false;
|
|
}
|
|
if(_optimizedPoses.begin()->first < 0)
|
|
{
|
|
std::map<int, Transform> poses(_optimizedPoses.lower_bound(1), _optimizedPoses.end());
|
|
currentNode = graph::findNearestNode(poses, _lastLocalizationPose);
|
|
}
|
|
else
|
|
{
|
|
currentNode = graph::findNearestNode(_optimizedPoses, _lastLocalizationPose);
|
|
}
|
|
}
|
|
if(currentNode && targetNode)
|
|
{
|
|
std::list<std::pair<int, Transform> > path = graph::computePath(
|
|
currentNode,
|
|
targetNode,
|
|
_memory,
|
|
global,
|
|
false,
|
|
_pathLinearVelocity,
|
|
_pathAngularVelocity);
|
|
|
|
//transform in current referential
|
|
Transform t = uValue(_optimizedPoses, currentNode, Transform::getIdentity());
|
|
_path.resize(path.size());
|
|
int oi = 0;
|
|
for(std::list<std::pair<int, Transform> >::iterator iter=path.begin(); iter!=path.end();++iter)
|
|
{
|
|
if(iter->first > 0)
|
|
{
|
|
// just keep nodes in the path
|
|
_path[oi].first = iter->first;
|
|
_path[oi++].second = t * iter->second;
|
|
}
|
|
}
|
|
_path.resize(oi);
|
|
if(!_path.empty() && !path.empty() && path.rbegin()->first < 0)
|
|
{
|
|
transformToLandmark = _path.back().second.inverse() * t * path.rbegin()->second;
|
|
}
|
|
}
|
|
else if(currentNode == 0)
|
|
{
|
|
UWARN("We should be localized before planning.");
|
|
}
|
|
}
|
|
UINFO("Total planning time = %fs (%d nodes, %f m long)", totalTimer.ticks(), (int)_path.size(), graph::computePathLength(_path));
|
|
|
|
if(_path.size() == 0)
|
|
{
|
|
_path.clear();
|
|
UWARN("Cannot compute a path!");
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
UINFO("Path generated! Size=%d", (int)_path.size());
|
|
if(ULogger::level() == ULogger::kInfo)
|
|
{
|
|
std::stringstream stream;
|
|
for(unsigned int i=0; i<_path.size(); ++i)
|
|
{
|
|
stream << _path[i].first;
|
|
if(i+1 < _path.size())
|
|
{
|
|
stream << " ";
|
|
}
|
|
}
|
|
UINFO("Path = [%s]", stream.str().c_str());
|
|
}
|
|
if(_goalsSavedInUserData)
|
|
{
|
|
// set goal to latest signature
|
|
std::string goalStr = uFormat("GOAL:%d", targetNode);
|
|
|
|
// use label is exist
|
|
if(_memory->getSignature(targetNode))
|
|
{
|
|
if(!_memory->getSignature(targetNode)->getLabel().empty())
|
|
{
|
|
goalStr = std::string("GOAL:")+_memory->getSignature(targetNode)->getLabel();
|
|
}
|
|
}
|
|
else if(global)
|
|
{
|
|
std::map<int, std::string> labels = _memory->getAllLabels();
|
|
std::map<int, std::string>::iterator iter = labels.find(targetNode);
|
|
if(iter != labels.end() && !iter->second.empty())
|
|
{
|
|
goalStr = std::string("GOAL:")+labels.at(targetNode);
|
|
}
|
|
}
|
|
setUserData(0, cv::Mat(1, int(goalStr.size()+1), CV_8SC1, (void *)goalStr.c_str()).clone());
|
|
}
|
|
_pathTransformToGoal = transformToLandmark;
|
|
|
|
updateGoalIndex();
|
|
return _path.size() || _pathStatus > 0;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Rtabmap::computePath(const Transform & targetPose, float tolerance)
|
|
{
|
|
this->clearPath(0);
|
|
|
|
UINFO("Planning a path to pose %s ", targetPose.prettyPrint().c_str());
|
|
if(tolerance < 0.0f)
|
|
{
|
|
tolerance = _localRadius;
|
|
}
|
|
|
|
std::list<std::pair<int, Transform> > pathPoses;
|
|
|
|
if(!_rgbdSlamMode)
|
|
{
|
|
UWARN("This method can only be used in RGBD-SLAM mode");
|
|
return false;
|
|
}
|
|
|
|
//Find the nearest node
|
|
UTimer timer;
|
|
std::map<int, Transform> nodes = _optimizedPoses;
|
|
std::multimap<int, int> links;
|
|
for(std::map<int, Transform>::iterator iter=nodes.upper_bound(0); iter!=nodes.end(); ++iter)
|
|
{
|
|
const Signature * s = _memory->getSignature(iter->first);
|
|
UASSERT(s);
|
|
for(std::map<int, Link>::const_iterator jter=s->getLinks().begin(); jter!=s->getLinks().end(); ++jter)
|
|
{
|
|
// only add links for which poses are in "nodes"
|
|
if(jter->second.from() != jter->second.to() && uContains(nodes, jter->second.to()))
|
|
{
|
|
links.insert(std::make_pair(jter->second.from(), jter->second.to()));
|
|
//links.insert(std::make_pair(jter->second.to(), jter->second.from())); // <-> (commented: already added when iterating in nodes)
|
|
}
|
|
}
|
|
}
|
|
UINFO("Time getting links = %fs", timer.ticks());
|
|
|
|
int currentNode = 0;
|
|
if(_memory->isIncremental())
|
|
{
|
|
if(!_memory->getLastWorkingSignature(true))
|
|
{
|
|
UWARN("Working memory is empty... cannot compute a path");
|
|
return false;
|
|
}
|
|
currentNode = _memory->getLastWorkingSignature(true)->id();
|
|
}
|
|
else
|
|
{
|
|
if(_lastLocalizationPose.isNull() || _optimizedPoses.empty())
|
|
{
|
|
UWARN("Last localization pose is null... cannot compute a path");
|
|
return false;
|
|
}
|
|
if(_optimizedPoses.begin()->first < 0)
|
|
{
|
|
std::map<int, Transform> poses(_optimizedPoses.lower_bound(1), _optimizedPoses.end());
|
|
currentNode = graph::findNearestNode(poses, _lastLocalizationPose);
|
|
}
|
|
else
|
|
{
|
|
currentNode = graph::findNearestNode(_optimizedPoses, _lastLocalizationPose);
|
|
}
|
|
}
|
|
|
|
int nearestId;
|
|
if(!_lastLocalizationPose.isNull() && _lastLocalizationPose.getDistance(targetPose) < tolerance)
|
|
{
|
|
// target can be reached from the current node
|
|
nearestId = currentNode;
|
|
}
|
|
else
|
|
{
|
|
nearestId = rtabmap::graph::findNearestNode(nodes, targetPose);
|
|
}
|
|
UINFO("Nearest node found=%d ,%fs", nearestId, timer.ticks());
|
|
if(nearestId > 0)
|
|
{
|
|
if(tolerance != 0.0f && targetPose.getDistance(nodes.at(nearestId)) > tolerance)
|
|
{
|
|
UWARN("Cannot plan farther than %f m from the graph! (distance=%f m from node %d)",
|
|
tolerance, targetPose.getDistance(nodes.at(nearestId)), nearestId);
|
|
}
|
|
else
|
|
{
|
|
UINFO("Computing path from location %d to %d", currentNode, nearestId);
|
|
UTimer timer;
|
|
_path = uListToVector(rtabmap::graph::computePath(nodes, links, currentNode, nearestId));
|
|
UINFO("A* time = %fs", timer.ticks());
|
|
|
|
if(_path.size() == 0)
|
|
{
|
|
UWARN("Cannot compute a path!");
|
|
}
|
|
else
|
|
{
|
|
UINFO("Path generated! Size=%d", (int)_path.size());
|
|
if(ULogger::level() == ULogger::kInfo)
|
|
{
|
|
std::stringstream stream;
|
|
for(unsigned int i=0; i<_path.size(); ++i)
|
|
{
|
|
stream << _path[i].first;
|
|
if(i+1 < _path.size())
|
|
{
|
|
stream << " ";
|
|
}
|
|
}
|
|
UINFO("Path = [%s]", stream.str().c_str());
|
|
}
|
|
|
|
UASSERT(uContains(nodes, _path.back().first));
|
|
_pathTransformToGoal = nodes.at(_path.back().first).inverse() * targetPose;
|
|
|
|
updateGoalIndex();
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UWARN("Nearest node not found in graph (size=%d) for pose %s", (int)nodes.size(), targetPose.prettyPrint().c_str());
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
std::vector<std::pair<int, Transform> > Rtabmap::getPathNextPoses() const
|
|
{
|
|
std::vector<std::pair<int, Transform> > poses;
|
|
if(_path.size())
|
|
{
|
|
UASSERT(_pathCurrentIndex < _path.size() && _pathGoalIndex < _path.size());
|
|
poses.resize(_pathGoalIndex-_pathCurrentIndex+1);
|
|
int oi=0;
|
|
for(unsigned int i=_pathCurrentIndex; i<=_pathGoalIndex; ++i)
|
|
{
|
|
std::map<int, Transform>::const_iterator iter = _optimizedPoses.find(_path[i].first);
|
|
if(iter != _optimizedPoses.end())
|
|
{
|
|
poses[oi++] = *iter;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
poses.resize(oi);
|
|
}
|
|
return poses;
|
|
}
|
|
|
|
std::vector<int> Rtabmap::getPathNextNodes() const
|
|
{
|
|
std::vector<int> ids;
|
|
if(_path.size())
|
|
{
|
|
UASSERT(_pathCurrentIndex < _path.size() && _pathGoalIndex < _path.size());
|
|
ids.resize(_pathGoalIndex-_pathCurrentIndex+1);
|
|
int oi = 0;
|
|
for(unsigned int i=_pathCurrentIndex; i<=_pathGoalIndex; ++i)
|
|
{
|
|
std::map<int, Transform>::const_iterator iter = _optimizedPoses.find(_path[i].first);
|
|
if(iter != _optimizedPoses.end())
|
|
{
|
|
ids[oi++] = iter->first;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
ids.resize(oi);
|
|
}
|
|
return ids;
|
|
}
|
|
|
|
int Rtabmap::getPathCurrentGoalId() const
|
|
{
|
|
if(_path.size())
|
|
{
|
|
UASSERT(_pathGoalIndex <= _path.size());
|
|
return _path[_pathGoalIndex].first;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void Rtabmap::updateGoalIndex()
|
|
{
|
|
if(!_rgbdSlamMode)
|
|
{
|
|
UWARN("This method can on be used in RGBD-SLAM mode!");
|
|
return;
|
|
}
|
|
|
|
if( _memory && _path.size())
|
|
{
|
|
// remove all previous virtual links
|
|
bool hasIntermediateNodes = false;
|
|
for(unsigned int i=0; i<_pathCurrentIndex && i<_path.size(); ++i)
|
|
{
|
|
const Signature * s = _memory->getSignature(_path[i].first);
|
|
if(s)
|
|
{
|
|
_memory->removeVirtualLinks(s->id());
|
|
}
|
|
if(s->getWeight() == -1)
|
|
{
|
|
hasIntermediateNodes = true;
|
|
}
|
|
}
|
|
|
|
// for the current index, only keep the newest virtual link
|
|
// This will make sure that the path is still connected even
|
|
// if the new signature is removed (e.g., because of a small displacement)
|
|
UASSERT(_pathCurrentIndex < _path.size());
|
|
const Signature * currentIndexS = _memory->getSignature(_path[_pathCurrentIndex].first);
|
|
UASSERT_MSG(currentIndexS != 0, uFormat("_path[%d].first=%d", _pathCurrentIndex, _path[_pathCurrentIndex].first).c_str());
|
|
std::multimap<int, Link> links = currentIndexS->getLinks(); // make a copy
|
|
bool latestVirtualLinkFound = false;
|
|
for(std::multimap<int, Link>::reverse_iterator iter=links.rbegin(); iter!=links.rend(); ++iter)
|
|
{
|
|
if(iter->second.type() == Link::kVirtualClosure)
|
|
{
|
|
if(latestVirtualLinkFound)
|
|
{
|
|
_memory->removeLink(currentIndexS->id(), iter->first);
|
|
}
|
|
else
|
|
{
|
|
latestVirtualLinkFound = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Make sure the next signatures on the path are linked together
|
|
float distanceSoFar = 0.0f;
|
|
for(unsigned int i=_pathCurrentIndex+1;
|
|
i<_path.size() && !hasIntermediateNodes;
|
|
++i)
|
|
{
|
|
if(i>0)
|
|
{
|
|
if(_localRadius > 0.0f)
|
|
{
|
|
distanceSoFar += _path[i-1].second.getDistance(_path[i].second);
|
|
}
|
|
|
|
if(_path[i].first != _path[i-1].first)
|
|
{
|
|
const Signature * s = _memory->getSignature(_path[i].first);
|
|
if(s)
|
|
{
|
|
if(s->getWeight() == -1)
|
|
{
|
|
hasIntermediateNodes = true;
|
|
break;
|
|
}
|
|
if(!s->hasLink(_path[i-1].first) && _memory->getSignature(_path[i-1].first) != 0)
|
|
{
|
|
Transform virtualLoop = _path[i].second.inverse() * _path[i-1].second;
|
|
_memory->addLink(Link(_path[i].first, _path[i-1].first, Link::kVirtualClosure, virtualLoop, cv::Mat::eye(6,6,CV_64FC1)*0.01)); // on the optimized path
|
|
UINFO("Added Virtual link between %d and %d", _path[i-1].first, _path[i].first);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(distanceSoFar > _localRadius)
|
|
{
|
|
UDEBUG("Farthest goal=%d : %f m", _path[i].first, distanceSoFar);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(hasIntermediateNodes)
|
|
{
|
|
UERROR("Cannot follow a path with a map containing intermediate nodes (not supported: don't use intermediate nodes if rtabmap's planner has to be used). Aborting current plan!");
|
|
this->clearPath(-1);
|
|
return;
|
|
}
|
|
|
|
UDEBUG("current node = %d current goal = %d", _path[_pathCurrentIndex].first, _path[_pathGoalIndex].first);
|
|
Transform currentPose;
|
|
if(_memory->isIncremental())
|
|
{
|
|
if(_memory->getLastWorkingSignature(true) == 0 ||
|
|
!uContains(_optimizedPoses, _memory->getLastWorkingSignature(true)->id()))
|
|
{
|
|
UERROR("Last node is null in memory or not in optimized poses. Aborting the plan...");
|
|
this->clearPath(-1);
|
|
return;
|
|
}
|
|
currentPose = _optimizedPoses.at(_memory->getLastWorkingSignature(true)->id());
|
|
}
|
|
else
|
|
{
|
|
if(_lastLocalizationPose.isNull())
|
|
{
|
|
UERROR("Last localization pose is null. Aborting the plan...");
|
|
this->clearPath(-1);
|
|
return;
|
|
}
|
|
currentPose = _lastLocalizationPose;
|
|
}
|
|
|
|
int goalId = _path.back().first;
|
|
if(uContains(_optimizedPoses, goalId))
|
|
{
|
|
//use local position to know if the goal is reached
|
|
float d = currentPose.getDistance(_optimizedPoses.at(goalId)*_pathTransformToGoal);
|
|
if(d < _goalReachedRadius)
|
|
{
|
|
UINFO("Goal %d reached!", goalId);
|
|
this->clearPath(1);
|
|
}
|
|
}
|
|
|
|
if(_path.size())
|
|
{
|
|
//Always check if the farthest node is accessible in local map (max to local space radius if set)
|
|
unsigned int goalIndex = _pathCurrentIndex;
|
|
float distanceFromCurrentNode = 0.0f;
|
|
bool sameGoalIndex = false;
|
|
for(unsigned int i=_pathCurrentIndex+1; i<_path.size(); ++i)
|
|
{
|
|
if(uContains(_optimizedPoses, _path[i].first))
|
|
{
|
|
if(_localRadius > 0.0f)
|
|
{
|
|
distanceFromCurrentNode = _path[_pathCurrentIndex].second.getDistance(_path[i].second);
|
|
}
|
|
|
|
if((goalIndex == _pathCurrentIndex && i == _path.size()-1) ||
|
|
_pathUnreachableNodes.find(i) == _pathUnreachableNodes.end())
|
|
{
|
|
goalIndex = i;
|
|
if(distanceFromCurrentNode > _localRadius)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
UASSERT(_pathGoalIndex < _path.size() && goalIndex < _path.size());
|
|
if(_pathGoalIndex != goalIndex)
|
|
{
|
|
UINFO("Updated current goal from %d to %d (%d/%d)",
|
|
(int)_path[_pathGoalIndex].first, _path[goalIndex].first, (int)goalIndex+1, (int)_path.size());
|
|
_pathGoalIndex = goalIndex;
|
|
}
|
|
else
|
|
{
|
|
sameGoalIndex = true;
|
|
}
|
|
|
|
// update nearest pose in the path
|
|
unsigned int nearestNodeIndex = 0;
|
|
float distance = -1.0f;
|
|
bool sameCurrentIndex = false;
|
|
UASSERT(_pathGoalIndex < _path.size());
|
|
for(unsigned int i=_pathCurrentIndex; i<=_pathGoalIndex; ++i)
|
|
{
|
|
std::map<int, Transform>::iterator iter = _optimizedPoses.find(_path[i].first);
|
|
if(iter != _optimizedPoses.end())
|
|
{
|
|
float d = currentPose.getDistanceSquared(iter->second);
|
|
if(distance == -1.0f || distance > d)
|
|
{
|
|
distance = d;
|
|
nearestNodeIndex = i;
|
|
}
|
|
}
|
|
}
|
|
if(distance < 0)
|
|
{
|
|
UERROR("The nearest pose on the path not found! Aborting the plan...");
|
|
this->clearPath(-1);
|
|
}
|
|
else
|
|
{
|
|
UDEBUG("Nearest node = %d", _path[nearestNodeIndex].first);
|
|
}
|
|
if(distance >= 0 && nearestNodeIndex != _pathCurrentIndex)
|
|
{
|
|
_pathCurrentIndex = nearestNodeIndex;
|
|
_pathUnreachableNodes.erase(nearestNodeIndex); // if we are on it, it is reachable
|
|
}
|
|
else
|
|
{
|
|
sameCurrentIndex = true;
|
|
}
|
|
|
|
bool isStuck = false;
|
|
if(sameGoalIndex && sameCurrentIndex && _pathStuckIterations>0)
|
|
{
|
|
float distanceToCurrentGoal = 0.0f;
|
|
std::map<int, Transform>::iterator iter = _optimizedPoses.find(_path[_pathGoalIndex].first);
|
|
if(iter != _optimizedPoses.end())
|
|
{
|
|
if(_pathGoalIndex == _pathCurrentIndex &&
|
|
_pathGoalIndex == _path.size()-1)
|
|
{
|
|
distanceToCurrentGoal = currentPose.getDistanceSquared(iter->second*_pathTransformToGoal);
|
|
}
|
|
else
|
|
{
|
|
distanceToCurrentGoal = currentPose.getDistanceSquared(iter->second);
|
|
}
|
|
}
|
|
|
|
if(distanceToCurrentGoal > 0.0f)
|
|
{
|
|
if(distanceToCurrentGoal >= _pathStuckDistance)
|
|
{
|
|
// we are not approaching the goal
|
|
isStuck = true;
|
|
if(_pathStuckDistance == 0.0f)
|
|
{
|
|
_pathStuckDistance = distanceToCurrentGoal;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// no nodes available, cannot plan
|
|
isStuck = true;
|
|
}
|
|
}
|
|
|
|
if(isStuck && ++_pathStuckCount > _pathStuckIterations)
|
|
{
|
|
UWARN("Current goal %d not reached since %d iterations (\"RGBD/PlanStuckIterations\"=%d), mark that node as unreachable.",
|
|
_path[_pathGoalIndex].first,
|
|
_pathStuckCount,
|
|
_pathStuckIterations);
|
|
_pathStuckCount = 0;
|
|
_pathStuckDistance = 0.0;
|
|
_pathUnreachableNodes.insert(_pathGoalIndex);
|
|
// select previous reachable one
|
|
while(_pathUnreachableNodes.find(_pathGoalIndex) != _pathUnreachableNodes.end())
|
|
{
|
|
if(_pathGoalIndex == 0 || --_pathGoalIndex <= _pathCurrentIndex)
|
|
{
|
|
// plan failed!
|
|
UERROR("No upcoming nodes on the path are reachable! Aborting the plan...");
|
|
this->clearPath(-1);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
else if(!isStuck)
|
|
{
|
|
_pathStuckCount = 0;
|
|
_pathStuckDistance = 0.0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Rtabmap::createGlobalScanMap()
|
|
{
|
|
UDEBUG("Creating global scan map (if scans are available)");
|
|
_globalScanMap.clear();
|
|
_globalScanMapPoses.clear();
|
|
std::vector<int> scanIndices;
|
|
std::map<int, Transform> scanViewpoints;
|
|
for(std::map<int, Transform>::iterator iter=_optimizedPoses.begin(); iter!=_optimizedPoses.end(); ++iter)
|
|
{
|
|
SensorData data = _memory->getNodeData(iter->first, false, true, false, false);
|
|
if(!data.laserScanCompressed().empty())
|
|
{
|
|
LaserScan scan;
|
|
data.uncompressDataConst(0, 0, &scan, 0, 0, 0, 0);
|
|
if(!scan.empty())
|
|
{
|
|
UDEBUG("Adding scan %d (format=%s, points=%d)", iter->first, scan.formatName().c_str(), scan.size());
|
|
scan = util3d::transformLaserScan(scan, iter->second*scan.localTransform());
|
|
if(_globalScanMap.empty() || _globalScanMap.format() == scan.format())
|
|
{
|
|
_globalScanMap += scan;
|
|
_globalScanMapPoses.insert(*iter);
|
|
scanViewpoints.insert(std::make_pair(iter->first, iter->second * scan.localTransform()));
|
|
scanIndices.resize(_globalScanMap.size(), iter->first);
|
|
}
|
|
else
|
|
{
|
|
UWARN("Incompatible scan formats (%s vs %s), cannot create global scan map.",
|
|
_globalScanMap.formatName().c_str(),
|
|
scan.formatName().c_str());
|
|
_globalScanMap.clear();
|
|
_globalScanMapPoses.clear();
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UDEBUG("Ignored %d (scan is empty), pose still added.", iter->first);
|
|
_globalScanMapPoses.insert(*iter);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UDEBUG("Ignored %d (no scan), pose still added.", iter->first);
|
|
_globalScanMapPoses.insert(*iter);
|
|
}
|
|
}
|
|
if(_globalScanMap.size() > 3)
|
|
{
|
|
float voxelSize = 0.0f;
|
|
int normalK = 0;
|
|
float normalRadius = 0.0f;
|
|
Parameters::parse(_parameters, Parameters::kMemLaserScanVoxelSize(), voxelSize);
|
|
Parameters::parse(_parameters, Parameters::kMemLaserScanNormalK(), normalK);
|
|
Parameters::parse(_parameters, Parameters::kMemLaserScanNormalRadius(), normalRadius);
|
|
|
|
if(voxelSize > 0.0f)
|
|
{
|
|
LaserScan voxelScan = util3d::commonFiltering(_globalScanMap, 1, 0, 0, voxelSize, normalK, normalRadius);
|
|
if(voxelScan.hasNormals())
|
|
{
|
|
// adjust with point of views
|
|
util3d::adjustNormalsToViewPoints(
|
|
scanViewpoints,
|
|
_globalScanMap,
|
|
scanIndices,
|
|
voxelScan);
|
|
}
|
|
_globalScanMap = voxelScan;
|
|
}
|
|
|
|
UINFO("Global scan map has been assembled (size=%d points, %d poses) "
|
|
"for proximity detection (only in localization mode %s=false and with %s=true)",
|
|
(int)_globalScanMap.size(),
|
|
(int)_globalScanMapPoses.size(),
|
|
Parameters::kMemIncrementalMemory().c_str(),
|
|
Parameters::kRGBDProximityGlobalScanMap().c_str());
|
|
|
|
//for debugging...
|
|
if(!_globalScanMap.empty() && ULogger::level() == ULogger::kDebug)
|
|
{
|
|
if(!_wDir.empty())
|
|
{
|
|
UWARN("Saving %s/rtabmap_global_scan_map.pcd (only saved when logger level is debug)", _wDir.c_str());
|
|
pcl::PCLPointCloud2::Ptr cloud2 = util3d::laserScanToPointCloud2(_globalScanMap);
|
|
pcl::io::savePCDFile(_wDir+"/rtabmap_global_scan_map.pcd", *cloud2);
|
|
}
|
|
else
|
|
{
|
|
UWARN("%s is enabled and logger is debug, but %s is not set, cannot save global scan map for debugging.",
|
|
Parameters::kRGBDProximityGlobalScanMap().c_str(), Parameters::kRtabmapWorkingDirectory().c_str());
|
|
}
|
|
}
|
|
}
|
|
if(!_globalScanMap.empty() && _globalScanMap.size()<100)
|
|
{
|
|
UWARN("Ignoring global scan map because it is too small (%d points).", (int)_globalScanMap.size());
|
|
_globalScanMap.clear();
|
|
_globalScanMapPoses.clear();
|
|
}
|
|
}
|
|
|
|
} // namespace rtabmap
|