11 #include <boost/heap/fibonacci_heap.hpp>
13 #include "FLANN/flann.hpp"
34 template <
typename kDescType = uint8_t,
36 int kEmbeddingDim = 64>
113 std::vector<ImageScore>* image_scores)
const;
119 std::vector<ImageScore>* image_scores)
const;
141 std::vector<ImageScore>* image_scores,
142 Eigen::MatrixXi* word_ids)
const;
146 const int num_neighbors,
147 const int num_checks,
148 const int num_threads)
const;
151 flann::AutotunedIndex<flann::L2<kDescType>> visual_word_index_;
154 flann::Matrix<kDescType> visual_words_;
160 std::unordered_set<int> image_ids_;
170 template <
typename kDescType,
int kDescDim,
int kEmbeddingDim>
172 : prepared_(false) {}
174 template <
typename kDescType,
int kDescDim,
int kEmbeddingDim>
176 if (visual_words_.ptr() !=
nullptr) {
177 delete[] visual_words_.ptr();
181 template <
typename kDescType,
int kDescDim,
int kEmbeddingDim>
183 return visual_words_.rows;
186 template <
typename kDescType,
int kDescDim,
int kEmbeddingDim>
195 if (ImageIndexed(image_id)) {
199 image_ids_.insert(image_id);
207 const Eigen::MatrixXi word_ids =
215 geometry.
x = geometries[i].x;
216 geometry.
y = geometries[i].y;
217 geometry.
scale = geometries[i].ComputeScale();
218 geometry.
orientation = geometries[i].ComputeOrientation();
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,
230 template <
typename kDescType,
int kDescDim,
int kEmbeddingDim>
232 const int image_id)
const {
233 return image_ids_.count(image_id) != 0;
236 template <
typename kDescType,
int kDescDim,
int kEmbeddingDim>
240 std::vector<ImageScore>* image_scores)
const {
242 Query(options, geometries,
descriptors, image_scores);
245 template <
typename kDescType,
int kDescDim,
int kEmbeddingDim>
250 std::vector<ImageScore>* image_scores)
const {
251 Eigen::MatrixXi word_ids;
252 QueryAndFindWordIds(options,
descriptors, image_scores, &word_ids);
261 std::unordered_set<int> image_ids;
262 for (
const auto& image_score : *image_scores) {
263 image_ids.insert(image_score.image_id);
268 std::pair<float, std::pair<const EntryType*, const EntryType*>>>
269 OrderedMatchListType;
273 std::unordered_map<int, std::unordered_map<int, OrderedMatchListType>>
275 std::unordered_map<int, std::unordered_map<int, OrderedMatchListType>>
278 std::vector<const EntryType*> word_matches;
280 std::vector<EntryType> query_entries;
291 query_entry.
geometry.
x = geometries[i].x;
292 query_entry.
geometry.
y = geometries[i].y;
295 query_entries.push_back(query_entry);
301 std::unordered_map<int, std::pair<float, const EntryType*>>>
304 for (
int j = 0; j < word_ids.cols(); ++j) {
305 const int word_id = word_ids(i, j);
307 if (word_id != InvertedIndexType::kInvalidWordId) {
308 inverted_index_.ConvertToBinaryDescriptor(
309 word_id, descriptor, &query_entries[i].descriptor);
311 const auto idf_weight = inverted_index_.GetIDFWeight(word_id);
312 const auto squared_idf_weight = idf_weight * idf_weight;
314 inverted_index_.FindMatches(word_id, image_ids, &word_matches);
316 for (
const auto& match : word_matches) {
317 const size_t hamming_dist =
318 (query_entries[i].descriptor ^ match->descriptor)
324 hamming_dist_weight_functor(hamming_dist) *
327 auto& feature_matches = image_matches[match->image_id];
328 const auto feature_match =
329 feature_matches.find(match->feature_idx);
331 if (feature_match == feature_matches.end() ||
332 feature_match->first < dist) {
333 feature_matches[match->feature_idx] =
342 for (
const auto& feature_matches : image_matches) {
343 const auto image_id = feature_matches.first;
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;
350 const auto entry_pair =
353 query_to_db_matches[image_id][i].emplace_back(dist, entry_pair);
354 db_to_query_matches[image_id][feature_idx].emplace_back(
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];
366 if (query_matches.empty()) {
375 typedef boost::heap::fibonacci_heap<std::pair<int, int>>
377 FibonacciHeapType query_heap;
378 FibonacciHeapType db_heap;
379 std::unordered_map<int, typename FibonacciHeapType::handle_type>
381 std::unordered_map<int, typename FibonacciHeapType::handle_type>
384 for (
auto& match_data : query_matches) {
385 std::sort(match_data.second.begin(), match_data.second.end(),
387 std::pair<
float, std::pair<
const EntryType*,
390 query_heap_handles[match_data.first] = query_heap.push(
395 for (
auto& match_data : db_matches) {
396 std::sort(match_data.second.begin(), match_data.second.end(),
398 std::pair<
float, std::pair<
const EntryType*,
401 db_heap_handles[match_data.first] = db_heap.push(
407 std::vector<FeatureGeometryMatch> matches;
409 auto db_top = db_heap.top();
410 auto query_top = query_heap.top();
412 while (!db_heap.empty() && !query_heap.empty()) {
415 const bool use_query =
416 (query_top.first >= db_top.first) && !query_heap.empty();
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;
426 const auto idx1 = heap1.top().second;
431 if (handles1.count(idx1) > 0) {
432 handles1.erase(idx1);
434 bool match_found =
false;
438 for (
auto& entry2 : matches1[idx1]) {
440 (use_query) ? entry2.second.second->feature_idx
441 : entry2.second.first->feature_idx;
443 if (handles2.count(idx2) > 0) {
447 match.
geometry1 = entry2.second.first->geometry;
449 entry2.second.second->geometry);
450 matches.push_back(match);
452 handles2.erase(idx2);
456 for (
auto& entry1 : matches2[idx2]) {
457 const auto other_idx1 =
458 (use_query) ? entry1.second.first
460 : entry1.second.second
462 if (handles1.count(other_idx1) > 0) {
463 (*handles1[other_idx1]).first += 1;
464 heap1.increase(handles1[other_idx1]);
468 (*handles2[idx2]).first += 1;
469 heap2.increase(handles2[idx2]);
475 if (!query_heap.empty()) {
476 query_top = query_heap.top();
479 if (!db_heap.empty()) {
480 db_top = db_heap.top();
486 image_score.score +=
VoteAndVerify(vote_and_verify_options, matches);
491 const size_t num_images = std::min<size_t>(
495 return score1.
score > score2.score;
498 if (num_images == image_scores->size()) {
499 std::sort(image_scores->begin(), image_scores->end(), SortFunc);
501 std::partial_sort(image_scores->begin(),
502 image_scores->begin() + num_images,
503 image_scores->end(), SortFunc);
504 image_scores->resize(num_images);
508 template <
typename kDescType,
int kDescDim,
int kEmbeddingDim>
510 inverted_index_.Finalize();
514 template <
typename kDescType,
int kDescDim,
int kEmbeddingDim>
521 flann::AutotunedIndexParams index_params;
522 index_params[
"target_precision"] =
525 flann::AutotunedIndex<flann::L2<kDescType>>(index_params);
526 visual_word_index_.buildIndex(visual_words_);
530 inverted_index_.Initialize(NumVisualWords());
533 inverted_index_.GenerateHammingEmbeddingProjection();
536 const int kNumNeighbors = 1;
537 const Eigen::MatrixXi word_ids =
540 inverted_index_.ComputeHammingEmbedding(
descriptors, word_ids);
543 template <
typename kDescType,
int kDescDim,
int kEmbeddingDim>
545 const std::string&
path) {
546 long int file_offset = 0;
551 if (visual_words_.ptr() !=
nullptr) {
552 delete[] visual_words_.ptr();
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);
563 visual_words_ = flann::Matrix<kDescType>(visual_words_data, rows, cols);
564 file_offset = file.tellg();
570 flann::AutotunedIndex<flann::L2<kDescType>>(visual_words_);
573 FILE* fin = fopen(
path.c_str(),
"rb");
576 visual_word_index_.loadIndex(fin);
577 file_offset = ftell(fin);
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);
591 inverted_index_.GetImageIds(&image_ids_);
594 template <
typename kDescType,
int kDescDim,
int kEmbeddingDim>
596 const std::string&
path) {
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]);
613 FILE* fout = fopen(
path.c_str(),
"ab");
615 visual_word_index_.saveIndex(fout);
622 std::ofstream file(
path, std::ios::binary | std::ios::app);
623 CHECK(file.is_open()) <<
path;
624 inverted_index_.Write(&file);
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.");
633 CHECK_GE(options.num_visual_words, options.branching);
634 CHECK_GE(
descriptors.rows(), options.num_visual_words);
636 const flann::Matrix<kDescType> descriptor_matrix(
640 std::vector<typename flann::L2<kDescType>::ResultType> centers_data(
642 flann::Matrix<typename flann::L2<kDescType>::ResultType> centers(
643 centers_data.data(), options.num_visual_words,
descriptors.cols());
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);
652 CHECK_LE(num_centers, options.num_visual_words);
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]);
660 visual_words_data[i] = centers_data[i];
664 if (visual_words_.ptr() !=
nullptr) {
665 delete[] visual_words_.ptr();
668 visual_words_ = flann::Matrix<kDescType>(visual_words_data, num_centers,
672 template <
typename kDescType,
int kDescDim,
int kEmbeddingDim>
673 void VisualIndex<kDescType, kDescDim, kEmbeddingDim>::QueryAndFindWordIds(
674 const QueryOptions& options,
676 std::vector<ImageScore>* image_scores,
677 Eigen::MatrixXi* word_ids)
const {
681 image_scores->clear();
685 *word_ids = FindWordIds(
descriptors, options.num_neighbors,
686 options.num_checks, options.num_threads);
687 inverted_index_.Query(
descriptors, *word_ids, image_scores);
689 auto SortFunc = [](
const ImageScore& score1,
const ImageScore& score2) {
690 return score1.score > score2.score;
693 size_t num_images = image_scores->size();
694 if (options.max_num_images >= 0) {
696 std::min<size_t>(image_scores->size(), options.max_num_images);
699 if (num_images == image_scores->size()) {
700 std::sort(image_scores->begin(), image_scores->end(), SortFunc);
702 std::partial_sort(image_scores->begin(),
703 image_scores->begin() + num_images,
704 image_scores->end(), SortFunc);
705 image_scores->resize(num_images);
709 template <
typename kDescType,
int kDescDim,
int kEmbeddingDim>
710 Eigen::MatrixXi VisualIndex<kDescType, kDescDim, kEmbeddingDim>::FindWordIds(
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");
718 CHECK_GT(num_neighbors, 0);
720 Eigen::Matrix<size_t, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>
722 word_ids.setConstant(InvertedIndexType::kInvalidWordId);
723 flann::Matrix<size_t> indices(word_ids.data(),
descriptors.rows(),
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);
732 const flann::Matrix<kDescType> query(
736 flann::SearchParams search_params(num_checks);
737 if (num_threads < 0) {
738 search_params.cores = std::thread::hardware_concurrency();
740 search_params.cores = num_threads;
742 if (search_params.cores <= 0) {
743 search_params.cores = 1;
746 visual_word_index_.knnSearch(query, indices, distances, num_neighbors,
749 return word_ids.cast<
int>();
static const size_t kMaxHammingDistance
Eigen::Matrix< kDescType, Eigen::Dynamic, kDescDim, Eigen::RowMajor > DescType
InvertedIndex< kDescType, kDescDim, kEmbeddingDim > InvertedIndexType
InvertedIndexType::DescType DescType
InvertedIndexType::EntryType EntryType
bool ImageIndexed(const int image_id) const
void Read(const std::string &path)
void Add(const IndexOptions &options, const int image_id, const GeomType &geometries, const DescType &descriptors)
void Build(const BuildOptions &options, const DescType &descriptors)
static const int kMaxNumThreads
FeatureKeypoints GeomType
void Query(const QueryOptions &options, const DescType &descriptors, std::vector< ImageScore > *image_scores) const
size_t NumVisualWords() const
void Write(const std::string &path)
CLOUDVIEWER_HOST_DEVICE Pair< First, Second > make_pair(const First &_first, const Second &_second)
static const std::string path
int VoteAndVerify(const VoteAndVerifyOptions &options, const std::vector< FeatureGeometryMatch > &matches)
std::vector< FeatureKeypoint > FeatureKeypoints
Eigen::MatrixXd::Index Index
std::vector< FeatureGeometry > geometries2
FeatureGeometry geometry1
int num_images_after_verification