ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
visual_index.h
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
8 #pragma once
9 
10 #include <Eigen/Core>
11 #include <boost/heap/fibonacci_heap.hpp>
12 
13 #include "FLANN/flann.hpp"
14 #include "feature/types.h"
18 #include "util/alignment.h"
19 #include "util/endian.h"
20 #include "util/logging.h"
21 #include "util/math.h"
22 
23 namespace colmap {
24 namespace retrieval {
25 
26 // Visual index for image retrieval using a vocabulary tree with Hamming
27 // embedding, based on the papers:
28 //
29 // Schönberger, Price, Sattler, Pollefeys, Frahm. "A Vote-and-Verify Strategy
30 // for Fast Spatial Verification in Image Retrieval". ACCV 2016.
31 //
32 // Arandjelovic, Zisserman: Scalable descriptor
33 // distinctiveness for location recognition. ACCV 2014.
34 template <typename kDescType = uint8_t,
35  int kDescDim = 128,
36  int kEmbeddingDim = 64>
37 class VisualIndex {
38 public:
39  static const int kMaxNumThreads = -1;
44 
45  struct IndexOptions {
46  // The number of nearest neighbor visual words that each feature
47  // descriptor is assigned to.
48  int num_neighbors = 1;
49 
50  // The number of checks in the nearest neighbor search.
51  int num_checks = 256;
52 
53  // The number of threads used in the index.
55  };
56 
57  struct QueryOptions {
58  // The maximum number of most similar images to retrieve.
59  int max_num_images = -1;
60 
61  // The number of nearest neighbor visual words that each feature
62  // descriptor is assigned to.
63  int num_neighbors = 5;
64 
65  // The number of checks in the nearest neighbor search.
66  int num_checks = 256;
67 
68  // Whether to perform spatial verification after image retrieval.
70 
71  // The number of threads used in the index.
73  };
74 
75  struct BuildOptions {
76  // The desired number of visual words, i.e. the number of leaf node
77  // clusters. Note that the actual number of visual words might be less.
78  int num_visual_words = 256 * 256;
79 
80  // The branching factor of the hierarchical k-means tree.
81  int branching = 256;
82 
83  // The number of iterations for the clustering.
84  int num_iterations = 11;
85 
86  // The target precision of the visual word search index.
87  double target_precision = 0.95;
88 
89  // The number of checks in the nearest neighbor search.
90  int num_checks = 256;
91 
92  // The number of threads used in the index.
94  };
95 
96  VisualIndex();
97  ~VisualIndex();
98 
99  size_t NumVisualWords() const;
100 
101  // Add image to the visual index.
102  void Add(const IndexOptions& options,
103  const int image_id,
104  const GeomType& geometries,
105  const DescType& descriptors);
106 
107  // Check if an image has been indexed.
108  bool ImageIndexed(const int image_id) const;
109 
110  // Query for most similar images in the visual index.
111  void Query(const QueryOptions& options,
112  const DescType& descriptors,
113  std::vector<ImageScore>* image_scores) const;
114 
115  // Query for most similar images in the visual index.
116  void Query(const QueryOptions& options,
117  const GeomType& geometries,
118  const DescType& descriptors,
119  std::vector<ImageScore>* image_scores) const;
120 
121  // Prepare the index after adding images and before querying.
122  void Prepare();
123 
124  // Build a visual index from a set of training descriptors by quantizing the
125  // descriptor space into visual words and compute their Hamming embedding.
126  void Build(const BuildOptions& options, const DescType& descriptors);
127 
128  // Read and write the visual index. This can be done for an index with and
129  // without indexed images.
130  void Read(const std::string& path);
131  void Write(const std::string& path);
132 
133 private:
134  // Quantize the descriptor space into visual words.
135  void Quantize(const BuildOptions& options, const DescType& descriptors);
136 
137  // Query for nearest neighbor images and return nearest neighbor visual word
138  // identifiers for each descriptor.
139  void QueryAndFindWordIds(const QueryOptions& options,
140  const DescType& descriptors,
141  std::vector<ImageScore>* image_scores,
142  Eigen::MatrixXi* word_ids) const;
143 
144  // Find the nearest neighbor visual words for the given descriptors.
145  Eigen::MatrixXi FindWordIds(const DescType& descriptors,
146  const int num_neighbors,
147  const int num_checks,
148  const int num_threads) const;
149 
150  // The search structure on the quantized descriptor space.
151  flann::AutotunedIndex<flann::L2<kDescType>> visual_word_index_;
152 
153  // The centroids of the visual words.
154  flann::Matrix<kDescType> visual_words_;
155 
156  // The inverted index of the database.
157  InvertedIndexType inverted_index_;
158 
159  // Identifiers of all indexed images.
160  std::unordered_set<int> image_ids_;
161 
162  // Whether the index is prepared.
163  bool prepared_;
164 };
165 
167 // Implementation
169 
170 template <typename kDescType, int kDescDim, int kEmbeddingDim>
172  : prepared_(false) {}
173 
174 template <typename kDescType, int kDescDim, int kEmbeddingDim>
176  if (visual_words_.ptr() != nullptr) {
177  delete[] visual_words_.ptr();
178  }
179 }
180 
181 template <typename kDescType, int kDescDim, int kEmbeddingDim>
183  return visual_words_.rows;
184 }
185 
186 template <typename kDescType, int kDescDim, int kEmbeddingDim>
188  const IndexOptions& options,
189  const int image_id,
190  const GeomType& geometries,
191  const DescType& descriptors) {
192  CHECK_EQ(geometries.size(), descriptors.rows());
193 
194  // If the image is already indexed, do nothing.
195  if (ImageIndexed(image_id)) {
196  return;
197  }
198 
199  image_ids_.insert(image_id);
200 
201  prepared_ = false;
202 
203  if (descriptors.rows() == 0) {
204  return;
205  }
206 
207  const Eigen::MatrixXi word_ids =
208  FindWordIds(descriptors, options.num_neighbors, options.num_checks,
209  options.num_threads);
210 
211  for (typename DescType::Index i = 0; i < descriptors.rows(); ++i) {
212  const auto& descriptor = descriptors.row(i);
213 
214  typename InvertedIndexType::GeomType geometry;
215  geometry.x = geometries[i].x;
216  geometry.y = geometries[i].y;
217  geometry.scale = geometries[i].ComputeScale();
218  geometry.orientation = geometries[i].ComputeOrientation();
219 
220  for (int n = 0; n < options.num_neighbors; ++n) {
221  const int word_id = word_ids(i, n);
222  if (word_id != InvertedIndexType::kInvalidWordId) {
223  inverted_index_.AddEntry(image_id, word_id, i, descriptor,
224  geometry);
225  }
226  }
227  }
228 }
229 
230 template <typename kDescType, int kDescDim, int kEmbeddingDim>
232  const int image_id) const {
233  return image_ids_.count(image_id) != 0;
234 }
235 
236 template <typename kDescType, int kDescDim, int kEmbeddingDim>
238  const QueryOptions& options,
239  const DescType& descriptors,
240  std::vector<ImageScore>* image_scores) const {
241  const GeomType geometries;
242  Query(options, geometries, descriptors, image_scores);
243 }
244 
245 template <typename kDescType, int kDescDim, int kEmbeddingDim>
247  const QueryOptions& options,
248  const GeomType& geometries,
249  const DescType& descriptors,
250  std::vector<ImageScore>* image_scores) const {
251  Eigen::MatrixXi word_ids;
252  QueryAndFindWordIds(options, descriptors, image_scores, &word_ids);
253 
254  if (options.num_images_after_verification <= 0) {
255  return;
256  }
257 
258  CHECK_EQ(descriptors.rows(), geometries.size());
259 
260  // Extract top-ranked images to verify.
261  std::unordered_set<int> image_ids;
262  for (const auto& image_score : *image_scores) {
263  image_ids.insert(image_score.image_id);
264  }
265 
266  // Find matches for top-ranked images
267  typedef std::vector<
268  std::pair<float, std::pair<const EntryType*, const EntryType*>>>
269  OrderedMatchListType;
270 
271  // Reference our matches (with their lowest distance) for both
272  // {query feature => db feature} and vice versa.
273  std::unordered_map<int, std::unordered_map<int, OrderedMatchListType>>
274  query_to_db_matches;
275  std::unordered_map<int, std::unordered_map<int, OrderedMatchListType>>
276  db_to_query_matches;
277 
278  std::vector<const EntryType*> word_matches;
279 
280  std::vector<EntryType> query_entries; // Convert query features, too.
281  query_entries.reserve(descriptors.rows());
282 
283  // NOTE: Currently, we are redundantly computing the feature weighting.
284  const HammingDistWeightFunctor<kEmbeddingDim> hamming_dist_weight_functor;
285 
286  for (typename DescType::Index i = 0; i < descriptors.rows(); ++i) {
287  const auto& descriptor = descriptors.row(i);
288 
289  EntryType query_entry;
290  query_entry.feature_idx = i;
291  query_entry.geometry.x = geometries[i].x;
292  query_entry.geometry.y = geometries[i].y;
293  query_entry.geometry.scale = geometries[i].ComputeScale();
294  query_entry.geometry.orientation = geometries[i].ComputeOrientation();
295  query_entries.push_back(query_entry);
296 
297  // For each db feature, keep track of the lowest distance (if db
298  // features are mapped to more than one visual word).
299  std::unordered_map<
300  int,
301  std::unordered_map<int, std::pair<float, const EntryType*>>>
302  image_matches;
303 
304  for (int j = 0; j < word_ids.cols(); ++j) {
305  const int word_id = word_ids(i, j);
306 
307  if (word_id != InvertedIndexType::kInvalidWordId) {
308  inverted_index_.ConvertToBinaryDescriptor(
309  word_id, descriptor, &query_entries[i].descriptor);
310 
311  const auto idf_weight = inverted_index_.GetIDFWeight(word_id);
312  const auto squared_idf_weight = idf_weight * idf_weight;
313 
314  inverted_index_.FindMatches(word_id, image_ids, &word_matches);
315 
316  for (const auto& match : word_matches) {
317  const size_t hamming_dist =
318  (query_entries[i].descriptor ^ match->descriptor)
319  .count();
320 
321  if (hamming_dist <=
322  hamming_dist_weight_functor.kMaxHammingDistance) {
323  const float dist =
324  hamming_dist_weight_functor(hamming_dist) *
325  squared_idf_weight;
326 
327  auto& feature_matches = image_matches[match->image_id];
328  const auto feature_match =
329  feature_matches.find(match->feature_idx);
330 
331  if (feature_match == feature_matches.end() ||
332  feature_match->first < dist) {
333  feature_matches[match->feature_idx] =
334  std::make_pair(dist, match);
335  }
336  }
337  }
338  }
339  }
340 
341  // Finally, cross-reference the query and db feature matches.
342  for (const auto& feature_matches : image_matches) {
343  const auto image_id = feature_matches.first;
344 
345  for (const auto& feature_match : feature_matches.second) {
346  const auto feature_idx = feature_match.first;
347  const auto dist = feature_match.second.first;
348  const auto db_match = feature_match.second.second;
349 
350  const auto entry_pair =
351  std::make_pair(&query_entries[i], db_match);
352 
353  query_to_db_matches[image_id][i].emplace_back(dist, entry_pair);
354  db_to_query_matches[image_id][feature_idx].emplace_back(
355  dist, entry_pair);
356  }
357  }
358  }
359 
360  // Verify top-ranked images using the found matches.
361  for (auto& image_score : *image_scores) {
362  auto& query_matches = query_to_db_matches[image_score.image_id];
363  auto& db_matches = db_to_query_matches[image_score.image_id];
364 
365  // No matches found.
366  if (query_matches.empty()) {
367  continue;
368  }
369 
370  // Enforce 1-to-1 matching: Build Fibonacci heaps for the query and
371  // database features, ordered by the minimum number of matches per
372  // feature. We'll select these matches one at a time. For convenience,
373  // we'll also pre-sort the matched feature lists by matching score.
374 
375  typedef boost::heap::fibonacci_heap<std::pair<int, int>>
376  FibonacciHeapType;
377  FibonacciHeapType query_heap;
378  FibonacciHeapType db_heap;
379  std::unordered_map<int, typename FibonacciHeapType::handle_type>
380  query_heap_handles;
381  std::unordered_map<int, typename FibonacciHeapType::handle_type>
382  db_heap_handles;
383 
384  for (auto& match_data : query_matches) {
385  std::sort(match_data.second.begin(), match_data.second.end(),
386  std::greater<
387  std::pair<float, std::pair<const EntryType*,
388  const EntryType*>>>());
389 
390  query_heap_handles[match_data.first] = query_heap.push(
391  std::make_pair(-static_cast<int>(match_data.second.size()),
392  match_data.first));
393  }
394 
395  for (auto& match_data : db_matches) {
396  std::sort(match_data.second.begin(), match_data.second.end(),
397  std::greater<
398  std::pair<float, std::pair<const EntryType*,
399  const EntryType*>>>());
400 
401  db_heap_handles[match_data.first] = db_heap.push(
402  std::make_pair(-static_cast<int>(match_data.second.size()),
403  match_data.first));
404  }
405 
406  // Keep tabs on what features have been already matched.
407  std::vector<FeatureGeometryMatch> matches;
408 
409  auto db_top = db_heap.top(); // (-num_available_matches, feature_idx)
410  auto query_top = query_heap.top();
411 
412  while (!db_heap.empty() && !query_heap.empty()) {
413  // Take the query or database feature with the smallest number of
414  // available matches.
415  const bool use_query =
416  (query_top.first >= db_top.first) && !query_heap.empty();
417 
418  // Find the best matching feature that hasn't already been matched.
419  auto& heap1 = (use_query) ? query_heap : db_heap;
420  auto& heap2 = (use_query) ? db_heap : query_heap;
421  auto& handles1 = (use_query) ? query_heap_handles : db_heap_handles;
422  auto& handles2 = (use_query) ? db_heap_handles : query_heap_handles;
423  auto& matches1 = (use_query) ? query_matches : db_matches;
424  auto& matches2 = (use_query) ? db_matches : query_matches;
425 
426  const auto idx1 = heap1.top().second;
427  heap1.pop();
428 
429  // Entries that have been matched (or processed and subsequently
430  // ignored) get their handles removed.
431  if (handles1.count(idx1) > 0) {
432  handles1.erase(idx1);
433 
434  bool match_found = false;
435 
436  // The matches have been ordered by Hamming distance, already --
437  // select the lowest available match.
438  for (auto& entry2 : matches1[idx1]) {
439  const auto idx2 =
440  (use_query) ? entry2.second.second->feature_idx
441  : entry2.second.first->feature_idx;
442 
443  if (handles2.count(idx2) > 0) {
444  if (!match_found) {
445  match_found = true;
446  FeatureGeometryMatch match;
447  match.geometry1 = entry2.second.first->geometry;
448  match.geometries2.push_back(
449  entry2.second.second->geometry);
450  matches.push_back(match);
451 
452  handles2.erase(idx2);
453 
454  // Remove this feature from consideration for all
455  // other features that matched to it.
456  for (auto& entry1 : matches2[idx2]) {
457  const auto other_idx1 =
458  (use_query) ? entry1.second.first
459  ->feature_idx
460  : entry1.second.second
461  ->feature_idx;
462  if (handles1.count(other_idx1) > 0) {
463  (*handles1[other_idx1]).first += 1;
464  heap1.increase(handles1[other_idx1]);
465  }
466  }
467  } else {
468  (*handles2[idx2]).first += 1;
469  heap2.increase(handles2[idx2]);
470  }
471  }
472  }
473  }
474 
475  if (!query_heap.empty()) {
476  query_top = query_heap.top();
477  }
478 
479  if (!db_heap.empty()) {
480  db_top = db_heap.top();
481  }
482  }
483 
484  // Finally, run verification for the current image.
485  VoteAndVerifyOptions vote_and_verify_options;
486  image_score.score += VoteAndVerify(vote_and_verify_options, matches);
487  }
488 
489  // Re-rank the images using the spatial verification scores.
490 
491  const size_t num_images = std::min<size_t>(
492  image_scores->size(), options.num_images_after_verification);
493 
494  auto SortFunc = [](const ImageScore& score1, const ImageScore& score2) {
495  return score1.score > score2.score;
496  };
497 
498  if (num_images == image_scores->size()) {
499  std::sort(image_scores->begin(), image_scores->end(), SortFunc);
500  } else {
501  std::partial_sort(image_scores->begin(),
502  image_scores->begin() + num_images,
503  image_scores->end(), SortFunc);
504  image_scores->resize(num_images);
505  }
506 }
507 
508 template <typename kDescType, int kDescDim, int kEmbeddingDim>
510  inverted_index_.Finalize();
511  prepared_ = true;
512 }
513 
514 template <typename kDescType, int kDescDim, int kEmbeddingDim>
516  const BuildOptions& options, const DescType& descriptors) {
517  // Quantize the descriptor space into visual words.
518  Quantize(options, descriptors);
519 
520  // Build the search index on the visual words.
521  flann::AutotunedIndexParams index_params;
522  index_params["target_precision"] =
523  static_cast<float>(options.target_precision);
524  visual_word_index_ =
525  flann::AutotunedIndex<flann::L2<kDescType>>(index_params);
526  visual_word_index_.buildIndex(visual_words_);
527 
528  // Initialize a new inverted index.
529  inverted_index_ = InvertedIndexType();
530  inverted_index_.Initialize(NumVisualWords());
531 
532  // Generate descriptor projection matrix.
533  inverted_index_.GenerateHammingEmbeddingProjection();
534 
535  // Learn the Hamming embedding.
536  const int kNumNeighbors = 1;
537  const Eigen::MatrixXi word_ids =
538  FindWordIds(descriptors, kNumNeighbors, options.num_checks,
539  options.num_threads);
540  inverted_index_.ComputeHammingEmbedding(descriptors, word_ids);
541 }
542 
543 template <typename kDescType, int kDescDim, int kEmbeddingDim>
545  const std::string& path) {
546  long int file_offset = 0;
547 
548  // Read the visual words.
549 
550  {
551  if (visual_words_.ptr() != nullptr) {
552  delete[] visual_words_.ptr();
553  }
554 
555  std::ifstream file(path, std::ios::binary);
556  CHECK(file.is_open()) << path;
557  const uint64_t rows = ReadBinaryLittleEndian<uint64_t>(&file);
558  const uint64_t cols = ReadBinaryLittleEndian<uint64_t>(&file);
559  kDescType* visual_words_data = new kDescType[rows * cols];
560  for (size_t i = 0; i < rows * cols; ++i) {
561  visual_words_data[i] = ReadBinaryLittleEndian<kDescType>(&file);
562  }
563  visual_words_ = flann::Matrix<kDescType>(visual_words_data, rows, cols);
564  file_offset = file.tellg();
565  }
566 
567  // Read the visual words search index.
568 
569  visual_word_index_ =
570  flann::AutotunedIndex<flann::L2<kDescType>>(visual_words_);
571 
572  {
573  FILE* fin = fopen(path.c_str(), "rb");
574  CHECK_NOTNULL(fin);
575  fseek(fin, file_offset, SEEK_SET);
576  visual_word_index_.loadIndex(fin);
577  file_offset = ftell(fin);
578  fclose(fin);
579  }
580 
581  // Read the inverted index.
582 
583  {
584  std::ifstream file(path, std::ios::binary);
585  CHECK(file.is_open()) << path;
586  file.seekg(file_offset, std::ios::beg);
587  inverted_index_.Read(&file);
588  }
589 
590  image_ids_.clear();
591  inverted_index_.GetImageIds(&image_ids_);
592 }
593 
594 template <typename kDescType, int kDescDim, int kEmbeddingDim>
596  const std::string& path) {
597  // Write the visual words.
598 
599  {
600  CHECK_NOTNULL(visual_words_.ptr());
601  std::ofstream file(path, std::ios::binary);
602  CHECK(file.is_open()) << path;
603  WriteBinaryLittleEndian<uint64_t>(&file, visual_words_.rows);
604  WriteBinaryLittleEndian<uint64_t>(&file, visual_words_.cols);
605  for (size_t i = 0; i < visual_words_.rows * visual_words_.cols; ++i) {
606  WriteBinaryLittleEndian<kDescType>(&file, visual_words_.ptr()[i]);
607  }
608  }
609 
610  // Write the visual words search index.
611 
612  {
613  FILE* fout = fopen(path.c_str(), "ab");
614  CHECK_NOTNULL(fout);
615  visual_word_index_.saveIndex(fout);
616  fclose(fout);
617  }
618 
619  // Write the inverted index.
620 
621  {
622  std::ofstream file(path, std::ios::binary | std::ios::app);
623  CHECK(file.is_open()) << path;
624  inverted_index_.Write(&file);
625  }
626 }
627 
628 template <typename kDescType, int kDescDim, int kEmbeddingDim>
630  const BuildOptions& options, const DescType& descriptors) {
631  static_assert(DescType::IsRowMajor, "Descriptors must be row-major.");
632 
633  CHECK_GE(options.num_visual_words, options.branching);
634  CHECK_GE(descriptors.rows(), options.num_visual_words);
635 
636  const flann::Matrix<kDescType> descriptor_matrix(
637  const_cast<kDescType*>(descriptors.data()), descriptors.rows(),
638  descriptors.cols());
639 
640  std::vector<typename flann::L2<kDescType>::ResultType> centers_data(
641  options.num_visual_words * descriptors.cols());
642  flann::Matrix<typename flann::L2<kDescType>::ResultType> centers(
643  centers_data.data(), options.num_visual_words, descriptors.cols());
644 
645  flann::KMeansIndexParams index_params;
646  index_params["branching"] = options.branching;
647  index_params["iterations"] = options.num_iterations;
648  index_params["centers_init"] = flann::FLANN_CENTERS_KMEANSPP;
649  const int num_centers = flann::hierarchicalClustering<flann::L2<kDescType>>(
650  descriptor_matrix, centers, index_params);
651 
652  CHECK_LE(num_centers, options.num_visual_words);
653 
654  const size_t visual_word_data_size = num_centers * descriptors.cols();
655  kDescType* visual_words_data = new kDescType[visual_word_data_size];
656  for (size_t i = 0; i < visual_word_data_size; ++i) {
657  if (std::is_integral<kDescType>::value) {
658  visual_words_data[i] = std::round(centers_data[i]);
659  } else {
660  visual_words_data[i] = centers_data[i];
661  }
662  }
663 
664  if (visual_words_.ptr() != nullptr) {
665  delete[] visual_words_.ptr();
666  }
667 
668  visual_words_ = flann::Matrix<kDescType>(visual_words_data, num_centers,
669  descriptors.cols());
670 }
671 
672 template <typename kDescType, int kDescDim, int kEmbeddingDim>
673 void VisualIndex<kDescType, kDescDim, kEmbeddingDim>::QueryAndFindWordIds(
674  const QueryOptions& options,
675  const DescType& descriptors,
676  std::vector<ImageScore>* image_scores,
677  Eigen::MatrixXi* word_ids) const {
678  CHECK(prepared_);
679 
680  if (descriptors.rows() == 0) {
681  image_scores->clear();
682  return;
683  }
684 
685  *word_ids = FindWordIds(descriptors, options.num_neighbors,
686  options.num_checks, options.num_threads);
687  inverted_index_.Query(descriptors, *word_ids, image_scores);
688 
689  auto SortFunc = [](const ImageScore& score1, const ImageScore& score2) {
690  return score1.score > score2.score;
691  };
692 
693  size_t num_images = image_scores->size();
694  if (options.max_num_images >= 0) {
695  num_images =
696  std::min<size_t>(image_scores->size(), options.max_num_images);
697  }
698 
699  if (num_images == image_scores->size()) {
700  std::sort(image_scores->begin(), image_scores->end(), SortFunc);
701  } else {
702  std::partial_sort(image_scores->begin(),
703  image_scores->begin() + num_images,
704  image_scores->end(), SortFunc);
705  image_scores->resize(num_images);
706  }
707 }
708 
709 template <typename kDescType, int kDescDim, int kEmbeddingDim>
710 Eigen::MatrixXi VisualIndex<kDescType, kDescDim, kEmbeddingDim>::FindWordIds(
711  const DescType& descriptors,
712  const int num_neighbors,
713  const int num_checks,
714  const int num_threads) const {
715  static_assert(DescType::IsRowMajor, "Descriptors must be row-major");
716 
717  CHECK_GT(descriptors.rows(), 0);
718  CHECK_GT(num_neighbors, 0);
719 
720  Eigen::Matrix<size_t, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>
721  word_ids(descriptors.rows(), num_neighbors);
722  word_ids.setConstant(InvertedIndexType::kInvalidWordId);
723  flann::Matrix<size_t> indices(word_ids.data(), descriptors.rows(),
724  num_neighbors);
725 
726  Eigen::Matrix<typename flann::L2<kDescType>::ResultType, Eigen::Dynamic,
727  Eigen::Dynamic, Eigen::RowMajor>
728  distance_matrix(descriptors.rows(), num_neighbors);
729  flann::Matrix<typename flann::L2<kDescType>::ResultType> distances(
730  distance_matrix.data(), descriptors.rows(), num_neighbors);
731 
732  const flann::Matrix<kDescType> query(
733  const_cast<kDescType*>(descriptors.data()), descriptors.rows(),
734  descriptors.cols());
735 
736  flann::SearchParams search_params(num_checks);
737  if (num_threads < 0) {
738  search_params.cores = std::thread::hardware_concurrency();
739  } else {
740  search_params.cores = num_threads;
741  }
742  if (search_params.cores <= 0) {
743  search_params.cores = 1;
744  }
745 
746  visual_word_index_.knnSearch(query, indices, distances, num_neighbors,
747  search_params);
748 
749  return word_ids.cast<int>();
750 }
751 
752 } // namespace retrieval
753 } // namespace colmap
int count
static const size_t kMaxHammingDistance
Definition: utils.h:30
Eigen::Matrix< kDescType, Eigen::Dynamic, kDescDim, Eigen::RowMajor > DescType
InvertedIndex< kDescType, kDescDim, kEmbeddingDim > InvertedIndexType
Definition: visual_index.h:40
InvertedIndexType::DescType DescType
Definition: visual_index.h:42
InvertedIndexType::EntryType EntryType
Definition: visual_index.h:43
bool ImageIndexed(const int image_id) const
Definition: visual_index.h:231
void Read(const std::string &path)
Definition: visual_index.h:544
void Add(const IndexOptions &options, const int image_id, const GeomType &geometries, const DescType &descriptors)
Definition: visual_index.h:187
void Build(const BuildOptions &options, const DescType &descriptors)
Definition: visual_index.h:515
static const int kMaxNumThreads
Definition: visual_index.h:39
void Query(const QueryOptions &options, const DescType &descriptors, std::vector< ImageScore > *image_scores) const
Definition: visual_index.h:237
void Write(const std::string &path)
Definition: visual_index.h:595
CLOUDVIEWER_HOST_DEVICE Pair< First, Second > make_pair(const First &_first, const Second &_second)
Definition: SlabTraits.h:49
static const std::string path
Definition: PointCloud.cpp:59
int VoteAndVerify(const VoteAndVerifyOptions &options, const std::vector< FeatureGeometryMatch > &matches)
std::vector< FeatureKeypoint > FeatureKeypoints
Definition: types.h:77
Eigen::MatrixXd::Index Index
Definition: knncpp.h:26
CorePointDescSet * descriptors
#define SEEK_SET
Definition: qioapi.cpp:38
std::vector< FeatureGeometry > geometries2
Definition: geometry.h:47