| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | #ifndef COLMAP_SRC_RETRIEVAL_INVERTED_FILE_H_ |
| | #define COLMAP_SRC_RETRIEVAL_INVERTED_FILE_H_ |
| |
|
| | #include <algorithm> |
| | #include <bitset> |
| | #include <cstdint> |
| | #include <fstream> |
| | #include <unordered_map> |
| | #include <unordered_set> |
| | #include <vector> |
| |
|
| | #include <Eigen/Core> |
| |
|
| | #include "retrieval/geometry.h" |
| | #include "retrieval/inverted_file_entry.h" |
| | #include "retrieval/utils.h" |
| | #include "util/alignment.h" |
| | #include "util/logging.h" |
| | #include "util/math.h" |
| |
|
| | namespace colmap { |
| | namespace retrieval { |
| |
|
| | |
| | |
| | |
| | |
| | template <int kEmbeddingDim> |
| | class InvertedFile { |
| | public: |
| | typedef Eigen::VectorXf DescType; |
| | typedef FeatureGeometry GeomType; |
| | typedef InvertedFileEntry<kEmbeddingDim> EntryType; |
| |
|
| | enum Status { |
| | UNUSABLE = 0x00, |
| | HAS_EMBEDDING = 0x01, |
| | ENTRIES_SORTED = 0x02, |
| | USABLE = 0x03, |
| | }; |
| |
|
| | InvertedFile(); |
| |
|
| | |
| | size_t NumEntries() const; |
| |
|
| | |
| | const std::vector<EntryType>& GetEntries() const; |
| |
|
| | |
| | bool HasHammingEmbedding() const; |
| |
|
| | |
| | bool EntriesSorted() const; |
| |
|
| | |
| | |
| | bool IsUsable() const; |
| |
|
| | |
| | |
| | |
| | |
| | void AddEntry(const int image_id, typename DescType::Index feature_idx, |
| | const DescType& descriptor, const GeomType& geometry); |
| |
|
| | |
| | |
| | void SortEntries(); |
| |
|
| | |
| | void ClearEntries(); |
| |
|
| | |
| | void Reset(); |
| |
|
| | |
| | void ConvertToBinaryDescriptor( |
| | const DescType& descriptor, |
| | std::bitset<kEmbeddingDim>* binary_descriptor) const; |
| |
|
| | |
| | void ComputeIDFWeight(const int num_total_images); |
| |
|
| | |
| | float IDFWeight() const; |
| |
|
| | |
| | |
| | |
| | void ComputeHammingEmbedding( |
| | const Eigen::Matrix<float, Eigen::Dynamic, kEmbeddingDim>& descriptors); |
| |
|
| | |
| | void ScoreFeature(const DescType& descriptor, |
| | std::vector<ImageScore>* image_scores) const; |
| |
|
| | |
| | void GetImageIds(std::unordered_set<int>* ids) const; |
| |
|
| | |
| | |
| | |
| | |
| | void ComputeImageSelfSimilarities( |
| | std::unordered_map<int, double>* self_similarities) const; |
| |
|
| | |
| | void Read(std::ifstream* ifs); |
| | void Write(std::ofstream* ofs) const; |
| |
|
| | private: |
| | |
| | uint8_t status_; |
| |
|
| | |
| | float idf_weight_; |
| |
|
| | |
| | std::vector<EntryType> entries_; |
| |
|
| | |
| | DescType thresholds_; |
| |
|
| | |
| | static const HammingDistWeightFunctor<kEmbeddingDim> |
| | hamming_dist_weight_functor_; |
| | }; |
| |
|
| | |
| | |
| | |
| |
|
| | template <int kEmbeddingDim> |
| | const HammingDistWeightFunctor<kEmbeddingDim> |
| | InvertedFile<kEmbeddingDim>::hamming_dist_weight_functor_; |
| |
|
| | template <int kEmbeddingDim> |
| | InvertedFile<kEmbeddingDim>::InvertedFile() |
| | : status_(UNUSABLE), idf_weight_(0.0f) { |
| | static_assert(kEmbeddingDim % 8 == 0, |
| | "Dimensionality of projected space needs to" |
| | " be a multiple of 8."); |
| | static_assert(kEmbeddingDim > 0, |
| | "Dimensionality of projected space needs to be > 0."); |
| |
|
| | thresholds_.resize(kEmbeddingDim); |
| | thresholds_.setZero(); |
| | } |
| |
|
| | template <int kEmbeddingDim> |
| | size_t InvertedFile<kEmbeddingDim>::NumEntries() const { |
| | return entries_.size(); |
| | } |
| |
|
| | template <int kEmbeddingDim> |
| | const std::vector<typename InvertedFile<kEmbeddingDim>::EntryType>& |
| | InvertedFile<kEmbeddingDim>::GetEntries() const { |
| | return entries_; |
| | } |
| |
|
| | template <int kEmbeddingDim> |
| | bool InvertedFile<kEmbeddingDim>::HasHammingEmbedding() const { |
| | return status_ & HAS_EMBEDDING; |
| | } |
| |
|
| | template <int kEmbeddingDim> |
| | bool InvertedFile<kEmbeddingDim>::EntriesSorted() const { |
| | return status_ & ENTRIES_SORTED; |
| | } |
| |
|
| | template <int kEmbeddingDim> |
| | bool InvertedFile<kEmbeddingDim>::IsUsable() const { |
| | return status_ & USABLE; |
| | } |
| |
|
| | template <int kEmbeddingDim> |
| | void InvertedFile<kEmbeddingDim>::AddEntry(const int image_id, |
| | typename DescType::Index feature_idx, |
| | const DescType& descriptor, |
| | const GeomType& geometry) { |
| | CHECK_GE(image_id, 0); |
| | CHECK_EQ(descriptor.size(), kEmbeddingDim); |
| | EntryType entry; |
| | entry.image_id = image_id; |
| | entry.feature_idx = feature_idx; |
| | entry.geometry = geometry; |
| | ConvertToBinaryDescriptor(descriptor, &entry.descriptor); |
| | entries_.push_back(entry); |
| | status_ &= ~ENTRIES_SORTED; |
| | } |
| |
|
| | template <int kEmbeddingDim> |
| | void InvertedFile<kEmbeddingDim>::SortEntries() { |
| | std::sort(entries_.begin(), entries_.end(), |
| | [](const EntryType& entry1, const EntryType& entry2) { |
| | return entry1.image_id < entry2.image_id; |
| | }); |
| | status_ |= ENTRIES_SORTED; |
| | } |
| |
|
| | template <int kEmbeddingDim> |
| | void InvertedFile<kEmbeddingDim>::ClearEntries() { |
| | entries_.clear(); |
| | status_ &= ~ENTRIES_SORTED; |
| | } |
| |
|
| | template <int kEmbeddingDim> |
| | void InvertedFile<kEmbeddingDim>::Reset() { |
| | status_ = UNUSABLE; |
| | idf_weight_ = 0.0f; |
| | entries_.clear(); |
| | thresholds_.setZero(); |
| | } |
| |
|
| | template <int kEmbeddingDim> |
| | void InvertedFile<kEmbeddingDim>::ConvertToBinaryDescriptor( |
| | const DescType& descriptor, |
| | std::bitset<kEmbeddingDim>* binary_descriptor) const { |
| | CHECK_EQ(descriptor.size(), kEmbeddingDim); |
| | for (int i = 0; i < kEmbeddingDim; ++i) { |
| | (*binary_descriptor)[i] = descriptor[i] > thresholds_[i]; |
| | } |
| | } |
| |
|
| | template <int kEmbeddingDim> |
| | void InvertedFile<kEmbeddingDim>::ComputeIDFWeight(const int num_total_images) { |
| | if (entries_.empty()) { |
| | return; |
| | } |
| |
|
| | std::unordered_set<int> image_ids; |
| | GetImageIds(&image_ids); |
| |
|
| | idf_weight_ = std::log(static_cast<double>(num_total_images) / |
| | static_cast<double>(image_ids.size())); |
| | } |
| |
|
| | template <int kEmbeddingDim> |
| | float InvertedFile<kEmbeddingDim>::IDFWeight() const { |
| | return idf_weight_; |
| | } |
| |
|
| | template <int kEmbeddingDim> |
| | void InvertedFile<kEmbeddingDim>::ComputeHammingEmbedding( |
| | const Eigen::Matrix<float, Eigen::Dynamic, kEmbeddingDim>& descriptors) { |
| | const int num_descriptors = static_cast<int>(descriptors.rows()); |
| | if (num_descriptors < 2) { |
| | return; |
| | } |
| |
|
| | std::vector<float> elements(num_descriptors); |
| | for (int n = 0; n < kEmbeddingDim; ++n) { |
| | for (int i = 0; i < num_descriptors; ++i) { |
| | elements[i] = descriptors(i, n); |
| | } |
| | thresholds_[n] = Median(elements); |
| | } |
| |
|
| | status_ |= HAS_EMBEDDING; |
| | } |
| |
|
| | template <int kEmbeddingDim> |
| | void InvertedFile<kEmbeddingDim>::ScoreFeature( |
| | const DescType& descriptor, std::vector<ImageScore>* image_scores) const { |
| | CHECK_EQ(descriptor.size(), kEmbeddingDim); |
| |
|
| | image_scores->clear(); |
| |
|
| | if (!IsUsable()) { |
| | return; |
| | } |
| |
|
| | if (entries_.size() == 0) { |
| | return; |
| | } |
| |
|
| | const float squared_idf_weight = idf_weight_ * idf_weight_; |
| |
|
| | std::bitset<kEmbeddingDim> bin_descriptor; |
| | ConvertToBinaryDescriptor(descriptor, &bin_descriptor); |
| |
|
| | ImageScore image_score; |
| | image_score.image_id = entries_.front().image_id; |
| | image_score.score = 0.0f; |
| | int num_image_votes = 0; |
| |
|
| | |
| | |
| | for (const auto& entry : entries_) { |
| | if (image_score.image_id < entry.image_id) { |
| | if (num_image_votes > 0) { |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | image_score.score /= std::sqrt(static_cast<float>(num_image_votes)); |
| | image_score.score *= squared_idf_weight; |
| | image_scores->push_back(image_score); |
| | } |
| |
|
| | image_score.image_id = entry.image_id; |
| | image_score.score = 0.0f; |
| | num_image_votes = 0; |
| | } |
| |
|
| | const size_t hamming_dist = (bin_descriptor ^ entry.descriptor).count(); |
| |
|
| | if (hamming_dist <= hamming_dist_weight_functor_.kMaxHammingDistance) { |
| | image_score.score += hamming_dist_weight_functor_(hamming_dist); |
| | num_image_votes += 1; |
| | } |
| | } |
| |
|
| | |
| | if (num_image_votes > 0) { |
| | image_score.score /= std::sqrt(static_cast<float>(num_image_votes)); |
| | image_score.score *= squared_idf_weight; |
| | image_scores->push_back(image_score); |
| | } |
| | } |
| |
|
| | template <int kEmbeddingDim> |
| | void InvertedFile<kEmbeddingDim>::GetImageIds( |
| | std::unordered_set<int>* ids) const { |
| | for (const EntryType& entry : entries_) { |
| | ids->insert(entry.image_id); |
| | } |
| | } |
| |
|
| | template <int kEmbeddingDim> |
| | void InvertedFile<kEmbeddingDim>::ComputeImageSelfSimilarities( |
| | std::unordered_map<int, double>* self_similarities) const { |
| | const double squared_idf_weight = idf_weight_ * idf_weight_; |
| | for (const auto& entry : entries_) { |
| | (*self_similarities)[entry.image_id] += squared_idf_weight; |
| | } |
| | } |
| |
|
| | template <int kEmbeddingDim> |
| | void InvertedFile<kEmbeddingDim>::Read(std::ifstream* ifs) { |
| | CHECK(ifs->is_open()); |
| |
|
| | ifs->read(reinterpret_cast<char*>(&status_), sizeof(uint8_t)); |
| | ifs->read(reinterpret_cast<char*>(&idf_weight_), sizeof(float)); |
| |
|
| | for (int i = 0; i < kEmbeddingDim; ++i) { |
| | ifs->read(reinterpret_cast<char*>(&thresholds_[i]), sizeof(float)); |
| | } |
| |
|
| | uint32_t num_entries = 0; |
| | ifs->read(reinterpret_cast<char*>(&num_entries), sizeof(uint32_t)); |
| | entries_.resize(num_entries); |
| |
|
| | for (uint32_t i = 0; i < num_entries; ++i) { |
| | entries_[i].Read(ifs); |
| | } |
| | } |
| |
|
| | template <int kEmbeddingDim> |
| | void InvertedFile<kEmbeddingDim>::Write(std::ofstream* ofs) const { |
| | CHECK(ofs->is_open()); |
| |
|
| | ofs->write(reinterpret_cast<const char*>(&status_), sizeof(uint8_t)); |
| | ofs->write(reinterpret_cast<const char*>(&idf_weight_), sizeof(float)); |
| |
|
| | for (int i = 0; i < kEmbeddingDim; ++i) { |
| | ofs->write(reinterpret_cast<const char*>(&thresholds_[i]), sizeof(float)); |
| | } |
| |
|
| | const uint32_t num_entries = static_cast<uint32_t>(entries_.size()); |
| | ofs->write(reinterpret_cast<const char*>(&num_entries), sizeof(uint32_t)); |
| |
|
| | for (uint32_t i = 0; i < num_entries; ++i) { |
| | entries_[i].Write(ofs); |
| | } |
| | } |
| |
|
| | } |
| | } |
| |
|
| | #endif |
| |
|