Files
rtabmap/corelib/src/VWDictionary.cpp
T

1696 lines
51 KiB
C++
Raw Normal View History

/*
Copyright (c) 2010-2016, Mathieu Labbe - IntRoLab - Universite de Sherbrooke
2014-08-11 17:00:55 +00:00
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.
*/
2012-06-24 17:19:34 +00:00
#include "rtabmap/core/VWDictionary.h"
#include "rtabmap/core/VisualWord.h"
2013-12-11 00:12:44 +00:00
#include "rtabmap/core/Signature.h"
#include "rtabmap/core/DBDriver.h"
#include "rtabmap/core/Parameters.h"
#include "rtabmap/core/FlannIndex.h"
#include "rtabmap/utilite/UtiLite.h"
2015-06-18 23:00:46 -04:00
#include <opencv2/opencv_modules.hpp>
2015-06-18 23:00:46 -04:00
#if CV_MAJOR_VERSION < 3
#ifdef HAVE_OPENCV_GPU
#include <opencv2/gpu/gpu.hpp>
#endif
2015-06-18 23:00:46 -04:00
#else
#include <opencv2/core/cuda.hpp>
#ifdef HAVE_OPENCV_CUDAFEATURES2D
#include <opencv2/cudafeatures2d.hpp>
#endif
#endif
#include <fstream>
#include <string>
2017-02-05 17:24:45 -05:00
#define KNN_CHECKS 32
namespace rtabmap
{
const int VWDictionary::ID_START = 1;
const int VWDictionary::ID_INVALID = 0;
VWDictionary::VWDictionary(const ParametersMap & parameters) :
_totalActiveReferences(0),
_incrementalDictionary(Parameters::defaultKpIncrementalDictionary()),
_incrementalFlann(Parameters::defaultKpIncrementalFlann()),
2018-02-01 22:17:46 -05:00
_rebalancingFactor(Parameters::defaultKpFlannRebalancingFactor()),
_byteToFloat(Parameters::defaultKpByteToFloat()),
_nndrRatio(Parameters::defaultKpNndrRatio()),
_newDictionaryPath(Parameters::defaultKpDictionaryPath()),
_newWordsComparedTogether(Parameters::defaultKpNewWordsComparedTogether()),
2025-09-03 16:55:54 -07:00
_serializeWithChecksum(Parameters::defaultKpSerializeWithChecksum()),
_lastWordId(0),
2016-02-15 19:35:24 -05:00
useDistanceL1_(false),
_flannIndex(new FlannIndex()),
2025-09-03 16:55:54 -07:00
_modified(true),
_strategy(kNNBruteForce)
{
this->setNNStrategy((NNStrategy)Parameters::defaultKpNNStrategy());
this->parseParameters(parameters);
}
VWDictionary::~VWDictionary()
{
this->clear();
delete _flannIndex;
}
void VWDictionary::parseParameters(const ParametersMap & parameters)
{
ParametersMap::const_iterator iter;
2013-12-11 00:12:44 +00:00
Parameters::parse(parameters, Parameters::kKpNndrRatio(), _nndrRatio);
Parameters::parse(parameters, Parameters::kKpNewWordsComparedTogether(), _newWordsComparedTogether);
2025-09-03 16:55:54 -07:00
Parameters::parse(parameters, Parameters::kKpSerializeWithChecksum(), _serializeWithChecksum);
Parameters::parse(parameters, Parameters::kKpIncrementalFlann(), _incrementalFlann);
2018-02-01 22:17:46 -05:00
Parameters::parse(parameters, Parameters::kKpFlannRebalancingFactor(), _rebalancingFactor);
bool byteToFloat = _byteToFloat;
Parameters::parse(parameters, Parameters::kKpByteToFloat(), _byteToFloat);
UASSERT_MSG(_nndrRatio > 0.0f, uFormat("String=%s value=%f", uContains(parameters, Parameters::kKpNndrRatio())?parameters.at(Parameters::kKpNndrRatio()).c_str():"", _nndrRatio).c_str());
bool incrementalDictionary = _incrementalDictionary;
if((iter=parameters.find(Parameters::kKpDictionaryPath())) != parameters.end())
{
_newDictionaryPath = (*iter).second.c_str();
}
if((iter=parameters.find(Parameters::kKpIncrementalDictionary())) != parameters.end())
{
incrementalDictionary = uStr2Bool((*iter).second.c_str());
}
// Verifying hypotheses strategy
bool treeUpdated = false;
if((iter=parameters.find(Parameters::kKpNNStrategy())) != parameters.end())
{
NNStrategy nnStrategy = (NNStrategy)std::atoi((*iter).second.c_str());
treeUpdated = this->setNNStrategy(nnStrategy);
}
if(!treeUpdated && byteToFloat!=_byteToFloat && _strategy == kNNFlannKdTree)
{
UINFO("KDTree: Binary to Float conversion approach has changed, re-initialize kd-tree.");
_dataTree = cv::Mat();
_notIndexedWords = uKeysSet(_visualWords);
_removedIndexedWords.clear();
this->update();
}
if(incrementalDictionary)
{
this->setIncrementalDictionary();
}
_incrementalDictionary = incrementalDictionary;
}
void VWDictionary::setIncrementalDictionary()
{
if(!_incrementalDictionary)
{
_incrementalDictionary = true;
if(_visualWords.size())
{
UWARN("Incremental dictionary set: already loaded visual words (%d) from the fixed dictionary will be included in the incremental one.", _visualWords.size());
}
}
_dictionaryPath = "";
_newDictionaryPath = "";
}
void VWDictionary::setFixedDictionary(const std::string & dictionaryPath)
{
UDEBUG("");
if(!dictionaryPath.empty())
{
if((!_incrementalDictionary && _dictionaryPath.compare(dictionaryPath) != 0) ||
_visualWords.size() == 0)
{
UINFO("incremental=%d, oldPath=%s newPath=%s, visual words=%d",
_incrementalDictionary?1:0, _dictionaryPath.c_str(), dictionaryPath.c_str(), (int)_visualWords.size());
if(UFile::getExtension(dictionaryPath).compare("db") == 0)
{
UWARN("Loading fixed vocabulary \"%s\", this may take a while...", dictionaryPath.c_str());
DBDriver * driver = DBDriver::create();
if(driver->openConnection(dictionaryPath, false))
{
2025-09-03 16:55:54 -07:00
driver->load(*this, false);
for(std::map<int, VisualWord*>::iterator iter=_visualWords.begin(); iter!=_visualWords.end(); ++iter)
{
iter->second->setSaved(true);
}
_incrementalDictionary = _visualWords.size()==0;
driver->closeConnection(false);
}
else
{
UERROR("Could not load dictionary from database %s", dictionaryPath.c_str());
}
delete driver;
}
else
{
UWARN("Loading fixed vocabulary \"%s\", this may take a while...", dictionaryPath.c_str());
std::ifstream file;
file.open(dictionaryPath.c_str(), std::ifstream::in);
if(file.good())
{
UDEBUG("Deleting old dictionary and loading the new one from \"%s\"", dictionaryPath.c_str());
UTimer timer;
// first line is the header
std::string str;
std::list<std::string> strList;
std::getline(file, str);
strList = uSplitNumChar(str);
int dimension = 0;
for(std::list<std::string>::iterator iter = strList.begin(); iter != strList.end(); ++iter)
{
if(uIsDigit(iter->at(0)))
{
dimension = std::atoi(iter->c_str());
break;
}
}
UDEBUG("descriptor dimension = %d", dimension);
if(dimension <= 0 || dimension > 1000)
{
UERROR("Invalid dictionary file, visual word dimension (%d) is not valid, \"%s\"", dimension, dictionaryPath.c_str());
}
else
{
// Process all words
while(file.good())
{
std::getline(file, str);
strList = uSplit(str);
2018-05-10 13:57:02 -04:00
if((int)strList.size() == dimension+1)
{
//first one is the visual word id
std::list<std::string>::iterator iter = strList.begin();
int id = std::atoi(iter->c_str());
cv::Mat descriptor(1, dimension, CV_32F);
++iter;
int i=0;
//get descriptor
for(;i<dimension && iter != strList.end(); ++i, ++iter)
{
descriptor.at<float>(i) = uStr2Float(*iter);
}
if(i != dimension)
{
UERROR("Loaded word has not the same size (%d) than descriptor size previously detected (%d).", i, dimension);
}
VisualWord * vw = new VisualWord(id, descriptor, 0);
vw->setSaved(true);
_visualWords.insert(_visualWords.end(), std::pair<int, VisualWord*>(id, vw));
_notIndexedWords.insert(_notIndexedWords.end(), id);
_unusedWords.insert(_unusedWords.end(), std::pair<int, VisualWord*>(id, vw));
}
else if(!str.empty())
{
UWARN("Cannot parse line \"%s\"", str.c_str());
}
}
if(_visualWords.size())
{
UWARN("Loaded %d words!", (int)_visualWords.size());
}
}
}
else
{
UERROR("Cannot open dictionary file \"%s\"", dictionaryPath.c_str());
}
file.close();
}
if(_visualWords.size() == 0)
{
_incrementalDictionary = _visualWords.size()==0;
UWARN("No words loaded, cannot set a fixed dictionary.", (int)_visualWords.size());
}
else
{
_dictionaryPath = dictionaryPath;
_newDictionaryPath = dictionaryPath;
_incrementalDictionary = false;
this->update();
UWARN("Loaded %d words!", (int)_visualWords.size());
}
}
else if(!_incrementalDictionary)
{
UDEBUG("Dictionary \"%s\" already loaded...", dictionaryPath.c_str());
}
else
{
UERROR("Cannot change to a fixed dictionary if there are already words (%d) in the incremental one.", _visualWords.size());
}
}
else if(_incrementalDictionary && _visualWords.size())
{
UWARN("Cannot change to fixed dictionary, %d words already loaded as incremental", (int)_visualWords.size());
}
else
{
_incrementalDictionary = false;
}
_dictionaryPath = dictionaryPath;
_newDictionaryPath = dictionaryPath;
}
2025-09-03 16:55:54 -07:00
bool VWDictionary::isModified() const
{
return _modified;
}
bool VWDictionary::setNNStrategy(NNStrategy strategy)
{
2015-06-18 23:00:46 -04:00
#if CV_MAJOR_VERSION < 3
2016-01-21 17:11:57 -05:00
#ifdef HAVE_OPENCV_GPU
2026-06-10 20:07:30 -07:00
if(strategy == kNNBruteForceGPU && cv::gpu::getCudaEnabledDeviceCount() <= 0)
{
UERROR("Nearest neighobr strategy \"kNNBruteForceGPU\" chosen but no CUDA devices found! Doing \"kNNBruteForce\" instead.");
strategy = kNNBruteForce;
}
2015-06-18 23:00:46 -04:00
#else
if(strategy == kNNBruteForceGPU)
{
UERROR("Nearest neighobr strategy \"kNNBruteForceGPU\" chosen but OpenCV is not built with GPU/cuda module! Doing \"kNNBruteForce\" instead.");
strategy = kNNBruteForce;
}
2016-01-21 17:11:57 -05:00
#endif
#else
2016-06-22 19:28:22 -04:00
#ifdef HAVE_OPENCV_CUDAFEATURES2D
2026-06-10 20:07:30 -07:00
if(strategy == kNNBruteForceGPU && cv::cuda::getCudaEnabledDeviceCount() <= 0)
{
UERROR("Nearest neighobr strategy \"kNNBruteForceGPU\" chosen but no CUDA devices found! Doing \"kNNBruteForce\" instead.");
strategy = kNNBruteForce;
}
2016-01-21 17:11:57 -05:00
#else
if(strategy == kNNBruteForceGPU)
{
UERROR("Nearest neighobr strategy \"kNNBruteForceGPU\" chosen but OpenCV cudafeatures2d module is not found! Doing \"kNNBruteForce\" instead.");
strategy = kNNBruteForce;
}
2016-01-21 17:11:57 -05:00
#endif
2015-06-18 23:00:46 -04:00
#endif
if(strategy>=kNNUndef)
{
UERROR("Nearest neighobr strategy \"%d\" chosen but this strategy cannot be used with a dictionary! Doing \"kNNBruteForce\" instead.");
strategy = kNNBruteForce;
}
bool update = _strategy != strategy;
_strategy = strategy;
if(update)
{
2020-10-05 17:34:32 -04:00
if(_notIndexedWords.size() != _visualWords.size() || !_dataTree.empty())
{
UINFO("Nearest neighbor strategy has changed, re-initialize search tree.");
}
_dataTree = cv::Mat();
_notIndexedWords = uKeysSet(_visualWords);
_removedIndexedWords.clear();
this->update();
return true;
}
return false;
}
int VWDictionary::getLastIndexedWordId() const
{
if(_mapIndexId.size())
{
return _mapIndexId.rbegin()->second;
}
else
{
return 0;
}
}
unsigned int VWDictionary::getIndexedWordsCount() const
{
return _flannIndex->indexedFeatures();
}
unsigned int VWDictionary::getIndexMemoryUsed() const
{
return _flannIndex->memoryUsed();
}
unsigned long VWDictionary::getMemoryUsed() const
2020-10-05 17:34:32 -04:00
{
long memoryUsage = sizeof(VWDictionary);
memoryUsage += getIndexMemoryUsed();
2026-05-16 21:11:02 -07:00
if(!_dataTree.empty())
{
memoryUsage += _dataTree.total()*_dataTree.elemSize();
}
if(!_visualWords.empty())
2020-10-05 17:34:32 -04:00
{
2020-10-16 14:55:50 -04:00
memoryUsage += _visualWords.size()*(sizeof(int) + _visualWords.rbegin()->second->getMemoryUsed() + sizeof(std::map<int, VisualWord *>::iterator)) + sizeof(std::map<int, VisualWord *>);
if(_dataTree.empty() &&
_visualWords.begin()->second->getDescriptor().type() == CV_8U &&
_strategy == kNNFlannKdTree)
2020-10-05 17:34:32 -04:00
{
// Binary descriptors were converted to float, and not included in _dataTree
memoryUsage += _visualWords.size() * _visualWords.begin()->second->getDescriptor().total() * sizeof(float) * (_byteToFloat?1:8);
2020-10-05 17:34:32 -04:00
}
}
if(!_unusedWords.empty())
{
// they are the same words than in _visualWords, so just add the pointer size
2020-10-16 14:55:50 -04:00
memoryUsage += _unusedWords.size()*(sizeof(int) + sizeof(VisualWord *)+sizeof(std::map<int, VisualWord *>::iterator)) + sizeof(std::map<int, VisualWord *>);
2020-10-05 17:34:32 -04:00
}
2020-10-16 14:55:50 -04:00
memoryUsage += _mapIndexId.size() * (sizeof(int)*2+sizeof(std::map<int ,int>::iterator)) + sizeof(std::map<int ,int>);
memoryUsage += _mapIdIndex.size() * (sizeof(int)*2+sizeof(std::map<int ,int>::iterator)) + sizeof(std::map<int ,int>);
memoryUsage += _notIndexedWords.size() * (sizeof(int)+sizeof(std::set<int>::iterator)) + sizeof(std::set<int>);
memoryUsage += _removedIndexedWords.size() * (sizeof(int)+sizeof(std::set<int>::iterator)) + sizeof(std::set<int>);
2020-10-05 17:34:32 -04:00
return memoryUsage;
}
cv::Mat VWDictionary::convertBinTo32F(const cv::Mat & descriptorsIn, bool byteToFloat)
{
if(byteToFloat)
{
// Old approach
cv::Mat descriptorsOut;
descriptorsIn.convertTo(descriptorsOut, CV_32F);
return descriptorsOut;
}
else
{
// New approach
UASSERT(descriptorsIn.type() == CV_8UC1);
cv::Mat descriptorsOut(descriptorsIn.rows, descriptorsIn.cols*8, CV_32FC1);
for(int i=0; i<descriptorsIn.rows; ++i)
{
const unsigned char * ptrIn = descriptorsIn.ptr(i);
float * ptrOut = descriptorsOut.ptr<float>(i);
for(int j=0; j<descriptorsIn.cols; ++j)
{
int jo = j*8;
ptrOut[jo] = (ptrIn[j] & 1) == 1?1.0f:0.0f;
ptrOut[jo+1] = (ptrIn[j] & (1<<1)) != 0?1.0f:0.0f;
ptrOut[jo+2] = (ptrIn[j] & (1<<2)) != 0?1.0f:0.0f;
ptrOut[jo+3] = (ptrIn[j] & (1<<3)) != 0?1.0f:0.0f;
ptrOut[jo+4] = (ptrIn[j] & (1<<4)) != 0?1.0f:0.0f;
ptrOut[jo+5] = (ptrIn[j] & (1<<5)) != 0?1.0f:0.0f;
ptrOut[jo+6] = (ptrIn[j] & (1<<6)) != 0?1.0f:0.0f;
ptrOut[jo+7] = (ptrIn[j] & (1<<7)) != 0?1.0f:0.0f;
}
}
return descriptorsOut;
}
}
cv::Mat VWDictionary::convert32FToBin(const cv::Mat & descriptorsIn, bool byteToFloat)
{
if(byteToFloat)
{
// Old approach
cv::Mat descriptorsOut;
descriptorsIn.convertTo(descriptorsOut, CV_8UC1);
return descriptorsOut;
}
else
{
// New approach
UASSERT(descriptorsIn.type() == CV_32FC1 && descriptorsIn.cols % 8 == 0);
cv::Mat descriptorsOut(descriptorsIn.rows, descriptorsIn.cols/8, CV_8UC1);
for(int i=0; i<descriptorsIn.rows; ++i)
{
const float * ptrIn = descriptorsIn.ptr<float>(i);
unsigned char * ptrOut = descriptorsOut.ptr(i);
for(int j=0; j<descriptorsOut.cols; ++j)
{
int jo = j*8;
ptrOut[j] =
(unsigned char)(ptrIn[jo] == 0?0:1) |
(ptrIn[jo+1] == 0?0:(1<<1)) |
(ptrIn[jo+2] == 0?0:(1<<2)) |
(ptrIn[jo+3] == 0?0:(1<<3)) |
(ptrIn[jo+4] == 0?0:(1<<4)) |
(ptrIn[jo+5] == 0?0:(1<<5)) |
(ptrIn[jo+6] == 0?0:(1<<6)) |
(ptrIn[jo+7] == 0?0:(1<<7));
}
}
return descriptorsOut;
}
}
void VWDictionary::update()
{
ULOGGER_DEBUG("incremental=%d", _incrementalDictionary?1:0);
if(!_incrementalDictionary)
{
// reload the fixed dictionary if it has been cleared or not yet initialized
this->setFixedDictionary(_newDictionaryPath);
if(!_incrementalDictionary && !_notIndexedWords.size())
{
// No need to update the search index if we
// use a fixed dictionary and the index is
// already built
return;
}
}
2013-12-11 00:12:44 +00:00
if(_notIndexedWords.size() || _visualWords.size() == 0 || _removedIndexedWords.size())
{
2025-09-03 16:55:54 -07:00
_modified = true;
bool firstUpdate = _removedIndexedWords.empty() && _visualWords.size() == _notIndexedWords.size();
UDEBUG("firstUpdate=%s (_removedIndexedWords=%ld, _visualWords=%ld, _notIndexedWords=%ld)",
firstUpdate?"true":"false", _removedIndexedWords.size(), _visualWords.size(), _notIndexedWords.size());
if(!firstUpdate &&
_incrementalFlann &&
_strategy < kNNBruteForce &&
_visualWords.size())
{
ULOGGER_DEBUG("Incremental FLANN: Removing %d words...", (int)_removedIndexedWords.size());
for(std::set<int>::iterator iter=_removedIndexedWords.begin(); iter!=_removedIndexedWords.end(); ++iter)
{
UASSERT(uContains(_mapIdIndex, *iter));
UASSERT(uContains(_mapIndexId, _mapIdIndex.at(*iter)));
_flannIndex->removePoint(_mapIdIndex.at(*iter));
_mapIndexId.erase(_mapIdIndex.at(*iter));
_mapIdIndex.erase(*iter);
}
ULOGGER_DEBUG("Incremental FLANN: Removing %d words... done!", (int)_removedIndexedWords.size());
if(_notIndexedWords.size())
{
2025-09-03 16:55:54 -07:00
UTimer timer;
timer.start();
ULOGGER_DEBUG("Incremental FLANN: Inserting %d words...", (int)_notIndexedWords.size(), _byteToFloat?"true":"false");
for(std::set<int>::iterator iter=_notIndexedWords.begin(); iter!=_notIndexedWords.end(); ++iter)
{
VisualWord* w = uValue(_visualWords, *iter, (VisualWord*)0);
UASSERT(w);
cv::Mat descriptor;
if(w->getDescriptor().type() == CV_8U)
{
useDistanceL1_ = true;
if(_strategy == kNNFlannKdTree)
{
descriptor = convertBinTo32F(w->getDescriptor(), _byteToFloat);
}
else
{
descriptor = w->getDescriptor();
}
}
else
{
descriptor = w->getDescriptor();
}
int index = 0;
if(!_flannIndex->isBuilt())
{
2025-09-03 16:55:54 -07:00
UDEBUG("Building FLANN index... (strategy=%s, byteToFloat=%s, useDistanceL1=%s, rebalancingFactor=%f)",
nnStrategyName(_strategy).c_str(), _byteToFloat?"true":"false", useDistanceL1_?"true":"false", _rebalancingFactor);
_flannIndex->buildIndex(
_strategy == kNNFlannNaive ? FlannIndex::FLANN_INDEX_LINEAR:
_strategy == kNNFlannLSH ? FlannIndex::FLANN_INDEX_LSH:
FlannIndex::FLANN_INDEX_KDTREE, // kNNFlannKdTree
descriptor, useDistanceL1_, _rebalancingFactor);
UDEBUG("Building FLANN index... done!");
}
else
{
UASSERT(descriptor.cols == _flannIndex->featuresDim());
UASSERT(descriptor.type() == _flannIndex->featuresType());
2020-10-05 17:34:32 -04:00
UASSERT(descriptor.rows == 1);
index = _flannIndex->addPoints(descriptor).front();
}
std::pair<std::map<int, int>::iterator, bool> inserted;
inserted = _mapIndexId.insert(std::pair<int, int>(index, w->id()));
UASSERT(inserted.second);
inserted = _mapIdIndex.insert(std::pair<int, int>(w->id(), index));
UASSERT(inserted.second);
}
2025-09-03 16:55:54 -07:00
ULOGGER_DEBUG("Incremental FLANN: Inserting %d words... done! (in %f s)", (int)_notIndexedWords.size(), timer.ticks());
}
}
else if(_strategy >= kNNBruteForce &&
_notIndexedWords.size() &&
_removedIndexedWords.size() == 0 &&
_visualWords.size())
{
2026-04-04 19:48:03 -07:00
const int IMGIDX_SHIFT = 18;
const int IMGIDX_ONE = (1 << IMGIDX_SHIFT); // a limit defined in https://github.com/opencv/opencv/blob/4.x/modules/features2d/src/matchers.cpp
if(_dataTree.rows >= IMGIDX_ONE)
{
UWARN("%s=%d is not a FLANN strategy and the number of words in the vocabulary (%d) is over %d (IMGIDX_ONE), so opencv may "
"assert on an IMGIDX_ONE check when adding new words. Use a FLANN strategy instead (%s<%d).",
Parameters::kKpNNStrategy().c_str(), _strategy, _dataTree.rows, IMGIDX_ONE, Parameters::kKpNNStrategy().c_str(), kNNBruteForce);
}
//just add not indexed words
int i = _dataTree.rows;
if(!_dataTree.empty()) {
_dataTree.reserve(_dataTree.rows + _notIndexedWords.size());
}
for(std::set<int>::iterator iter=_notIndexedWords.begin(); iter!=_notIndexedWords.end(); ++iter)
{
VisualWord* w = uValue(_visualWords, *iter, (VisualWord*)0);
UASSERT(w);
if(_dataTree.empty())
{
_dataTree = w->getDescriptor().clone();
}
else
{
UASSERT(w->getDescriptor().cols == _dataTree.cols);
UASSERT(w->getDescriptor().type() == _dataTree.type());
_dataTree.push_back(w->getDescriptor());
}
_mapIndexId.insert(_mapIndexId.end(), std::pair<int, int>(i, w->id()));
std::pair<std::map<int, int>::iterator, bool> inserted = _mapIdIndex.insert(std::pair<int, int>(w->id(), i));
UASSERT(inserted.second);
++i;
}
}
else
{
_mapIndexId.clear();
_mapIdIndex.clear();
_dataTree = cv::Mat();
_flannIndex->release();
2013-12-11 00:12:44 +00:00
if(_visualWords.size())
{
UTimer timer;
timer.start();
int dim = _visualWords.begin()->second->getDescriptor().cols;
int type;
if(_visualWords.begin()->second->getDescriptor().type() == CV_8U)
{
useDistanceL1_ = true;
if(_strategy == kNNFlannKdTree)
{
type = CV_32F;
if(!_byteToFloat)
{
dim *= 8;
}
}
else
{
type = _visualWords.begin()->second->getDescriptor().type();
}
}
else
{
type = _visualWords.begin()->second->getDescriptor().type();
}
UASSERT(type == CV_32F || type == CV_8U);
UASSERT(dim > 0);
// Create the data matrix
_dataTree = cv::Mat(_visualWords.size(), dim, type); // SURF descriptors are CV_32F
std::map<int, VisualWord*>::const_iterator iter = _visualWords.begin();
for(unsigned int i=0; i < _visualWords.size(); ++i, ++iter)
{
cv::Mat descriptor;
if(iter->second->getDescriptor().type() == CV_8U)
{
if(_strategy == kNNFlannKdTree)
{
descriptor = convertBinTo32F(iter->second->getDescriptor(), _byteToFloat);
}
else
{
descriptor = iter->second->getDescriptor();
}
}
else
{
descriptor = iter->second->getDescriptor();
}
UASSERT_MSG(descriptor.type() == type, uFormat("%d vs %d", descriptor.type(), type).c_str());
UASSERT_MSG(descriptor.cols == dim, uFormat("%d vs %d", descriptor.cols, dim).c_str());
descriptor.copyTo(_dataTree.row(i));
_mapIndexId.insert(_mapIndexId.end(), std::pair<int, int>(i, iter->second->id()));
_mapIdIndex.insert(_mapIdIndex.end(), std::pair<int, int>(iter->second->id(), i));
}
ULOGGER_DEBUG("_mapIndexId.size() = %d, words.size()=%d, _dim=%d",_mapIndexId.size(), _visualWords.size(), dim);
ULOGGER_DEBUG("copying data = %f s", timer.ticks());
if(_strategy < kNNBruteForce)
{
_flannIndex->buildIndex(
_strategy == kNNFlannNaive ? FlannIndex::FLANN_INDEX_LINEAR:
_strategy == kNNFlannLSH ? FlannIndex::FLANN_INDEX_LSH:
FlannIndex::FLANN_INDEX_KDTREE, // kNNFlannKdTree
_dataTree,
useDistanceL1_,
_incrementalDictionary&&_incrementalFlann?_rebalancingFactor:1);
ULOGGER_DEBUG("Time to create kd tree = %f s", timer.ticks());
}
}
}
UDEBUG("Dictionary updated! (size=%d added=%d removed=%d)",
_dataTree.rows, _notIndexedWords.size(), _removedIndexedWords.size());
}
2013-12-11 00:12:44 +00:00
else
{
UDEBUG("Dictionary has not changed, so no need to update it! (size=%d)", _dataTree.rows);
2013-12-11 00:12:44 +00:00
}
_notIndexedWords.clear();
_removedIndexedWords.clear();
UDEBUG("");
}
2025-09-03 16:55:54 -07:00
std::vector<unsigned char> VWDictionary::serializeIndex() const
{
if(_strategy >= kNNBruteForce) {
UINFO("Not flann strategy, ignoring serialization...");
return std::vector<unsigned char>();
}
if(!_flannIndex->isBuilt() || !_removedIndexedWords.empty() || !_notIndexedWords.empty() || _visualWords.empty()) {
UWARN("Flann index is not buit, or there are words not indexed, cannot do serialization.");
return std::vector<unsigned char>();
}
return _flannIndex->serializeIndex(_serializeWithChecksum);
}
bool VWDictionary::deserializeIndex(const std::vector<unsigned char> & data)
2025-09-03 16:55:54 -07:00
{
return deserializeIndex(data.data(), data.size());
2025-09-03 16:55:54 -07:00
}
bool VWDictionary::deserializeIndex(const unsigned char * data, size_t size)
2025-09-03 16:55:54 -07:00
{
if(data== NULL || size == 0)
{
UWARN("Trying to deserialize empty data, aborting.");
return false;
2025-09-03 16:55:54 -07:00
}
UDEBUG("Loading flann index... (data size=%ld bytes)", size);
if(_strategy >= kNNBruteForce) {
//ignore
return false;
2025-09-03 16:55:54 -07:00
}
if(_flannIndex->isBuilt()) {
UERROR("Flann index is already built, cannot deserialize data!");
return false;
2025-09-03 16:55:54 -07:00
}
if(_visualWords.empty()) {
UERROR("Descriptors should be added before deserializing flann index! See VWDictionary::addWord()");
return false;
2025-09-03 16:55:54 -07:00
}
if(!(_removedIndexedWords.empty() && _visualWords.size() == _notIndexedWords.size())) {
UERROR("State of dictionary not as expected before deserializing. (removed words=%ld, words=%ld, not indexed=%ld)",
_removedIndexedWords.size(), _visualWords.size(), _notIndexedWords.size());
return false;
2025-09-03 16:55:54 -07:00
}
std::map<int, int> mapIndexId;
std::map<int, int> mapIdIndex;
cv::Mat dataTree;
UTimer timer;
timer.start();
int dim = _visualWords.begin()->second->getDescriptor().cols;
int type;
if(_visualWords.begin()->second->getDescriptor().type() == CV_8U)
{
useDistanceL1_ = true;
if(_strategy == kNNFlannKdTree)
{
type = CV_32F;
if(!_byteToFloat)
{
dim *= 8;
}
}
else
{
type = _visualWords.begin()->second->getDescriptor().type();
}
}
else
{
type = _visualWords.begin()->second->getDescriptor().type();
}
UASSERT(type == CV_32F || type == CV_8U);
UASSERT(dim > 0);
// Create the data matrix
dataTree = cv::Mat(_visualWords.size(), dim, type); // SURF descriptors are CV_32F
std::map<int, VisualWord*>::const_iterator iter = _visualWords.begin();
for(unsigned int i=0; i < _visualWords.size(); ++i, ++iter)
{
cv::Mat descriptor;
if(iter->second->getDescriptor().type() == CV_8U)
{
if(_strategy == kNNFlannKdTree)
{
descriptor = convertBinTo32F(iter->second->getDescriptor(), _byteToFloat);
}
else
{
descriptor = iter->second->getDescriptor();
}
}
else
{
descriptor = iter->second->getDescriptor();
}
UASSERT_MSG(descriptor.type() == type, uFormat("%d vs %d", descriptor.type(), type).c_str());
UASSERT_MSG(descriptor.cols == dim, uFormat("%d vs %d", descriptor.cols, dim).c_str());
descriptor.copyTo(dataTree.row(i));
mapIndexId.insert(mapIndexId.end(), std::pair<int, int>(i, iter->second->id()));
mapIdIndex.insert(mapIdIndex.end(), std::pair<int, int>(iter->second->id(), i));
}
ULOGGER_DEBUG("mapIndexId.size() = %d, words.size()=%d, dim=%d", mapIndexId.size(), _visualWords.size(), dim);
ULOGGER_DEBUG("copying data = %f s", timer.ticks());
std::string errorMsg;
if(_flannIndex->loadIndex(
data,
size,
_strategy == kNNFlannNaive ? FlannIndex::FLANN_INDEX_LINEAR:
_strategy == kNNFlannLSH ? FlannIndex::FLANN_INDEX_LSH:
FlannIndex::FLANN_INDEX_KDTREE,
dataTree,
useDistanceL1_,
_incrementalDictionary && _incrementalFlann ? _rebalancingFactor:1,
&errorMsg))
{
_mapIndexId = mapIndexId;
_mapIdIndex = mapIdIndex;
_dataTree = dataTree;
_notIndexedWords.clear();
_modified = false;
}
else {
UWARN("Failed deserializing flann index data (error: %s), the index will be rebuilt on next update.", errorMsg.c_str());
_flannIndex->release(); // reset to initial state
return false;
2025-09-03 16:55:54 -07:00
}
ULOGGER_DEBUG("Time to load flann index = %f s", timer.ticks());
return true;
2025-09-03 16:55:54 -07:00
}
void VWDictionary::clear(bool printWarningsIfNotEmpty)
{
2013-12-11 00:12:44 +00:00
ULOGGER_DEBUG("");
if(printWarningsIfNotEmpty)
2012-06-24 17:19:34 +00:00
{
if(_visualWords.size() && _incrementalDictionary)
{
UWARN("Visual dictionary would be already empty here (%d words still in dictionary).", (int)_visualWords.size());
}
if(_notIndexedWords.size())
{
UWARN("Not indexed words should be empty here (%d words still not indexed)", (int)_notIndexedWords.size());
}
2012-06-24 17:19:34 +00:00
}
for(std::map<int, VisualWord *>::iterator i=_visualWords.begin(); i!=_visualWords.end(); ++i)
{
delete (*i).second;
}
_visualWords.clear();
2013-12-11 00:12:44 +00:00
_notIndexedWords.clear();
_removedIndexedWords.clear();
_totalActiveReferences = 0;
_lastWordId = 0;
2012-06-24 17:19:34 +00:00
_dataTree = cv::Mat();
_mapIndexId.clear();
_mapIdIndex.clear();
2012-06-24 17:19:34 +00:00
_unusedWords.clear();
_flannIndex->release();
2016-02-15 19:35:24 -05:00
useDistanceL1_ = false;
2025-09-03 16:55:54 -07:00
_modified = true;
}
int VWDictionary::getNextId()
{
return ++_lastWordId;
}
bool VWDictionary::addWordRef(int wordId, int signatureId)
{
VisualWord * vw = 0;
vw = uValue(_visualWords, wordId, vw);
if(vw)
{
vw->addRef(signatureId);
_totalActiveReferences += 1;
2013-12-11 00:12:44 +00:00
_unusedWords.erase(vw->id());
return true;
}
else
{
UWARN("Not found word %d (dict size=%d)", wordId, (int)_visualWords.size());
return false;
}
}
void VWDictionary::removeAllWordRef(int wordId, int signatureId)
{
VisualWord * vw = 0;
vw = uValue(_visualWords, wordId, vw);
if(vw)
{
_totalActiveReferences -= vw->removeAllRef(signatureId);
if(vw->getReferences().size() == 0)
{
_unusedWords.insert(std::pair<int, VisualWord*>(vw->id(), vw));
}
}
}
std::list<int> VWDictionary::addNewWords(
const cv::Mat & descriptorsIn,
int signatureId)
{
UDEBUG("id=%d descriptors=%d", signatureId, descriptorsIn.rows);
UTimer timer;
std::list<int> wordIds;
if(descriptorsIn.rows == 0 || descriptorsIn.cols == 0)
{
UERROR("Descriptors size is null!");
return wordIds;
}
if(!_incrementalDictionary && _visualWords.empty())
{
UERROR("Dictionary mode is set to fixed but no words are in it!");
return wordIds;
}
// verify we have the same features
int dim = 0;
int type = -1;
if(_visualWords.size())
{
dim = _visualWords.begin()->second->getDescriptor().cols;
type = _visualWords.begin()->second->getDescriptor().type();
UASSERT(type == CV_32F || type == CV_8U);
}
static std::string moreInfo = uFormat(
"This could happen if the computer doesn't have access to same "
"feature detectors than when the database was created. This could "
"also happen if we enabled \"%s\" but the first frame received "
"was empty, thus features were re-extracted with a different detector "
"than the one used by the odometry.",
Parameters::kMemUseOdomFeatures().c_str());
if(dim && dim != descriptorsIn.cols)
{
UERROR("Descriptors (size=%d) are not the same size as already added words in dictionary (size=%d). %s", descriptorsIn.cols, dim, moreInfo.c_str());
return wordIds;
}
if(type>=0 && type != descriptorsIn.type())
{
UERROR("Descriptors (type=%d) are not the same type as already added words in dictionary (type=%d). %s", descriptorsIn.type(), type, moreInfo.c_str());
return wordIds;
}
// now compare with the actual index
2016-02-15 19:35:24 -05:00
cv::Mat descriptors;
if(descriptorsIn.type() == CV_8U)
{
useDistanceL1_ = true;
if(_strategy == kNNFlannKdTree)
2016-02-15 19:35:24 -05:00
{
descriptors = convertBinTo32F(descriptorsIn, _byteToFloat);
2016-02-15 19:35:24 -05:00
}
else
{
descriptors = descriptorsIn;
}
}
else
{
descriptors = descriptorsIn;
}
dim = 0;
type = -1;
if(_dataTree.rows || _flannIndex->isBuilt())
{
dim = _flannIndex->isBuilt()?_flannIndex->featuresDim():_dataTree.cols;
type = _flannIndex->isBuilt()?_flannIndex->featuresType():_dataTree.type();
2014-11-12 15:25:15 +00:00
UASSERT(type == CV_32F || type == CV_8U);
}
if(dim && dim != descriptors.cols)
{
UERROR("Descriptors (size=%d) are not the same size as already added words in dictionary(size=%d)", descriptors.cols, dim);
return wordIds;
}
2014-11-12 15:25:15 +00:00
if(type>=0 && type != descriptors.type())
{
UERROR("Descriptors (type=%d) are not the same type as already added words in dictionary(type=%d)", descriptors.type(), type);
return wordIds;
}
2013-12-11 00:12:44 +00:00
int dupWordsCountFromDict= 0;
int dupWordsCountFromLast= 0;
unsigned int k=2; // k nearest neighbors
cv::Mat newWords;
std::vector<int> newWordsId;
cv::Mat results;
cv::Mat dists;
std::vector<std::vector<cv::DMatch> > matches;
bool bruteForce = false;
2020-04-23 22:56:42 -04:00
bool isL2NotSqr = false;
UTimer timerLocal;
timerLocal.start();
if(_flannIndex->isBuilt() || (!_dataTree.empty() && _dataTree.rows >= (int)k))
{
//Find nearest neighbors
2026-04-04 19:48:03 -07:00
UDEBUG("newPts.total()=%d _strategy=%d", descriptors.rows, _strategy);
if(_strategy == kNNFlannNaive || _strategy == kNNFlannKdTree || _strategy == kNNFlannLSH)
{
2017-02-05 17:24:45 -05:00
_flannIndex->knnSearch(descriptors, results, dists, k, KNN_CHECKS);
}
else if(_strategy == kNNBruteForce)
{
bruteForce = true;
cv::BFMatcher matcher(descriptors.type()==CV_8U?cv::NORM_HAMMING:cv::NORM_L2SQR);
matcher.knnMatch(descriptors, _dataTree, matches, k);
}
else if(_strategy == kNNBruteForceGPU)
{
bruteForce = true;
2015-06-18 23:00:46 -04:00
#if CV_MAJOR_VERSION < 3
2016-01-21 17:11:57 -05:00
#ifdef HAVE_OPENCV_GPU
cv::gpu::GpuMat newDescriptorsGpu(descriptors);
cv::gpu::GpuMat lastDescriptorsGpu(_dataTree);
if(descriptors.type()==CV_8U)
{
cv::gpu::BruteForceMatcher_GPU<cv::Hamming> gpuMatcher;
gpuMatcher.knnMatch(newDescriptorsGpu, lastDescriptorsGpu, matches, k);
}
else
{
cv::gpu::BruteForceMatcher_GPU<cv::L2<float> > gpuMatcher;
gpuMatcher.knnMatch(newDescriptorsGpu, lastDescriptorsGpu, matches, k);
2020-04-23 22:56:42 -04:00
isL2NotSqr = true;
}
2016-01-21 17:11:57 -05:00
#else
UERROR("Cannot use brute Force GPU because OpenCV is not built with gpu module.");
#endif
2015-06-18 23:00:46 -04:00
#else
#ifdef HAVE_OPENCV_CUDAFEATURES2D
cv::cuda::GpuMat newDescriptorsGpu(descriptors);
cv::cuda::GpuMat lastDescriptorsGpu(_dataTree);
cv::Ptr<cv::cuda::DescriptorMatcher> gpuMatcher;
if(descriptors.type()==CV_8U)
2015-06-18 23:00:46 -04:00
{
gpuMatcher = cv::cuda::DescriptorMatcher::createBFMatcher(cv::NORM_HAMMING);
gpuMatcher->knnMatch(newDescriptorsGpu, lastDescriptorsGpu, matches, k);
2015-06-18 23:00:46 -04:00
}
else
{
gpuMatcher = cv::cuda::DescriptorMatcher::createBFMatcher(cv::NORM_L2);
gpuMatcher->knnMatch(newDescriptorsGpu, lastDescriptorsGpu, matches, k);
2020-04-23 22:56:42 -04:00
isL2NotSqr = true;
2015-06-18 23:00:46 -04:00
}
2016-01-21 17:11:57 -05:00
#else
UERROR("Cannot use brute Force GPU because OpenCV is not built with cuda module.");
2015-06-18 23:00:46 -04:00
#endif
#endif
2012-03-03 01:46:30 +00:00
}
else
{
UFATAL("");
}
// In case of binary descriptors
if(dists.type() == CV_32S)
{
cv::Mat temp;
dists.convertTo(temp, CV_32F);
dists = temp;
}
UDEBUG("Time to find nn = %f s", timerLocal.ticks());
}
// Process results
for(int i = 0; i < descriptors.rows; ++i)
{
std::multimap<float, int> fullResults; // Contains results from the kd-tree search and the naive search in new words
if(!bruteForce && dists.cols)
{
for(int j=0; j<dists.cols; ++j)
{
float d = dists.at<float>(i,j);
int index = results.at<int>(i, j);
if(index<0) {
continue;
2018-07-10 13:09:52 -04:00
}
int id = uValue(_mapIndexId, index);
if(d >= 0.0f && id != 0)
{
fullResults.insert(std::pair<float, int>(d, id));
}
else
{
break;
}
}
}
else if(bruteForce && matches.size())
{
for(unsigned int j=0; j<matches.at(i).size(); ++j)
{
float d = matches.at(i).at(j).distance;
int id = uValue(_mapIndexId, matches.at(i).at(j).trainIdx);
if(d >= 0.0f && id != 0)
{
2020-04-23 22:56:42 -04:00
if(isL2NotSqr)
{
// Make it compatible with L2SQR format of flann
d*=d;
}
fullResults.insert(std::pair<float, int>(d, id));
}
else
{
break;
}
}
}
// Check if this descriptor matches with a word from the last signature (a word not already added to the tree)
if(_newWordsComparedTogether && newWords.rows)
{
std::vector<std::vector<cv::DMatch> > matchesNewWords;
cv::BFMatcher matcher(descriptors.type()==CV_8U?cv::NORM_HAMMING:useDistanceL1_?cv::NORM_L1:cv::NORM_L2SQR);
UASSERT(descriptors.cols == newWords.cols && descriptors.type() == newWords.type());
matcher.knnMatch(descriptors.row(i), newWords, matchesNewWords, newWords.rows>1?2:1);
UASSERT(matchesNewWords.size() == 1);
for(unsigned int j=0; j<matchesNewWords.at(0).size(); ++j)
{
float d = matchesNewWords.at(0).at(j).distance;
int id = newWordsId[matchesNewWords.at(0).at(j).trainIdx];
if(d >= 0.0f && id != 0)
{
fullResults.insert(std::pair<float, int>(d, id));
}
else
{
break;
}
}
}
if(_incrementalDictionary)
{
bool badDist = false;
if(fullResults.size() == 0)
{
badDist = true;
}
if(!badDist)
{
if(fullResults.size() >= 2)
{
// Apply NNDR
if(fullResults.begin()->first > _nndrRatio * (++fullResults.begin())->first)
{
badDist = true; // Rejected
}
}
else
{
badDist = true; // Rejected
}
}
if(badDist)
{
// use original descriptor
VisualWord * vw = new VisualWord(getNextId(), descriptorsIn.row(i), signatureId);
_visualWords.insert(_visualWords.end(), std::pair<int, VisualWord *>(vw->id(), vw));
_notIndexedWords.insert(_notIndexedWords.end(), vw->id());
newWords.push_back(descriptors.row(i));
newWordsId.push_back(vw->id());
wordIds.push_back(vw->id());
UASSERT(vw->id()>0);
}
else
{
if(_notIndexedWords.find(fullResults.begin()->second) != _notIndexedWords.end())
{
++dupWordsCountFromLast;
}
else
{
++dupWordsCountFromDict;
}
this->addWordRef(fullResults.begin()->second, signatureId);
wordIds.push_back(fullResults.begin()->second);
}
}
else if(fullResults.size())
{
// If the dictionary is not incremental, just take the nearest word
++dupWordsCountFromDict;
this->addWordRef(fullResults.begin()->second, signatureId);
wordIds.push_back(fullResults.begin()->second);
UASSERT(fullResults.begin()->second>0);
}
}
ULOGGER_DEBUG("naive search and add ref/words time = %f s", timerLocal.ticks());
ULOGGER_DEBUG("%d new words added...", _notIndexedWords.size());
ULOGGER_DEBUG("%d duplicated words added (from current image = %d)...",
dupWordsCountFromDict+dupWordsCountFromLast, dupWordsCountFromLast);
UDEBUG("total time %fs", timer.ticks());
_totalActiveReferences += _notIndexedWords.size();
return wordIds;
}
std::vector<int> VWDictionary::findNN(const std::list<VisualWord *> & vws) const
{
UTimer timer;
timer.start();
if(_visualWords.size() && vws.size())
{
int type = (*vws.begin())->getDescriptor().type();
int dim = (*vws.begin())->getDescriptor().cols;
if(dim != _visualWords.begin()->second->getDescriptor().cols)
{
UERROR("Descriptors (size=%d) are not the same size as already added words in dictionary(size=%d)", (*vws.begin())->getDescriptor().cols, dim);
return std::vector<int>(vws.size(), 0);
}
if(type != _visualWords.begin()->second->getDescriptor().type())
{
UERROR("Descriptors (type=%d) are not the same type as already added words in dictionary(type=%d)", (*vws.begin())->getDescriptor().type(), type);
return std::vector<int>(vws.size(), 0);
}
// fill the request matrix
int index = 0;
VisualWord * vw;
cv::Mat query(vws.size(), dim, type);
for(std::list<VisualWord *>::const_iterator iter=vws.begin(); iter!=vws.end(); ++iter, ++index)
{
vw = *iter;
UASSERT(vw);
UASSERT(vw->getDescriptor().cols == dim);
UASSERT(vw->getDescriptor().type() == type);
vw->getDescriptor().copyTo(query.row(index));
}
ULOGGER_DEBUG("Preparation time = %fs", timer.ticks());
return findNN(query);
}
return std::vector<int>(vws.size(), 0);
}
std::vector<int> VWDictionary::findNN(const cv::Mat & queryIn) const
{
UTimer timer;
timer.start();
std::vector<int> resultIds(queryIn.rows, 0);
unsigned int k=2; // k nearest neighbor
if(_visualWords.size() && queryIn.rows)
{
// verify we have the same features
int dim = _visualWords.begin()->second->getDescriptor().cols;
int type = _visualWords.begin()->second->getDescriptor().type();
UASSERT(type == CV_32F || type == CV_8U);
if(dim != queryIn.cols)
{
UERROR("Descriptors (size=%d) are not the same size as already added words in dictionary(size=%d)", queryIn.cols, dim);
return resultIds;
}
if(type != queryIn.type())
{
UERROR("Descriptors (type=%d) are not the same type as already added words in dictionary(type=%d)", queryIn.type(), type);
return resultIds;
}
// now compare with the actual index
cv::Mat query;
if(queryIn.type() == CV_8U)
{
if(_strategy == kNNFlannKdTree)
{
query = convertBinTo32F(queryIn, _byteToFloat);
}
else
{
query = queryIn;
}
}
else
{
query = queryIn;
}
dim = 0;
type = -1;
if(_dataTree.rows || _flannIndex->isBuilt())
{
dim = _flannIndex->isBuilt()?_flannIndex->featuresDim():_dataTree.cols;
type = _flannIndex->isBuilt()?_flannIndex->featuresType():_dataTree.type();
UASSERT(type == CV_32F || type == CV_8U);
}
if(dim && dim != query.cols)
{
UERROR("Descriptors (size=%d) are not the same size as already added words in dictionary(size=%d)", query.cols, dim);
return resultIds;
}
if(type>=0 && type != query.type())
{
UERROR("Descriptors (type=%d) are not the same type as already added words in dictionary(type=%d)", query.type(), type);
return resultIds;
}
std::vector<std::vector<cv::DMatch> > matches;
bool bruteForce = false;
2020-04-23 22:56:42 -04:00
bool isL2NotSqr = false;
cv::Mat results;
cv::Mat dists;
if(_flannIndex->isBuilt() || (!_dataTree.empty() && _dataTree.rows >= (int)k))
{
//Find nearest neighbors
UDEBUG("query.rows=%d ", query.rows);
if(_strategy == kNNFlannNaive || _strategy == kNNFlannKdTree || _strategy == kNNFlannLSH)
{
2017-02-05 17:24:45 -05:00
_flannIndex->knnSearch(query, results, dists, k, KNN_CHECKS);
}
else if(_strategy == kNNBruteForce)
{
bruteForce = true;
cv::BFMatcher matcher(query.type()==CV_8U?cv::NORM_HAMMING:cv::NORM_L2SQR);
matcher.knnMatch(query, _dataTree, matches, k);
}
else if(_strategy == kNNBruteForceGPU)
{
bruteForce = true;
2015-06-18 23:00:46 -04:00
#if CV_MAJOR_VERSION < 3
2016-01-21 17:11:57 -05:00
#ifdef HAVE_OPENCV_GPU
cv::gpu::GpuMat newDescriptorsGpu(query);
cv::gpu::GpuMat lastDescriptorsGpu(_dataTree);
if(query.type()==CV_8U)
{
cv::gpu::BruteForceMatcher_GPU<cv::Hamming> gpuMatcher;
gpuMatcher.knnMatch(newDescriptorsGpu, lastDescriptorsGpu, matches, k);
}
else
{
cv::gpu::BruteForceMatcher_GPU<cv::L2<float> > gpuMatcher;
gpuMatcher.knnMatch(newDescriptorsGpu, lastDescriptorsGpu, matches, k);
2020-04-23 22:56:42 -04:00
isL2NotSqr = true;
}
2016-01-21 17:11:57 -05:00
#else
UERROR("Cannot use brute Force GPU because OpenCV is not built with gpu module.");
#endif
2015-06-18 23:00:46 -04:00
#else
#ifdef HAVE_OPENCV_CUDAFEATURES2D
cv::cuda::GpuMat newDescriptorsGpu(query);
cv::cuda::GpuMat lastDescriptorsGpu(_dataTree);
2019-01-28 19:28:24 -05:00
cv::cuda::GpuMat matchesGpu;
2015-06-18 23:00:46 -04:00
cv::Ptr<cv::cuda::DescriptorMatcher> gpuMatcher;
if(query.type()==CV_8U)
2015-06-18 23:00:46 -04:00
{
gpuMatcher = cv::cuda::DescriptorMatcher::createBFMatcher(cv::NORM_HAMMING);
2019-01-28 19:28:24 -05:00
gpuMatcher->knnMatchAsync(newDescriptorsGpu, lastDescriptorsGpu, matchesGpu, k);
2015-06-18 23:00:46 -04:00
}
else
{
gpuMatcher = cv::cuda::DescriptorMatcher::createBFMatcher(cv::NORM_L2);
2019-01-28 19:28:24 -05:00
gpuMatcher->knnMatchAsync(newDescriptorsGpu, lastDescriptorsGpu, matchesGpu, k);
2020-04-23 22:56:42 -04:00
isL2NotSqr = true;
2015-06-18 23:00:46 -04:00
}
2019-01-28 19:28:24 -05:00
gpuMatcher->knnMatchConvert(matchesGpu, matches);
2016-01-21 17:11:57 -05:00
#else
UERROR("Cannot use brute Force GPU because OpenCV is not built with cuda module.");
2015-06-18 23:00:46 -04:00
#endif
#endif
}
else
{
UFATAL("");
}
// In case of binary descriptors
if(dists.type() == CV_32S)
{
cv::Mat temp;
dists.convertTo(temp, CV_32F);
dists = temp;
}
}
ULOGGER_DEBUG("Search dictionary time = %fs", timer.ticks());
std::map<int, int> mapIndexIdNotIndexed;
std::vector<std::vector<cv::DMatch> > matchesNotIndexed;
if(!_notIndexedWords.empty())
{
cv::Mat dataNotIndexed = cv::Mat::zeros(_notIndexedWords.size(), query.cols, query.type());
unsigned int index = 0;
VisualWord * vw;
for(std::set<int>::iterator iter = _notIndexedWords.begin(); iter != _notIndexedWords.end(); ++iter, ++index)
{
vw = _visualWords.at(*iter);
cv::Mat descriptor;
if(vw->getDescriptor().type() == CV_8U)
{
if(_strategy == kNNFlannKdTree)
{
descriptor = convertBinTo32F(vw->getDescriptor(), _byteToFloat);
}
else
{
descriptor = vw->getDescriptor();
}
}
else
{
descriptor = vw->getDescriptor();
}
UASSERT(vw != 0 && descriptor.cols == query.cols && descriptor.type() == query.type());
descriptor.copyTo(dataNotIndexed.row(index));
mapIndexIdNotIndexed.insert(mapIndexIdNotIndexed.end(), std::pair<int,int>(index, vw->id()));
}
// Find nearest neighbor
ULOGGER_DEBUG("Searching in words not indexed...");
cv::BFMatcher matcher(query.type()==CV_8U?cv::NORM_HAMMING:useDistanceL1_?cv::NORM_L1:cv::NORM_L2SQR);
matcher.knnMatch(query, dataNotIndexed, matchesNotIndexed, dataNotIndexed.rows>1?2:1);
}
ULOGGER_DEBUG("Search not yet indexed words time = %fs", timer.ticks());
for(int i=0; i<query.rows; ++i)
{
std::multimap<float, int> fullResults; // Contains results from the kd-tree search [and the naive search in new words]
if(!bruteForce && dists.cols)
{
for(int j=0; j<dists.cols; ++j)
{
float d = dists.at<float>(i,j);
int index = results.at<int>(i, j);
if(index < 0) {
continue;
2018-07-10 13:09:52 -04:00
}
int id = uValue(_mapIndexId, index);
if(d >= 0.0f && id != 0)
{
fullResults.insert(std::pair<float, int>(d, id));
}
}
}
else if(bruteForce && matches.size())
{
for(unsigned int j=0; j<matches.at(i).size(); ++j)
{
float d = matches.at(i).at(j).distance;
int id = uValue(_mapIndexId, matches.at(i).at(j).trainIdx);
if(d >= 0.0f && id != 0)
{
2020-04-23 22:56:42 -04:00
if(isL2NotSqr)
{
// make it compatible with L2SQR from FLANN
d*=d;
}
fullResults.insert(std::pair<float, int>(d, id));
}
}
}
// not indexed..
if(matchesNotIndexed.size())
{
for(unsigned int j=0; j<matchesNotIndexed.at(i).size(); ++j)
{
float d = matchesNotIndexed.at(i).at(j).distance;
int id = uValue(mapIndexIdNotIndexed, matchesNotIndexed.at(i).at(j).trainIdx);
if(d >= 0.0f && id != 0)
{
fullResults.insert(std::pair<float, int>(d, id));
}
else
{
break;
}
}
}
if(_incrementalDictionary)
{
bool badDist = false;
if(fullResults.size() == 0)
{
badDist = true;
}
if(!badDist)
{
if(fullResults.size() >= 2)
{
// Apply NNDR
if(fullResults.begin()->first > _nndrRatio * (++fullResults.begin())->first)
{
badDist = true; // Rejected
}
}
else
{
badDist = true; // Rejected
}
}
if(!badDist)
{
resultIds[i] = fullResults.begin()->second; // Accepted
}
}
else if(fullResults.size())
{
//Just take the nearest if the dictionary is not incremental
resultIds[i] = fullResults.begin()->second; // Accepted
}
}
ULOGGER_DEBUG("badDist check time = %fs", timer.ticks());
}
return resultIds;
}
void VWDictionary::addWord(VisualWord * vw)
{
if(vw)
{
2025-09-03 16:55:54 -07:00
_visualWords.insert(_visualWords.end(), std::pair<int, VisualWord *>(vw->id(), vw));
_notIndexedWords.insert(_notIndexedWords.end(), vw->id());
if(vw->getReferences().size())
{
_totalActiveReferences += uSum(uValues(vw->getReferences()));
}
else
{
2025-09-03 16:55:54 -07:00
_unusedWords.insert(_unusedWords.end(), std::pair<int, VisualWord *>(vw->id(), vw));
}
if(_lastWordId < vw->id())
{
_lastWordId = vw->id();
}
}
}
const VisualWord * VWDictionary::getWord(int id) const
{
2012-03-03 01:46:30 +00:00
return uValue(_visualWords, id, (VisualWord *)0);
}
VisualWord * VWDictionary::getUnusedWord(int id) const
{
2012-03-03 01:46:30 +00:00
return uValue(_unusedWords, id, (VisualWord *)0);
}
std::vector<VisualWord*> VWDictionary::getUnusedWords() const
{
return uValues(_unusedWords);
}
std::vector<int> VWDictionary::getUnusedWordIds() const
{
return uKeys(_unusedWords);
}
void VWDictionary::removeWords(const std::vector<VisualWord*> & words)
{
//UDEBUG("Removing %d words from dictionary (current size=%d)", (int)words.size(), (int)_visualWords.size());
for(unsigned int i=0; i<words.size(); ++i)
{
_visualWords.erase(words[i]->id());
_unusedWords.erase(words[i]->id());
2013-12-11 00:12:44 +00:00
if(_notIndexedWords.erase(words[i]->id()) == 0)
{
_removedIndexedWords.insert(words[i]->id());
}
}
}
2014-11-12 15:25:15 +00:00
void VWDictionary::deleteUnusedWords()
{
std::vector<VisualWord*> unusedWords = uValues(_unusedWords);
removeWords(unusedWords);
for(unsigned int i=0; i<unusedWords.size(); ++i)
{
delete unusedWords[i];
}
}
void VWDictionary::exportDictionary(const char * fileNameReferences, const char * fileNameDescriptors) const
{
2016-05-05 11:53:09 -04:00
UDEBUG("");
2016-02-15 19:35:24 -05:00
if(_visualWords.empty())
{
UWARN("Dictionary is empty, cannot export it!");
return;
}
2016-05-05 11:53:09 -04:00
if(_visualWords.begin()->second->getDescriptor().type() != CV_32FC1)
2016-02-15 19:35:24 -05:00
{
UERROR("Exporting binary descriptors is not implemented!");
return;
}
FILE* foutRef = 0;
FILE* foutDesc = 0;
#ifdef _MSC_VER
fopen_s(&foutRef, fileNameReferences, "w");
fopen_s(&foutDesc, fileNameDescriptors, "w");
#else
foutRef = fopen(fileNameReferences, "w");
foutDesc = fopen(fileNameDescriptors, "w");
#endif
if(foutRef)
{
fprintf(foutRef, "WordID SignaturesID...\n");
}
if(foutDesc)
{
if(_visualWords.begin() == _visualWords.end())
{
fprintf(foutDesc, "WordID Descriptors...\n");
}
else
{
2016-05-05 11:53:09 -04:00
UDEBUG("");
fprintf(foutDesc, "WordID Descriptors...%d\n", (*_visualWords.begin()).second->getDescriptor().cols);
}
}
2016-05-05 11:53:09 -04:00
UDEBUG("Export %d words...", _visualWords.size());
for(std::map<int, VisualWord *>::const_iterator iter=_visualWords.begin(); iter!=_visualWords.end(); ++iter)
{
// References
if(foutRef)
{
fprintf(foutRef, "%d ", (*iter).first);
const std::map<int, int> ref = (*iter).second->getReferences();
for(std::map<int, int>::const_iterator jter=ref.begin(); jter!=ref.end(); ++jter)
{
for(int i=0; i<(*jter).second; ++i)
{
fprintf(foutRef, "%d ", (*jter).first);
}
}
fprintf(foutRef, "\n");
}
//Descriptors
if(foutDesc)
{
fprintf(foutDesc, "%d ", (*iter).first);
const float * desc = (const float *)(*iter).second->getDescriptor().data;
int dim = (*iter).second->getDescriptor().cols;
for(int i=0; i<dim; i++)
{
fprintf(foutDesc, "%f ", desc[i]);
}
fprintf(foutDesc, "\n");
}
}
if(foutRef)
fclose(foutRef);
if(foutDesc)
fclose(foutDesc);
}
} // namespace rtabmap