/* * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke * * This file is part of RTAB-Map. * * RTAB-Map is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * RTAB-Map is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with RTAB-Map. If not, see . */ #include #include #include #include #include #include #include "rtabmap/core/Memory.h" #include "rtabmap/core/Signature.h" #include "rtabmap/core/Parameters.h" #include "rtabmap/core/RtabmapEvent.h" #include "rtabmap/core/VWDictionary.h" #include "VisualWord.h" #include "rtabmap/core/Features2d.h" #include "rtabmap/core/util3d.h" #include "DBDriverSqlite3.h" #include "rtabmap/core/util3d.h" #include "rtabmap/core/Statistics.h" #include namespace rtabmap { const int Memory::kIdStart = 0; const int Memory::kIdVirtual = -1; const int Memory::kIdInvalid = 0; Memory::Memory(const ParametersMap & parameters) : _dbDriver(0), _similarityThreshold(Parameters::defaultMemRehearsalSimilarity()), _rawDataKept(Parameters::defaultMemImageKept()), _keepRehearsedNodesInDb(Parameters::defaultMemRehearsedNodesKept()), _incrementalMemory(Parameters::defaultMemIncrementalMemory()), _maxStMemSize(Parameters::defaultMemSTMSize()), _recentWmRatio(Parameters::defaultMemRecentWmRatio()), _idUpdatedToNewOneRehearsal(Parameters::defaultMemRehearsalIdUpdatedToNewOne()), _generateIds(Parameters::defaultMemGenerateIds()), _badSignaturesIgnored(Parameters::defaultMemBadSignaturesIgnored()), _idCount(kIdStart), _idMapCount(kIdStart), _lastSignature(0), _lastGlobalLoopClosureParentId(0), _lastGlobalLoopClosureChildId(0), _memoryChanged(false), _signaturesAdded(0), _keypointDetector(0), _keypointDescriptor(0), _badSignRatio(Parameters::defaultKpBadSignRatio()), _tfIdfLikelihoodUsed(Parameters::defaultKpTfIdfLikelihoodUsed()), _parallelized(Parameters::defaultKpParallelized()), _wordsPerImageTarget(Parameters::defaultKpWordsPerImage()), _roiRatios(std::vector(4, 0.0f)), _bowMinInliers(Parameters::defaultLccBowMinInliers()), _bowInlierDistance(Parameters::defaultLccBowInlierDistance()), _bowIterations(Parameters::defaultLccBowIterations()), _bowMaxDepth(Parameters::defaultLccBowMaxDepth()), _icpDecimation(Parameters::defaultLccIcp3Decimation()), _icpMaxDepth(Parameters::defaultLccIcp3MaxDepth()), _icpVoxelSize(Parameters::defaultLccIcp3VoxelSize()), _icpSamples(Parameters::defaultLccIcp3Samples()), _icpMaxCorrespondenceDistance(Parameters::defaultLccIcp3MaxCorrespondenceDistance()), _icpMaxIterations(Parameters::defaultLccIcp3Iterations()), _icpMaxFitness(Parameters::defaultLccIcp3MaxFitness()), _icp2MaxCorrespondenceDistance(Parameters::defaultLccIcp2MaxCorrespondenceDistance()), _icp2MaxIterations(Parameters::defaultLccIcp2Iterations()), _icp2MaxFitness(Parameters::defaultLccIcp2MaxFitness()), _icp2CorrespondenceRatio(Parameters::defaultLccIcp2CorrespondenceRatio()), _icp2VoxelSize(Parameters::defaultLccIcp2VoxelSize()) { _vwd = new VWDictionary(parameters); this->parseParameters(parameters); } bool Memory::init(const std::string & dbUrl, bool dbOverwritten, const ParametersMap & parameters, bool postInitEvents) { if(postInitEvents) UEventsManager::post(new RtabmapEventInit(RtabmapEventInit::kInitializing)); UDEBUG(""); this->parseParameters(parameters); if(postInitEvents) UEventsManager::post(new RtabmapEventInit("Clearing memory...")); DBDriver * tmpDriver = 0; if(!_memoryChanged) { if(_dbDriver) { tmpDriver = _dbDriver; _dbDriver = 0; // HACK for the clear() below to think that there is no db } } this->clear(); if(postInitEvents) UEventsManager::post(new RtabmapEventInit("Clearing memory, done!")); if(tmpDriver) { _dbDriver = tmpDriver; } if(_dbDriver) { if(postInitEvents) UEventsManager::post(new RtabmapEventInit("Closing database connection...")); _dbDriver->closeConnection(); if(postInitEvents) UEventsManager::post(new RtabmapEventInit("Closing database connection, done!")); } if(_dbDriver == 0 && !dbUrl.empty()) { _dbDriver = new DBDriverSqlite3(parameters); } bool success = true; if(_dbDriver) { success = false; if(postInitEvents) UEventsManager::post(new RtabmapEventInit(std::string("Connecting to database ") + dbUrl + "...")); if(_dbDriver->openConnection(dbUrl, dbOverwritten)) { success = true; if(postInitEvents) UEventsManager::post(new RtabmapEventInit(std::string("Connecting to database ") + dbUrl + ", done!")); // Load the last working memory... if(postInitEvents) UEventsManager::post(new RtabmapEventInit(std::string("Loading last signatures..."))); std::list dbSignatures; _dbDriver->loadLastNodes(dbSignatures); for(std::list::reverse_iterator iter=dbSignatures.rbegin(); iter!=dbSignatures.rend(); ++iter) { // ignore bad signatures if(!((*iter)->isBadSignature() && _badSignaturesIgnored)) { _signatures.insert(std::pair((*iter)->id(), *iter)); if((int)_stMem.size() <= _maxStMemSize) { _stMem.insert((*iter)->id()); } else { _workingMem.insert((*iter)->id()); } } else { delete *iter; } } if(postInitEvents) UEventsManager::post(new RtabmapEventInit(std::string("Loading last signatures, done! (") + uNumber2Str(int(_workingMem.size() + _stMem.size())) + " loaded)")); // Assign the last signature if(_stMem.size()>0) { _lastSignature = uValue(_signatures, *_stMem.rbegin(), (Signature*)0); } // Last id _dbDriver->getLastNodeId(_idCount); _idMapCount = _lastSignature?_lastSignature->mapId():kIdStart; } else { if(postInitEvents) UEventsManager::post(new RtabmapEventInit(RtabmapEventInit::kError, std::string("Connecting to database ") + dbUrl + ", path is invalid!")); } } else { _idCount = kIdStart; _idMapCount = kIdStart; } _workingMem.insert(kIdVirtual); UDEBUG("ids start with %d", _idCount+1); UDEBUG("map ids start with %d", _idMapCount); // Now load the dictionary if we have a connection if(_dbDriver && _dbDriver->isConnected()) { if(postInitEvents) UEventsManager::post(new RtabmapEventInit("Loading dictionary...")); _dbDriver->load(_vwd); UDEBUG("%d words loaded!", _vwd->getUnusedWordsSize()); _vwd->update(); if(postInitEvents) UEventsManager::post(new RtabmapEventInit(uFormat("Loading dictionary, done! (%d words)", (int)_vwd->getUnusedWordsSize()))); } if(postInitEvents) UEventsManager::post(new RtabmapEventInit(std::string("Adding word references..."))); // Enable loaded signatures Signature * ss; const std::map & signatures = this->getSignatures(); for(std::map::const_iterator i=signatures.begin(); i!=signatures.end(); ++i) { ss = this->_getSignature(i->first); if(ss) { const std::multimap & words = ss->getWords(); if(words.size()) { UDEBUG("node=%d, word references=%d", ss->id(), words.size()); for(std::multimap::const_iterator iter = words.begin(); iter!=words.end(); ++iter) { _vwd->addWordRef(iter->first, i->first); } ss->setEnabled(true); } } } if(postInitEvents) UEventsManager::post(new RtabmapEventInit(uFormat("Adding word references, done! (%d)", _vwd->getTotalActiveReferences()))); if(_vwd->getUnusedWordsSize()) { UWARN("_vwd->getUnusedWordsSize() must be empty... size=%d", _vwd->getUnusedWordsSize()); } UDEBUG("Total word references added = %d", _vwd->getTotalActiveReferences()); if(postInitEvents) UEventsManager::post(new RtabmapEventInit(RtabmapEventInit::kInitialized)); return success; } Memory::~Memory() { UDEBUG(""); if(!_memoryChanged) { UDEBUG(""); if(_dbDriver) { _dbDriver->closeConnection(); delete _dbDriver; _dbDriver = 0; } this->clear(); } else { UDEBUG(""); this->clear(); if(_dbDriver) { _dbDriver->emptyTrashes(); _dbDriver->closeConnection(); delete _dbDriver; _dbDriver = 0; } } if(_keypointDetector) { delete _keypointDetector; } if(_keypointDescriptor) { delete _keypointDescriptor; } if(_vwd) { delete _vwd; } } void Memory::parseParameters(const ParametersMap & parameters) { UDEBUG(""); ParametersMap::const_iterator iter; Parameters::parse(parameters, Parameters::kMemImageKept(), _rawDataKept); Parameters::parse(parameters, Parameters::kMemRehearsedNodesKept(), _keepRehearsedNodesInDb); Parameters::parse(parameters, Parameters::kMemRehearsalIdUpdatedToNewOne(), _idUpdatedToNewOneRehearsal); Parameters::parse(parameters, Parameters::kMemGenerateIds(), _generateIds); Parameters::parse(parameters, Parameters::kMemBadSignaturesIgnored(), _badSignaturesIgnored); Parameters::parse(parameters, Parameters::kMemRehearsalSimilarity(), _similarityThreshold); Parameters::parse(parameters, Parameters::kMemRecentWmRatio(), _recentWmRatio); Parameters::parse(parameters, Parameters::kMemSTMSize(), _maxStMemSize); UASSERT_MSG(_maxStMemSize > 0, uFormat("value=%d", _maxStMemSize).c_str()); UASSERT_MSG(_similarityThreshold >= 0.0f && _similarityThreshold <= 1.0f, uFormat("value=%f", _similarityThreshold).c_str()); UASSERT_MSG(_recentWmRatio >= 0.0f && _recentWmRatio <= 1.0f, uFormat("value=%f", _recentWmRatio).c_str()); // SLAM mode vs Localization mode iter = parameters.find(Parameters::kMemIncrementalMemory()); if(iter != parameters.end()) { bool value = uStr2Bool(iter->second.c_str()); if(value == false && _incrementalMemory) { // From SLAM to localization, change map id this->incrementMapId(); } _incrementalMemory = value; } if(_dbDriver) { _dbDriver->parseParameters(parameters); } Parameters::parse(parameters, Parameters::kLccBowMinInliers(), _bowMinInliers); Parameters::parse(parameters, Parameters::kLccBowInlierDistance(), _bowInlierDistance); Parameters::parse(parameters, Parameters::kLccBowIterations(), _bowIterations); Parameters::parse(parameters, Parameters::kLccBowMaxDepth(), _bowMaxDepth); Parameters::parse(parameters, Parameters::kLccIcp3Decimation(), _icpDecimation); Parameters::parse(parameters, Parameters::kLccIcp3MaxDepth(), _icpMaxDepth); Parameters::parse(parameters, Parameters::kLccIcp3VoxelSize(), _icpVoxelSize); Parameters::parse(parameters, Parameters::kLccIcp3Samples(), _icpSamples); Parameters::parse(parameters, Parameters::kLccIcp3MaxCorrespondenceDistance(), _icpMaxCorrespondenceDistance); Parameters::parse(parameters, Parameters::kLccIcp3Iterations(), _icpMaxIterations); Parameters::parse(parameters, Parameters::kLccIcp3MaxFitness(), _icpMaxFitness); Parameters::parse(parameters, Parameters::kLccIcp2MaxCorrespondenceDistance(), _icp2MaxCorrespondenceDistance); Parameters::parse(parameters, Parameters::kLccIcp2Iterations(), _icp2MaxIterations); Parameters::parse(parameters, Parameters::kLccIcp2MaxFitness(), _icp2MaxFitness); Parameters::parse(parameters, Parameters::kLccIcp2CorrespondenceRatio(), _icp2CorrespondenceRatio); Parameters::parse(parameters, Parameters::kLccIcp2VoxelSize(), _icp2VoxelSize); UASSERT_MSG(_bowMinInliers >= 1, uFormat("value=%d", _bowMinInliers).c_str()); UASSERT_MSG(_bowInlierDistance > 0.0f, uFormat("value=%f", _bowInlierDistance).c_str()); UASSERT_MSG(_bowIterations > 0, uFormat("value=%d", _bowIterations).c_str()); UASSERT_MSG(_bowMaxDepth >= 0.0f, uFormat("value=%f", _bowMaxDepth).c_str()); UASSERT_MSG(_icpDecimation > 0, uFormat("value=%d", _icpDecimation).c_str()); UASSERT_MSG(_icpMaxDepth >= 0.0f, uFormat("value=%f", _icpMaxDepth).c_str()); UASSERT_MSG(_icpVoxelSize >= 0, uFormat("value=%d", _icpVoxelSize).c_str()); UASSERT_MSG(_icpSamples >= 0, uFormat("value=%d", _icpSamples).c_str()); UASSERT_MSG(_icpMaxCorrespondenceDistance > 0.0f, uFormat("value=%f", _icpMaxCorrespondenceDistance).c_str()); UASSERT_MSG(_icpMaxIterations > 0, uFormat("value=%d", _icpMaxIterations).c_str()); UASSERT_MSG(_icpMaxFitness > 0.0f, uFormat("value=%f", _icpMaxFitness).c_str()); UASSERT_MSG(_icp2MaxCorrespondenceDistance > 0.0f, uFormat("value=%f", _icp2MaxCorrespondenceDistance).c_str()); UASSERT_MSG(_icp2MaxIterations > 0, uFormat("value=%d", _icp2MaxIterations).c_str()); UASSERT_MSG(_icp2MaxFitness > 0.0f, uFormat("value=%f", _icp2MaxFitness).c_str()); UASSERT_MSG(_icp2CorrespondenceRatio >=0.0f && _icp2CorrespondenceRatio <=1.0f, uFormat("value=%f", _icp2MaxFitness).c_str()); UASSERT_MSG(_icp2VoxelSize >= 0, uFormat("value=%d", _icp2VoxelSize).c_str()); // Keypoint stuff if(_vwd) { _vwd->parseParameters(parameters); } Parameters::parse(parameters, Parameters::kKpTfIdfLikelihoodUsed(), _tfIdfLikelihoodUsed); Parameters::parse(parameters, Parameters::kKpParallelized(), _parallelized); Parameters::parse(parameters, Parameters::kKpBadSignRatio(), _badSignRatio); Parameters::parse(parameters, Parameters::kKpWordsPerImage(), _wordsPerImageTarget); if((iter=parameters.find(Parameters::kKpRoiRatios())) != parameters.end()) { this->setRoi((*iter).second); } //Keypoint detector KeypointDetector::DetectorType detectorStrategy = KeypointDetector::kDetectorUndef; if((iter=parameters.find(Parameters::kKpDetectorStrategy())) != parameters.end()) { detectorStrategy = (KeypointDetector::DetectorType)std::atoi((*iter).second.c_str()); } if(!_keypointDetector || detectorStrategy!=KeypointDetector::kDetectorUndef) { UDEBUG("new detector strategy %d", int(detectorStrategy)); if(_keypointDetector) { delete _keypointDetector; _keypointDetector = 0; } if(_keypointDescriptor) { delete _keypointDescriptor; _keypointDescriptor = 0; } switch(detectorStrategy) { case KeypointDetector::kDetectorSift: _keypointDetector = new SIFTDetector(parameters); _keypointDescriptor = new SIFTDescriptor(parameters); break; case KeypointDetector::kDetectorSurf: default: _keypointDetector = new SURFDetector(parameters); _keypointDescriptor = new SURFDescriptor(parameters); break; } } else { if(_keypointDetector) { _keypointDetector->parseParameters(parameters); } if(_keypointDescriptor) { _keypointDescriptor->parseParameters(parameters); } } } void Memory::preUpdate() { _signaturesAdded = 0; this->cleanUnusedWords(); if(_vwd && !_parallelized) { //When parallelized, it is done in CreateSignature _vwd->update(); } } bool Memory::update(const Image & image, Statistics * stats) { UDEBUG(""); UTimer timer; UTimer totalTimer; timer.start(); float t; //============================================================ // Pre update... //============================================================ UDEBUG("pre-updating..."); this->preUpdate(); t=timer.ticks()*1000; if(stats) stats->addStatistic(Statistics::kTimingMemPre_update(), t); UDEBUG("time preUpdate=%f ms", t); //============================================================ // Create a signature with the image received. //============================================================ Signature * signature = this->createSignature(image, this->isRawDataKept()); if (signature == 0) { UERROR("Failed to create a signature..."); return false; } t=timer.ticks()*1000; if(stats) stats->addStatistic(Statistics::kTimingMemSignature_creation(), t); UDEBUG("time creating signature=%f ms", t); // It will be added to the short-term memory, no need to delete it... this->addSignatureToStm(signature); _lastSignature = signature; //============================================================ // Rehearsal step... // Compare with the X last signatures. If different, add this // signature like a parent to the memory tree, otherwise add // it as a child to the similar signature. //============================================================ if(_incrementalMemory) { this->rehearsal(signature, stats); t=timer.ticks()*1000; if(stats) stats->addStatistic(Statistics::kTimingMemRehearsal(), t); UDEBUG("time rehearsal=%f ms", t); } else { if(_workingMem.size() <= 1) { UWARN("The working memory is empty and the memory is not " "incremental (Mem/IncrementalMemory=False), no loop closure " "can be detected! Please set Mem/IncrementalMemory=true to increase " "the memory with new images or decrease the STM size (which is %d " "including the new one added).", (int)_stMem.size()); } } //============================================================ // Transfer the oldest signature of the short-term memory to the working memory //============================================================ while(_stMem.size() && (int)_stMem.size() > _maxStMemSize) { UDEBUG("Inserting node %d from STM in WM...", *_stMem.begin()); _workingMem.insert(_workingMem.end(), *_stMem.begin()); _stMem.erase(*_stMem.begin()); ++_signaturesAdded; } if(!_memoryChanged && _incrementalMemory) { _memoryChanged = true; } UDEBUG("totalTimer = %fs", totalTimer.ticks()); if(stats) stats->addStatistic(Statistics::kLoopLast_loop_closure_parent(), _lastGlobalLoopClosureParentId); if(stats) stats->addStatistic(Statistics::kLoopLast_loop_closure_child(), _lastGlobalLoopClosureChildId); return true; } void Memory::setRoi(const std::string & roi) { std::list strValues = uSplit(roi, ' '); if(strValues.size() != 4) { ULOGGER_ERROR("The number of values must be 4 (roi=\"%s\")", roi.c_str()); } else { std::vector tmpValues(4); unsigned int i=0; for(std::list::iterator iter = strValues.begin(); iter!=strValues.end(); ++iter) { tmpValues[i] = std::atof((*iter).c_str()); ++i; } if(tmpValues[0] >= 0 && tmpValues[0] < 1 && tmpValues[0] < 1.0f-tmpValues[1] && tmpValues[1] >= 0 && tmpValues[1] < 1 && tmpValues[1] < 1.0f-tmpValues[0] && tmpValues[2] >= 0 && tmpValues[2] < 1 && tmpValues[2] < 1.0f-tmpValues[3] && tmpValues[3] >= 0 && tmpValues[3] < 1 && tmpValues[3] < 1.0f-tmpValues[2]) { _roiRatios = tmpValues; } else { ULOGGER_ERROR("The roi ratios are not valid (roi=\"%s\")", roi.c_str()); } } } void Memory::addSignatureToStm(Signature * signature) { UTimer timer; // add signature on top of the short-term memory if(signature) { UDEBUG("adding %d", signature->id()); // Update neighbors if(_stMem.size()) { if(_signatures.at(*_stMem.rbegin())->mapId() == signature->mapId()) { Transform motionEstimate; if(!signature->getPose().isNull() && !_signatures.at(*_stMem.rbegin())->getPose().isNull()) { motionEstimate = _signatures.at(*_stMem.rbegin())->getPose().inverse() * signature->getPose(); _signatures.at(*_stMem.rbegin())->addNeighbor(signature->id(), motionEstimate); } else { _signatures.at(*_stMem.rbegin())->addNeighbor(signature->id()); } signature->addNeighbor(*_stMem.rbegin(), motionEstimate.isNull()?Transform():motionEstimate.inverse()); UDEBUG("Min STM id = %d", *_stMem.begin()); } else { UDEBUG("Ignoring neighbor link between %d and %d because they are not in the same map! (%d vs %d)", *_stMem.rbegin(), signature->id(), _signatures.at(*_stMem.rbegin())->mapId(), signature->mapId()); } } _signatures.insert(_signatures.end(), std::pair(signature->id(), signature)); _stMem.insert(_stMem.end(), signature->id()); if(_vwd) { UDEBUG("%d words ref for the signature %d", signature->getWords().size(), signature->id()); } if(signature->getWords().size()) { signature->setEnabled(true); } } UDEBUG("time = %fs", timer.ticks()); } void Memory::addSignatureToWm(Signature * signature) { if(signature) { UDEBUG("Inserting node %d in WM...", signature->id()); _workingMem.insert(signature->id()); _signatures.insert(std::pair(signature->id(), signature)); ++_signaturesAdded; } else { UERROR("Signature is null ?!?"); } } const Signature * Memory::getSignature(int id) const { return _getSignature(id); } Signature * Memory::_getSignature(int id) const { return uValue(_signatures, id, (Signature*)0); } int Memory::getVWDictionarySize() const { return _vwd->getVisualWords().size(); } void Memory::getPose(int locationId, Transform & pose, bool lookInDatabase) const { const Signature * s = getSignature(locationId); int mapId = -1; if(s) { pose = s->getPose(); mapId = s->mapId(); if(pose.isNull()) { UERROR("Pose of %d is null?!?", locationId); } } else if(lookInDatabase && _dbDriver) { _dbDriver->getPose(locationId, pose, mapId); } } std::map Memory::getNeighborLinks(int signatureId, bool ignoreNeighborByLoopClosure, bool lookInDatabase) const { std::map links; Signature * sTop = uValue(_signatures, signatureId, (Signature*)0); if(sTop) { std::list loops; loops.push_back(sTop); while(loops.size()) { Signature * s = *loops.begin(); loops.pop_front(); if(s) { const std::map & neighbors = s->getNeighbors(); links.insert(neighbors.begin(), neighbors.end()); if(!ignoreNeighborByLoopClosure) { const std::map & loopIds = s->getLoopClosureIds(); for(std::map::const_iterator iter = loopIds.begin(); iter!=loopIds.end(); ++iter) { if(iter->first > 0 && _stMem.find(iter->first) == _stMem.end()) { loops.push_back(uValue(_signatures, iter->first, (Signature*)0)); } } } } } if(!ignoreNeighborByLoopClosure) { // Check for child loop closure ids const std::map & childTopIds = sTop->getChildLoopClosureIds(); for(std::map::const_iterator iter = childTopIds.begin(); iter!=childTopIds.end(); ++iter) { if(iter->first > 0 && _stMem.find(iter->first) == _stMem.end()) { loops.push_back(uValue(_signatures, iter->first, (Signature*)0)); } } while(loops.size()) { Signature * s = *loops.begin(); loops.pop_front(); if(s) { const std::map & neighbors = s->getNeighbors(); links.insert(neighbors.begin(), neighbors.end()); const std::map & childIds = s->getChildLoopClosureIds(); for(std::map::const_iterator iter = childIds.begin(); iter!=childIds.end(); ++iter) { if(iter->first > 0 && _stMem.find(iter->first) == _stMem.end()) { loops.push_back(uValue(_signatures, iter->first, (Signature*)0)); } } } } } } else if(lookInDatabase && _dbDriver) { std::map neighbors; _dbDriver->loadNeighbors(signatureId, neighbors); links.insert(neighbors.begin(), neighbors.end()); } else { UWARN("Cannot find signature %d in memory", signatureId); } return links; } // return map, including signatureId // maxCheckedInDatabase = -1 means no limit to check in database (default) // maxCheckedInDatabase = 0 means don't check in database std::map Memory::getNeighborsId(int signatureId, unsigned int margin, // 0 means infinite margin int maxCheckedInDatabase, // default -1 (no limit) bool incrementMarginOnLoop, // default false bool ignoreLoopIds, // default false double * dbAccessTime ) const { //UDEBUG("signatureId=%d, neighborsMargin=%d", signatureId, margin); if(dbAccessTime) { *dbAccessTime = 0; } std::map ids; if(signatureId<=0) { return ids; } int nbLoadedFromDb = 0; std::list curentMarginList; std::set currentMargin; std::set nextMargin; nextMargin.insert(signatureId); unsigned int m = 0; while((margin == 0 || m < margin) && nextMargin.size()) { curentMarginList = std::list(nextMargin.begin(), nextMargin.end()); nextMargin.clear(); // first pass: count number of node in current margin in database for(std::list::iterator jter = curentMarginList.begin(); jter!=curentMarginList.end();++jter) { if(!uContains(ids, *jter)) { const Signature * s = this->getSignature(*jter); if(!s) { ++nbLoadedFromDb; } } } for(std::list::iterator jter = curentMarginList.begin(); jter!=curentMarginList.end(); ++jter) { if(ids.insert(std::pair(*jter, m)).second) { //UDEBUG("Added %d with margin %d", *jter, m); // Look up in STM/WM if all ids are here, if not... load them from the database const Signature * s = this->getSignature(*jter); std::map tmpNeighborIds; std::map tmpLoopClosureIds; std::map tmpChildLoopClosureIds; const std::map * neighborIds = &tmpNeighborIds; const std::map * loopClosureIds = &tmpLoopClosureIds; const std::map * childLoopClosureIds = &tmpChildLoopClosureIds; if(s) { neighborIds = &s->getNeighbors(); if(!ignoreLoopIds) { loopClosureIds = &s->getLoopClosureIds(); childLoopClosureIds = &s->getChildLoopClosureIds(); } } else if(maxCheckedInDatabase == -1 || (maxCheckedInDatabase > 0 && _dbDriver && nbLoadedFromDb < maxCheckedInDatabase)) { UTimer timer; _dbDriver->loadNeighbors(*jter, tmpNeighborIds); if(!ignoreLoopIds) { _dbDriver->loadLoopClosures(*jter, tmpLoopClosureIds, tmpChildLoopClosureIds); } if(dbAccessTime) { *dbAccessTime += timer.getElapsedTime(); } } // Neighbor links for(std::map::const_iterator iter=neighborIds->begin(); iter!=neighborIds->end(); ++iter) { if( (ignoreLoopIds || (loopClosureIds->find(iter->first) == loopClosureIds->end() && childLoopClosureIds->find(iter->first) == childLoopClosureIds->end())) && !uContains(ids, iter->first) && nextMargin.find(iter->first) == nextMargin.end()) { nextMargin.insert(iter->first); } } // Parent links for(std::map::const_iterator iter=loopClosureIds->begin(); iter!=loopClosureIds->end(); ++iter) { if( iter->first && !uContains(ids, iter->first)/* && isInNeighborLimits(iter->first, limits)*/) { if(incrementMarginOnLoop) { nextMargin.insert(iter->first); //UDEBUG("next of %d + %d", *jter, iter->first); } else { if(currentMargin.insert(iter->first).second) { const Signature * s = this->getSignature(iter->first); if(!s) { // update db count because it's on current margin ++nbLoadedFromDb; } curentMarginList.push_back(iter->first); //UDEBUG("current of %d + %d", *jter, iter->first); } } } } //Child links for(std::map::const_iterator iter=childLoopClosureIds->begin(); iter!=childLoopClosureIds->end(); ++iter) { if( iter->first && !uContains(ids, iter->first)/* && isInNeighborLimits(iter->first, limits)*/) { if(incrementMarginOnLoop) { nextMargin.insert(iter->first); //UDEBUG("next of %d + %d", *jter, iter->first); } else { if(currentMargin.insert(iter->first).second) { const Signature * s = this->getSignature(iter->first); if(!s) { // update db count because it's on current margin ++nbLoadedFromDb; } curentMarginList.push_back(iter->first); //UDEBUG("current of %d + %d", *jter, iter->first); } } } } } } ++m; } return ids; } void Memory::getLoopClosureIds(int signatureId, std::map & loopClosureIds, std::map & childLoopClosureIds, bool lookInDatabase) const { const Signature * s = this->getSignature(signatureId); loopClosureIds.clear(); childLoopClosureIds.clear(); if(s) { loopClosureIds = s->getLoopClosureIds(); childLoopClosureIds = s->getChildLoopClosureIds(); } else if(lookInDatabase && _dbDriver) { _dbDriver->loadLoopClosures(signatureId, loopClosureIds, childLoopClosureIds); } } int Memory::getNextId() { return ++_idCount; } int Memory::incrementMapId() { //don't increment if there is no location in the current map const Signature * s = getLastWorkingSignature(); if(s && s->mapId() == _idMapCount) { return ++_idMapCount; } return _idMapCount; } int Memory::getDatabaseMemoryUsed() const { int memoryUsed = 0; if(_dbDriver) { memoryUsed = _dbDriver->getMemoryUsed()/(1024*1024); //Byte to MB } return memoryUsed; } double Memory::getDbSavingTime() const { return _dbDriver?_dbDriver->getEmptyTrashesTime():0; } std::set Memory::getAllSignatureIds() const { std::set ids; if(_dbDriver) { _dbDriver->getAllNodeIds(ids); } for(std::map::const_iterator iter = _signatures.begin(); iter!=_signatures.end(); ++iter) { ids.insert(iter->first); } return ids; } void Memory::clear() { UDEBUG(""); this->cleanUnusedWords(); if(_dbDriver) { _dbDriver->emptyTrashes(); _dbDriver->join(); } // Save some stats to the db, save only when the mem is not empty if(_dbDriver && (_stMem.size() || _workingMem.size())) { unsigned int memSize = _workingMem.size() + _stMem.size(); if(_workingMem.size() && *_workingMem.begin() < 0) { --memSize; } // this is only a safe check...not supposed to occur. UASSERT_MSG(memSize == _signatures.size(), uFormat("The number of signatures don't match! _workingMem=%d, _stMem=%d, _signatures=%d", _workingMem.size(), _stMem.size(), _signatures.size()).c_str()); UDEBUG("Adding statistics after run..."); _dbDriver->addStatisticsAfterRun(memSize, _lastSignature?_lastSignature->id():0, UProcessInfo::getMemoryUsage(), _dbDriver->getMemoryUsed(), _vwd->getVisualWords().size()); } UDEBUG(""); //Get the tree root (parents) std::map mem = _signatures; for(std::map::iterator i=mem.begin(); i!=mem.end(); ++i) { if(i->second) { UDEBUG("deleting from the working and the short-term memory: %d", i->first); this->moveToTrash(i->second); } } if(_workingMem.size() != 0 && !(_workingMem.size() == 1 && *_workingMem.begin() == kIdVirtual)) { ULOGGER_ERROR("_workingMem must be empty here, size=%d", _workingMem.size()); } _workingMem.clear(); if(_stMem.size() != 0) { ULOGGER_ERROR("_stMem must be empty here, size=%d", _stMem.size()); } _stMem.clear(); if(_signatures.size()!=0) { ULOGGER_ERROR("_signatures must be empty here, size=%d", _signatures.size()); } _signatures.clear(); UDEBUG(""); // Wait until the db trash has finished cleaning the memory if(_dbDriver) { _dbDriver->emptyTrashes(); } UDEBUG(""); _lastSignature = 0; _lastGlobalLoopClosureParentId = 0; _lastGlobalLoopClosureChildId = 0; _idCount = kIdStart; _idMapCount = kIdStart; _memoryChanged = false; if(_dbDriver) { _dbDriver->join(true); cleanUnusedWords(); _dbDriver->emptyTrashes(); } else { cleanUnusedWords(); } if(_vwd) { _vwd->clear(); } UDEBUG(""); } /** * Compute the likelihood of the signature with some others in the memory. * Important: Assuming that all other ids are under 'signature' id. * If an error occurs, the result is empty. */ std::map Memory::computeLikelihood(const Signature * signature, const std::list & ids) { if(!_tfIdfLikelihoodUsed) { UTimer timer; timer.start(); std::map likelihood; if(!signature) { ULOGGER_ERROR("The signature is null"); return likelihood; } else if(ids.empty()) { UWARN("ids list is empty"); return likelihood; } for(std::list::const_iterator iter = ids.begin(); iter!=ids.end(); ++iter) { float sim = 0.0f; if(*iter > 0) { const Signature * sB = this->getSignature(*iter); if(!sB) { UFATAL("Signature %d not found in WM ?!?", *iter); } sim = signature->compareTo(sB); } likelihood.insert(likelihood.end(), std::pair(*iter, sim)); } UDEBUG("compute likelihood... %f s", timer.ticks()); return likelihood; } else { // TODO cleanup , old way... UTimer timer; timer.start(); std::map likelihood; std::map calculatedWordsRatio; if(!signature) { ULOGGER_ERROR("The signature is null"); return likelihood; } else if(ids.empty()) { UWARN("ids list is empty"); return likelihood; } for(std::list::const_iterator iter = ids.begin(); iter!=ids.end(); ++iter) { likelihood.insert(likelihood.end(), std::pair(*iter, 0.0f)); } const std::list & wordIds = uUniqueKeys(signature->getWords()); float nwi; // nwi is the number of a specific word referenced by a place float ni; // ni is the total of words referenced by a place float nw; // nw is the number of places referenced by a specific word float N; // N is the total number of places float logNnw; const VisualWord * vw; N = this->getSignatures().size(); if(N) { UDEBUG("processing... "); // Pour chaque mot dans la signature SURF for(std::list::const_iterator i=wordIds.begin(); i!=wordIds.end(); ++i) { // "Inverted index" - Pour chaque endroit contenu dans chaque mot vw = _vwd->getWord(*i); if(vw) { const std::map & refs = vw->getReferences(); nw = refs.size(); if(nw) { logNnw = log10(N/nw); if(logNnw) { for(std::map::const_iterator j=refs.begin(); j!=refs.end(); ++j) { std::map::iterator iter = likelihood.find(j->first); if(iter != likelihood.end()) { nwi = j->second; ni = this->getNi(j->first); if(ni != 0) { //UDEBUG("%d, %f %f %f %f", vw->id(), logNnw, nwi, ni, ( nwi * logNnw ) / ni); iter->second += ( nwi * logNnw ) / ni; } } } } } } } } UDEBUG("compute likelihood %f s", timer.ticks()); return likelihood; } } // Weights of the signatures in the working memory std::map Memory::getWeights() const { std::map weights; for(std::set::const_iterator iter=_workingMem.begin(); iter!=_workingMem.end(); ++iter) { if(*iter > 0) { const Signature * s = this->getSignature(*iter); if(!s) { UFATAL("Location %d must exist in memory", *iter); } weights.insert(weights.end(), std::make_pair(*iter, s->getWeight())); } else { weights.insert(weights.end(), std::make_pair(*iter, -1)); } } return weights; } std::list Memory::forget(const std::set & ignoredIds) { UDEBUG(""); std::list signaturesRemoved; if(_vwd->isIncremental()) { int newWords = 0; int wordsRemoved = 0; // Get how many new words added for the last run... newWords = _vwd->getNotIndexedWordsCount(); // So we need to remove at least "newWords" words from the // dictionary to respect the limit. while(wordsRemoved < newWords) { std::list signatures = this->getRemovableSignatures(1, ignoredIds); if(signatures.size()) { Signature * s = dynamic_cast(signatures.front()); if(s) { signaturesRemoved.push_back(s->id()); this->moveToTrash(s); wordsRemoved = _vwd->getUnusedWordsSize(); } else { break; } } else { break; } } UDEBUG("newWords=%d, wordsRemoved=%d", newWords, wordsRemoved); } else { UDEBUG(""); // Remove one more than total added during the iteration std::list signatures = getRemovableSignatures(_signaturesAdded+1, ignoredIds); for(std::list::iterator iter=signatures.begin(); iter!=signatures.end(); ++iter) { signaturesRemoved.push_back((*iter)->id()); // When a signature is deleted, it notifies the memory // and it is removed from the memory list this->moveToTrash(*iter); } UDEBUG("signaturesRemoved=%d, _signaturesAdded=%d", (int)signatures.size(), _signaturesAdded); } return signaturesRemoved; } std::list Memory::cleanup(const std::list & ignoredIds) { UDEBUG(""); std::list signaturesRemoved; // bad signature if((_lastSignature->isBadSignature() && _badSignaturesIgnored) || !_incrementalMemory) { if(_lastSignature->isBadSignature()) { UDEBUG("Bad signature! %d", _lastSignature->id()); } signaturesRemoved.push_back(_lastSignature->id()); moveToTrash(_lastSignature, _incrementalMemory); } return signaturesRemoved; } void Memory::emptyTrash() { if(_dbDriver) { _dbDriver->emptyTrashes(true); } } void Memory::joinTrashThread() { if(_dbDriver) { UDEBUG(""); _dbDriver->join(); UDEBUG(""); } } class WeightIdKey { public: WeightIdKey(int w, int i) : weight(w), id(i) {} bool operator<(const WeightIdKey & k) const { if(weight < k.weight) { return true; } else if(weight == k.weight) { if(id < k.id) { return true; } } return false; } int weight, id; }; std::list Memory::getRemovableSignatures(int count, const std::set & ignoredIds) { //UDEBUG(""); std::list removableSignatures; std::map signatureMap; // Find the last index to check... const std::set & wm = _workingMem; UDEBUG("mem.size()=%d, ignoredIds.size()=%d", wm.size(), ignoredIds.size()); if(wm.size()) { int recentWmMaxSize = _recentWmRatio * float(wm.size()); bool recentWmImmunized = false; // look for the position of the lastLoopClosureId in WM int currentRecentWmSize = 0; if(_lastGlobalLoopClosureParentId > 0 && _stMem.find(_lastGlobalLoopClosureParentId) == _stMem.end()) { // If set, it must be in WM std::set::const_iterator iter = _workingMem.find(_lastGlobalLoopClosureParentId); while(iter != _workingMem.end()) { ++currentRecentWmSize; ++iter; } if(currentRecentWmSize>1 && currentRecentWmSize < recentWmMaxSize) { recentWmImmunized = true; } else if(currentRecentWmSize == 0 && _workingMem.size() > 1) { UERROR("Last loop closure id not found in WM (%d)", _lastGlobalLoopClosureParentId); } UDEBUG("currentRecentWmSize=%d, recentWmMaxSize=%d, _recentWmRatio=%f, end recent wM = %d", currentRecentWmSize, recentWmMaxSize, _recentWmRatio, _lastGlobalLoopClosureParentId); } // Ignore neighbor of the last location in STM (for neighbor links redirection issue during Rehearsal). Signature * lastInSTM = 0; if(_stMem.size()) { lastInSTM = _signatures.at(*_stMem.begin()); } for(std::set::const_iterator memIter = wm.begin(); memIter != wm.end(); ++memIter) { if( (recentWmImmunized && *memIter > _lastGlobalLoopClosureParentId) || *memIter == _lastGlobalLoopClosureParentId) { // ignore recent memory } else if(*memIter > 0 && ignoredIds.find(*memIter) == ignoredIds.end() && (!lastInSTM || !lastInSTM->hasNeighbor(*memIter))) { Signature * s = this->_getSignature(*memIter); if(s) { // Its loop closures must not be in STM to be removable, rehearsal issue bool foundInSTM = false; for(std::map::const_iterator iter = s->getLoopClosureIds().begin(); iter!=s->getLoopClosureIds().end(); ++iter) { if(_stMem.find(iter->first) != _stMem.end()) { UDEBUG("Ignored %d because it has a parent (%d) in STM", s->id(), iter->first); foundInSTM = true; break; } } // Its neighbors must not be in STM to be removable, rehearsal issue if(!foundInSTM) { for(std::set::iterator iter = _stMem.begin(); iter!=_stMem.end(); ++iter) { if(s->hasNeighbor(*iter)) { UDEBUG("Ignored %d because it has a neighbor (%d) in STM", s->id(), *iter); foundInSTM = true; break; } } } if(!foundInSTM) { // less weighted signature priority to be transferred signatureMap.insert(std::make_pair(WeightIdKey(s->getWeight(), s->id()), s)); } } else { ULOGGER_ERROR("Not supposed to occur!!!"); } } else { //UDEBUG("Ignoring id %d", memIter->first); } } int recentWmCount = 0; std::set addedSignatures; // make the list of removable signatures // Criteria : Weight -> ID UDEBUG("signatureMap.size()=%d", (int)signatureMap.size()); for(std::map::iterator iter=signatureMap.begin(); iter!=signatureMap.end(); ++iter) { bool removable = true; if(removable) { if(!recentWmImmunized) { UDEBUG("weight=%d, id=%d, lcCount=%d, lcId=%d, childId=%d", iter->first.weight, iter->second->id(), int(iter->second->getLoopClosureIds().size()), iter->second->getLoopClosureIds().size()?iter->second->getLoopClosureIds().rbegin()->first:0, iter->second->getChildLoopClosureIds().size()?iter->second->getChildLoopClosureIds().rbegin()->first:0); removableSignatures.push_back(iter->second); addedSignatures.insert(iter->second->id()); if(iter->second->id() > _lastGlobalLoopClosureParentId) { ++recentWmCount; if(currentRecentWmSize - recentWmCount < recentWmMaxSize) { UDEBUG("switched recentWmImmunized"); recentWmImmunized = true; } } } else if(iter->second->id() < _lastGlobalLoopClosureParentId) { UDEBUG("weight=%d, id=%d, lcCount=%d, lcId=%d, childId=%d", iter->first.weight, iter->second->id(), int(iter->second->getLoopClosureIds().size()), iter->second->getLoopClosureIds().size()?iter->second->getLoopClosureIds().rbegin()->first:0, iter->second->getChildLoopClosureIds().size()?iter->second->getChildLoopClosureIds().rbegin()->first:0); removableSignatures.push_back(iter->second); addedSignatures.insert(iter->second->id()); } if(removableSignatures.size() >= (unsigned int)count) { break; } } } } else { ULOGGER_WARN("not enough signatures to get an old one..."); } return removableSignatures; } void Memory::moveToTrash(Signature * s, bool saveToDatabase) { UDEBUG("id=%d", s?s->id():0); if(s) { // If not saved to database or it is a bad signature (not saved), remove links! if(!saveToDatabase || (!s->isSaved() && s->isBadSignature() && _badSignaturesIgnored)) { UASSERT_MSG(this->isInSTM(s->id()), uFormat("Deleting location (%d) outside the STM is not implemented!", s->id()).c_str()); const std::map & neighbors = s->getNeighbors(); for(std::map::const_iterator iter=neighbors.begin(); iter!=neighbors.end(); ++iter) { Signature * n = this->_getSignature(iter->first); // neighbor to s if(n) { if(iter->first > s->id() && (n->getNeighbors().size() > 1 || !n->hasNeighbor(s->id()))) { UWARN("Neighbor %d of %d is newer, removing neighbor link may split the map!", iter->first, s->id()); } n->removeNeighbor(s->id()); if(s == _lastSignature) { _lastSignature = n; } } else { UERROR("neighbor %d of %d not in WM/STM?!?", iter->first, s->id()); } } s->removeNeighbors(); std::map children = s->getChildLoopClosureIds(); for(std::map::const_iterator iter=children.begin(); iter!=children.end(); ++iter) { Signature * child = _getSignature(iter->first); if(child) { child->removeLoopClosureId(s->id()); child->setWeight(child->getWeight() + s->getWeight()); // copy weight } else { UERROR("loop child %d of %d not in WM/STM?!?", iter->first, s->id()); } s->removeChildLoopClosureId(iter->first); } std::map parents = s->getLoopClosureIds(); for(std::map::const_iterator iter=parents.begin(); iter!=parents.end(); ++iter) { Signature * p = _getSignature(iter->first); if(p) { p->removeChildLoopClosureId(s->id()); } else { UERROR("loop parent %d of %d not in WM/STM?!?", iter->first, s->id()); } s->removeLoopClosureId(iter->first); } s->setWeight(0); } this->disableWordsRef(s->id(), saveToDatabase); _workingMem.erase(s->id()); _stMem.erase(s->id()); _signatures.erase(s->id()); if(_lastSignature == s) { _lastSignature = 0; if(_stMem.size()) { _lastSignature = this->_getSignature(*_stMem.rbegin()); } } if( saveToDatabase && _dbDriver && s->id()>0) { _dbDriver->asyncSave(s); } else { delete s; } } } int Memory::getLastSignatureId() const { return _idCount; } const Signature * Memory::getLastWorkingSignature() const { UDEBUG(""); return _lastSignature; } void Memory::deleteLocation(int locationId) { UINFO("Deleting location %d", locationId); Signature * location = _getSignature(locationId); if(location) { this->moveToTrash(location, false); } } void Memory::rejectLoopClosure(int oldId, int newId) { Signature * oldS = this->_getSignature(oldId); Signature * newS = this->_getSignature(newId); if(oldS && newS) { UDEBUG("removing loop closure from location %d", newS->id()); const std::map & children = newS->getChildLoopClosureIds(); for(std::map::const_iterator iter=children.begin(); iter!=children.end(); ++iter) { if(iter->first == oldId) { oldS->removeLoopClosureId(newS->id()); oldS->setWeight(oldS->getWeight()+1); newS->removeChildLoopClosureId(iter->first); newS->setWeight(newS->getWeight()>0?newS->getWeight()-1:0); break; } } if(newS->getChildLoopClosureIds().size() == 0 && newId == _lastGlobalLoopClosureParentId) { _lastGlobalLoopClosureParentId = 0; _lastGlobalLoopClosureChildId = 0; } } else { if(!newS) { UERROR("Signature %d is not in working memory... cannot remove loop closure links.", newS->id()); } if(!oldS) { UERROR("Signature %d is not in working memory... cannot remove loop closure links.", oldS->id()); } } } // compute transform newId -> oldId Transform Memory::computeVisualTransform(int oldId, int newId) const { const Signature * oldS = this->getSignature(oldId); const Signature * newS = this->getSignature(newId); Transform transform; if(oldS && newId) { return computeVisualTransform(*oldS, *newS); } return Transform(); } // compute transform newId -> oldId Transform Memory::computeVisualTransform(const Signature & oldS, const Signature & newS) const { Transform transform; // Guess transform from visual words if(!oldS.getWords3().empty() && !newS.getWords3().empty()) { pcl::PointCloud::Ptr inliersOld(new pcl::PointCloud); pcl::PointCloud::Ptr inliersNew(new pcl::PointCloud); util3d::findCorrespondences( oldS.getWords3(), newS.getWords3(), *inliersOld, *inliersNew, _bowMaxDepth); if((int)inliersOld->size() >= _bowMinInliers) { UDEBUG("Correspondences = %d", (int)inliersOld->size()); int inliersCount = 0; Transform t = util3d::transformFromXYZCorrespondences( inliersOld, inliersNew, _bowInlierDistance, _bowIterations, &inliersCount); if(!t.isNull() && inliersCount >= _bowMinInliers) { transform = t; } else if(inliersCount < _bowMinInliers) { UINFO("Not enough inliers %d/%d between %d and %d", inliersCount, _bowMinInliers, oldS.id(), newS.id()); } } else { UDEBUG("Not enough inliers %d/%d between %d and %d", (int)inliersOld->size(), _bowMinInliers, oldS.id(), newS.id()); } } else if(!oldS.isBadSignature() && !newS.isBadSignature()) { UERROR("Words 3D empty?!?"); } return transform; } // compute transform newId -> oldId Transform Memory::computeIcpTransform(int oldId, int newId, Transform guess, bool icp3D) { Signature * oldS = this->_getSignature(oldId); Signature * newS = this->_getSignature(newId); if(oldS && newS && _dbDriver) { std::list depthToLoad; std::set added; if(icp3D) { //Depth required, if not in RAM, load it from LTM if(oldS->getDepth().empty()) { depthToLoad.push_back(oldS); added.insert(oldS->id()); } if(newS->getDepth().empty()) { depthToLoad.push_back(newS); added.insert(newS->id()); } } else { //Depth required, if not in RAM, load it from LTM if(oldS->getDepth2D().size() == 0 && added.find(oldS->id()) == added.end()) { depthToLoad.push_back(oldS); } if(newS->getDepth2D().size() == 0 && added.find(newS->id()) == added.end()) { depthToLoad.push_back(newS); } } if(depthToLoad.size()) { _dbDriver->loadNodeData(depthToLoad, true); } } Transform t; if(oldS && newS) { t = computeIcpTransform(*oldS, *newS, guess, icp3D); } return t; } // get transform from the new to old node Transform Memory::computeIcpTransform(const Signature & oldS, const Signature & newS, Transform guess, bool icp3D) const { if(guess.isNull()) { //Make a guess using odometry guess = oldS.getPose().inverse() * newS.getPose(); UASSERT_MSG(oldS.mapId() == newS.mapId(), "Compute ICP from two different maps is not implemented!"); } else { guess = guess.inverse(); // from pose to cloud data } UDEBUG("Guess transform = %s", guess.prettyPrint().c_str()); Transform transform; // ICP with guess transform if(icp3D) { UDEBUG("3D ICP"); util3d::CompressionThread ctOld(oldS.getDepth(), true); util3d::CompressionThread ctNew(newS.getDepth(), true); ctOld.start(); ctNew.start(); ctOld.join(); ctNew.join(); cv::Mat oldDepth = ctOld.getUncompressedData(); cv::Mat newDepth = ctNew.getUncompressedData(); if(!oldDepth.empty() && !newDepth.empty()) { pcl::PointCloud::Ptr oldCloudXYZ = util3d::getICPReadyCloud( oldDepth, oldS.getDepthConstant(), _icpDecimation, _icpMaxDepth, _icpVoxelSize, _icpSamples, oldS.getLocalTransform()); pcl::PointCloud::Ptr newCloudXYZ = util3d::getICPReadyCloud( newDepth, newS.getDepthConstant(), _icpDecimation, _icpMaxDepth, _icpVoxelSize, _icpSamples, guess * newS.getLocalTransform()); pcl::PointCloud::Ptr oldCloud = util3d::computeNormals(oldCloudXYZ); pcl::PointCloud::Ptr newCloud = util3d::computeNormals(newCloudXYZ); std::vector indices; newCloud = util3d::removeNaNNormalsFromPointCloud(newCloud); oldCloud = util3d::removeNaNNormalsFromPointCloud(oldCloud); // 3D double fitness = 0; bool hasConverged = false; Transform icpT; if(newCloud->size() && oldCloud->size()) { icpT = util3d::icpPointToPlane(newCloud, oldCloud, _icpMaxCorrespondenceDistance, _icpMaxIterations, hasConverged, fitness); } else { UWARN("Clouds empty ?!?"); } //pcl::io::savePCDFile("old.pcd", *oldCloud); //pcl::io::savePCDFile("newguess.pcd", *newCloud); //newCloud = util3d::transformPointCloud(newCloud, icpT); //pcl::io::savePCDFile("newicp.pcd", *newCloud); UDEBUG("fitness=%f", fitness); if(hasConverged && (_icpMaxFitness == 0 || fitness < _icpMaxFitness)) { transform = icpT * guess; transform = transform.inverse(); } else { UWARN("Cannot compute transform (hasConverged=%s fitness=%f/%f)", hasConverged?"true":"false", fitness, _icpMaxFitness); } } else { UERROR("Depths 3D empty?!?"); } } else // icp 2D { UDEBUG("2D ICP"); // We are 2D here, make sure the guess has only YAW rotation float x,y,z,r,p,yaw; guess.getTranslationAndEulerAngles(x,y,z, r,p,yaw); guess = util3d::transformFromEigen3f(pcl::getTransformation(x,y,0, 0, 0, yaw)); if(r!=0 || p!=0) { UINFO("2D ICP: Dropping z (%f), roll (%f) and pitch (%f) rotation!", z, r, p); } util3d::CompressionThread ctOld(oldS.getDepth2D(), false); util3d::CompressionThread ctNew(newS.getDepth2D(), false); ctOld.start(); ctNew.start(); ctOld.join(); ctNew.join(); cv::Mat oldDepth2D = ctOld.getUncompressedData(); cv::Mat newDepth2D = ctNew.getUncompressedData(); if(!oldDepth2D.empty() && !newDepth2D.empty()) { // 2D pcl::PointCloud::Ptr oldCloud = util3d::cvMat2Cloud(oldDepth2D); pcl::PointCloud::Ptr newCloud = util3d::cvMat2Cloud(newDepth2D, guess); //voxelize if(_icp2VoxelSize > 0.0f) { oldCloud = util3d::voxelize(oldCloud, _icp2VoxelSize); newCloud = util3d::voxelize(newCloud, _icp2VoxelSize); } double fitness = 0.0f; bool hasConverged = false; Transform icpT; float correspondencesRatio = -1.0f; if(newCloud->size() && oldCloud->size()) { icpT = util3d::icp2D(newCloud, oldCloud, _icp2MaxCorrespondenceDistance, _icp2MaxIterations, hasConverged, fitness); //pcl::io::savePCDFile("lccold.pcd", *oldCloud); //pcl::io::savePCDFile("lccnewguess.pcd", *newCloud); newCloud = util3d::transformPointCloud(newCloud, icpT); //pcl::io::savePCDFile("lccnewicp.pcd", *newCloud); // verify if there are enough correspondences int correspondences = util3d::getCorrespondencesCount(newCloud, oldCloud, _icp2MaxCorrespondenceDistance); correspondencesRatio = float(correspondences)/float(oldCloud->size()>newCloud->size()?oldCloud->size():newCloud->size()); UDEBUG("hasConverged=%s, fitness=%f, correspondences=%d/%d (%f%%)", hasConverged?"true":"false", fitness, correspondences, (int)oldCloud->size(), correspondencesRatio*100.0f); } else { UWARN("Clouds empty ?!?"); } if(hasConverged && (_icp2MaxFitness == 0 || fitness < _icp2MaxFitness) && correspondencesRatio >= _icp2CorrespondenceRatio) { transform = icpT * guess; transform = transform.inverse(); } else { UWARN("Cannot compute transform (hasConverged=%s fitness=%f/%f correspondencesRatio=%f/%f)", hasConverged?"true":"false", fitness, _icpMaxFitness, correspondencesRatio, _icp2CorrespondenceRatio); } } else { UERROR("Depths 2D empty?!?"); } } return transform; } // poses of newId and oldId must be in "poses" Transform Memory::computeScanMatchingTransform( int newId, int oldId, const std::map & poses) { // make sure that all depth2D are loaded std::list depthToLoad; for(std::map::const_iterator iter = poses.begin(); iter!=poses.end(); ++iter) { Signature * s = _getSignature(iter->first); UASSERT(s != 0); if(s->getDepth2D().size() == 0) { depthToLoad.push_back(s); } } if(depthToLoad.size() && _dbDriver) { _dbDriver->loadNodeData(depthToLoad, true); } pcl::PointCloud::Ptr assembledOldClouds(new pcl::PointCloud); for(std::map::const_iterator iter = poses.begin(); iter!=poses.end(); ++iter) { if(iter->first != newId) { const Signature * s = this->getSignature(iter->first); if(s->getDepth2D().size()) { *assembledOldClouds += *util3d::cvMat2Cloud(util3d::uncompressData(s->getDepth2D()), iter->second); } else { UWARN("Depth2D not found for signature %d", iter->first); } } } //voxelize if(assembledOldClouds->size() && _icp2VoxelSize > 0.0f) { assembledOldClouds = util3d::voxelize(assembledOldClouds, _icp2VoxelSize); } // get the new cloud const Signature * newS = getSignature(newId); pcl::PointCloud::Ptr newCloud; UASSERT(uContains(poses, newId)); newCloud = util3d::cvMat2Cloud(util3d::uncompressData(newS->getDepth2D()), poses.at(newId)); //voxelize if(newCloud->size() && _icp2VoxelSize > 0.0f) { newCloud = util3d::voxelize(newCloud, _icp2VoxelSize); } //pcl::io::savePCDFile("old.pcd", *assembledOldClouds); //pcl::io::savePCDFile("new.pcd", *newCloud); Transform transform; if(assembledOldClouds->size() && newCloud->size()) { double fitness = 0.0f; bool hasConverged = false; Transform icpT = util3d::icp2D(newCloud, assembledOldClouds, _icp2MaxCorrespondenceDistance, _icp2MaxIterations, hasConverged, fitness); UDEBUG("icpT=%s", icpT.prettyPrint().c_str()); newCloud = util3d::transformPointCloud(newCloud, icpT); //pcl::io::savePCDFile("newCorrected.pcd", *newCloud); // verify if there enough correspondences int correspondences = util3d::getCorrespondencesCount(newCloud, assembledOldClouds, _icp2MaxCorrespondenceDistance); float correspondencesRatio = float(correspondences)/float(newCloud->size()); UDEBUG("fitness=%f, correspondences=%d/%d (%f%%)", fitness, correspondences, (int)newCloud->size(), correspondencesRatio); if(hasConverged && (_icp2MaxFitness == 0 || fitness < _icp2MaxFitness) && correspondencesRatio >= _icp2CorrespondenceRatio) { transform = poses.at(newId).inverse()*icpT.inverse() * poses.at(oldId); //newCloud = util3d::cvMat2Cloud(util3d::uncompressData(newS->getDepth2D()), poses.at(oldId)*transform.inverse()); //pcl::io::savePCDFile("newFinal.pcd", *newCloud); } else { UWARN("Constraints failed... hasConverged=%s, fitness=%f, correspondences=%d/%d (%f%%)", hasConverged?"true":"false", fitness, correspondences, (int)newCloud->size(), correspondencesRatio); } } return transform; } // Transform from new to old bool Memory::addLoopClosureLink(int oldId, int newId, const Transform & transform, bool global) { ULOGGER_INFO("old=%d, new=%d transform: %s", oldId, newId, transform.prettyPrint().c_str()); Signature * oldS = _getSignature(oldId); Signature * newS = _getSignature(newId); if(oldS && newS) { const std::map & oldLoopclosureIds = oldS->getLoopClosureIds(); if(oldLoopclosureIds.size() && oldLoopclosureIds.find(newS->id()) != oldLoopclosureIds.end()) { // do nothing, already merged UDEBUG("already merged, old=%d, new=%d", oldId, newId); return true; } UDEBUG("Add loop closure link between %d and %d", oldS->id(), newS->id()); oldS->addLoopClosureId(newS->id(), transform.inverse()); newS->addChildLoopClosureId(oldS->id(), transform); if(_incrementalMemory && global) { _lastGlobalLoopClosureParentId = newS->id(); _lastGlobalLoopClosureChildId = oldS->id(); // udpate weights only if the memory is incremental newS->setWeight(newS->getWeight() + oldS->getWeight()); oldS->setWeight(0); } return true; } else { if(!newS) { UERROR("newId=%d, oldId=%d, Signature %d not found in working/st memories", newId, oldId, newId); } if(!oldS) { UERROR("newId=%d, oldId=%d, Signature %d not found in working/st memories", newId, oldId, oldId); } } return false; } void Memory::updateNeighborLink(int fromId, int toId, const Transform & transform) { Signature * fromS = this->_getSignature(fromId); Signature * toS = this->_getSignature(toId); if(fromS->hasNeighbor(toId) && toS->hasNeighbor(fromId)) { fromS->removeNeighbor(toId); toS->removeNeighbor(fromId); fromS->addNeighbor(toId, transform); toS->addNeighbor(fromId, transform.inverse()); } else { UERROR("fromId=%d and toId=%d are not neighbors!", fromId, toId); } } void Memory::dumpMemory(std::string directory) const { UINFO("Dumping memory to directory \"%s\"", directory.c_str()); this->dumpDictionary((directory+"DumpMemoryWordRef.txt").c_str(), (directory+"DumpMemoryWordDesc.txt").c_str()); this->dumpSignatures((directory + "DumpMemorySign.txt").c_str(), false); this->dumpSignatures((directory + "DumpMemorySign3.txt").c_str(), true); this->dumpMemoryTree((directory + "DumpMemoryTree.txt").c_str()); } void Memory::dumpDictionary(const char * fileNameRef, const char * fileNameDesc) const { if(_vwd) { _vwd->exportDictionary(fileNameRef, fileNameDesc); } } void Memory::dumpSignatures(const char * fileNameSign, bool words3D) const { FILE* foutSign = 0; #ifdef _MSC_VER fopen_s(&foutSign, fileNameSign, "w"); #else foutSign = fopen(fileNameSign, "w"); #endif if(foutSign) { if(words3D) { fprintf(foutSign, "SignatureID WordsID... (Max features depth=%f)\n", _bowMaxDepth); } else { fprintf(foutSign, "SignatureID WordsID...\n"); } const std::map & signatures = this->getSignatures(); for(std::map::const_iterator iter=signatures.begin(); iter!=signatures.end(); ++iter) { fprintf(foutSign, "%d ", iter->first); const Signature * ss = dynamic_cast(iter->second); if(ss) { if(words3D) { const std::multimap & ref = ss->getWords3(); for(std::multimap::const_iterator jter=ref.begin(); jter!=ref.end(); ++jter) { //show only valid point according to current parameters if(pcl::isFinite(jter->second) && (jter->second.x != 0 || jter->second.y != 0 || jter->second.z != 0) && (_bowMaxDepth <= 0 || jter->second.x <= _bowMaxDepth)) { fprintf(foutSign, "%d ", (*jter).first); } } } else { const std::multimap & ref = ss->getWords(); for(std::multimap::const_iterator jter=ref.begin(); jter!=ref.end(); ++jter) { fprintf(foutSign, "%d ", (*jter).first); } } } fprintf(foutSign, "\n"); } fclose(foutSign); } } void Memory::dumpMemoryTree(const char * fileNameTree) const { FILE* foutTree = 0; #ifdef _MSC_VER fopen_s(&foutTree, fileNameTree, "w"); #else foutTree = fopen(fileNameTree, "w"); #endif if(foutTree) { fprintf(foutTree, "SignatureID Weight NbLoopClosureIds LoopClosureIds... NbChildLoopClosureIds ChildLoopClosureIds...\n"); for(std::map::const_iterator i=_signatures.begin(); i!=_signatures.end(); ++i) { fprintf(foutTree, "%d %d", i->first, i->second->getWeight()); const std::map & loopIds = i->second->getLoopClosureIds(); fprintf(foutTree, " %d", (int)loopIds.size()); for(std::map::const_iterator j=loopIds.begin(); j!=loopIds.end(); ++j) { fprintf(foutTree, " %d", j->first); } const std::map & childIds = i->second->getChildLoopClosureIds(); fprintf(foutTree, " %d", (int)childIds.size()); for(std::map::const_iterator j=childIds.begin(); j!=childIds.end(); ++j) { fprintf(foutTree, " %d", j->first); } fprintf(foutTree, "\n"); } fclose(foutTree); } } void Memory::rehearsal(Signature * signature, Statistics * stats) { UTimer timer; if(signature->getNeighbors().size() != 1) { return; } //============================================================ // Compare with the last //============================================================ int id = signature->getNeighbors().begin()->first; UDEBUG("Comparing with last signature (%d)...", id); const Signature * sB = this->getSignature(id); if(!sB) { UFATAL("Signature %d null?!?", id); } float sim = signature->compareTo(sB); int merged = 0; if(sim >= _similarityThreshold) { if(_incrementalMemory) { if(this->rehearsalMerge(id, signature->id())) { merged = id; } } else { signature->setWeight(signature->getWeight() + 1 + sB->getWeight()); } } if(stats) stats->addStatistic(Statistics::kMemoryRehearsal_merged(), merged); if(stats) stats->addStatistic(Statistics::kMemoryRehearsal_sim(), sim); UDEBUG("merged=%d, sim=%f t=%fs", merged, sim, timer.ticks()); } bool Memory::rehearsalMerge(int oldId, int newId) { ULOGGER_INFO("old=%d, new=%d", oldId, newId); Signature * oldS = _getSignature(oldId); Signature * newS = _getSignature(newId); if(oldS && newS && _incrementalMemory) { const std::map & oldLoopclosureIds = oldS->getLoopClosureIds(); if(oldLoopclosureIds.size() && oldLoopclosureIds.find(newS->id()) != oldLoopclosureIds.end()) { // do nothing, already merged UWARN("already merged, old=%d, new=%d", oldId, newId); return false; } UASSERT(!newS->isSaved()); UDEBUG("Rehearsal merge %d and %d", oldS->id(), newS->id()); // During rehearsal in STM if(_idUpdatedToNewOneRehearsal) { // update weight newS->setWeight(newS->getWeight() + 1 + oldS->getWeight()); oldS->addLoopClosureId(newS->id()); // to keep track of the merged location if(_lastGlobalLoopClosureParentId == oldS->id()) { _lastGlobalLoopClosureParentId = newS->id(); } } else { // update weight oldS->setWeight(newS->getWeight() + 1 + oldS->getWeight()); newS->addLoopClosureId(oldS->id()); // to keep track of the merged location if(_lastSignature == newS) { _lastSignature = oldS; } } if(_idUpdatedToNewOneRehearsal) { // redirect neighbor links const std::map & neighbors = oldS->getNeighbors(); for(std::map::const_iterator iter = neighbors.begin(); iter!=neighbors.end(); ++iter) { int link = iter->first; Transform t = iter->second; if(link != newS->id() && link != oldS->id()) { Signature * s = this->_getSignature(link); if(s) { // modify neighbor "from" s->changeNeighborIds(oldS->id(), newS->id()); if(!newS->hasNeighbor(link)) { newS->addNeighbor(link, t); } } else { UERROR("Didn't find neighbor %d of %d in RAM...", link, oldS->id()); } } } oldS->removeNeighbors(); // redirect child loop closure links std::map childIds = oldS->getChildLoopClosureIds(); for(std::map::const_iterator iter = childIds.begin(); iter!=childIds.end(); ++iter) { if(iter->first == newS->id()) { UERROR(""); } newS->addChildLoopClosureId(iter->first, iter->second); Signature * s = _getSignature(iter->first); if(s) { UDEBUG("changed child Loop closure %d from %d to %d", iter->first, oldS->id(), newS->id()); s->changeLoopClosureId(oldS->id(), newS->id()); } else { UERROR("A location (%d, child of %d) in WM/STM cannot be transferred if its loop closure id is in STM", iter->first, oldS->id()); } oldS->removeChildLoopClosureId(iter->first); } // Set old image to new signature this->copyData(oldS, newS); } //remove mutual links oldS->removeNeighbor(newId); newS->removeNeighbor(oldId); // remove location bool saveToDb = _keepRehearsedNodesInDb; moveToTrash(_idUpdatedToNewOneRehearsal?oldS:newS, saveToDb); return true; } else { if(!newS) { UERROR("newId=%d, oldId=%d, Signature %d not found in working/st memories", newId, oldId, newId); } if(!oldS) { UERROR("newId=%d, oldId=%d, Signature %d not found in working/st memories", newId, oldId, oldId); } } return false; } std::vector Memory::getImage(int signatureId) const { std::vector image; const Signature * s = this->getSignature(signatureId); if(s) { image = s->getImage(); } if(image.empty() && this->isRawDataKept() && _dbDriver) { _dbDriver->getNodeData(signatureId, image); } return image; } void Memory::getImageDepth( int locationId, std::vector & rgb, std::vector & depth, std::vector & depth2d, float & depthConstant, Transform & localTransform) const { const Signature * s = this->getSignature(locationId); if(s) { rgb = s->getImage(); depth = s->getDepth(); depth2d = s->getDepth2D(); depthConstant = s->getDepthConstant(); localTransform = s->getLocalTransform(); } if(rgb.empty() && this->isRawDataKept() && _dbDriver) { _dbDriver->getNodeData(locationId, rgb, depth, depth2d, depthConstant, localTransform); } } void Memory::generateGraph(const std::string & fileName, std::set ids) { if(!_dbDriver) { UERROR("A database must must loaded first..."); return; } if(!fileName.empty()) { FILE* fout = 0; #ifdef _MSC_VER fopen_s(&fout, fileName.c_str(), "w"); #else fout = fopen(fileName.c_str(), "w"); #endif if (!fout) { UERROR("Cannot open file %s!", fileName.c_str()); return; } if(ids.size() == 0) { _dbDriver->getAllNodeIds(ids); UDEBUG("ids.size()=%d", ids.size()); for(std::map::iterator iter=_signatures.begin(); iter!=_signatures.end(); ++iter) { ids.insert(iter->first); } } const char * colorG = "green"; const char * colorP = "pink"; ; UINFO("Generating map with %d locations", ids.size()); fprintf(fout, "digraph G {\n"); for(std::set::iterator i=ids.begin(); i!=ids.end(); ++i) { if(_signatures.find(*i) == _signatures.end()) { int id = *i; std::map loopIds; std::map childIds; _dbDriver->loadLoopClosures(id, loopIds, childIds); std::map neighbors; _dbDriver->loadNeighbors(id, neighbors); int weight = 0; _dbDriver->getWeight(id, weight); for(std::map::iterator iter = neighbors.begin(); iter!=neighbors.end(); ++iter) { if(id!=iter->first) { int weightNeighbor = 0; if(_signatures.find(iter->first) == _signatures.end()) { _dbDriver->getWeight(iter->first, weightNeighbor); } else { weightNeighbor = _signatures.find(iter->first)->second->getWeight(); } //UDEBUG("Add neighbor link from %d to %d", id, iter->first); fprintf(fout, " \"%d\\n%d\" -> \"%d\\n%d\"\n", id, weight, iter->first, weightNeighbor); } } // loop closure links... for(std::map::iterator iter = loopIds.begin(); iter!=loopIds.end(); ++iter) { int weightNeighbor = 0; if(_signatures.find(iter->first) == _signatures.end()) { _dbDriver->getWeight(iter->first, weightNeighbor); } else { weightNeighbor = _signatures.find(iter->first)->second->getWeight(); } //UDEBUG("Add loop link from %d to %d", id, iter->first); fprintf(fout, " \"%d\\n%d\" -> \"%d\\n%d\" [label=\"L\", fontcolor=%s, fontsize=8];\n", id, weight, iter->first, weightNeighbor, colorG); } for(std::map::iterator iter = childIds.begin(); iter!=childIds.end(); ++iter) { int weightNeighbor = 0; if(_signatures.find(iter->first) == _signatures.end()) { _dbDriver->getWeight(iter->first, weightNeighbor); } else { weightNeighbor = _signatures.find(iter->first)->second->getWeight(); } //UDEBUG("Add child link from %d to %d", id, iter->first); fprintf(fout, " \"%d\\n%d\" -> \"%d\\n%d\" [label=\"C\", fontcolor=%s, fontsize=8];\n", id, weight, iter->first, weightNeighbor, colorP); } } } for(std::map::iterator i=_signatures.begin(); i!=_signatures.end(); ++i) { if(ids.find(i->first) != ids.end()) { int id = i->second->id(); const std::map & loopIds = i->second->getLoopClosureIds(); //don't show children when _loopClosuresMerged is on //if(!_loopClosuresMerged || (loopIds.size() == 0)) { const std::map & neighbors = i->second->getNeighbors(); int weight = i->second->getWeight(); for(std::map::const_iterator iter = neighbors.begin(); iter!=neighbors.end(); ++iter) { if(id != iter->first) { int weightNeighbor = 0; const Signature * s = this->getSignature(iter->first); if(s) { weightNeighbor = s->getWeight(); } else { _dbDriver->getWeight(iter->first, weightNeighbor); } //UDEBUG("Add neighbor link from %d to %d", id, iter->first); fprintf(fout, " \"%d\\n%d\" -> \"%d\\n%d\";\n", id, weight, iter->first, weightNeighbor); } } // loop closure link for(std::map::const_iterator iter = loopIds.begin(); iter!=loopIds.end(); ++iter) { int weightNeighbor = 0; if(_signatures.find(iter->first) == _signatures.end()) { _dbDriver->getWeight(iter->first, weightNeighbor); } else { weightNeighbor = _signatures.find(iter->first)->second->getWeight(); } //UDEBUG("Add loop link from %d to %d", id, iter->first); fprintf(fout, " \"%d\\n%d\" -> \"%d\\n%d\" [label=\"L\", fontcolor=%s, fontsize=8];\n", id, weight, iter->first, weightNeighbor, colorG); } // child loop closure link const std::map & childIds = i->second->getChildLoopClosureIds(); for(std::map::const_iterator iter = childIds.begin(); iter!=childIds.end(); ++iter) { int weightNeighbor = 0; if(_signatures.find(iter->first) == _signatures.end()) { _dbDriver->getWeight(iter->first, weightNeighbor); } else { weightNeighbor = _signatures.find(iter->first)->second->getWeight(); } //UDEBUG("Add child link from %d to %d", id, iter->first); fprintf(fout, " \"%d\\n%d\" -> \"%d\\n%d\" [label=\"C\", fontcolor=%s, fontsize=8];\n", id, weight, iter->first, weightNeighbor, colorP); } } } } fprintf(fout, "}\n"); fclose(fout); UINFO("Graph saved to \"%s\"", fileName.c_str()); } } // Only used to generate a .dot file class GraphNode { public: GraphNode(int id, GraphNode * parent = 0) : _parent(parent), _id(id) { if(_parent) { _parent->addChild(this); } } virtual ~GraphNode() { //We copy the set because when a child is destroyed, it is removed from its parent. std::set children = _children; _children.clear(); for(std::set::iterator iter=children.begin(); iter!=children.end(); ++iter) { delete *iter; } children.clear(); if(_parent) { _parent->removeChild(this); } } int id() const {return _id;} bool isAncestor(int id) const { if(_parent) { if(_parent->id() == id) { return true; } return _parent->isAncestor(id); } return false; } void expand(std::list > & paths, std::list currentPath = std::list()) const { currentPath.push_back(_id); if(_children.size() == 0) { paths.push_back(currentPath); return; } for(std::set::const_iterator iter=_children.begin(); iter!=_children.end(); ++iter) { (*iter)->expand(paths, currentPath); } } private: void addChild(GraphNode * child) { _children.insert(child); } void removeChild(GraphNode * child) { _children.erase(child); } private: std::set _children; GraphNode * _parent; int _id; }; //recursive void Memory::createGraph(GraphNode * parent, unsigned int maxDepth, const std::set & endIds) { if(maxDepth == 0 || !parent) { return; } std::map neighbors = this->getNeighborsId(parent->id(), 1, -1, false); for(std::map::iterator iter=neighbors.begin(); iter!=neighbors.end(); ++iter) { if(!parent->isAncestor(iter->first)) { GraphNode * n = new GraphNode(iter->first, parent); if(endIds.find(iter->first) == endIds.end()) { this->createGraph(n, maxDepth-1, endIds); } } } } // Keypoint stuff std::multimap Memory::getWords(int signatureId) const { std::multimap words; if(signatureId>0) { const Signature * s = this->getSignature(signatureId); if(s) { const Signature * ks = dynamic_cast(s); if(ks) { words = ks->getWords(); } } else if(_dbDriver) { std::list ids; ids.push_back(signatureId); std::list signatures; _dbDriver->loadSignatures(ids, signatures); if(signatures.size()) { const Signature * ks = dynamic_cast(signatures.front()); if(ks) { words = ks->getWords(); } } for(std::list::iterator iter = signatures.begin(); iter!=signatures.end(); ++iter) { delete *iter; } } } return words; } int Memory::getNi(int signatureId) const { int ni = 0; const Signature * s = this->getSignature(signatureId); if(s) // Must be a SurfSignature { ni = ((Signature *)s)->getWords().size(); } else { _dbDriver->getInvertedIndexNi(signatureId, ni); } return ni; } void Memory::copyData(const Signature * from, Signature * to) { // The signatures must be KeypointSignature UTimer timer; timer.start(); if(from && to) { // words 2d this->disableWordsRef(to->id()); to->setWords(from->getWords()); std::list id; id.push_back(to->id()); this->enableWordsRef(id); if(from->isSaved() && _dbDriver) { std::vector image; std::vector depth; std::vector depth2d; float depthConstant; Transform localTransform; _dbDriver->getNodeData(from->id(), image, depth, depth2d, depthConstant, localTransform); to->setImage(image); to->setDepth(depth, depthConstant); to->setDepth2D(depth2d); to->setLocalTransform(localTransform); UDEBUG("Loaded image data from database"); } else { to->setImage(from->getImage()); to->setDepth(from->getDepth(), from->getDepthConstant()); to->setDepth2D(from->getDepth2D()); to->setLocalTransform(from->getLocalTransform()); } to->setPose(from->getPose()); to->setWords3(from->getWords3()); } else { ULOGGER_ERROR("Can't merge the signatures because there are not same type."); } UDEBUG("Merging time = %fs", timer.ticks()); } void Memory::extractKeypointsAndDescriptors( const cv::Mat & image, std::vector & keypoints, cv::Mat & descriptors) { if(_wordsPerImageTarget >= 0) { UTimer timer; if(_keypointDetector) { cv::Rect roi = KeypointDetector::computeRoi(image, _roiRatios); keypoints = _keypointDetector->generateKeypoints(image, _wordsPerImageTarget, roi); UDEBUG("time keypoints (%d) = %fs", (int)keypoints.size(), timer.ticks()); } if(keypoints.size()) { descriptors = _keypointDescriptor->generateDescriptors(image, keypoints); UDEBUG("time descriptors (%d) = %fs", descriptors.rows, timer.ticks()); } } else { UDEBUG("_wordsPerImageTarget(%d)<0 so don't extract any descriptors...", _wordsPerImageTarget); } } class PreUpdateThread : public UThreadNode { public: PreUpdateThread(VWDictionary * vwp) : _vwp(vwp) {} virtual ~PreUpdateThread() {} private: void mainLoop() { if(_vwp) { _vwp->update(); } this->kill(); } VWDictionary * _vwp; }; Signature * Memory::createSignature(const Image & image, bool keepRawData) { UASSERT(image.image().empty() || image.image().type() == CV_8UC1 || image.image().type() == CV_8UC3); UASSERT(image.depth().empty() || image.depth().type() == CV_16UC1); UASSERT(image.depth2d().empty() || image.depth2d().type() == CV_32FC2); PreUpdateThread preUpdateThread(_vwd); UTimer timer; timer.start(); std::vector keypoints; cv::Mat descriptors; int id = image.id(); if(_generateIds) { id = this->getNextId(); } else { if(id <= 0) { UWARN("Received image ID is null. " "Please set parameter Mem/GenerateIds to \"true\" or " "make sure the input source provides image ids (seq)."); return 0; } else if(id > _idCount) { _idCount = id; } else { UWARN("Id of acquired image (%d) is smaller than the last in memory (%d). " "Please set parameter Mem/GenerateIds to \"true\" or " "make sure the input source provides image ids (seq) over the last in " "memory, which is %d.", id, _idCount, _idCount); return 0; } } int treeSize= _workingMem.size() + _stMem.size(); int meanWordsPerLocation = 0; if(treeSize > 0) { meanWordsPerLocation = _vwd->getTotalActiveReferences() / treeSize; } if(_parallelized) { preUpdateThread.start(); } if(!image.descriptors().empty()) { // DESCRIPTORS if(image.descriptors().rows && image.descriptors().rows >= _badSignRatio * float(meanWordsPerLocation)) { UASSERT(image.descriptors().type() == CV_32F); descriptors = image.descriptors(); keypoints = image.keypoints(); } limitKeypoints(keypoints, descriptors, _wordsPerImageTarget); } else { // IMAGE RAW this->extractKeypointsAndDescriptors(image.image(), keypoints, descriptors); UDEBUG("ratio=%f, meanWordsPerLocation=%d", _badSignRatio, meanWordsPerLocation); if(descriptors.rows && descriptors.rows < _badSignRatio * float(meanWordsPerLocation)) { descriptors = cv::Mat(); } } if(_parallelized) { preUpdateThread.join(); // Wait the dictionary to be updated } std::list wordIds; if(descriptors.rows) { if(_parallelized) { UDEBUG("time descriptor and memory update (%d of size=%d) = %fs", descriptors.rows, descriptors.cols, timer.ticks()); } else { UDEBUG("time descriptor (%d of size=%d) = %fs", descriptors.rows, descriptors.cols, timer.ticks()); } wordIds = _vwd->addNewWords(descriptors, id); UDEBUG("time addNewWords %fs", timer.ticks()); } else if(id>0) { UDEBUG("id %d is a bad signature", id); } std::multimap words; if(wordIds.size() > 0) { std::vector::iterator kpIter = keypoints.begin(); for(std::list::iterator iter=wordIds.begin(); iter!=wordIds.end(); ++iter) { if(kpIter != keypoints.end()) { words.insert(std::pair(*iter, *kpIter)); ++kpIter; } else { if(keypoints.size()) { UWARN("Words (%d) and keypoints(%d) are not the same size ?!?", (int)wordIds.size(), (int)keypoints.size()); } words.insert(std::pair(*iter, cv::KeyPoint())); } } } //3d words std::multimap words3; if(!image.depth().empty() && image.depthConstant()) { words3 = util3d::generateWords3(words, image.depth(), image.depthConstant(), image.localTransform()); } Signature * s; if(keepRawData) { std::vector imageBytes; std::vector depthBytes; util3d::CompressionThread ctImage(image.image(), std::string(".jpg")); util3d::CompressionThread ctDepth(image.depth(), std::string(".png")); ctImage.start(); ctDepth.start(); ctImage.join(); ctDepth.join(); imageBytes = ctImage.getCompressedData(); depthBytes = ctDepth.getCompressedData(); s = new Signature(id, _idMapCount, words, words3, image.pose(), util3d::compressData(image.depth2d()), imageBytes, depthBytes, image.depthConstant(), image.localTransform()); } else { s = new Signature(id, _idMapCount, words, words3, image.pose(), util3d::compressData(image.depth2d())); } UDEBUG("time new signature (id=%d) %fs", id, timer.ticks()); if(words.size()) { s->setEnabled(true); // All references are already activated in the dictionary at this point (see _vwd->addNewWords()) } return s; } void Memory::disableWordsRef(int signatureId, bool saveToDatabase) { UDEBUG("id=%d", signatureId); Signature * ss = dynamic_cast(this->_getSignature(signatureId)); if(ss && ss->isEnabled()) { const std::multimap & words = ss->getWords(); const std::list & keys = uUniqueKeys(words); int count = _vwd->getTotalActiveReferences(); // First remove all references for(std::list::const_iterator i=keys.begin(); i!=keys.end(); ++i) { _vwd->removeAllWordRef(*i, signatureId); if(!saveToDatabase) { // assume just removed word doesn't have any other references VisualWord * w = _vwd->getUnusedWord(*i); if(w) { std::vector wordToDelete; wordToDelete.push_back(w); _vwd->removeWords(wordToDelete); delete w; } } } count -= _vwd->getTotalActiveReferences(); ss->setEnabled(false); UDEBUG("%d words total ref removed from signature %d... (total active ref = %d)", count, ss->id(), _vwd->getTotalActiveReferences()); } } void Memory::cleanUnusedWords() { if(_vwd->isIncremental()) { std::vector removedWords = _vwd->getUnusedWords(); UDEBUG("Removing %d words (dictionary size=%d)...", removedWords.size(), _vwd->getVisualWords().size()); if(removedWords.size()) { // remove them from the dictionary _vwd->removeWords(removedWords); for(unsigned int i=0; iasyncSave(removedWords[i]); } else { delete removedWords[i]; } } } } } void Memory::enableWordsRef(const std::list & signatureIds) { UDEBUG("size=%d", signatureIds.size()); UTimer timer; timer.start(); std::map refsToChange; // std::set oldWordIds; std::list surfSigns; for(std::list::const_iterator i=signatureIds.begin(); i!=signatureIds.end(); ++i) { Signature * ss = dynamic_cast(this->_getSignature(*i)); if(ss && !ss->isEnabled()) { surfSigns.push_back(ss); std::list uniqueKeys = uUniqueKeys(ss->getWords()); //Find words in the signature which they are not in the current dictionary for(std::list::const_iterator k=uniqueKeys.begin(); k!=uniqueKeys.end(); ++k) { if(_vwd->getWord(*k) == 0 && _vwd->getUnusedWord(*k) == 0) { oldWordIds.insert(oldWordIds.end(), *k); } } } } UDEBUG("oldWordIds.size()=%d, getOldIds time=%fs", oldWordIds.size(), timer.ticks()); // the words were deleted, so try to math it with an active word std::list vws; if(oldWordIds.size() && _dbDriver) { // get the descriptors _dbDriver->loadWords(oldWordIds, vws); } UDEBUG("loading words(%d) time=%fs", oldWordIds.size(), timer.ticks()); if(vws.size()) { //Search in the dictionary bool reactivatedWordsComparedToNewWords = true; std::vector vwActiveIds = _vwd->findNN(vws, reactivatedWordsComparedToNewWords); UDEBUG("find active ids (number=%d) time=%fs", vws.size(), timer.ticks()); int i=0; for(std::list::iterator iterVws=vws.begin(); iterVws!=vws.end(); ++iterVws) { if(vwActiveIds[i] > 0) { //UDEBUG("Match found %d with %d", (*iterVws)->id(), vwActiveIds[i]); refsToChange.insert(refsToChange.end(), std::pair((*iterVws)->id(), vwActiveIds[i])); if((*iterVws)->isSaved()) { delete (*iterVws); } else if(_dbDriver) { _dbDriver->asyncSave(*iterVws); } } else { //add to dictionary _vwd->addWord(*iterVws); // take ownership } ++i; } UDEBUG("Added %d to dictionary, time=%fs", vws.size()-refsToChange.size(), timer.ticks()); //update the global references map and update the signatures reactivated for(std::map::const_iterator iter=refsToChange.begin(); iter != refsToChange.end(); ++iter) { //uInsert(_wordRefsToChange, (const std::pair)*iter); // This will be used to change references in the database for(std::list::iterator j=surfSigns.begin(); j!=surfSigns.end(); ++j) { (*j)->changeWordsRef(iter->first, iter->second); } } UDEBUG("changing ref, total=%d, time=%fs", refsToChange.size(), timer.ticks()); } int count = _vwd->getTotalActiveReferences(); // Reactivate references and signatures for(std::list::iterator j=surfSigns.begin(); j!=surfSigns.end(); ++j) { const std::vector & keys = uKeys((*j)->getWords()); // Add all references for(std::vector::const_iterator i=keys.begin(); i!=keys.end(); ++i) { _vwd->addWordRef(*i, (*j)->id()); } if(keys.size()) { (*j)->setEnabled(true); } } count = _vwd->getTotalActiveReferences() - count; UDEBUG("%d words total ref added from %d signatures, time=%fs...", count, surfSigns.size(), timer.ticks()); } std::set Memory::reactivateSignatures(const std::list & ids, unsigned int maxLoaded, double & timeDbAccess) { // get the signatures, if not in the working memory, they // will be loaded from the database in an more efficient way // than how it is done in the Memory UDEBUG(""); UTimer timer; std::list idsToLoad; std::map::iterator wmIter; for(std::list::const_iterator i=ids.begin(); i!=ids.end(); ++i) { if(!this->getSignature(*i) && !uContains(idsToLoad, *i)) { if(!maxLoaded || idsToLoad.size() < maxLoaded) { idsToLoad.push_back(*i); UINFO("Loading location %d from database...", *i); } } } UDEBUG("idsToLoad = %d", idsToLoad.size()); std::list reactivatedSigns; if(_dbDriver) { _dbDriver->loadSignatures(idsToLoad, reactivatedSigns); } timeDbAccess = timer.getElapsedTime(); std::list idsLoaded; for(std::list::iterator i=reactivatedSigns.begin(); i!=reactivatedSigns.end(); ++i) { idsLoaded.push_back((*i)->id()); //append to working memory this->addSignatureToWm(*i); } this->enableWordsRef(idsLoaded); UDEBUG("time = %fs", timer.ticks()); return std::set(idsToLoad.begin(), idsToLoad.end()); } void Memory::getMetricConstraints( const std::vector & ids, std::map & poses, std::multimap & links, bool lookInDatabase) { for(unsigned int i=0; igetPose(ids[i], pose, lookInDatabase); if(!pose.isNull()) { poses.insert(std::make_pair(ids[i], pose)); } } for(unsigned int i=0; i neighbors = this->getNeighborLinks(ids[i], true, lookInDatabase); // only direct neighbors for(std::map::iterator jter=neighbors.begin(); jter!=neighbors.end(); ++jter) { if(!jter->second.isNull() && uContains(poses, jter->first)) { bool edgeAlreadyAdded = false; for(std::multimap::iterator iter = links.lower_bound(jter->first); iter != links.end() && iter->first == jter->first; ++iter) { if(iter->second.to() == ids[i]) { edgeAlreadyAdded = true; } } if(!edgeAlreadyAdded) { links.insert(std::make_pair(ids[i], Link(ids[i], jter->first, jter->second, Link::kNeighbor))); } } } std::map loops, children; this->getLoopClosureIds(ids[i], loops, children, lookInDatabase); for(std::map::iterator jter=children.begin(); jter!=children.end(); ++jter) { if(!jter->second.isNull() && uContains(poses, jter->first)) { links.insert(std::make_pair(ids[i], Link(ids[i], jter->first, jter->second, Link::kGlobalClosure))); } } } } } } // namespace rtabmap