ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
inverted_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 <Eigen/Dense>
12 #include <algorithm>
13 #include <bitset>
14 #include <cstdint>
15 #include <fstream>
16 #include <unordered_map>
17 #include <unordered_set>
18 #include <vector>
19 
21 #include "util/alignment.h"
22 #include "util/random.h"
23 
24 namespace colmap {
25 namespace retrieval {
26 
27 // Implements an inverted index system. The template parameter is the length of
28 // the binary vectors in the Hamming Embedding.
29 // This class is based on an original implementation by Torsten Sattler.
30 template <typename kDescType, int kDescDim, int kEmbeddingDim>
32 public:
33  const static int kInvalidWordId;
34  typedef Eigen::Matrix<kDescType, Eigen::Dynamic, kDescDim, Eigen::RowMajor>
38  typedef Eigen::Matrix<float, Eigen::Dynamic, kDescDim> ProjMatrixType;
39  typedef Eigen::VectorXf ProjDescType;
40 
42 
43  // The number of visual words in the index.
44  int NumVisualWords() const;
45 
46  // Initializes the inverted index with num_words empty inverted files.
47  void Initialize(const int num_words);
48 
49  // Finalizes the inverted index by sorting each inverted file such that all
50  // entries are in ascending order of image ids.
51  void Finalize();
52 
53  // Generate projection matrix for Hamming embedding.
55 
56  // Compute Hamming embedding thresholds from a set of descriptors with
57  // given visual word identifies.
59  const Eigen::VectorXi& word_ids);
60 
61  // Add single entry to the index.
62  void AddEntry(const int image_id,
63  const int word_id,
64  typename DescType::Index feature_idx,
65  const DescType& descriptor,
66  const GeomType& geometry);
67 
68  // Clear all index entries.
69  void ClearEntries();
70 
71  // Query the inverted file and return a list of sorted images.
72  void Query(const DescType& descriptors,
73  const Eigen::MatrixXi& word_ids,
74  std::vector<ImageScore>* image_scores) const;
75 
77  const int word_id,
78  const DescType& descriptor,
79  std::bitset<kEmbeddingDim>* binary_descriptor) const;
80 
81  float GetIDFWeight(const int word_id) const;
82 
83  void FindMatches(const int word_id,
84  const std::unordered_set<int>& image_ids,
85  std::vector<const EntryType*>* matches) const;
86 
87  // Compute the self-similarity for the image.
88  float ComputeSelfSimilarity(const Eigen::MatrixXi& word_ids) const;
89 
90  // Get the identifiers of all indexed images.
91  void GetImageIds(std::unordered_set<int>* image_ids) const;
92 
93  // Read/write the inverted index from/to a binary file.
94  void Read(std::ifstream* ifs);
95  void Write(std::ofstream* ofs) const;
96 
97 private:
98  void ComputeWeightsAndNormalizationConstants();
99 
100  // The individual inverted indices.
101  std::vector<InvertedFile<kEmbeddingDim>,
102  Eigen::aligned_allocator<InvertedFile<kEmbeddingDim>>>
103  inverted_files_;
104 
105  // For each image in the database, a normalization factor to be used to
106  // normalize the votes.
107  std::unordered_map<int, float> normalization_constants_;
108 
109  // The projection matrix used to project SIFT descriptors.
110  ProjMatrixType proj_matrix_;
111 };
112 
114 // Implementation
116 
117 template <typename kDescType, int kDescDim, int kEmbeddingDim>
119  std::numeric_limits<int>::max();
120 
121 template <typename kDescType, int kDescDim, int kEmbeddingDim>
123  proj_matrix_.resize(kEmbeddingDim, kDescDim);
124  proj_matrix_.setIdentity();
125 }
126 
127 template <typename kDescType, int kDescDim, int kEmbeddingDim>
129  return static_cast<int>(inverted_files_.size());
130 }
131 
132 template <typename kDescType, int kDescDim, int kEmbeddingDim>
134  const int num_words) {
135  CHECK_GT(num_words, 0);
136  inverted_files_.resize(num_words);
137  for (auto& inverted_file : inverted_files_) {
138  inverted_file.Reset();
139  }
140 }
141 
142 template <typename kDescType, int kDescDim, int kEmbeddingDim>
144  CHECK_GT(NumVisualWords(), 0);
145 
146  for (auto& inverted_file : inverted_files_) {
147  inverted_file.SortEntries();
148  }
149 
150  ComputeWeightsAndNormalizationConstants();
151 }
152 
153 template <typename kDescType, int kDescDim, int kEmbeddingDim>
156  Eigen::MatrixXf random_matrix(kDescDim, kDescDim);
157  for (Eigen::MatrixXf::Index i = 0; i < random_matrix.size(); ++i) {
158  random_matrix(i) = RandomGaussian(0.0f, 1.0f);
159  }
160  const Eigen::MatrixXf Q = random_matrix.colPivHouseholderQr().matrixQ();
161  proj_matrix_ = Q.topRows<kEmbeddingDim>();
162 }
163 
164 template <typename kDescType, int kDescDim, int kEmbeddingDim>
166  const DescType& descriptors, const Eigen::VectorXi& word_ids) {
167  CHECK_EQ(descriptors.rows(), word_ids.rows());
168  CHECK_EQ(descriptors.cols(), kDescDim);
169 
170  // Skip every inverted file with less than kMinEntries entries.
171  const size_t kMinEntries = 5;
172 
173  // Determines for each word the corresponding descriptors.
174  std::vector<std::vector<int>> indices_per_word(NumVisualWords());
175  for (Eigen::MatrixXi::Index i = 0; i < word_ids.rows(); ++i) {
176  indices_per_word.at(word_ids(i)).push_back(i);
177  }
178 
179  // For each word, learn the Hamming embedding threshold and the local
180  // descriptor space densities.
181  for (int i = 0; i < NumVisualWords(); ++i) {
182  const auto& indices = indices_per_word[i];
183  if (indices.size() < kMinEntries) {
184  continue;
185  }
186 
187  Eigen::Matrix<float, Eigen::Dynamic, kEmbeddingDim> proj_desc(
188  indices.size(), kEmbeddingDim);
189  for (size_t j = 0; j < indices.size(); ++j) {
190  proj_desc.row(j) = proj_matrix_ * descriptors.row(indices[j])
191  .transpose()
192  .template cast<float>();
193  }
194 
195  inverted_files_[i].ComputeHammingEmbedding(proj_desc);
196  }
197 }
198 
199 template <typename kDescType, int kDescDim, int kEmbeddingDim>
201  const int image_id,
202  const int word_id,
203  typename DescType::Index feature_idx,
204  const DescType& descriptor,
205  const GeomType& geometry) {
206  CHECK_EQ(descriptor.size(), kDescDim);
207  const ProjDescType proj_desc =
208  proj_matrix_ * descriptor.transpose().template cast<float>();
209  inverted_files_.at(word_id).AddEntry(image_id, feature_idx, proj_desc,
210  geometry);
211 }
212 
213 template <typename kDescType, int kDescDim, int kEmbeddingDim>
215  for (auto& inverted_file : inverted_files_) {
216  inverted_file.ClearEntries();
217  }
218 }
219 
220 template <typename kDescType, int kDescDim, int kEmbeddingDim>
222  const DescType& descriptors,
223  const Eigen::MatrixXi& word_ids,
224  std::vector<ImageScore>* image_scores) const {
225  CHECK_EQ(descriptors.cols(), kDescDim);
226 
227  image_scores->clear();
228 
229  // Computes the self-similarity score for the query image.
230  const float self_similarity = ComputeSelfSimilarity(word_ids);
231  float normalization_weight = 1.0f;
232  if (self_similarity > 0.0f) {
233  normalization_weight = 1.0f / std::sqrt(self_similarity);
234  }
235 
236  std::unordered_map<int, int> score_map;
237  std::vector<ImageScore> inverted_file_scores;
238 
239  for (typename DescType::Index i = 0; i < descriptors.rows(); ++i) {
240  const ProjDescType proj_descriptor =
241  proj_matrix_ *
242  descriptors.row(i).transpose().template cast<float>();
243  for (Eigen::MatrixXi::Index n = 0; n < word_ids.cols(); ++n) {
244  const int word_id = word_ids(i, n);
245  if (word_id == kInvalidWordId) {
246  continue;
247  }
248 
249  inverted_files_.at(word_id).ScoreFeature(proj_descriptor,
250  &inverted_file_scores);
251 
252  for (const ImageScore& score : inverted_file_scores) {
253  const auto score_map_it = score_map.find(score.image_id);
254  if (score_map_it == score_map.end()) {
255  // Image not found in another inverted file.
256  score_map.emplace(score.image_id,
257  static_cast<int>(image_scores->size()));
258  image_scores->push_back(score);
259  } else {
260  // Image already found in another inverted file, so
261  // accumulate.
262  (*image_scores).at(score_map_it->second).score +=
263  score.score;
264  }
265  }
266  }
267  }
268 
269  // Normalization.
270  for (ImageScore& score : *image_scores) {
271  score.score *= normalization_weight *
272  normalization_constants_.at(score.image_id);
273  }
274 }
275 
276 template <typename kDescType, int kDescDim, int kEmbeddingDim>
279  const int word_id,
280  const DescType& descriptor,
281  std::bitset<kEmbeddingDim>* binary_descriptor) const {
282  const ProjDescType proj_desc =
283  proj_matrix_ * descriptor.transpose().template cast<float>();
284  inverted_files_.at(word_id).ConvertToBinaryDescriptor(proj_desc,
285  binary_descriptor);
286 }
287 
288 template <typename kDescType, int kDescDim, int kEmbeddingDim>
290  const int word_id) const {
291  return inverted_files_.at(word_id).IDFWeight();
292 }
293 
294 template <typename kDescType, int kDescDim, int kEmbeddingDim>
296  const int word_id,
297  const std::unordered_set<int>& image_ids,
298  std::vector<const EntryType*>* matches) const {
299  matches->clear();
300  const auto& entries = inverted_files_.at(word_id).GetEntries();
301  for (const auto& entry : entries) {
302  if (image_ids.count(entry.image_id)) {
303  matches->emplace_back(&entry);
304  }
305  }
306 }
307 
308 template <typename kDescType, int kDescDim, int kEmbeddingDim>
310  const Eigen::MatrixXi& word_ids) const {
311  double self_similarity = 0.0;
312  for (Eigen::MatrixXi::Index i = 0; i < word_ids.size(); ++i) {
313  const int word_id = word_ids(i);
314  if (word_id != kInvalidWordId) {
315  const auto& inverted_file = inverted_files_.at(word_id);
316  self_similarity +=
317  inverted_file.IDFWeight() * inverted_file.IDFWeight();
318  }
319  }
320  return static_cast<float>(self_similarity);
321 }
322 
323 template <typename kDescType, int kDescDim, int kEmbeddingDim>
325  std::unordered_set<int>* image_ids) const {
326  for (const auto& inverted_file : inverted_files_) {
327  inverted_file.GetImageIds(image_ids);
328  }
329 }
330 
331 template <typename kDescType, int kDescDim, int kEmbeddingDim>
333  std::ifstream* ifs) {
334  CHECK(ifs->is_open());
335 
336  int32_t num_words = 0;
337  ifs->read(reinterpret_cast<char*>(&num_words), sizeof(int32_t));
338  CHECK_GT(num_words, 0);
339 
340  Initialize(num_words);
341 
342  int32_t N_t = 0;
343  ifs->read(reinterpret_cast<char*>(&N_t), sizeof(int32_t));
344  CHECK_EQ(N_t, kEmbeddingDim)
345  << "The length of the binary strings should be " << kEmbeddingDim
346  << " but is " << N_t << ". The indices are not compatible!";
347 
348  for (int i = 0; i < kEmbeddingDim; ++i) {
349  for (int j = 0; j < kDescDim; ++j) {
350  ifs->read(reinterpret_cast<char*>(&proj_matrix_(i, j)),
351  sizeof(float));
352  }
353  }
354 
355  for (auto& inverted_file : inverted_files_) {
356  inverted_file.Read(ifs);
357  }
358 
359  int32_t num_images = 0;
360  ifs->read(reinterpret_cast<char*>(&num_images), sizeof(int32_t));
361  CHECK_GE(num_images, 0);
362 
363  normalization_constants_.clear();
364  normalization_constants_.reserve(num_images);
365  for (int32_t i = 0; i < num_images; ++i) {
366  int image_id;
367  float value;
368  ifs->read(reinterpret_cast<char*>(&image_id), sizeof(int));
369  ifs->read(reinterpret_cast<char*>(&value), sizeof(float));
370  normalization_constants_[image_id] = value;
371  }
372 }
373 
374 template <typename kDescType, int kDescDim, int kEmbeddingDim>
376  std::ofstream* ofs) const {
377  CHECK(ofs->is_open());
378 
379  int32_t num_words = static_cast<int32_t>(NumVisualWords());
380  ofs->write(reinterpret_cast<const char*>(&num_words), sizeof(int32_t));
381  CHECK_GT(num_words, 0);
382 
383  const int32_t N_t = static_cast<int32_t>(kEmbeddingDim);
384  ofs->write(reinterpret_cast<const char*>(&N_t), sizeof(int32_t));
385 
386  for (int i = 0; i < kEmbeddingDim; ++i) {
387  for (int j = 0; j < kDescDim; ++j) {
388  ofs->write(reinterpret_cast<const char*>(&proj_matrix_(i, j)),
389  sizeof(float));
390  }
391  }
392 
393  for (const auto& inverted_file : inverted_files_) {
394  inverted_file.Write(ofs);
395  }
396 
397  const int32_t num_images = normalization_constants_.size();
398  ofs->write(reinterpret_cast<const char*>(&num_images), sizeof(int32_t));
399 
400  for (const auto& constant : normalization_constants_) {
401  ofs->write(reinterpret_cast<const char*>(&constant.first), sizeof(int));
402  ofs->write(reinterpret_cast<const char*>(&constant.second),
403  sizeof(float));
404  }
405 }
406 
407 template <typename kDescType, int kDescDim, int kEmbeddingDim>
410  std::unordered_set<int> image_ids;
411  GetImageIds(&image_ids);
412 
413  for (auto& inverted_file : inverted_files_) {
414  inverted_file.ComputeIDFWeight(image_ids.size());
415  }
416 
417  std::unordered_map<int, double> self_similarities(image_ids.size());
418  for (const auto& inverted_file : inverted_files_) {
419  inverted_file.ComputeImageSelfSimilarities(&self_similarities);
420  }
421 
422  normalization_constants_.clear();
423  normalization_constants_.reserve(image_ids.size());
424  for (const auto& self_similarity : self_similarities) {
425  if (self_similarity.second > 0.0) {
426  normalization_constants_[self_similarity.first] =
427  static_cast<float>(1.0 / std::sqrt(self_similarity.second));
428  } else {
429  normalization_constants_[self_similarity.first] = 0.0f;
430  }
431  }
432 }
433 
434 } // namespace retrieval
435 } // namespace colmap
void ComputeHammingEmbedding(const DescType &descriptors, const Eigen::VectorXi &word_ids)
Eigen::Matrix< float, Eigen::Dynamic, kDescDim > ProjMatrixType
InvertedFile< kEmbeddingDim >::GeomType GeomType
void GetImageIds(std::unordered_set< int > *image_ids) const
void ConvertToBinaryDescriptor(const int word_id, const DescType &descriptor, std::bitset< kEmbeddingDim > *binary_descriptor) const
void Initialize(const int num_words)
InvertedFile< kEmbeddingDim >::EntryType EntryType
void Write(std::ofstream *ofs) const
Eigen::Matrix< kDescType, Eigen::Dynamic, kDescDim, Eigen::RowMajor > DescType
void Query(const DescType &descriptors, const Eigen::MatrixXi &word_ids, std::vector< ImageScore > *image_scores) const
float GetIDFWeight(const int word_id) const
float ComputeSelfSimilarity(const Eigen::MatrixXi &word_ids) const
void AddEntry(const int image_id, const int word_id, typename DescType::Index feature_idx, const DescType &descriptor, const GeomType &geometry)
void Read(std::ifstream *ifs)
void FindMatches(const int word_id, const std::unordered_set< int > &image_ids, std::vector< const EntryType * > *matches) const
float
T RandomGaussian(const T mean, const T stddev)
Definition: random.h:86
Eigen::MatrixXd::Index Index
Definition: knncpp.h:26
CorePointDescSet * descriptors