Add FlannIndex abstract interface and implement NanoFlannIndex subclass (#1744)

* Add FlannIndex abstract interface and implement NanoFlannIndex subclass

* Refactored: made NanoFlann a new NN type instead of inheriting FlannIndex. Added tests. Vendoring nanoflann.h directly in the repo. RegistrationVis now use NANOFLANN_INDEX_KDTREE_SINGLE (instead of FLANN_INDEX_KDTREE_SINGLE) flann index for 2d points matching.

* cleanup comments, added FlannIndex doxygen

* Fixing windows tests

* updating flaky test

* Simplified interface, added flann kdtree single approach selectable by parameters.

* RegVis: symmetry of nanoflann for two branches of guess feature matching

* cv::BFMatcher baseline

* Small cmake optimization FLANN_KDTREE_MEM_OPT only defined for FlannIndex

* Refactored where FLANN_KDTREE_MEM_OPT is defined

* fixed file name already exist

* cleanup

* fixup build

---------

Co-authored-by: matlabbe <[email protected]>
This commit is contained in:
Muhammad
2026-08-16 09:51:39 -07:00
committed by GitHub
co-authored by matlabbe
parent df52523a0c
commit f647014f54
28 changed files with 7537 additions and 179 deletions
+69 -28
View File
@@ -56,6 +56,43 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
namespace rtabmap
{
// Whether the strategy searches with a FlannIndex, as opposed to the brute
// force ones matching against the _dataTree matrix.
static bool isFlannStrategy(VWDictionary::NNStrategy strategy)
{
return strategy == VWDictionary::kNNFlannNaive ||
strategy == VWDictionary::kNNFlannKdTree ||
strategy == VWDictionary::kNNFlannLSH ||
strategy == VWDictionary::kNNNanoFlannKdTree ||
strategy == VWDictionary::kNNFlannKdTreeSingle;
}
// Whether the strategy indexes float descriptors in a kd-tree, in which case
// binary descriptors have to be converted first.
static bool isKdTreeStrategy(VWDictionary::NNStrategy strategy)
{
return strategy == VWDictionary::kNNFlannKdTree ||
strategy == VWDictionary::kNNNanoFlannKdTree ||
strategy == VWDictionary::kNNFlannKdTreeSingle;
}
static FlannIndex::flann_algorithm_t flannAlgorithm(VWDictionary::NNStrategy strategy)
{
switch(strategy)
{
case VWDictionary::kNNFlannNaive:
return FlannIndex::FLANN_INDEX_LINEAR;
case VWDictionary::kNNFlannLSH:
return FlannIndex::FLANN_INDEX_LSH;
case VWDictionary::kNNNanoFlannKdTree:
return FlannIndex::NANOFLANN_INDEX_KDTREE_SINGLE;
case VWDictionary::kNNFlannKdTreeSingle:
return FlannIndex::FLANN_INDEX_KDTREE_SINGLE;
default:
return FlannIndex::FLANN_INDEX_KDTREE; // kNNFlannKdTree
}
}
const int VWDictionary::ID_START = 1;
const int VWDictionary::ID_INVALID = 0;
@@ -115,7 +152,17 @@ void VWDictionary::parseParameters(const ParametersMap & parameters)
NNStrategy nnStrategy = (NNStrategy)std::atoi((*iter).second.c_str());
treeUpdated = this->setNNStrategy(nnStrategy);
}
if(!treeUpdated && byteToFloat!=_byteToFloat && _strategy == kNNFlannKdTree)
if(_strategy == kNNFlannKdTreeSingle && _incrementalDictionary && _incrementalFlann)
{
UWARN("%s=%d (%s) rebuilds its whole index every time a word is added, which "
"is very slow with %s=true. It is meant for an index built once and "
"searched once, like the one matching the features of two frames.",
Parameters::kKpNNStrategy().c_str(), (int)_strategy,
nnStrategyName(_strategy).c_str(),
Parameters::kKpIncrementalFlann().c_str());
}
if(!treeUpdated && byteToFloat!=_byteToFloat && isKdTreeStrategy(_strategy))
{
UINFO("KDTree: Binary to Float conversion approach has changed, re-initialize kd-tree.");
this->rebuildIndex();
@@ -392,7 +439,7 @@ unsigned long VWDictionary::getMemoryUsed() const
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)
isKdTreeStrategy(_strategy))
{
// Binary descriptors were converted to float, and not included in _dataTree
memoryUsage += _visualWords.size() * _visualWords.begin()->second->getDescriptor().total() * sizeof(float) * (_byteToFloat?1:8);
@@ -507,7 +554,7 @@ void VWDictionary::update()
if(!firstUpdate &&
_incrementalFlann &&
_strategy < kNNBruteForce &&
isFlannStrategy(_strategy) &&
_visualWords.size())
{
ULOGGER_DEBUG("Incremental FLANN: Removing %d words...", (int)_removedIndexedWords.size());
@@ -535,7 +582,7 @@ void VWDictionary::update()
if(w->getDescriptor().type() == CV_8U)
{
useDistanceL1_ = true;
if(_strategy == kNNFlannKdTree)
if(isKdTreeStrategy(_strategy))
{
descriptor = convertBinTo32F(w->getDescriptor(), _byteToFloat);
}
@@ -555,9 +602,7 @@ void VWDictionary::update()
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
flannAlgorithm(_strategy),
descriptor, useDistanceL1_, _rebalancingFactor);
UDEBUG("Building FLANN index... done!");
}
@@ -577,7 +622,7 @@ void VWDictionary::update()
ULOGGER_DEBUG("Incremental FLANN: Inserting %d words... done! (in %f s)", (int)_notIndexedWords.size(), timer.ticks());
}
}
else if(_strategy >= kNNBruteForce &&
else if(!isFlannStrategy(_strategy) &&
_notIndexedWords.size() &&
_removedIndexedWords.size() == 0 &&
_visualWords.size())
@@ -587,8 +632,8 @@ void VWDictionary::update()
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);
"assert on an IMGIDX_ONE check when adding new words. Use a FLANN strategy instead (e.g. %s=%d).",
Parameters::kKpNNStrategy().c_str(), _strategy, _dataTree.rows, IMGIDX_ONE, Parameters::kKpNNStrategy().c_str(), kNNFlannKdTree);
}
//just add not indexed words
@@ -633,7 +678,7 @@ void VWDictionary::update()
if(_visualWords.begin()->second->getDescriptor().type() == CV_8U)
{
useDistanceL1_ = true;
if(_strategy == kNNFlannKdTree)
if(isKdTreeStrategy(_strategy))
{
type = CV_32F;
if(!_byteToFloat)
@@ -662,7 +707,7 @@ void VWDictionary::update()
cv::Mat descriptor;
if(iter->second->getDescriptor().type() == CV_8U)
{
if(_strategy == kNNFlannKdTree)
if(isKdTreeStrategy(_strategy))
{
descriptor = convertBinTo32F(iter->second->getDescriptor(), _byteToFloat);
}
@@ -687,12 +732,10 @@ void VWDictionary::update()
ULOGGER_DEBUG("_mapIndexId.size() = %d, words.size()=%d, _dim=%d",(int)_mapIndexId.size(), (int)_visualWords.size(), dim);
ULOGGER_DEBUG("copying data = %f s", timer.ticks());
if(_strategy < kNNBruteForce)
if(isFlannStrategy(_strategy))
{
_flannIndex->buildIndex(
_strategy == kNNFlannNaive ? FlannIndex::FLANN_INDEX_LINEAR:
_strategy == kNNFlannLSH ? FlannIndex::FLANN_INDEX_LSH:
FlannIndex::FLANN_INDEX_KDTREE, // kNNFlannKdTree
flannAlgorithm(_strategy),
_dataTree,
useDistanceL1_,
_incrementalDictionary&&_incrementalFlann?_rebalancingFactor:1);
@@ -714,7 +757,7 @@ void VWDictionary::update()
std::vector<unsigned char> VWDictionary::serializeIndex() const
{
if(_strategy >= kNNBruteForce) {
if(!isFlannStrategy(_strategy)) {
UINFO("Not flann strategy, ignoring serialization...");
return std::vector<unsigned char>();
}
@@ -739,7 +782,7 @@ bool VWDictionary::deserializeIndex(const unsigned char * data, size_t size)
return false;
}
UDEBUG("Loading flann index... (data size=%ld bytes)", size);
if(_strategy >= kNNBruteForce) {
if(!isFlannStrategy(_strategy)) {
//ignore
return false;
}
@@ -772,7 +815,7 @@ bool VWDictionary::deserializeIndex(const unsigned char * data, size_t size)
if(_visualWords.begin()->second->getDescriptor().type() == CV_8U)
{
useDistanceL1_ = true;
if(_strategy == kNNFlannKdTree)
if(isKdTreeStrategy(_strategy))
{
type = CV_32F;
if(!_byteToFloat)
@@ -801,7 +844,7 @@ bool VWDictionary::deserializeIndex(const unsigned char * data, size_t size)
cv::Mat descriptor;
if(iter->second->getDescriptor().type() == CV_8U)
{
if(_strategy == kNNFlannKdTree)
if(isKdTreeStrategy(_strategy))
{
descriptor = convertBinTo32F(iter->second->getDescriptor(), _byteToFloat);
}
@@ -830,9 +873,7 @@ bool VWDictionary::deserializeIndex(const unsigned char * data, size_t size)
if(_flannIndex->loadIndex(
data,
size,
_strategy == kNNFlannNaive ? FlannIndex::FLANN_INDEX_LINEAR:
_strategy == kNNFlannLSH ? FlannIndex::FLANN_INDEX_LSH:
FlannIndex::FLANN_INDEX_KDTREE,
flannAlgorithm(_strategy),
dataTree,
useDistanceL1_,
_incrementalDictionary && _incrementalFlann ? _rebalancingFactor:1,
@@ -975,7 +1016,7 @@ std::list<int> VWDictionary::addNewWords(
if(descriptorsIn.type() == CV_8U)
{
useDistanceL1_ = true;
if(_strategy == kNNFlannKdTree)
if(isKdTreeStrategy(_strategy))
{
descriptors = convertBinTo32F(descriptorsIn, _byteToFloat);
}
@@ -1031,7 +1072,7 @@ std::list<int> VWDictionary::addNewWords(
//Find nearest neighbors
UDEBUG("newPts.total()=%d _strategy=%d", descriptors.rows, _strategy);
if(_strategy == kNNFlannNaive || _strategy == kNNFlannKdTree || _strategy == kNNFlannLSH)
if(isFlannStrategy(_strategy))
{
_flannIndex->knnSearch(descriptors, results, dists, k, KNN_CHECKS);
}
@@ -1308,7 +1349,7 @@ std::vector<int> VWDictionary::findNN(const cv::Mat & queryIn) const
cv::Mat query;
if(queryIn.type() == CV_8U)
{
if(_strategy == kNNFlannKdTree)
if(isKdTreeStrategy(_strategy))
{
query = convertBinTo32F(queryIn, _byteToFloat);
}
@@ -1353,7 +1394,7 @@ std::vector<int> VWDictionary::findNN(const cv::Mat & queryIn) const
//Find nearest neighbors
UDEBUG("query.rows=%d ", query.rows);
if(_strategy == kNNFlannNaive || _strategy == kNNFlannKdTree || _strategy == kNNFlannLSH)
if(isFlannStrategy(_strategy))
{
_flannIndex->knnSearch(query, results, dists, k, KNN_CHECKS);
}
@@ -1436,7 +1477,7 @@ std::vector<int> VWDictionary::findNN(const cv::Mat & queryIn) const
cv::Mat descriptor;
if(vw->getDescriptor().type() == CV_8U)
{
if(_strategy == kNNFlannKdTree)
if(isKdTreeStrategy(_strategy))
{
descriptor = convertBinTo32F(vw->getDescriptor(), _byteToFloat);
}