ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
inverted_file.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 <algorithm>
12 #include <bitset>
13 #include <cstdint>
14 #include <fstream>
15 #include <unordered_map>
16 #include <unordered_set>
17 #include <vector>
18 
19 #include "retrieval/geometry.h"
21 #include "retrieval/utils.h"
22 #include "util/alignment.h"
23 #include "util/logging.h"
24 #include "util/math.h"
25 
26 namespace colmap {
27 namespace retrieval {
28 
29 // Implements an inverted file, including the ability to compute image scores
30 // and matches. The template parameter is the length of the binary vectors
31 // in the Hamming Embedding.
32 // This class is based on an original implementation by Torsten Sattler.
33 template <int kEmbeddingDim>
34 class InvertedFile {
35 public:
36  typedef Eigen::VectorXf DescType;
39 
40  enum Status {
41  UNUSABLE = 0x00,
42  HAS_EMBEDDING = 0x01,
44  USABLE = 0x03,
45  };
46 
47  InvertedFile();
48 
49  // The number of added entries.
50  size_t NumEntries() const;
51 
52  // Return all entries in the file.
53  const std::vector<EntryType>& GetEntries() const;
54 
55  // Whether the Hamming embedding was computed for this file.
56  bool HasHammingEmbedding() const;
57 
58  // Whether the entries in this file are sorted.
59  bool EntriesSorted() const;
60 
61  // Whether this file is usable for scoring, i.e. the entries are sorted and
62  // the Hamming embedding has been computed.
63  bool IsUsable() const;
64 
65  // Adds an inverted file entry given a projected descriptor and its image
66  // information stored in an inverted file entry. In particular, this
67  // function generates the binary descriptor for the inverted file entry and
68  // then stores the entry in the inverted file.
69  void AddEntry(const int image_id,
70  typename DescType::Index feature_idx,
71  const DescType& descriptor,
72  const GeomType& geometry);
73 
74  // Sorts the inverted file entries in ascending order of image ids. This is
75  // required for efficient scoring and must be called before ScoreFeature.
76  void SortEntries();
77 
78  // Clear all entries in this file.
79  void ClearEntries();
80 
81  // Reset all computed weights/thresholds and clear all entries.
82  void Reset();
83 
84  // Given a projected descriptor, returns the corresponding binary string.
86  const DescType& descriptor,
87  std::bitset<kEmbeddingDim>* binary_descriptor) const;
88 
89  // Compute the idf-weight for this inverted file.
90  void ComputeIDFWeight(const int num_total_images);
91 
92  // Return the idf-weight of this inverted file.
93  float IDFWeight() const;
94 
95  // Given a set of descriptors, learns the thresholds required for the
96  // Hamming embedding. Each row in descriptors represents a single descriptor
97  // projected into the kEmbeddingDim dimensional space used for Hamming
98  // embedding.
100  const Eigen::Matrix<float, Eigen::Dynamic, kEmbeddingDim>&
101  descriptors);
102 
103  // Given a query feature, performs inverted file scoring.
104  void ScoreFeature(const DescType& descriptor,
105  std::vector<ImageScore>* image_scores) const;
106 
107  // Get the identifiers of all indexed images in this file.
108  void GetImageIds(std::unordered_set<int>* ids) const;
109 
110  // For each image in the inverted file, computes the self-similarity of each
111  // image in the file (the part caused by this word) and adds the weight to
112  // the entry corresponding to that image. This function is useful to
113  // determine the normalization factor for each image that is used during
114  // retrieval.
116  std::unordered_map<int, double>* self_similarities) const;
117 
118  // Read/write the inverted file from/to a binary file.
119  void Read(std::ifstream* ifs);
120  void Write(std::ofstream* ofs) const;
121 
122 private:
123  // Whether the inverted file is initialized.
124  uint8_t status_;
125 
126  // The inverse document frequency weight of this inverted file.
127  float idf_weight_;
128 
129  // The entries of the inverted file system.
130  std::vector<EntryType> entries_;
131 
132  // The thresholds used for Hamming embedding.
133  DescType thresholds_;
134 
135  // The functor to derive a voting weight from a Hamming distance.
137  hamming_dist_weight_functor_;
138 };
139 
141 // Implementation
143 
144 template <int kEmbeddingDim>
145 const HammingDistWeightFunctor<kEmbeddingDim>
146  InvertedFile<kEmbeddingDim>::hamming_dist_weight_functor_;
147 
148 template <int kEmbeddingDim>
150  : status_(UNUSABLE), idf_weight_(0.0f) {
151  static_assert(kEmbeddingDim % 8 == 0,
152  "Dimensionality of projected space needs to"
153  " be a multiple of 8.");
154  static_assert(kEmbeddingDim > 0,
155  "Dimensionality of projected space needs to be > 0.");
156 
157  thresholds_.resize(kEmbeddingDim);
158  thresholds_.setZero();
159 }
160 
161 template <int kEmbeddingDim>
163  return entries_.size();
164 }
165 
166 template <int kEmbeddingDim>
167 const std::vector<typename InvertedFile<kEmbeddingDim>::EntryType>&
169  return entries_;
170 }
171 
172 template <int kEmbeddingDim>
174  return status_ & HAS_EMBEDDING;
175 }
176 
177 template <int kEmbeddingDim>
179  return status_ & ENTRIES_SORTED;
180 }
181 
182 template <int kEmbeddingDim>
184  return status_ & USABLE;
185 }
186 
187 template <int kEmbeddingDim>
189  typename DescType::Index feature_idx,
190  const DescType& descriptor,
191  const GeomType& geometry) {
192  CHECK_GE(image_id, 0);
193  CHECK_EQ(descriptor.size(), kEmbeddingDim);
194  EntryType entry;
195  entry.image_id = image_id;
196  entry.feature_idx = feature_idx;
197  entry.geometry = geometry;
198  ConvertToBinaryDescriptor(descriptor, &entry.descriptor);
199  entries_.push_back(entry);
200  status_ &= ~ENTRIES_SORTED;
201 }
202 
203 template <int kEmbeddingDim>
205  std::sort(entries_.begin(), entries_.end(),
206  [](const EntryType& entry1, const EntryType& entry2) {
207  return entry1.image_id < entry2.image_id;
208  });
209  status_ |= ENTRIES_SORTED;
210 }
211 
212 template <int kEmbeddingDim>
214  entries_.clear();
215  status_ &= ~ENTRIES_SORTED;
216 }
217 
218 template <int kEmbeddingDim>
220  status_ = UNUSABLE;
221  idf_weight_ = 0.0f;
222  entries_.clear();
223  thresholds_.setZero();
224 }
225 
226 template <int kEmbeddingDim>
228  const DescType& descriptor,
229  std::bitset<kEmbeddingDim>* binary_descriptor) const {
230  CHECK_EQ(descriptor.size(), kEmbeddingDim);
231  for (int i = 0; i < kEmbeddingDim; ++i) {
232  (*binary_descriptor)[i] = descriptor[i] > thresholds_[i];
233  }
234 }
235 
236 template <int kEmbeddingDim>
237 void InvertedFile<kEmbeddingDim>::ComputeIDFWeight(const int num_total_images) {
238  if (entries_.empty()) {
239  return;
240  }
241 
242  std::unordered_set<int> image_ids;
243  GetImageIds(&image_ids);
244 
245  idf_weight_ = std::log(static_cast<double>(num_total_images) /
246  static_cast<double>(image_ids.size()));
247 }
248 
249 template <int kEmbeddingDim>
251  return idf_weight_;
252 }
253 
254 template <int kEmbeddingDim>
256  const Eigen::Matrix<float, Eigen::Dynamic, kEmbeddingDim>&
257  descriptors) {
258  const int num_descriptors = static_cast<int>(descriptors.rows());
259  if (num_descriptors < 2) {
260  return;
261  }
262 
263  std::vector<float> elements(num_descriptors);
264  for (int n = 0; n < kEmbeddingDim; ++n) {
265  for (int i = 0; i < num_descriptors; ++i) {
266  elements[i] = descriptors(i, n);
267  }
268  thresholds_[n] = Median(elements);
269  }
270 
271  status_ |= HAS_EMBEDDING;
272 }
273 
274 template <int kEmbeddingDim>
276  const DescType& descriptor,
277  std::vector<ImageScore>* image_scores) const {
278  CHECK_EQ(descriptor.size(), kEmbeddingDim);
279 
280  image_scores->clear();
281 
282  if (!IsUsable()) {
283  return;
284  }
285 
286  if (entries_.size() == 0) {
287  return;
288  }
289 
290  const float squared_idf_weight = idf_weight_ * idf_weight_;
291 
292  std::bitset<kEmbeddingDim> bin_descriptor;
293  ConvertToBinaryDescriptor(descriptor, &bin_descriptor);
294 
295  ImageScore image_score;
296  image_score.image_id = entries_.front().image_id;
297  image_score.score = 0.0f;
298  int num_image_votes = 0;
299 
300  // Note that this assumes that the entries are sorted using SortEntries
301  // according to their image identifiers.
302  for (const auto& entry : entries_) {
303  if (image_score.image_id < entry.image_id) {
304  if (num_image_votes > 0) {
305  // Finalizes the voting since we now know how many features from
306  // the database image match the current image feature. This is
307  // required to perform burstiness normalization (cf. Eqn. 2 in
308  // Arandjelovic, Zisserman: Scalable descriptor
309  // distinctiveness for location recognition. ACCV 2014).
310  // Notice that the weight from the descriptor matching is
311  // already accumulated in image_score.score, i.e., we only need
312  // to apply the burstiness weighting.
313  image_score.score /=
314  std::sqrt(static_cast<float>(num_image_votes));
315  image_score.score *= squared_idf_weight;
316  image_scores->push_back(image_score);
317  }
318 
319  image_score.image_id = entry.image_id;
320  image_score.score = 0.0f;
321  num_image_votes = 0;
322  }
323 
324  const size_t hamming_dist = (bin_descriptor ^ entry.descriptor).count();
325 
326  if (hamming_dist <= hamming_dist_weight_functor_.kMaxHammingDistance) {
327  image_score.score += hamming_dist_weight_functor_(hamming_dist);
328  num_image_votes += 1;
329  }
330  }
331 
332  // Add the voting for the largest image_id in the entries.
333  if (num_image_votes > 0) {
334  image_score.score /= std::sqrt(static_cast<float>(num_image_votes));
335  image_score.score *= squared_idf_weight;
336  image_scores->push_back(image_score);
337  }
338 }
339 
340 template <int kEmbeddingDim>
342  std::unordered_set<int>* ids) const {
343  for (const EntryType& entry : entries_) {
344  ids->insert(entry.image_id);
345  }
346 }
347 
348 template <int kEmbeddingDim>
350  std::unordered_map<int, double>* self_similarities) const {
351  const double squared_idf_weight = idf_weight_ * idf_weight_;
352  for (const auto& entry : entries_) {
353  (*self_similarities)[entry.image_id] += squared_idf_weight;
354  }
355 }
356 
357 template <int kEmbeddingDim>
358 void InvertedFile<kEmbeddingDim>::Read(std::ifstream* ifs) {
359  CHECK(ifs->is_open());
360 
361  ifs->read(reinterpret_cast<char*>(&status_), sizeof(uint8_t));
362  ifs->read(reinterpret_cast<char*>(&idf_weight_), sizeof(float));
363 
364  for (int i = 0; i < kEmbeddingDim; ++i) {
365  ifs->read(reinterpret_cast<char*>(&thresholds_[i]), sizeof(float));
366  }
367 
368  uint32_t num_entries = 0;
369  ifs->read(reinterpret_cast<char*>(&num_entries), sizeof(uint32_t));
370  entries_.resize(num_entries);
371 
372  for (uint32_t i = 0; i < num_entries; ++i) {
373  entries_[i].Read(ifs);
374  }
375 }
376 
377 template <int kEmbeddingDim>
378 void InvertedFile<kEmbeddingDim>::Write(std::ofstream* ofs) const {
379  CHECK(ofs->is_open());
380 
381  ofs->write(reinterpret_cast<const char*>(&status_), sizeof(uint8_t));
382  ofs->write(reinterpret_cast<const char*>(&idf_weight_), sizeof(float));
383 
384  for (int i = 0; i < kEmbeddingDim; ++i) {
385  ofs->write(reinterpret_cast<const char*>(&thresholds_[i]),
386  sizeof(float));
387  }
388 
389  const uint32_t num_entries = static_cast<uint32_t>(entries_.size());
390  ofs->write(reinterpret_cast<const char*>(&num_entries), sizeof(uint32_t));
391 
392  for (uint32_t i = 0; i < num_entries; ++i) {
393  entries_[i].Write(ofs);
394  }
395 }
396 
397 } // namespace retrieval
398 } // namespace colmap
int count
void ComputeIDFWeight(const int num_total_images)
void ScoreFeature(const DescType &descriptor, std::vector< ImageScore > *image_scores) const
void GetImageIds(std::unordered_set< int > *ids) const
InvertedFileEntry< kEmbeddingDim > EntryType
Definition: inverted_file.h:38
void AddEntry(const int image_id, typename DescType::Index feature_idx, const DescType &descriptor, const GeomType &geometry)
void Read(std::ifstream *ifs)
const std::vector< EntryType > & GetEntries() const
void ComputeImageSelfSimilarities(std::unordered_map< int, double > *self_similarities) const
void Write(std::ofstream *ofs) const
void ConvertToBinaryDescriptor(const DescType &descriptor, std::bitset< kEmbeddingDim > *binary_descriptor) const
void ComputeHammingEmbedding(const Eigen::Matrix< float, Eigen::Dynamic, kEmbeddingDim > &descriptors)
double Median(const std::vector< T > &elems)
Definition: math.h:189
Eigen::MatrixXd::Index Index
Definition: knncpp.h:26
CorePointDescSet * descriptors