mirror of
https://github.com/introlab/rtabmap_ros.git
synced 2026-10-04 00:37:46 +08:00
3477 lines
115 KiB
C++
3477 lines
115 KiB
C++
/*
|
|
Copyright (c) 2010-2014, 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/Graph.h"
|
|
#include "rtabmap/core/Signature.h"
|
|
|
|
#include "rtabmap/core/EpipolarGeometry.h"
|
|
|
|
#include "rtabmap/core/Memory.h"
|
|
#include "rtabmap/core/VWDictionary.h"
|
|
#include "BayesFilter.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 "SimpleIni.h"
|
|
|
|
#include <pcl/search/kdtree.h>
|
|
#include <pcl/filters/crop_box.h>
|
|
#include <pcl/io/pcd_io.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()),
|
|
_maxTimeAllowed(Parameters::defaultRtabmapTimeThr()), // 700 ms
|
|
_maxMemoryAllowed(Parameters::defaultRtabmapMemoryThr()), // 0=inf
|
|
_loopThr(Parameters::defaultRtabmapLoopThr()),
|
|
_loopRatio(Parameters::defaultRtabmapLoopRatio()),
|
|
_maxRetrieved(Parameters::defaultRtabmapMaxRetrieved()),
|
|
_maxLocalRetrieved(Parameters::defaultRGBDMaxLocalRetrieved()),
|
|
_statisticLogsBufferedInRAM(Parameters::defaultRtabmapStatisticLogsBufferedInRAM()),
|
|
_statisticLogged(Parameters::defaultRtabmapStatisticLogged()),
|
|
_statisticLoggedHeaders(Parameters::defaultRtabmapStatisticLoggedHeaders()),
|
|
_rgbdSlamMode(Parameters::defaultRGBDEnabled()),
|
|
_rgbdLinearUpdate(Parameters::defaultRGBDLinearUpdate()),
|
|
_rgbdAngularUpdate(Parameters::defaultRGBDAngularUpdate()),
|
|
_newMapOdomChangeDistance(Parameters::defaultRGBDNewMapOdomChangeDistance()),
|
|
_globalLoopClosureIcpType(Parameters::defaultLccIcpType()),
|
|
_poseScanMatching(Parameters::defaultRGBDPoseScanMatching()),
|
|
_localLoopClosureDetectionTime(Parameters::defaultRGBDLocalLoopDetectionTime()),
|
|
_localLoopClosureDetectionSpace(Parameters::defaultRGBDLocalLoopDetectionSpace()),
|
|
_localRadius(Parameters::defaultRGBDLocalRadius()),
|
|
_localImmunizationRatio(Parameters::defaultRGBDLocalImmunizationRatio()),
|
|
_localDetectMaxGraphDepth(Parameters::defaultRGBDLocalLoopDetectionMaxGraphDepth()),
|
|
_localPathFilteringRadius(Parameters::defaultRGBDLocalLoopDetectionPathFilteringRadius()),
|
|
_localPathOdomPosesUsed(Parameters::defaultRGBDLocalLoopDetectionPathOdomPosesUsed()),
|
|
_databasePath(""),
|
|
_optimizeFromGraphEnd(Parameters::defaultRGBDOptimizeFromGraphEnd()),
|
|
_reextractLoopClosureFeatures(Parameters::defaultLccReextractActivated()),
|
|
_reextractNNType(Parameters::defaultLccReextractNNType()),
|
|
_reextractNNDR(Parameters::defaultLccReextractNNDR()),
|
|
_reextractFeatureType(Parameters::defaultLccReextractFeatureType()),
|
|
_reextractMaxWords(Parameters::defaultLccReextractMaxWords()),
|
|
_startNewMapOnLoopClosure(Parameters::defaultRtabmapStartNewMapOnLoopClosure()),
|
|
_goalReachedRadius(Parameters::defaultRGBDGoalReachedRadius()),
|
|
_planVirtualLinks(Parameters::defaultRGBDPlanVirtualLinks()),
|
|
_goalsSavedInUserData(Parameters::defaultRGBDGoalsSavedInUserData()),
|
|
_loopClosureHypothesis(0,0.0f),
|
|
_highestHypothesis(0,0.0f),
|
|
_lastProcessTime(0.0),
|
|
_epipolarGeometry(0),
|
|
_bayesFilter(0),
|
|
_graphOptimizer(0),
|
|
_memory(0),
|
|
_foutFloat(0),
|
|
_foutInt(0),
|
|
_wDir("."),
|
|
_mapCorrection(Transform::getIdentity()),
|
|
_mapTransform(Transform::getIdentity()),
|
|
_pathCurrentIndex(0),
|
|
_pathGoalIndex(0),
|
|
_pathTransformToGoal(Transform::getIdentity())
|
|
{
|
|
}
|
|
|
|
Rtabmap::~Rtabmap() {
|
|
UDEBUG("");
|
|
this->close();
|
|
}
|
|
|
|
std::string Rtabmap::getVersion()
|
|
{
|
|
return RTABMAP_VERSION;
|
|
return ""; // Second return only to avoid compiler warning with RTABMAP_VERSION not yet set.
|
|
}
|
|
|
|
void Rtabmap::setupLogFiles(bool overwrite)
|
|
{
|
|
flushStatisticLogs();
|
|
// Log files
|
|
if(_foutFloat)
|
|
{
|
|
fclose(_foutFloat);
|
|
_foutFloat = 0;
|
|
}
|
|
if(_foutInt)
|
|
{
|
|
fclose(_foutInt);
|
|
_foutInt = 0;
|
|
}
|
|
|
|
if(_statisticLogged)
|
|
{
|
|
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");
|
|
}
|
|
|
|
ULOGGER_DEBUG("Log file (int)=%s", (_wDir+"/"+LOG_I).c_str());
|
|
ULOGGER_DEBUG("Log file (float)=%s", (_wDir+"/"+LOG_F).c_str());
|
|
}
|
|
else
|
|
{
|
|
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)
|
|
{
|
|
ParametersMap::const_iterator iter;
|
|
if((iter=parameters.find(Parameters::kRtabmapWorkingDirectory())) != parameters.end())
|
|
{
|
|
this->setWorkingDirectory(iter->second.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.");
|
|
}
|
|
|
|
bool newDatabase = _databasePath.empty() || !UFile::exists(_databasePath);
|
|
|
|
// If not exist, create a memory
|
|
if(!_memory)
|
|
{
|
|
_memory = new Memory(parameters);
|
|
_memory->init(_databasePath, false, parameters, true);
|
|
}
|
|
|
|
// Parse all parameters
|
|
this->parseParameters(parameters);
|
|
|
|
if(_databasePath.empty())
|
|
{
|
|
_statisticLogged = false;
|
|
}
|
|
setupLogFiles(newDatabase);
|
|
}
|
|
|
|
void Rtabmap::init(const std::string & configFile, const std::string & databasePath)
|
|
{
|
|
// 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());
|
|
this->readParameters(configFile, param);
|
|
}
|
|
|
|
this->init(param, databasePath);
|
|
}
|
|
|
|
void Rtabmap::close()
|
|
{
|
|
UINFO("");
|
|
_highestHypothesis = std::make_pair(0,0.0f);
|
|
_loopClosureHypothesis = std::make_pair(0,0.0f);
|
|
_lastProcessTime = 0.0;
|
|
_optimizedPoses.clear();
|
|
_constraints.clear();
|
|
_mapCorrection.setIdentity();
|
|
_mapTransform.setIdentity();
|
|
_lastLocalizationPose.setNull();
|
|
this->clearPath();
|
|
|
|
flushStatisticLogs();
|
|
if(_foutFloat)
|
|
{
|
|
fclose(_foutFloat);
|
|
_foutFloat = 0;
|
|
}
|
|
if(_foutInt)
|
|
{
|
|
fclose(_foutInt);
|
|
_foutInt = 0;
|
|
}
|
|
|
|
if(_epipolarGeometry)
|
|
{
|
|
delete _epipolarGeometry;
|
|
_epipolarGeometry = 0;
|
|
}
|
|
if(_memory)
|
|
{
|
|
delete _memory;
|
|
_memory = 0;
|
|
}
|
|
if(_bayesFilter)
|
|
{
|
|
delete _bayesFilter;
|
|
_bayesFilter = 0;
|
|
}
|
|
if(_graphOptimizer)
|
|
{
|
|
delete _graphOptimizer;
|
|
_graphOptimizer = 0;
|
|
}
|
|
_databasePath.clear();
|
|
parseParameters(Parameters::getDefaultParameters()); // reset to default parameters
|
|
_modifiedParameters.clear();
|
|
}
|
|
|
|
void Rtabmap::parseParameters(const ParametersMap & parameters)
|
|
{
|
|
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::kRtabmapTimeThr(), _maxTimeAllowed);
|
|
Parameters::parse(parameters, Parameters::kRtabmapMemoryThr(), _maxMemoryAllowed);
|
|
Parameters::parse(parameters, Parameters::kRtabmapLoopThr(), _loopThr);
|
|
Parameters::parse(parameters, Parameters::kRtabmapLoopRatio(), _loopRatio);
|
|
Parameters::parse(parameters, Parameters::kRtabmapMaxRetrieved(), _maxRetrieved);
|
|
Parameters::parse(parameters, Parameters::kRGBDMaxLocalRetrieved(), _maxLocalRetrieved);
|
|
Parameters::parse(parameters, Parameters::kRtabmapStatisticLogsBufferedInRAM(), _statisticLogsBufferedInRAM);
|
|
Parameters::parse(parameters, Parameters::kRtabmapStatisticLogged(), _statisticLogged);
|
|
Parameters::parse(parameters, Parameters::kRtabmapStatisticLoggedHeaders(), _statisticLoggedHeaders);
|
|
Parameters::parse(parameters, Parameters::kRGBDEnabled(), _rgbdSlamMode);
|
|
Parameters::parse(parameters, Parameters::kRGBDLinearUpdate(), _rgbdLinearUpdate);
|
|
Parameters::parse(parameters, Parameters::kRGBDAngularUpdate(), _rgbdAngularUpdate);
|
|
Parameters::parse(parameters, Parameters::kRGBDNewMapOdomChangeDistance(), _newMapOdomChangeDistance);
|
|
Parameters::parse(parameters, Parameters::kRGBDPoseScanMatching(), _poseScanMatching);
|
|
Parameters::parse(parameters, Parameters::kRGBDLocalLoopDetectionTime(), _localLoopClosureDetectionTime);
|
|
Parameters::parse(parameters, Parameters::kRGBDLocalLoopDetectionSpace(), _localLoopClosureDetectionSpace);
|
|
Parameters::parse(parameters, Parameters::kRGBDLocalRadius(), _localRadius);
|
|
Parameters::parse(parameters, Parameters::kRGBDLocalImmunizationRatio(), _localImmunizationRatio);
|
|
Parameters::parse(parameters, Parameters::kRGBDLocalLoopDetectionMaxGraphDepth(), _localDetectMaxGraphDepth);
|
|
Parameters::parse(parameters, Parameters::kRGBDLocalLoopDetectionPathFilteringRadius(), _localPathFilteringRadius);
|
|
Parameters::parse(parameters, Parameters::kRGBDLocalLoopDetectionPathOdomPosesUsed(), _localPathOdomPosesUsed);
|
|
Parameters::parse(parameters, Parameters::kRGBDOptimizeFromGraphEnd(), _optimizeFromGraphEnd);
|
|
Parameters::parse(parameters, Parameters::kLccReextractActivated(), _reextractLoopClosureFeatures);
|
|
Parameters::parse(parameters, Parameters::kLccReextractNNType(), _reextractNNType);
|
|
Parameters::parse(parameters, Parameters::kLccReextractNNDR(), _reextractNNDR);
|
|
Parameters::parse(parameters, Parameters::kLccReextractFeatureType(), _reextractFeatureType);
|
|
Parameters::parse(parameters, Parameters::kLccReextractMaxWords(), _reextractMaxWords);
|
|
Parameters::parse(parameters, Parameters::kRtabmapStartNewMapOnLoopClosure(), _startNewMapOnLoopClosure);
|
|
Parameters::parse(parameters, Parameters::kRGBDGoalReachedRadius(), _goalReachedRadius);
|
|
Parameters::parse(parameters, Parameters::kRGBDPlanVirtualLinks(), _planVirtualLinks);
|
|
Parameters::parse(parameters, Parameters::kRGBDGoalsSavedInUserData(), _goalsSavedInUserData);
|
|
|
|
// RGB-D SLAM stuff
|
|
if((iter=parameters.find(Parameters::kLccIcpType())) != parameters.end())
|
|
{
|
|
int icpType = std::atoi((*iter).second.c_str());
|
|
if(icpType >= 0 && icpType <= 2)
|
|
{
|
|
_globalLoopClosureIcpType = icpType;
|
|
}
|
|
else
|
|
{
|
|
UERROR("Icp type must be 0, 1 or 2 (value=%d)", icpType);
|
|
}
|
|
}
|
|
|
|
// 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
|
|
graph::Optimizer::Type optimizerType = graph::Optimizer::kTypeUndef;
|
|
if((iter=parameters.find(Parameters::kRGBDOptimizeStrategy())) != parameters.end())
|
|
{
|
|
optimizerType = (graph::Optimizer::Type)std::atoi((*iter).second.c_str());
|
|
}
|
|
if(optimizerType!=graph::Optimizer::kTypeUndef)
|
|
{
|
|
UDEBUG("new detector strategy %d", int(optimizerType));
|
|
if(_graphOptimizer)
|
|
{
|
|
delete _graphOptimizer;
|
|
_graphOptimizer = 0;
|
|
}
|
|
|
|
_graphOptimizer = graph::Optimizer::create(optimizerType, parameters);
|
|
}
|
|
else if(_graphOptimizer)
|
|
{
|
|
_graphOptimizer->parseParameters(parameters);
|
|
}
|
|
else
|
|
{
|
|
optimizerType = (graph::Optimizer::Type)Parameters::defaultRGBDOptimizeStrategy();
|
|
_graphOptimizer = graph::Optimizer::create(optimizerType, parameters);
|
|
}
|
|
|
|
if(_memory)
|
|
{
|
|
_memory->parseParameters(parameters);
|
|
}
|
|
|
|
VhStrategy vhStrategy = kVhUndef;
|
|
// Verifying hypotheses strategy
|
|
if((iter=parameters.find(Parameters::kRtabmapVhStrategy())) != parameters.end())
|
|
{
|
|
vhStrategy = (VhStrategy)std::atoi((*iter).second.c_str());
|
|
}
|
|
if(!_epipolarGeometry && vhStrategy == kVhEpipolar)
|
|
{
|
|
_epipolarGeometry = new EpipolarGeometry(parameters);
|
|
}
|
|
else if(_epipolarGeometry && vhStrategy == kVhNone)
|
|
{
|
|
delete _epipolarGeometry;
|
|
_epipolarGeometry = 0;
|
|
}
|
|
else if(_epipolarGeometry)
|
|
{
|
|
_epipolarGeometry->parseParameters(parameters);
|
|
}
|
|
|
|
// Bayes filter, create one if not exists
|
|
if(!_bayesFilter)
|
|
{
|
|
_bayesFilter = new BayesFilter(parameters);
|
|
}
|
|
else
|
|
{
|
|
_bayesFilter->parseParameters(parameters);
|
|
}
|
|
|
|
for(ParametersMap::const_iterator iter = parameters.begin(); iter!=parameters.end(); ++iter)
|
|
{
|
|
uInsert(_modifiedParameters, ParametersPair(iter->first, iter->second));
|
|
}
|
|
}
|
|
|
|
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();
|
|
if(s)
|
|
{
|
|
return s->id();
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
std::multimap<int, cv::KeyPoint> Rtabmap::getWords(int locationId) const
|
|
{
|
|
if(_memory)
|
|
{
|
|
const Signature * s = _memory->getSignature(locationId);
|
|
if(s)
|
|
{
|
|
return s->getWords();
|
|
}
|
|
}
|
|
return std::multimap<int, cv::KeyPoint>();
|
|
}
|
|
|
|
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_;
|
|
}
|
|
/*
|
|
bool Rtabmap::getMetricData(int locationId, cv::Mat & rgb, cv::Mat & depth, float & depthConstant, Transform & pose, Transform & localTransform) const
|
|
{
|
|
if(_memory)
|
|
{
|
|
const Signature * s = _memory->getSignature(locationId);
|
|
if(s && _optimizedPoses.find(s->id()) != _optimizedPoses.end())
|
|
{
|
|
rgb = s->getImage();
|
|
depth = s->getDepth();
|
|
depthConstant = s->getDepthConstant();
|
|
pose = _optimizedPoses.at(s->id());
|
|
localTransform = s->getLocalTransform();
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
*/
|
|
Transform Rtabmap::getPose(int locationId) const
|
|
{
|
|
if(_memory)
|
|
{
|
|
const Signature * s = _memory->getSignature(locationId);
|
|
if(s && _optimizedPoses.find(s->id()) != _optimizedPoses.end())
|
|
{
|
|
return _optimizedPoses.at(s->id());
|
|
}
|
|
}
|
|
return Transform();
|
|
}
|
|
|
|
int Rtabmap::triggerNewMap()
|
|
{
|
|
int mapId = -1;
|
|
if(_memory)
|
|
{
|
|
mapId = _memory->incrementMapId();
|
|
UINFO("New map triggered, new map = %d", mapId);
|
|
_optimizedPoses.clear();
|
|
_constraints.clear();
|
|
}
|
|
return mapId;
|
|
}
|
|
|
|
bool Rtabmap::labelLocation(int id, const std::string & label)
|
|
{
|
|
if(_memory)
|
|
{
|
|
if(id > 0)
|
|
{
|
|
return _memory->labelSignature(id, label);
|
|
}
|
|
else if(_memory->getLastWorkingSignature())
|
|
{
|
|
return _memory->labelSignature(_memory->getLastWorkingSignature()->id(), label);
|
|
}
|
|
else
|
|
{
|
|
UERROR("Last signature is null! Cannot set label \"%s\"", label.c_str());
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Rtabmap::setUserData(int id, const std::vector<unsigned char> & data)
|
|
{
|
|
if(_memory)
|
|
{
|
|
if(id > 0)
|
|
{
|
|
return _memory->setUserData(id, data);
|
|
}
|
|
else if(_memory->getLastWorkingSignature())
|
|
{
|
|
return _memory->setUserData(_memory->getLastWorkingSignature()->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::generateTOROGraph(const std::string & path, bool optimized, bool global)
|
|
{
|
|
if(_memory && _memory->getLastWorkingSignature())
|
|
{
|
|
std::map<int, Transform> poses;
|
|
std::multimap<int, Link> constraints;
|
|
|
|
if(optimized)
|
|
{
|
|
this->optimizeCurrentMap(_memory->getLastWorkingSignature()->id(), global, poses, &constraints);
|
|
}
|
|
else
|
|
{
|
|
std::map<int, int> ids = _memory->getNeighborsId(_memory->getLastWorkingSignature()->id(), 0, global?-1:0, true);
|
|
_memory->getMetricConstraints(uKeysSet(ids), poses, constraints, global);
|
|
}
|
|
|
|
graph::TOROOptimizer::saveGraph(path, poses, constraints);
|
|
}
|
|
}
|
|
|
|
void Rtabmap::exportPoses(const std::string & path, bool optimized, bool global)
|
|
{
|
|
if(_memory && _memory->getLastWorkingSignature())
|
|
{
|
|
std::map<int, Transform> poses;
|
|
std::multimap<int, Link> constraints;
|
|
|
|
if(optimized)
|
|
{
|
|
this->optimizeCurrentMap(_memory->getLastWorkingSignature()->id(), global, poses, &constraints);
|
|
}
|
|
else
|
|
{
|
|
std::map<int, int> ids = _memory->getNeighborsId(_memory->getLastWorkingSignature()->id(), 0, global?-1:0, true);
|
|
_memory->getMetricConstraints(uKeysSet(ids), poses, constraints, global);
|
|
}
|
|
|
|
this->dumpPoses(path, poses);
|
|
}
|
|
}
|
|
|
|
void Rtabmap::resetMemory()
|
|
{
|
|
_highestHypothesis = std::make_pair(0,0.0f);
|
|
_loopClosureHypothesis = std::make_pair(0,0.0f);
|
|
_lastProcessTime = 0.0;
|
|
_optimizedPoses.clear();
|
|
_constraints.clear();
|
|
_mapCorrection.setIdentity();
|
|
_mapTransform.setIdentity();
|
|
_lastLocalizationPose.setNull();
|
|
this->clearPath();
|
|
|
|
if(_memory)
|
|
{
|
|
_memory->init(_databasePath, true, _modifiedParameters, true);
|
|
if(_memory->getLastWorkingSignature())
|
|
{
|
|
optimizeCurrentMap(_memory->getLastWorkingSignature()->id(), false, _optimizedPoses, &_constraints);
|
|
}
|
|
if(_bayesFilter)
|
|
{
|
|
_bayesFilter->reset();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UERROR("RTAB-Map is not initialized. No memory to reset...");
|
|
}
|
|
this->setupLogFiles(true);
|
|
}
|
|
|
|
//============================================================
|
|
// MAIN LOOP
|
|
//============================================================
|
|
bool Rtabmap::process(
|
|
const SensorData & data,
|
|
const Transform & odomPose,
|
|
const cv::Mat & covariance)
|
|
{
|
|
UDEBUG("");
|
|
|
|
//============================================================
|
|
// Initialization
|
|
//============================================================
|
|
UTimer timer;
|
|
UTimer timerTotal;
|
|
double timeMemoryUpdate = 0;
|
|
double timeScanMatching = 0;
|
|
double timeLocalTimeDetection = 0;
|
|
double timeLocalSpaceDetection = 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 timeJoiningTrash = 0;
|
|
double timeStatsCreation = 0;
|
|
|
|
float hypothesisRatio = 0.0f; // Only used for statistics
|
|
bool rejectedHypothesis = 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 localLoopClosuresInTimeFound = 0;
|
|
bool scanMatchingSuccess = false;
|
|
|
|
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
|
|
|
|
//============================================================
|
|
// 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.
|
|
//============================================================
|
|
if(_rgbdSlamMode)
|
|
{
|
|
if(odomPose.isNull())
|
|
{
|
|
UERROR("RGB-D SLAM mode is enabled and no odometry is provided. "
|
|
"Image %d is ignored!", data.id());
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// Detect if the odometry is reset. If yes, trigger a new map.
|
|
if(_memory->getLastWorkingSignature())
|
|
{
|
|
const Transform & lastPose = _memory->getLastWorkingSignature()->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, covariance, &statistics_))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(!_memory->update(data, Transform(), cv::Mat(), &statistics_))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
signature = _memory->getLastWorkingSignature();
|
|
if(!signature)
|
|
{
|
|
UFATAL("Not supposed to be here...last signature is null?!?");
|
|
}
|
|
|
|
ULOGGER_INFO("Processing signature %d", signature->id());
|
|
timeMemoryUpdate = timer.ticks();
|
|
ULOGGER_INFO("timeMemoryUpdate=%fs", timeMemoryUpdate);
|
|
|
|
//============================================================
|
|
// Metric
|
|
//============================================================
|
|
bool smallDisplacement = false;
|
|
if(_rgbdSlamMode)
|
|
{
|
|
//Verify if there was a rehearsal
|
|
int rehearsedId = (int)uValue(statistics_.data(), Statistics::kMemoryRehearsal_merged(), 0.0f);
|
|
if(rehearsedId > 0)
|
|
{
|
|
_optimizedPoses.erase(rehearsedId);
|
|
}
|
|
else if(_rgbdLinearUpdate > 0.0f && _rgbdAngularUpdate > 0.0f)
|
|
{
|
|
//============================================================
|
|
// Minimum displacement required to add to Memory
|
|
//============================================================
|
|
const std::map<int, Link> & links = signature->getLinks();
|
|
if(links.size() == 1)
|
|
{
|
|
float x,y,z, roll,pitch,yaw;
|
|
links.begin()->second.transform().getTranslationAndEulerAngles(x,y,z, roll,pitch,yaw);
|
|
if((_rgbdLinearUpdate==0.0f || (
|
|
fabs(x) < _rgbdLinearUpdate &&
|
|
fabs(y) < _rgbdLinearUpdate &&
|
|
fabs(z) < _rgbdLinearUpdate)) &&
|
|
(_rgbdAngularUpdate==0.0f || (
|
|
fabs(roll) < _rgbdAngularUpdate &&
|
|
fabs(pitch) < _rgbdAngularUpdate &&
|
|
fabs(yaw) < _rgbdAngularUpdate)))
|
|
{
|
|
// This will disable global loop closure detection, only retrieval will be done.
|
|
// The location will also be deleted at the end.
|
|
smallDisplacement = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
Transform newPose = _mapCorrection * signature->getPose();
|
|
_optimizedPoses.insert(std::make_pair(signature->id(), newPose));
|
|
_lastLocalizationPose = newPose; // used in localization mode only (path planning)
|
|
|
|
//============================================================
|
|
// Scan matching
|
|
//============================================================
|
|
if(_poseScanMatching &&
|
|
signature->getLinks().size() == 1 &&
|
|
!signature->sensorData().laserScanCompressed().empty() &&
|
|
rehearsedId == 0) // don't do it if rehearsal happened
|
|
{
|
|
UINFO("Odometry correction by scan matching");
|
|
int oldId = signature->getLinks().begin()->first;
|
|
const Signature * oldS = _memory->getSignature(oldId);
|
|
UASSERT(oldS != 0);
|
|
std::string rejectedMsg;
|
|
Transform guess = signature->getLinks().begin()->second.transform();
|
|
double variance = 1.0;
|
|
int inliers = 0;
|
|
float inliersRatio = 0;
|
|
Transform t = _memory->computeIcpTransform(oldId, signature->id(), guess, false, &rejectedMsg, &inliers, &variance, &inliersRatio);
|
|
if(!t.isNull())
|
|
{
|
|
scanMatchingSuccess = true;
|
|
UINFO("Scan matching: update neighbor link (%d->%d) from %s to %s",
|
|
signature->id(),
|
|
oldId,
|
|
signature->getLinks().at(oldId).transform().prettyPrint().c_str(),
|
|
t.prettyPrint().c_str());
|
|
_memory->updateLink(signature->id(), oldId, t, variance, variance);
|
|
}
|
|
else
|
|
{
|
|
UINFO("Scan matching rejected: %s", rejectedMsg.c_str());
|
|
}
|
|
statistics_.addStatistic(Statistics::kOdomCorrectionAccepted(), scanMatchingSuccess?1.0f:0);
|
|
statistics_.addStatistic(Statistics::kOdomCorrectionInliers(), inliers);
|
|
statistics_.addStatistic(Statistics::kOdomCorrectionInliers_ratio(), inliersRatio);
|
|
statistics_.addStatistic(Statistics::kOdomCorrectionVariance(), variance);
|
|
}
|
|
timeScanMatching = timer.ticks();
|
|
ULOGGER_INFO("timeScanMatching=%fs", timeScanMatching);
|
|
|
|
if(signature->getLinks().size() == 1)
|
|
{
|
|
// 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 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);
|
|
_optimizedPoses.erase(s->id());
|
|
_constraints.erase(--_constraints.end());
|
|
}
|
|
}
|
|
_constraints.insert(std::make_pair(tmp.from(), tmp));
|
|
}
|
|
|
|
//============================================================
|
|
// Local loop closure in TIME
|
|
//============================================================
|
|
if(_localLoopClosureDetectionTime &&
|
|
rehearsedId == 0 && // don't do it if rehearsal happened
|
|
signature->getWords3().size())
|
|
{
|
|
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())
|
|
{
|
|
std::string rejectedMsg;
|
|
UDEBUG("Check local transform between %d and %d", signature->id(), *iter);
|
|
double variance = 1.0;
|
|
int inliers = -1;
|
|
Transform transform = _memory->computeVisualTransform(*iter, signature->id(), &rejectedMsg, &inliers, &variance);
|
|
if(!transform.isNull() && _globalLoopClosureIcpType > 0)
|
|
{
|
|
transform = _memory->computeIcpTransform(*iter, signature->id(), transform, _globalLoopClosureIcpType==1, &rejectedMsg, 0, &variance);
|
|
variance = 1.0f; // ICP, set variance to 1
|
|
}
|
|
if(!transform.isNull())
|
|
{
|
|
UDEBUG("Add local loop closure in TIME (%d->%d) %s",
|
|
signature->id(),
|
|
*iter,
|
|
transform.prettyPrint().c_str());
|
|
// Add a loop constraint
|
|
if(_memory->addLink(Link(signature->id(), *iter, Link::kLocalTimeClosure, transform, variance, variance)))
|
|
{
|
|
++localLoopClosuresInTimeFound;
|
|
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());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
timeLocalTimeDetection = timer.ticks();
|
|
UINFO("timeLocalTimeDetection=%fs", timeLocalTimeDetection);
|
|
|
|
//============================================================
|
|
// Bayes filter update
|
|
//============================================================
|
|
int previousId = signature->getLinks().size() == 1?signature->getLinks().begin()->first:0;
|
|
// Not a bad signature, not a small displacemnt unless the previous signature didn't have a loop closure
|
|
if(!signature->isBadSignature() && (!smallDisplacement || _memory->getLoopClosureLinks(previousId, false).size() == 0))
|
|
{
|
|
// If the working memory is empty, don't do the detection. It happens when it
|
|
// 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...");
|
|
|
|
// select only not empty signatures (may happen often if intermediate nodes are created)
|
|
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(!_bayesFilter->isBadSignaturesIgnored() || !s->isBadSignature())
|
|
{
|
|
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)
|
|
{
|
|
// Loop closure Threshold
|
|
// When _loopThr=0, accept loop closure if the hypothesis is over
|
|
// the virtual (new) place hypothesis.
|
|
if(_highestHypothesis.second >= _loopThr)
|
|
{
|
|
rejectedHypothesis = true;
|
|
if(posterior.size() <= 2)
|
|
{
|
|
// Ignore loop closure if there is only one loop closure hypothesis
|
|
UDEBUG("rejected hypothesis: single hypothesis");
|
|
}
|
|
else if(_epipolarGeometry && !_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;
|
|
rejectedHypothesis = false;
|
|
}
|
|
|
|
timeHypothesesValidation = timer.ticks();
|
|
ULOGGER_INFO("timeHypothesesValidation=%fs",timeHypothesesValidation);
|
|
}
|
|
else if(_highestHypothesis.second < _loopRatio*lastHighestHypothesis.second)
|
|
{
|
|
// Used for Precision-Recall computation.
|
|
// When analysing logs, it's convenient to know
|
|
// if the hypothesis would be rejected if T_loop would be lower.
|
|
rejectedHypothesis = true;
|
|
}
|
|
|
|
//for statistic...
|
|
hypothesisRatio = _loopClosureHypothesis.second>0?_highestHypothesis.second/_loopClosureHypothesis.second:0;
|
|
}
|
|
} // if(_memory->getWorkingMemSize())
|
|
}// !isBadSignature
|
|
else if(!signature->isBadSignature() && smallDisplacement)
|
|
{
|
|
_highestHypothesis = lastHighestHypothesis;
|
|
}
|
|
|
|
//============================================================
|
|
// 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;
|
|
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,
|
|
&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,
|
|
&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;
|
|
int maxLocalLocationsImmunized = _localImmunizationRatio * float(_memory->getWorkingMem().size());
|
|
if(_rgbdSlamMode)
|
|
{
|
|
// Priority on locations on the planned path
|
|
if(_path.size())
|
|
{
|
|
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(distanceSoFar <= _localRadius)
|
|
{
|
|
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
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UDEBUG("Stop on node %d (dist=%fm > %fm)",
|
|
_path[i].first, distanceSoFar, _localRadius);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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(!_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(immunizedLocally >= maxLocalLocationsImmunized)
|
|
{
|
|
// set 20 to avoid this warning when starting mapping
|
|
if(maxLocalLocationsImmunized > 20)
|
|
{
|
|
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::getNodesInRadius(signature->id(), _optimizedPoses, _localRadius);
|
|
// sort by distance
|
|
std::multimap<float, int> nearNodesByDist;
|
|
for(std::map<int, float>::iterator iter=nearNodes.begin(); iter!=nearNodes.end(); ++iter)
|
|
{
|
|
nearNodesByDist.insert(std::make_pair(iter->second, iter->first));
|
|
}
|
|
UINFO("near nodes=%d, max local immunized=%d, ratio=%f WM=%d",
|
|
(int)nearNodesByDist.size(),
|
|
maxLocalLocationsImmunized,
|
|
_localImmunizationRatio,
|
|
(int)_memory->getWorkingMem().size());
|
|
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);
|
|
UASSERT(s!=0);
|
|
// If there is a change of direction, better to be retrieving
|
|
// ALL nearest signatures than only newest neighbors
|
|
const std::map<int, Link> & links = s->getLinks();
|
|
for(std::map<int, Link>::const_reverse_iterator jter=links.rbegin();
|
|
jter!=links.rend() && retrievalLocalIds.size() < _maxLocalRetrieved;
|
|
++jter)
|
|
{
|
|
if(_memory->getSignature(jter->first) == 0)
|
|
{
|
|
UINFO("retrieval of node %d on local map", jter->first);
|
|
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());
|
|
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 - retrievalLocalIds.size() + 1, true, false);
|
|
for(std::map<int, int>::reverse_iterator jter=ids.rbegin();
|
|
jter!=ids.rend() && retrievalLocalIds.size() < _maxLocalRetrieved;
|
|
++jter)
|
|
{
|
|
if(_memory->getSignature(jter->first) == 0 &&
|
|
retrievalLocalIdsSet.find(jter->first) == retrievalLocalIdsSet.end())
|
|
{
|
|
UINFO("retrieval of node %d on local map", jter->first);
|
|
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(), 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());
|
|
}
|
|
timeReactivations = timer.ticks();
|
|
ULOGGER_INFO("timeReactivations=%fs", timeReactivations);
|
|
|
|
//=============================================================
|
|
// Update loop closure links
|
|
// (updated: place this after retrieval to be sure that neighbors of the loop closure are in RAM)
|
|
//=============================================================
|
|
int loopClosureVisualInliers = 0; // for statistics
|
|
if(_loopClosureHypothesis.first>0)
|
|
{
|
|
//Compute transform if metric data are present
|
|
Transform transform;
|
|
double variance = 1;
|
|
if(_rgbdSlamMode)
|
|
{
|
|
std::string rejectedMsg;
|
|
if(_reextractLoopClosureFeatures)
|
|
{
|
|
ParametersMap customParameters = _modifiedParameters; // get BOW LCC parameters
|
|
// override some parameters
|
|
uInsert(customParameters, ParametersPair(Parameters::kMemIncrementalMemory(), "true")); // make sure it is incremental
|
|
uInsert(customParameters, ParametersPair(Parameters::kMemRehearsalSimilarity(), "1.0")); // desactivate rehearsal
|
|
uInsert(customParameters, ParametersPair(Parameters::kMemBinDataKept(), "false"));
|
|
uInsert(customParameters, ParametersPair(Parameters::kMemSTMSize(), "0"));
|
|
uInsert(customParameters, ParametersPair(Parameters::kKpIncrementalDictionary(), "true")); // make sure it is incremental
|
|
uInsert(customParameters, ParametersPair(Parameters::kKpNewWordsComparedTogether(), "false"));
|
|
uInsert(customParameters, ParametersPair(Parameters::kKpNNStrategy(), uNumber2Str(_reextractNNType))); // bruteforce
|
|
uInsert(customParameters, ParametersPair(Parameters::kKpNndrRatio(), uNumber2Str(_reextractNNDR)));
|
|
uInsert(customParameters, ParametersPair(Parameters::kKpDetectorStrategy(), uNumber2Str(_reextractFeatureType))); // FAST/BRIEF
|
|
uInsert(customParameters, ParametersPair(Parameters::kKpWordsPerImage(), uNumber2Str(_reextractMaxWords)));
|
|
uInsert(customParameters, ParametersPair(Parameters::kKpBadSignRatio(), "0"));
|
|
uInsert(customParameters, ParametersPair(Parameters::kKpRoiRatios(), "0.0 0.0 0.0 0.0"));
|
|
uInsert(customParameters, ParametersPair(Parameters::kMemGenerateIds(), "false"));
|
|
|
|
//for(ParametersMap::iterator iter = customParameters.begin(); iter!=customParameters.end(); ++iter)
|
|
//{
|
|
// UDEBUG("%s=%s", iter->first.c_str(), iter->second.c_str());
|
|
//}
|
|
|
|
Memory memory(customParameters);
|
|
|
|
UTimer timeT;
|
|
|
|
// Add signatures
|
|
SensorData dataFrom = data;
|
|
dataFrom.setId(signature->id());
|
|
SensorData dataTo = _memory->getNodeData(_loopClosureHypothesis.first, true);
|
|
UDEBUG("timeTo = %fs", timeT.ticks());
|
|
|
|
if(!dataFrom.depthOrRightRaw().empty() &&
|
|
!dataTo.depthOrRightRaw().empty() &&
|
|
dataFrom.id() != Memory::kIdInvalid &&
|
|
dataTo.id() != Memory::kIdInvalid)
|
|
{
|
|
memory.update(dataTo);
|
|
UDEBUG("timeUpTo = %fs", timeT.ticks());
|
|
memory.update(dataFrom);
|
|
UDEBUG("timeUpFrom = %fs", timeT.ticks());
|
|
|
|
transform = memory.computeVisualTransform(dataTo.id(), dataFrom.id(), &rejectedMsg, &loopClosureVisualInliers, &variance);
|
|
UDEBUG("timeTransform = %fs", timeT.ticks());
|
|
}
|
|
else
|
|
{
|
|
// Fallback to normal way (raw data not kept in database...)
|
|
UWARN("Loop closure: Some images not found in memory for re-extracting "
|
|
"features, is Mem/RawDataKept=false? Falling back with already extracted 3D features.");
|
|
transform = _memory->computeVisualTransform(_loopClosureHypothesis.first, signature->id(), &rejectedMsg, &loopClosureVisualInliers, &variance);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
transform = _memory->computeVisualTransform(_loopClosureHypothesis.first, signature->id(), &rejectedMsg, &loopClosureVisualInliers, &variance);
|
|
}
|
|
if(!transform.isNull() && _globalLoopClosureIcpType > 0)
|
|
{
|
|
transform = _memory->computeIcpTransform(_loopClosureHypothesis.first, signature->id(), transform, _globalLoopClosureIcpType == 1, &rejectedMsg, 0, &variance);
|
|
}
|
|
rejectedHypothesis = transform.isNull();
|
|
if(rejectedHypothesis)
|
|
{
|
|
UINFO("Rejected loop closure %d -> %d: %s",
|
|
_loopClosureHypothesis.first, signature->id(), rejectedMsg.c_str());
|
|
}
|
|
}
|
|
if(!rejectedHypothesis)
|
|
{
|
|
// Make the new one the parent of the old one
|
|
rejectedHypothesis = !_memory->addLink(Link(signature->id(), _loopClosureHypothesis.first, Link::kGlobalClosure, transform, variance, variance));
|
|
}
|
|
|
|
if(rejectedHypothesis)
|
|
{
|
|
_loopClosureHypothesis.first = 0;
|
|
}
|
|
else
|
|
{
|
|
const Signature * oldS = _memory->getSignature(_loopClosureHypothesis.first);
|
|
UASSERT(oldS != 0);
|
|
// Old map -> new map, used for localization correction on loop closure
|
|
_mapTransform = oldS->getPose() * transform.inverse() * signature->getPose().inverse();
|
|
}
|
|
}
|
|
|
|
timeAddLoopClosureLink = timer.ticks();
|
|
ULOGGER_INFO("timeAddLoopClosureLink=%fs", timeAddLoopClosureLink);
|
|
|
|
int localSpaceClosuresAddedVisually = 0;
|
|
int localSpaceClosuresAddedByICPOnly = 0;
|
|
int lastLocalSpaceClosureId = 0;
|
|
int localSpacePaths = 0;
|
|
if(_localLoopClosureDetectionSpace &&
|
|
_localRadius > 0)
|
|
{
|
|
if(_graphOptimizer->iterations() == 0)
|
|
{
|
|
UWARN("Cannot do local loop closure detection in space if graph optimization is disabled!");
|
|
}
|
|
else if(_memory->isIncremental() || _loopClosureHypothesis.first == 0)
|
|
{
|
|
// In localization mode, no need to check local loop
|
|
// closures if we are already localized by a global closure.
|
|
|
|
// don't do it if it is a small displacement unless the previous signature didn't have a loop closure
|
|
if(!smallDisplacement || _memory->getLoopClosureLinks(previousId, false).size() == 0)
|
|
{
|
|
|
|
//============================================================
|
|
// LOCAL LOOP CLOSURE SPACE
|
|
//============================================================
|
|
|
|
//
|
|
// 1) compare visually with nearest locations
|
|
//
|
|
float r = _localRadius;
|
|
if(_localPathFilteringRadius > 0 && _localPathFilteringRadius<_localRadius)
|
|
{
|
|
r = _localPathFilteringRadius;
|
|
}
|
|
|
|
std::map<int, float> nearestIds;
|
|
if(_memory->isIncremental())
|
|
{
|
|
nearestIds = _memory->getNeighborsIdRadius(signature->id(), r, _optimizedPoses, _localDetectMaxGraphDepth);
|
|
}
|
|
else
|
|
{
|
|
nearestIds = graph::getNodesInRadius(signature->id(), _optimizedPoses, r);
|
|
}
|
|
std::map<int, Transform> nearestPoses;
|
|
for(std::map<int, float>::iterator iter=nearestIds.begin(); iter!=nearestIds.end(); ++iter)
|
|
{
|
|
nearestPoses.insert(std::make_pair(iter->first, _optimizedPoses.at(iter->first)));
|
|
}
|
|
// segment poses by paths, only one detection per path
|
|
std::list<std::map<int, Transform> > nearestPaths = getPaths(nearestPoses);
|
|
for(std::list<std::map<int, Transform> >::iterator iter=nearestPaths.begin();
|
|
iter!=nearestPaths.end() && (_memory->isIncremental() || lastLocalSpaceClosureId == 0);
|
|
++iter)
|
|
{
|
|
std::map<int, Transform> & path = *iter;
|
|
UASSERT(path.size());
|
|
//find the nearest pose on the path
|
|
int nearestId = rtabmap::graph::findNearestNode(path, _optimizedPoses.at(signature->id()));
|
|
UASSERT(nearestId > 0);
|
|
|
|
// nearest pose must not be linked to current location, and not in STM
|
|
if(!signature->hasLink(nearestId) &&
|
|
_memory->getStMem().find(nearestId) == _memory->getStMem().end())
|
|
{
|
|
double variance = 1.0;
|
|
Transform transform;
|
|
if(_reextractLoopClosureFeatures)
|
|
{
|
|
ParametersMap customParameters = _modifiedParameters; // get BOW LCC parameters
|
|
// override some parameters
|
|
uInsert(customParameters, ParametersPair(Parameters::kMemIncrementalMemory(), "true")); // make sure it is incremental
|
|
uInsert(customParameters, ParametersPair(Parameters::kMemRehearsalSimilarity(), "1.0")); // desactivate rehearsal
|
|
uInsert(customParameters, ParametersPair(Parameters::kMemBinDataKept(), "false"));
|
|
uInsert(customParameters, ParametersPair(Parameters::kMemSTMSize(), "0"));
|
|
uInsert(customParameters, ParametersPair(Parameters::kKpIncrementalDictionary(), "true")); // make sure it is incremental
|
|
uInsert(customParameters, ParametersPair(Parameters::kKpNewWordsComparedTogether(), "false"));
|
|
uInsert(customParameters, ParametersPair(Parameters::kKpNNStrategy(), uNumber2Str(_reextractNNType))); // bruteforce
|
|
uInsert(customParameters, ParametersPair(Parameters::kKpNndrRatio(), uNumber2Str(_reextractNNDR)));
|
|
uInsert(customParameters, ParametersPair(Parameters::kKpDetectorStrategy(), uNumber2Str(_reextractFeatureType))); // FAST/BRIEF
|
|
uInsert(customParameters, ParametersPair(Parameters::kKpWordsPerImage(), uNumber2Str(_reextractMaxWords)));
|
|
uInsert(customParameters, ParametersPair(Parameters::kKpBadSignRatio(), "0"));
|
|
uInsert(customParameters, ParametersPair(Parameters::kKpRoiRatios(), "0.0 0.0 0.0 0.0"));
|
|
uInsert(customParameters, ParametersPair(Parameters::kMemGenerateIds(), "false"));
|
|
|
|
//for(ParametersMap::iterator iter = customParameters.begin(); iter!=customParameters.end(); ++iter)
|
|
//{
|
|
// UDEBUG("%s=%s", iter->first.c_str(), iter->second.c_str());
|
|
//}
|
|
|
|
Memory memory(customParameters);
|
|
|
|
UTimer timeT;
|
|
|
|
// Add signatures
|
|
SensorData dataFrom = data;
|
|
dataFrom.setId(signature->id());
|
|
SensorData dataTo = _memory->getNodeData(nearestId, true);
|
|
UDEBUG("timeTo = %fs", timeT.ticks());
|
|
|
|
if(!dataFrom.depthOrRightRaw().empty() &&
|
|
!dataTo.depthOrRightRaw().empty() &&
|
|
dataFrom.id() != Memory::kIdInvalid &&
|
|
dataTo.id() != Memory::kIdInvalid)
|
|
{
|
|
memory.update(dataTo);
|
|
UDEBUG("timeUpTo = %fs", timeT.ticks());
|
|
memory.update(dataFrom);
|
|
UDEBUG("timeUpFrom = %fs", timeT.ticks());
|
|
|
|
transform = memory.computeVisualTransform(dataTo.id(), dataFrom.id(), 0, 0, &variance);
|
|
UDEBUG("timeTransform = %fs", timeT.ticks());
|
|
}
|
|
else
|
|
{
|
|
// Fallback to normal way (raw data not kept in database...)
|
|
UWARN("Loop closure: Some images not found in memory for re-extracting "
|
|
"features, is Mem/RawDataKept=false? Falling back with already extracted 3D features.");
|
|
transform = _memory->computeVisualTransform(nearestId, signature->id(), 0, 0, &variance);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
transform = _memory->computeVisualTransform(nearestId, signature->id(), 0, 0, &variance);
|
|
}
|
|
if(!transform.isNull() && _globalLoopClosureIcpType > 0)
|
|
{
|
|
transform = _memory->computeIcpTransform(nearestId, signature->id(), transform, _globalLoopClosureIcpType == 1, 0, 0, &variance);
|
|
}
|
|
if(!transform.isNull())
|
|
{
|
|
UINFO("[Visual] Add local loop closure in SPACE (%d->%d) %s",
|
|
signature->id(),
|
|
nearestId,
|
|
transform.prettyPrint().c_str());
|
|
_memory->addLink(Link(signature->id(), nearestId, Link::kLocalSpaceClosure, transform, variance, variance));
|
|
|
|
if(_loopClosureHypothesis.first == 0)
|
|
{
|
|
// Old map -> new map, used for localization correction on loop closure
|
|
const Signature * oldS = _memory->getSignature(nearestId);
|
|
UASSERT(oldS != 0);
|
|
_mapTransform = oldS->getPose() * transform.inverse() * signature->getPose().inverse();
|
|
++localSpaceClosuresAddedVisually;
|
|
lastLocalSpaceClosureId = nearestId;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// 2) compare locally with nearest locations by scan matching
|
|
//
|
|
if( !signature->sensorData().laserScanCompressed().empty() &&
|
|
(_memory->isIncremental() || lastLocalSpaceClosureId == 0))
|
|
{
|
|
// In localization mode, no need to check local loop
|
|
// closures if we are already localized by at least one
|
|
// local visual closure above.
|
|
|
|
std::map<int, Transform> forwardPoses;
|
|
forwardPoses = this->getForwardWMPoses(
|
|
signature->id(),
|
|
0,
|
|
_localRadius,
|
|
_localDetectMaxGraphDepth);
|
|
|
|
std::list<std::map<int, Transform> > forwardPaths = getPaths(forwardPoses);
|
|
localSpacePaths = (int)forwardPaths.size();
|
|
|
|
for(std::list<std::map<int, Transform> >::iterator iter=forwardPaths.begin();
|
|
iter!=forwardPaths.end() && (_memory->isIncremental() || lastLocalSpaceClosureId == 0);
|
|
++iter)
|
|
{
|
|
std::map<int, Transform> & path = *iter;
|
|
UASSERT(path.size());
|
|
|
|
//find the nearest pose on the path
|
|
int nearestId = rtabmap::graph::findNearestNode(path, _optimizedPoses.at(signature->id()));
|
|
UASSERT(nearestId > 0);
|
|
|
|
// nearest pose must be close and not linked to current location
|
|
if(!signature->hasLink(nearestId) &&
|
|
(_localPathFilteringRadius <= 0.0f ||
|
|
_optimizedPoses.at(signature->id()).getDistanceSquared(_optimizedPoses.at(nearestId)) < _localPathFilteringRadius*_localPathFilteringRadius))
|
|
{
|
|
// Assemble scans in the path and do ICP only
|
|
if(_localPathOdomPosesUsed)
|
|
{
|
|
//optimize the path's poses locally
|
|
path = optimizeGraph(nearestId, uKeysSet(path), false);
|
|
// transform local poses in optimized graph referential
|
|
Transform t = _optimizedPoses.at(nearestId) * path.at(nearestId).inverse();
|
|
for(std::map<int, Transform>::iterator jter=path.begin(); jter!=path.end(); ++jter)
|
|
{
|
|
jter->second = t * jter->second;
|
|
}
|
|
}
|
|
if(_localPathFilteringRadius > 0.0f)
|
|
{
|
|
// path filtering
|
|
std::map<int, Transform> filteredPath = graph::radiusPosesFiltering(path, _localPathFilteringRadius, CV_PI, true);
|
|
// make sure the nearest and farthest poses are still here
|
|
filteredPath.insert(*path.find(nearestId));
|
|
filteredPath.insert(*path.begin());
|
|
filteredPath.insert(*path.rbegin());
|
|
path = filteredPath;
|
|
}
|
|
|
|
if(path.size() > 2) // more than current+nearest
|
|
{
|
|
// add current node to poses
|
|
path.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())
|
|
{
|
|
Transform transform = _memory->computeScanMatchingTransform(signature->id(), nearestId, path, 0, 0, 0);
|
|
if(!transform.isNull())
|
|
{
|
|
UINFO("[Scan matching] Add local loop closure in SPACE (%d->%d) %s",
|
|
signature->id(),
|
|
nearestId,
|
|
transform.prettyPrint().c_str());
|
|
// set Identify covariance for laser scan matching only
|
|
_memory->addLink(Link(signature->id(), nearestId, Link::kLocalSpaceClosure, transform, 1, 1));
|
|
|
|
++localSpaceClosuresAddedByICPOnly;
|
|
|
|
// no local loop closure added visually
|
|
if(localSpaceClosuresAddedVisually == 0 && _loopClosureHypothesis.first == 0)
|
|
{
|
|
// Old map -> new map, used for localization correction on loop closure
|
|
const Signature * oldS = _memory->getSignature(nearestId);
|
|
UASSERT(oldS != 0);
|
|
_mapTransform = oldS->getPose() * transform.inverse() * signature->getPose().inverse();
|
|
lastLocalSpaceClosureId = nearestId;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
timeLocalSpaceDetection = timer.ticks();
|
|
ULOGGER_INFO("timeLocalSpaceDetection=%fs", timeLocalSpaceDetection);
|
|
|
|
//============================================================
|
|
// Optimize map graph
|
|
//============================================================
|
|
if(_rgbdSlamMode &&
|
|
(_loopClosureHypothesis.first>0 || // can be different map of the current one
|
|
localLoopClosuresInTimeFound>0 || // only same map of the current one
|
|
scanMatchingSuccess || // only same map of the current one
|
|
lastLocalSpaceClosureId>0 || // can be different map of the current one
|
|
signaturesRetrieved.size())) // can be different map of the current one
|
|
{
|
|
if(_memory->isIncremental())
|
|
{
|
|
UINFO("Update map correction: SLAM mode");
|
|
// SLAM mode!
|
|
optimizeCurrentMap(signature->id(), false, _optimizedPoses, &_constraints);
|
|
UASSERT(_optimizedPoses.find(signature->id()) != _optimizedPoses.end());
|
|
|
|
// Update map correction, it should be identify when optimizing from the last node
|
|
_mapCorrection = _optimizedPoses.at(signature->id()) * signature->getPose().inverse();
|
|
_mapTransform.setIdentity(); // reset mapTransform (used for localization only)
|
|
_lastLocalizationPose = _optimizedPoses.at(signature->id()); // update in case we switch to localization mode
|
|
if(_mapCorrection.getNormSquared() > 0.001f && _optimizeFromGraphEnd)
|
|
{
|
|
UERROR("Map correction should be identity when optimizing from the last node. T=%s", _mapCorrection.prettyPrint().c_str());
|
|
}
|
|
}
|
|
else if(_loopClosureHypothesis.first > 0 || lastLocalSpaceClosureId > 0 || signaturesRetrieved.size())
|
|
{
|
|
UINFO("Update map correction: Localization mode");
|
|
int oldId = _loopClosureHypothesis.first>0?_loopClosureHypothesis.first:lastLocalSpaceClosureId?lastLocalSpaceClosureId:_highestHypothesis.first;
|
|
UASSERT(oldId != 0);
|
|
if(signaturesRetrieved.size() || _optimizedPoses.find(oldId) == _optimizedPoses.end())
|
|
{
|
|
// update optimized poses
|
|
optimizeCurrentMap(oldId, false, _optimizedPoses, &_constraints);
|
|
}
|
|
UASSERT(_optimizedPoses.find(oldId) != _optimizedPoses.end());
|
|
|
|
// Localization mode! only update map correction
|
|
const Signature * oldS = _memory->getSignature(oldId);
|
|
UASSERT(oldS != 0);
|
|
Transform correction = _optimizedPoses.at(oldId) * oldS->getPose().inverse();
|
|
_mapCorrection = correction * _mapTransform;
|
|
_lastLocalizationPose = _mapCorrection * signature->getPose();
|
|
}
|
|
else
|
|
{
|
|
UERROR("Not supposed to be here!");
|
|
}
|
|
}
|
|
|
|
//============================================================
|
|
// 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) &&
|
|
uContains(_optimizedPoses, _path[_pathCurrentIndex].first))
|
|
{
|
|
Transform virtualLoop = _optimizedPoses.at(signature->id()).inverse() * _optimizedPoses.at(_path[_pathCurrentIndex].first);
|
|
if(_localRadius > 0.0f && virtualLoop.getNorm() < _localRadius)
|
|
{
|
|
_memory->addLink(Link(signature->id(), _path[_pathCurrentIndex].first, Link::kVirtualClosure, virtualLoop, 100, 100)); // set high variance
|
|
}
|
|
}
|
|
}
|
|
|
|
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:lastLocalSpaceClosureId?lastLocalSpaceClosureId:_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();
|
|
|
|
// Posterior is empty if a bad signature is detected
|
|
float vpHypothesis = posterior.size()?posterior.at(Memory::kIdVirtual):0.0f;
|
|
|
|
// prepare statistics
|
|
if(_loopClosureHypothesis.first || _publishStats)
|
|
{
|
|
ULOGGER_INFO("sending stats...");
|
|
statistics_.setRefImageId(signature->id());
|
|
if(_loopClosureHypothesis.first != Memory::kIdInvalid)
|
|
{
|
|
statistics_.setLoopClosureId(_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::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::kLoopReactivateId(), retrievalId);
|
|
statistics_.addStatistic(Statistics::kLoopHypothesis_ratio(), hypothesisRatio);
|
|
statistics_.addStatistic(Statistics::kLoopVisualInliers(), loopClosureVisualInliers);
|
|
statistics_.addStatistic(Statistics::kLoopLast_id(), _memory->getLastGlobalLoopClosureId());
|
|
|
|
statistics_.addStatistic(Statistics::kLocalLoopTime_closures(), localLoopClosuresInTimeFound);
|
|
statistics_.addStatistic(Statistics::kLocalLoopSpace_closures_added_visually(), localSpaceClosuresAddedVisually);
|
|
statistics_.addStatistic(Statistics::kLocalLoopSpace_closures_added_icp_only(), localSpaceClosuresAddedByICPOnly);
|
|
statistics_.addStatistic(Statistics::kLocalLoopSpace_paths(), localSpacePaths);
|
|
statistics_.addStatistic(Statistics::kLocalLoopSpace_last_closure_id(), lastLocalSpaceClosureId);
|
|
statistics_.setLocalLoopClosureId(lastLocalSpaceClosureId);
|
|
if(_loopClosureHypothesis.first || lastLocalSpaceClosureId)
|
|
{
|
|
UASSERT(uContains(sLoop->getLinks(), signature->id()));
|
|
UINFO("Set loop closure transform = %s", sLoop->getLinks().at(signature->id()).transform().prettyPrint().c_str());
|
|
statistics_.setLoopClosureTransform(sLoop->getLinks().at(signature->id()).transform());
|
|
}
|
|
statistics_.setMapCorrection(_mapCorrection);
|
|
UINFO("Set map correction = %s", _mapCorrection.prettyPrint().c_str());
|
|
|
|
// timings...
|
|
statistics_.addStatistic(Statistics::kTimingMemory_update(), timeMemoryUpdate*1000);
|
|
statistics_.addStatistic(Statistics::kTimingScan_matching(), timeScanMatching*1000);
|
|
statistics_.addStatistic(Statistics::kTimingLocal_detection_TIME(), timeLocalTimeDetection*1000);
|
|
statistics_.addStatistic(Statistics::kTimingLocal_detection_SPACE(), timeLocalSpaceDetection*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());
|
|
|
|
// Surf specific parameters
|
|
statistics_.addStatistic(Statistics::kKeypointDictionary_size(), dictionarySize);
|
|
|
|
//Epipolar geometry constraint
|
|
statistics_.addStatistic(Statistics::kLoopRejectedHypothesis(), rejectedHypothesis?1.0f:0);
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
// 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->id());
|
|
if(_publishLastSignatureData)
|
|
{
|
|
lastSignatureData = *signature;
|
|
}
|
|
|
|
//By default, remove all signatures with a loop closure link if they are not in reactivateIds
|
|
//This will also remove rehearsed signatures
|
|
std::list<int> signaturesRemoved = _memory->cleanup();
|
|
|
|
// 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(_startNewMapOnLoopClosure &&
|
|
_memory->isIncremental() && // only in mapping mode
|
|
signature->getLinks().size() == 0 && // alone in the current map
|
|
_memory->getWorkingMem().size()>1) // The working memory should not be empty
|
|
{
|
|
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(smallDisplacement && _loopClosureHypothesis.first == 0 && lastLocalSpaceClosureId == 0)
|
|
{
|
|
// 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());
|
|
}
|
|
|
|
// 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((_maxTimeAllowed != 0 && totalTime*1000>_maxTimeAllowed) ||
|
|
(_maxMemoryAllowed != 0 && _memory->getWorkingMem().size() > _maxMemoryAllowed))
|
|
{
|
|
ULOGGER_INFO("Removing old signatures because time limit is reached %f>%f or memory is reached %d>%d...", totalTime*1000, _maxTimeAllowed, _memory->getWorkingMem().size(), _maxMemoryAllowed);
|
|
std::list<int> transferred = _memory->forget(immunizedLocations);
|
|
signaturesRemoved.insert(signaturesRemoved.end(), transferred.begin(), transferred.end());
|
|
}
|
|
_lastProcessTime = totalTime;
|
|
|
|
//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
|
|
if(_memory->getLastWorkingSignature())
|
|
{
|
|
std::map<int, int> ids = _memory->getNeighborsId(_memory->getLastWorkingSignature()->id(), 0, 0, true);
|
|
for(std::map<int, Transform>::iterator iter=_optimizedPoses.begin(); iter!=_optimizedPoses.end();)
|
|
{
|
|
if(!uContains(ids, iter->first))
|
|
{
|
|
_optimizedPoses.erase(iter++);
|
|
}
|
|
else
|
|
{
|
|
++iter;
|
|
}
|
|
}
|
|
for(std::multimap<int, Link>::iterator iter=_constraints.begin(); iter!=_constraints.end();)
|
|
{
|
|
if(!uContains(ids, iter->second.from()) || !uContains(ids, iter->second.to()))
|
|
{
|
|
_constraints.erase(iter++);
|
|
}
|
|
else
|
|
{
|
|
++iter;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_optimizedPoses.clear();
|
|
_constraints.clear();
|
|
}
|
|
}
|
|
|
|
|
|
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());
|
|
|
|
std::map<int, Signature> signatures;
|
|
if(_publishLastSignatureData)
|
|
{
|
|
signatures.insert(std::make_pair(lastSignatureData.id(), lastSignatureData));
|
|
}
|
|
// Set local graph
|
|
std::map<int, Transform> poses;
|
|
std::multimap<int, Link> constraints;
|
|
if(!_rgbdSlamMode)
|
|
{
|
|
// 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;
|
|
}
|
|
for(std::map<int, Transform>::iterator iter=poses.begin(); iter!=poses.end(); ++iter)
|
|
{
|
|
Transform odomPose;
|
|
int weight = -1;
|
|
int mapId = -1;
|
|
std::string label;
|
|
double stamp = 0;
|
|
std::vector<unsigned char> userData;
|
|
_memory->getNodeInfo(iter->first, odomPose, mapId, weight, label, stamp, userData, false);
|
|
signatures.insert(std::make_pair(iter->first,
|
|
Signature(iter->first,
|
|
mapId,
|
|
weight,
|
|
stamp,
|
|
label,
|
|
odomPose,
|
|
userData)));
|
|
}
|
|
statistics_.setPoses(poses);
|
|
statistics_.setConstraints(constraints);
|
|
statistics_.setSignatures(signatures);
|
|
statistics_.addStatistic(Statistics::kMemoryLocal_graph_size(), poses.size());
|
|
localGraphSize = poses.size();
|
|
}
|
|
|
|
//Start trashing
|
|
_memory->emptyTrash();
|
|
|
|
// Log info...
|
|
// TODO : use a specific class which will handle the RtabmapEvent
|
|
if(_foutFloat && _foutInt)
|
|
{
|
|
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,
|
|
timeScanMatching,
|
|
timeLocalTimeDetection,
|
|
timeLocalSpaceDetection,
|
|
timeMapOptimization);
|
|
std::string logI = uFormat("%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()),
|
|
rejectedHypothesis?1:0,
|
|
0,
|
|
0,
|
|
int(signaturesRetrieved.size()),
|
|
lcHypothesisReactivated,
|
|
refUniqueWordsCount,
|
|
retrievalId,
|
|
0,
|
|
rehearsalMaxId,
|
|
rehearsalMaxId>0?1:0,
|
|
localGraphSize,
|
|
data.id());
|
|
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();
|
|
}
|
|
UDEBUG("End process");
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Rtabmap::process(const cv::Mat & image, int id)
|
|
{
|
|
return this->process(SensorData(image, id), Transform());
|
|
}
|
|
|
|
// 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::setWorkingDirectory(std::string path)
|
|
{
|
|
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)
|
|
{
|
|
_wDir = path;
|
|
if(_memory)
|
|
{
|
|
this->resetMemory();
|
|
}
|
|
else
|
|
{
|
|
setupLogFiles();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ULOGGER_ERROR("Directory \"%s\" doesn't exist!", path.c_str());
|
|
}
|
|
}
|
|
|
|
void Rtabmap::rejectLoopClosure(int oldId, int newId)
|
|
{
|
|
UDEBUG("_loopClosureHypothesis.first=%d", _loopClosureHypothesis.first);
|
|
if(_loopClosureHypothesis.first)
|
|
{
|
|
_loopClosureHypothesis.first = 0;
|
|
if(_memory)
|
|
{
|
|
_memory->removeLink(oldId, newId);
|
|
}
|
|
if(uContains(statistics_.data(), rtabmap::Statistics::kLoopRejectedHypothesis()))
|
|
{
|
|
statistics_.addStatistic(rtabmap::Statistics::kLoopRejectedHypothesis(), 1.0f);
|
|
}
|
|
statistics_.setLoopClosureId(0);
|
|
}
|
|
}
|
|
|
|
void Rtabmap::dumpData() const
|
|
{
|
|
UDEBUG("");
|
|
if(_memory)
|
|
{
|
|
_memory->dumpMemory(this->getWorkingDir());
|
|
}
|
|
}
|
|
|
|
void Rtabmap::dumpPoses(
|
|
const std::string & path,
|
|
const std::map<int, Transform> & poses) const
|
|
{
|
|
UDEBUG("");
|
|
FILE* fout = 0;
|
|
#ifdef _MSC_VER
|
|
fopen_s(&fout, path.c_str(), "w");
|
|
#else
|
|
fout = fopen(path.c_str(), "w");
|
|
#endif
|
|
if(fout)
|
|
{
|
|
for(std::map<int, Transform>::const_iterator iter=poses.begin(); iter!=poses.end(); ++iter)
|
|
{
|
|
// in camera frame
|
|
const float * p = (const float *)(*iter).second.data();
|
|
|
|
fprintf(fout, "%f", p[0]);
|
|
for(int i=1; i<(*iter).second.size(); i++)
|
|
{
|
|
fprintf(fout, " %f", p[i]);
|
|
}
|
|
fprintf(fout, "\n");
|
|
}
|
|
fclose(fout);
|
|
}
|
|
}
|
|
|
|
// 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::getNodesInRadius(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;
|
|
}
|
|
|
|
// Get paths in front of the robot, returned optimized poses
|
|
std::list<std::map<int, Transform> > Rtabmap::getPaths(std::map<int, Transform> poses) const
|
|
{
|
|
std::list<std::map<int, Transform> > paths;
|
|
if(_memory && poses.size())
|
|
{
|
|
// Segment poses connected only by neighbor links
|
|
while(poses.size())
|
|
{
|
|
std::map<int, Transform> path;
|
|
for(std::map<int, Transform>::iterator iter=poses.begin(); iter!=poses.end();)
|
|
{
|
|
if(path.size() == 0 || uContains(_memory->getNeighborLinks(path.rbegin()->first), iter->first))
|
|
{
|
|
path.insert(*iter);
|
|
poses.erase(iter++);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
UASSERT(path.size());
|
|
paths.push_back(path);
|
|
}
|
|
|
|
}
|
|
return paths;
|
|
}
|
|
|
|
void Rtabmap::optimizeCurrentMap(
|
|
int id,
|
|
bool lookInDatabase,
|
|
std::map<int, Transform> & optimizedPoses,
|
|
std::multimap<int, Link> * constraints) const
|
|
{
|
|
//Optimize the map
|
|
optimizedPoses.clear();
|
|
UINFO("Optimize map: around location %d", id);
|
|
if(_memory && id > 0)
|
|
{
|
|
UTimer timer;
|
|
std::map<int, int> ids = _memory->getNeighborsId(id, 0, lookInDatabase?-1:0, true, false);
|
|
if(!_optimizeFromGraphEnd && ids.size() > 1)
|
|
{
|
|
id = ids.begin()->first;
|
|
}
|
|
UINFO("get %d ids time %f s", (int)ids.size(), timer.ticks());
|
|
|
|
optimizedPoses = Rtabmap::optimizeGraph(id, uKeysSet(ids), lookInDatabase, constraints);
|
|
|
|
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());
|
|
}
|
|
UINFO("optimize time %f s", timer.ticks());
|
|
}
|
|
}
|
|
|
|
std::map<int, Transform> Rtabmap::optimizeGraph(
|
|
int fromId,
|
|
const std::set<int> & ids,
|
|
bool lookInDatabase,
|
|
std::multimap<int, Link> * constraints) 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);
|
|
UINFO("get constraints (%d poses, %d edges) time %f s", (int)poses.size(), (int)edgeConstraints.size(), timer.ticks());
|
|
|
|
if(constraints)
|
|
{
|
|
*constraints = edgeConstraints;
|
|
}
|
|
|
|
UASSERT(_graphOptimizer!=0);
|
|
if(_graphOptimizer->iterations() == 0)
|
|
{
|
|
// Optimization desactivated! Return not optimized poses.
|
|
optimizedPoses = poses;
|
|
}
|
|
else
|
|
{
|
|
optimizedPoses = _graphOptimizer->optimize(fromId, poses, edgeConstraints);
|
|
}
|
|
UINFO("Optimization time %f s", timer.ticks());
|
|
|
|
return optimizedPoses;
|
|
}
|
|
|
|
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;
|
|
if(value > mean+stdDev && mean)
|
|
{
|
|
iter->second = (value-(stdDev-epsilon))/mean;
|
|
if(value > max)
|
|
{
|
|
max = value;
|
|
maxId = iter->first;
|
|
}
|
|
}
|
|
else if(value == 1.0f && stdDev == 0)
|
|
{
|
|
iter->second = 1.0f;
|
|
if(value > max)
|
|
{
|
|
max = value;
|
|
maxId = iter->first;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
iter->second = 1.0f;
|
|
}
|
|
}
|
|
|
|
if(stdDev > epsilon && max)
|
|
{
|
|
likelihood.begin()->second = mean/stdDev + 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)
|
|
{
|
|
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(!_bayesFilter->isBadSignaturesIgnored() || !s->isBadSignature())
|
|
{
|
|
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");
|
|
}
|
|
}
|
|
|
|
void Rtabmap::get3DMap(
|
|
std::map<int, Signature> & signatures,
|
|
std::map<int, Transform> & poses,
|
|
std::multimap<int, Link> & constraints,
|
|
bool optimized,
|
|
bool global) const
|
|
{
|
|
UDEBUG("");
|
|
if(_memory && _memory->getLastWorkingSignature())
|
|
{
|
|
if(_rgbdSlamMode)
|
|
{
|
|
if(optimized)
|
|
{
|
|
this->optimizeCurrentMap(_memory->getLastWorkingSignature()->id(), global, poses, &constraints);
|
|
}
|
|
else
|
|
{
|
|
std::map<int, int> ids = _memory->getNeighborsId(_memory->getLastWorkingSignature()->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()->id(), 0, global?-1:0, true);
|
|
_memory->getMetricConstraints(uKeysSet(ids), poses, constraints, global);
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
for(std::set<int>::iterator iter = ids.begin(); iter!=ids.end(); ++iter)
|
|
{
|
|
Transform odomPose;
|
|
int weight = -1;
|
|
int mapId = -1;
|
|
std::string label;
|
|
double stamp = 0;
|
|
std::vector<unsigned char> userData;
|
|
_memory->getNodeInfo(*iter, odomPose, mapId, weight, label, stamp, userData, true);
|
|
SensorData data = _memory->getNodeData(*iter);
|
|
data.setId(*iter);
|
|
signatures.insert(std::make_pair(*iter,
|
|
Signature(*iter,
|
|
mapId,
|
|
weight,
|
|
stamp,
|
|
label,
|
|
odomPose,
|
|
userData,
|
|
data)));
|
|
}
|
|
}
|
|
else if(_memory && (_memory->getStMem().size() || _memory->getWorkingMem().size() > 1))
|
|
{
|
|
UERROR("Last working signature is null!?");
|
|
}
|
|
else if(_memory == 0)
|
|
{
|
|
UWARN("Memory not initialized...");
|
|
}
|
|
}
|
|
|
|
void Rtabmap::getGraph(
|
|
std::map<int, Transform> & poses,
|
|
std::multimap<int, Link> & constraints,
|
|
bool optimized,
|
|
bool global,
|
|
std::map<int, Signature> * signatures)
|
|
{
|
|
if(_memory && _memory->getLastWorkingSignature())
|
|
{
|
|
if(_rgbdSlamMode)
|
|
{
|
|
if(optimized)
|
|
{
|
|
this->optimizeCurrentMap(_memory->getLastWorkingSignature()->id(), global, poses, &constraints);
|
|
}
|
|
else
|
|
{
|
|
std::map<int, int> ids = _memory->getNeighborsId(_memory->getLastWorkingSignature()->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()->id(), 0, global?-1:0, true);
|
|
_memory->getMetricConstraints(uKeysSet(ids), poses, constraints, global);
|
|
}
|
|
|
|
if(signatures)
|
|
{
|
|
for(std::map<int, Transform>::iterator iter=poses.begin(); iter!=poses.end(); ++iter)
|
|
{
|
|
Transform odomPose;
|
|
int weight = -1;
|
|
int mapId = -1;
|
|
std::string label;
|
|
double stamp = 0;
|
|
std::vector<unsigned char> userData;
|
|
_memory->getNodeInfo(iter->first, odomPose, mapId, weight, label, stamp, userData, global);
|
|
signatures->insert(std::make_pair(iter->first,
|
|
Signature(iter->first,
|
|
mapId,
|
|
weight,
|
|
stamp,
|
|
label,
|
|
odomPose,
|
|
userData)));
|
|
}
|
|
}
|
|
}
|
|
else if(_memory && (_memory->getStMem().size() || _memory->getWorkingMem().size()))
|
|
{
|
|
UERROR("Last working signature is null!?");
|
|
}
|
|
else if(_memory == 0)
|
|
{
|
|
UWARN("Memory not initialized...");
|
|
}
|
|
}
|
|
|
|
void Rtabmap::clearPath()
|
|
{
|
|
_path.clear();
|
|
_pathCurrentIndex=0;
|
|
_pathGoalIndex = 0;
|
|
_pathTransformToGoal.setIdentity();
|
|
if(_memory)
|
|
{
|
|
_memory->removeAllVirtualLinks();
|
|
}
|
|
}
|
|
|
|
bool Rtabmap::computePath(
|
|
int targetNode,
|
|
std::map<int, Transform> nodes,
|
|
const std::multimap<int, rtabmap::Link> & constraints)
|
|
{
|
|
if(_memory)
|
|
{
|
|
int currentNode;
|
|
if(_memory->isIncremental())
|
|
{
|
|
if(!_memory->getLastWorkingSignature())
|
|
{
|
|
UWARN("Working memory is empty... cannot compute a path");
|
|
return false;
|
|
}
|
|
currentNode = _memory->getLastWorkingSignature()->id();
|
|
}
|
|
else
|
|
{
|
|
if(_lastLocalizationPose.isNull() || _optimizedPoses.size() == 0)
|
|
{
|
|
UWARN("Last localization pose is null... cannot compute a path");
|
|
return false;
|
|
}
|
|
currentNode = graph::findNearestNode(_optimizedPoses, _lastLocalizationPose);
|
|
}
|
|
|
|
if(!uContains(nodes, currentNode))
|
|
{
|
|
UWARN("Last signature %d not found in the graph! Cannot compute a path", currentNode);
|
|
return false;
|
|
}
|
|
|
|
if(!uContains(nodes, targetNode))
|
|
{
|
|
UWARN("Goal %d not found in the graph! Cannot compute a path", targetNode);
|
|
return false;
|
|
}
|
|
|
|
// transform nodes into current referential
|
|
if(_optimizedPoses.size())
|
|
{
|
|
if(uContains(nodes, currentNode) && uContains(_optimizedPoses, currentNode))
|
|
{
|
|
Transform t = _optimizedPoses.at(currentNode) * nodes.at(currentNode).inverse();
|
|
for(std::map<int, Transform>::iterator iter=nodes.begin(); iter!=nodes.end(); ++iter)
|
|
{
|
|
iter->second = t * iter->second;
|
|
}
|
|
}
|
|
}
|
|
|
|
std::multimap<int, int> links;
|
|
for(std::multimap<int, rtabmap::Link>::const_iterator iter=constraints.begin(); iter!=constraints.end(); ++iter)
|
|
{
|
|
links.insert(std::make_pair(iter->first, iter->second.to()));
|
|
links.insert(std::make_pair(iter->second.to(), iter->first)); // <->
|
|
}
|
|
// Add links between neighbor nodes in the goal radius.
|
|
if(_planVirtualLinks)
|
|
{
|
|
std::multimap<int, int> clusters = rtabmap::graph::radiusPosesClustering(nodes, _goalReachedRadius, CV_PI);
|
|
for(std::multimap<int, int>::iterator iter=clusters.begin(); iter!=clusters.end(); ++iter)
|
|
{
|
|
if(graph::findLink(links, iter->first, iter->second) == links.end())
|
|
{
|
|
links.insert(*iter);
|
|
links.insert(std::make_pair(iter->second, iter->first)); // <->
|
|
}
|
|
}
|
|
}
|
|
|
|
UINFO("Computing path from location %d to %d", currentNode, targetNode);
|
|
UTimer timer;
|
|
_path = uListToVector(rtabmap::graph::computePath(nodes, links, currentNode, targetNode));
|
|
UINFO("A* time = %fs", timer.ticks());
|
|
|
|
if(_path.size() == 0)
|
|
{
|
|
_path.clear();
|
|
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());
|
|
}
|
|
if(_goalsSavedInUserData)
|
|
{
|
|
// set goal to latest signature
|
|
std::string goalStr = uFormat("GOAL:%d", targetNode);
|
|
setUserData(0, uStr2Bytes(goalStr));
|
|
}
|
|
}
|
|
|
|
return _path.size()>0;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// return true if path is updated
|
|
bool Rtabmap::computePath(int targetNode, bool global)
|
|
{
|
|
UINFO("Planning a path to node %d (global=%d)", targetNode, global?1:0);
|
|
this->clearPath();
|
|
|
|
if(!_rgbdSlamMode)
|
|
{
|
|
UWARN("A path can only be computed in RGBD-SLAM mode");
|
|
return false;
|
|
}
|
|
|
|
UTimer totalTimer;
|
|
UTimer timer;
|
|
std::map<int, Transform> nodes;
|
|
std::multimap<int, Link> constraints;
|
|
this->getGraph(nodes, constraints, true, global);
|
|
UINFO("Time creating graph (global=%s) = %fs", global?"true":"false", timer.ticks());
|
|
|
|
if(computePath(targetNode, nodes, constraints))
|
|
{
|
|
updateGoalIndex();
|
|
}
|
|
UINFO("Time computing path (A*) = %fs", timer.ticks());
|
|
UINFO("Total planning time = %fs (%d nodes, %f m long)", totalTimer.ticks(), (int)_path.size(), graph::computePathLength(_path));
|
|
|
|
return _path.size()>0;
|
|
}
|
|
|
|
bool Rtabmap::computePath(const Transform & targetPose, bool global)
|
|
{
|
|
UINFO("Planning a path to pose %s (global=%d)", targetPose.prettyPrint().c_str(), global?1:0);
|
|
|
|
this->clearPath();
|
|
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;
|
|
std::multimap<int, Link> constraints;
|
|
std::map<int, int> mapIds;
|
|
std::map<int, double> stamps;
|
|
std::map<int, std::string> labels;
|
|
std::map<int, std::vector<unsigned char> > userDatas;
|
|
this->getGraph(nodes, constraints, true, global);
|
|
UINFO("Time creating graph (global=%s) = %fs", global?"true":"false", timer.ticks());
|
|
|
|
int nearestId = rtabmap::graph::findNearestNode(nodes, targetPose);
|
|
UINFO("Nearest node found=%d ,%fs", nearestId, timer.ticks());
|
|
if(nearestId > 0)
|
|
{
|
|
if(_localRadius != 0.0f && targetPose.getDistance(nodes.at(nearestId)) > _localRadius)
|
|
{
|
|
UWARN("Cannot plan farther than %f m from the graph! (distance=%f m from node %d)",
|
|
_localRadius, targetPose.getDistance(nodes.at(nearestId)), nearestId);
|
|
}
|
|
else
|
|
{
|
|
if(computePath(nearestId, nodes, constraints))
|
|
{
|
|
UASSERT(_path.size() > 0);
|
|
UASSERT(uContains(nodes, _path.back().first));
|
|
_pathTransformToGoal = nodes.at(_path.back().first).inverse() * targetPose;
|
|
|
|
updateGoalIndex();
|
|
}
|
|
UINFO("Time computing path = %fs", timer.ticks());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UWARN("Nearest node not found in graph (size=%d) for pose %s", (int)nodes.size(), targetPose.prettyPrint().c_str());
|
|
}
|
|
|
|
return _path.size()>0;
|
|
}
|
|
|
|
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
|
|
for(unsigned int i=0; i<_pathCurrentIndex && i<_path.size(); ++i)
|
|
{
|
|
if(_memory->getSignature(_path[i].first))
|
|
{
|
|
_memory->removeVirtualLinks(_path[i].first);
|
|
}
|
|
}
|
|
// for the current index, only keep the newest virtual link
|
|
UASSERT(_pathCurrentIndex < _path.size());
|
|
const Signature * currentIndexS = _memory->getSignature(_path[_pathCurrentIndex].first);
|
|
UASSERT(currentIndexS != 0);
|
|
std::map<int, Link> links = currentIndexS->getLinks(); // make a copy
|
|
bool latestVirtualLinkFound = false;
|
|
for(std::map<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;
|
|
i<_path.size();
|
|
++i)
|
|
{
|
|
if(i>0)
|
|
{
|
|
if(_localRadius > 0.0f)
|
|
{
|
|
distanceSoFar += _path[i-1].second.getDistance(_path[i].second);
|
|
}
|
|
if(distanceSoFar <= _localRadius)
|
|
{
|
|
const Signature * s = _memory->getSignature(_path[i].first);
|
|
if(s)
|
|
{
|
|
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, 1, 1)); // on the optimized path, set Identity variance
|
|
UINFO("Added Virtual link between %d and %d", _path[i-1].first, _path[i].first);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
UDEBUG("current node = %d current goal = %d", _path[_pathCurrentIndex].first, _path[_pathGoalIndex].first);
|
|
Transform currentPose;
|
|
if(_memory->isIncremental())
|
|
{
|
|
if(_memory->getLastWorkingSignature() == 0 ||
|
|
!uContains(_optimizedPoses, _memory->getLastWorkingSignature()->id()))
|
|
{
|
|
UERROR("Last node is null in memory or not in optimized poses. Aborting the plan...");
|
|
this->clearPath();
|
|
return;
|
|
}
|
|
currentPose = _optimizedPoses.at(_memory->getLastWorkingSignature()->id());
|
|
}
|
|
else
|
|
{
|
|
if(_lastLocalizationPose.isNull())
|
|
{
|
|
UERROR("Last localization pose is null. Aborting the plan...");
|
|
this->clearPath();
|
|
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();
|
|
}
|
|
}
|
|
|
|
if(_path.size())
|
|
{
|
|
//Always check if the farthest node is accessible in local map (max to local space radius if set)
|
|
int goalIndex = _pathCurrentIndex;
|
|
float distanceFromCurrentNode = 0.0f;
|
|
for(unsigned int i=_pathCurrentIndex; i<_path.size(); ++i)
|
|
{
|
|
if(uContains(_optimizedPoses, _path[i].first))
|
|
{
|
|
if(_localRadius > 0.0f)
|
|
{
|
|
distanceFromCurrentNode = currentPose.getDistance(_optimizedPoses.at(_path[i].first));
|
|
}
|
|
|
|
if(distanceFromCurrentNode <= _localRadius)
|
|
{
|
|
goalIndex = i;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
UASSERT(_pathGoalIndex < _path.size() && goalIndex >= 0 && goalIndex < (int)_path.size());
|
|
if((int)_pathGoalIndex != goalIndex)
|
|
{
|
|
UINFO("Updated current goal from %d to %d (%d/%d)",
|
|
(int)_path[_pathGoalIndex].first, _path[goalIndex].first, goalIndex+1, (int)_path.size());
|
|
_pathGoalIndex = goalIndex;
|
|
}
|
|
|
|
// update nearest pose in the path
|
|
unsigned int nearestNodeIndex = 0;
|
|
float distance = -1.0f;
|
|
UASSERT(_pathGoalIndex < _path.size() && _pathGoalIndex >= 0);
|
|
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();
|
|
}
|
|
else
|
|
{
|
|
UDEBUG("Nearest node = %d", _path[nearestNodeIndex].first);
|
|
}
|
|
if(distance >= 0 && nearestNodeIndex != _pathCurrentIndex)
|
|
{
|
|
_pathCurrentIndex = nearestNodeIndex;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Rtabmap::readParameters(const std::string & configFile, ParametersMap & parameters)
|
|
{
|
|
CSimpleIniA ini;
|
|
ini.LoadFile(configFile.c_str());
|
|
const CSimpleIniA::TKeyVal * keyValMap = ini.GetSection("Core");
|
|
if(keyValMap)
|
|
{
|
|
for(CSimpleIniA::TKeyVal::const_iterator iter=keyValMap->begin(); iter!=keyValMap->end(); ++iter)
|
|
{
|
|
std::string key = (*iter).first.pItem;
|
|
if(key.compare("Version") == 0)
|
|
{
|
|
// Compare version in ini with the current RTAB-Map version
|
|
std::vector<std::string> version = uListToVector(uSplit((*iter).second, '.'));
|
|
if(version.size() == 3)
|
|
{
|
|
if(!RTABMAP_VERSION_COMPARE(std::atoi(version[0].c_str()), std::atoi(version[1].c_str()), std::atoi(version[2].c_str())))
|
|
{
|
|
if(configFile.find(".rtabmap") != std::string::npos)
|
|
{
|
|
UWARN("Version in the config file \"%s\" is more recent (\"%s\") than "
|
|
"current RTAB-Map version used (\"%s\"). The config file will be upgraded "
|
|
"to new version.",
|
|
configFile.c_str(),
|
|
(*iter).second,
|
|
RTABMAP_VERSION);
|
|
}
|
|
else
|
|
{
|
|
UERROR("Version in the config file \"%s\" is more recent (\"%s\") than "
|
|
"current RTAB-Map version used (\"%s\"). New parameters (if there are some) will "
|
|
"be ignored.",
|
|
configFile.c_str(),
|
|
(*iter).second,
|
|
RTABMAP_VERSION);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
key = uReplaceChar(key, '\\', '/'); // Ini files use \ by default for separators, so replace them
|
|
ParametersMap::iterator jter = parameters.find(key);
|
|
if(jter != parameters.end())
|
|
{
|
|
parameters.erase(jter);
|
|
}
|
|
parameters.insert(ParametersPair(key, (*iter).second));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ULOGGER_WARN("Section \"Core\" in %s doesn't exist... "
|
|
"Ignore this warning if the ini file does not exist yet. "
|
|
"The ini file will be automatically created when this node will close.", configFile.c_str());
|
|
}
|
|
}
|
|
|
|
void Rtabmap::writeParameters(const std::string & configFile, const ParametersMap & parameters)
|
|
{
|
|
CSimpleIniA ini;
|
|
ini.LoadFile(configFile.c_str());
|
|
|
|
// Save current version
|
|
ini.SetValue("Core", "Version", RTABMAP_VERSION, NULL, true);
|
|
|
|
for(ParametersMap::const_iterator i=parameters.begin(); i!=parameters.end(); ++i)
|
|
{
|
|
std::string key = (*i).first;
|
|
key = uReplaceChar(key, '/', '\\'); // Ini files use \ by default for separators, so replace the /
|
|
ini.SetValue("Core", key.c_str(), (*i).second.c_str(), NULL, true);
|
|
}
|
|
|
|
ini.SaveFile(configFile.c_str());
|
|
}
|
|
|
|
} // namespace rtabmap
|