ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
sift.cc
Go to the documentation of this file.
1 // Copyright (c) 2018, ETH Zurich and UNC Chapel Hill.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are met:
6 //
7 // * Redistributions of source code must retain the above copyright
8 // notice, this list of conditions and the following disclaimer.
9 //
10 // * Redistributions in binary form must reproduce the above copyright
11 // notice, this list of conditions and the following disclaimer in the
12 // documentation and/or other materials provided with the distribution.
13 //
14 // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of
15 // its contributors may be used to endorse or promote products derived
16 // from this software without specific prior written permission.
17 //
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
22 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 // POSSIBILITY OF SUCH DAMAGE.
29 //
30 // Author: Johannes L. Schoenberger (jsch-at-demuc-dot-de)
31 
32 #include "feature/sift.h"
33 
34 #include <array>
35 #include <fstream>
36 #include <memory>
37 
38 #include "FLANN/flann.hpp"
39 #include "GL/glew.h"
40 #include "SiftGPU/SiftGPU.h"
41 #include "VLFeat/covdet.h"
42 #include "VLFeat/sift.h"
43 #include "feature/utils.h"
44 #include "util/cuda.h"
45 #include "util/logging.h"
46 #include "util/math.h"
47 #include "util/misc.h"
48 #include "util/opengl_utils.h"
49 
50 namespace colmap {
51 namespace {
52 
53 size_t FindBestMatchesOneWayBruteForce(const Eigen::MatrixXi& dists,
54  const float max_ratio,
55  const float max_distance,
56  std::vector<int>* matches) {
57  // SIFT descriptor vectors are normalized to length 512.
58  const float kDistNorm = 1.0f / (512.0f * 512.0f);
59 
60  size_t num_matches = 0;
61  matches->resize(dists.rows(), -1);
62 
63  for (Eigen::Index i1 = 0; i1 < dists.rows(); ++i1) {
64  int best_i2 = -1;
65  int best_dist = 0;
66  int second_best_dist = 0;
67  for (Eigen::Index i2 = 0; i2 < dists.cols(); ++i2) {
68  const int dist = dists(i1, i2);
69  if (dist > best_dist) {
70  best_i2 = i2;
71  second_best_dist = best_dist;
72  best_dist = dist;
73  } else if (dist > second_best_dist) {
74  second_best_dist = dist;
75  }
76  }
77 
78  // Check if any match found.
79  if (best_i2 == -1) {
80  continue;
81  }
82 
83  const float best_dist_normed =
84  std::acos(std::min(kDistNorm * best_dist, 1.0f));
85 
86  // Check if match distance passes threshold.
87  if (best_dist_normed > max_distance) {
88  continue;
89  }
90 
91  const float second_best_dist_normed =
92  std::acos(std::min(kDistNorm * second_best_dist, 1.0f));
93 
94  // Check if match passes ratio test. Keep this comparison >= in order to
95  // ensure that the case of best == second_best is detected.
96  if (best_dist_normed >= max_ratio * second_best_dist_normed) {
97  continue;
98  }
99 
100  num_matches += 1;
101  (*matches)[i1] = best_i2;
102  }
103 
104  return num_matches;
105 }
106 
107 void FindBestMatchesBruteForce(const Eigen::MatrixXi& dists,
108  const float max_ratio,
109  const float max_distance,
110  const bool cross_check,
111  FeatureMatches* matches) {
112  matches->clear();
113 
114  std::vector<int> matches12;
115  const size_t num_matches12 = FindBestMatchesOneWayBruteForce(
116  dists, max_ratio, max_distance, &matches12);
117 
118  if (cross_check) {
119  std::vector<int> matches21;
120  const size_t num_matches21 = FindBestMatchesOneWayBruteForce(
121  dists.transpose(), max_ratio, max_distance, &matches21);
122  matches->reserve(std::min(num_matches12, num_matches21));
123  for (size_t i1 = 0; i1 < matches12.size(); ++i1) {
124  if (matches12[i1] != -1 && matches21[matches12[i1]] != -1 &&
125  matches21[matches12[i1]] == static_cast<int>(i1)) {
126  FeatureMatch match;
127  match.point2D_idx1 = i1;
128  match.point2D_idx2 = matches12[i1];
129  matches->push_back(match);
130  }
131  }
132  } else {
133  matches->reserve(num_matches12);
134  for (size_t i1 = 0; i1 < matches12.size(); ++i1) {
135  if (matches12[i1] != -1) {
136  FeatureMatch match;
137  match.point2D_idx1 = i1;
138  match.point2D_idx2 = matches12[i1];
139  matches->push_back(match);
140  }
141  }
142  }
143 }
144 
145 // Mutexes that ensure that only one thread extracts/matches on the same GPU
146 // at the same time, since SiftGPU internally uses static variables.
147 static std::map<int, std::unique_ptr<std::mutex>> sift_extraction_mutexes;
148 static std::map<int, std::unique_ptr<std::mutex>> sift_matching_mutexes;
149 
150 // VLFeat uses a different convention to store its descriptors. This transforms
151 // the VLFeat format into the original SIFT format that is also used by SiftGPU.
152 FeatureDescriptors TransformVLFeatToUBCFeatureDescriptors(
153  const FeatureDescriptors& vlfeat_descriptors) {
154  FeatureDescriptors ubc_descriptors(vlfeat_descriptors.rows(),
155  vlfeat_descriptors.cols());
156  const std::array<int, 8> q{{0, 7, 6, 5, 4, 3, 2, 1}};
157  for (FeatureDescriptors::Index n = 0; n < vlfeat_descriptors.rows(); ++n) {
158  for (int i = 0; i < 4; ++i) {
159  for (int j = 0; j < 4; ++j) {
160  for (int k = 0; k < 8; ++k) {
161  ubc_descriptors(n, 8 * (j + 4 * i) + q[k]) =
162  vlfeat_descriptors(n, 8 * (j + 4 * i) + k);
163  }
164  }
165  }
166  }
167  return ubc_descriptors;
168 }
169 
170 Eigen::MatrixXi ComputeSiftDistanceMatrix(
171  const FeatureKeypoints* keypoints1,
172  const FeatureKeypoints* keypoints2,
173  const FeatureDescriptors& descriptors1,
174  const FeatureDescriptors& descriptors2,
175  const std::function<bool(float, float, float, float)>& guided_filter) {
176  if (guided_filter != nullptr) {
177  CHECK_NOTNULL(keypoints1);
178  CHECK_NOTNULL(keypoints2);
179  CHECK_EQ(keypoints1->size(), descriptors1.rows());
180  CHECK_EQ(keypoints2->size(), descriptors2.rows());
181  }
182 
183  const Eigen::Matrix<int, Eigen::Dynamic, 128> descriptors1_int =
184  descriptors1.cast<int>();
185  const Eigen::Matrix<int, Eigen::Dynamic, 128> descriptors2_int =
186  descriptors2.cast<int>();
187 
188  Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> dists(
189  descriptors1.rows(), descriptors2.rows());
190 
191  for (FeatureDescriptors::Index i1 = 0; i1 < descriptors1.rows(); ++i1) {
192  for (FeatureDescriptors::Index i2 = 0; i2 < descriptors2.rows(); ++i2) {
193  if (guided_filter != nullptr &&
194  guided_filter((*keypoints1)[i1].x, (*keypoints1)[i1].y,
195  (*keypoints2)[i2].x, (*keypoints2)[i2].y)) {
196  dists(i1, i2) = 0;
197  } else {
198  dists(i1, i2) =
199  descriptors1_int.row(i1).dot(descriptors2_int.row(i2));
200  }
201  }
202  }
203 
204  return dists;
205 }
206 
207 void FindNearestNeighborsFLANN(
208  const FeatureDescriptors& query,
209  const FeatureDescriptors& database,
210  Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>*
211  indices,
212  Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>*
213  distances) {
214  if (query.rows() == 0 || database.rows() == 0) {
215  return;
216  }
217 
218  const size_t kNumNearestNeighbors = 2;
219  const size_t kNumTreesInForest = 4;
220 
221  const size_t num_nearest_neighbors = std::min(
222  kNumNearestNeighbors, static_cast<size_t>(database.rows()));
223 
224  indices->resize(query.rows(), num_nearest_neighbors);
225  distances->resize(query.rows(), num_nearest_neighbors);
226  const flann::Matrix<uint8_t> query_matrix(
227  const_cast<uint8_t*>(query.data()), query.rows(), 128);
228  const flann::Matrix<uint8_t> database_matrix(
229  const_cast<uint8_t*>(database.data()), database.rows(), 128);
230 
231  flann::Matrix<int> indices_matrix(indices->data(), query.rows(),
232  num_nearest_neighbors);
233  std::vector<float> distances_vector(query.rows() * num_nearest_neighbors);
234  flann::Matrix<float> distances_matrix(distances_vector.data(), query.rows(),
235  num_nearest_neighbors);
236  flann::Index<flann::L2<uint8_t>> index(
237  database_matrix, flann::KDTreeIndexParams(kNumTreesInForest));
238  index.buildIndex();
239  index.knnSearch(query_matrix, indices_matrix, distances_matrix,
240  num_nearest_neighbors, flann::SearchParams(128));
241 
242  for (Eigen::Index query_index = 0; query_index < indices->rows();
243  ++query_index) {
244  for (Eigen::Index k = 0; k < indices->cols(); ++k) {
245  const Eigen::Index database_index = indices->coeff(query_index, k);
246  distances->coeffRef(query_index, k) =
247  query.row(query_index)
248  .cast<int>()
249  .dot(database.row(database_index).cast<int>());
250  }
251  }
252 }
253 
254 size_t FindBestMatchesOneWayFLANN(
255  const Eigen::
256  Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>&
257  indices,
258  const Eigen::
259  Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>&
260  distances,
261  const float max_ratio,
262  const float max_distance,
263  std::vector<int>* matches) {
264  // SIFT descriptor vectors are normalized to length 512.
265  const float kDistNorm = 1.0f / (512.0f * 512.0f);
266 
267  size_t num_matches = 0;
268  matches->resize(indices.rows(), -1);
269 
270  for (int d1_idx = 0; d1_idx < indices.rows(); ++d1_idx) {
271  int best_i2 = -1;
272  int best_dist = 0;
273  int second_best_dist = 0;
274  for (int n_idx = 0; n_idx < indices.cols(); ++n_idx) {
275  const int d2_idx = indices(d1_idx, n_idx);
276  const int dist = distances(d1_idx, n_idx);
277  if (dist > best_dist) {
278  best_i2 = d2_idx;
279  second_best_dist = best_dist;
280  best_dist = dist;
281  } else if (dist > second_best_dist) {
282  second_best_dist = dist;
283  }
284  }
285 
286  // Check if any match found.
287  if (best_i2 == -1) {
288  continue;
289  }
290 
291  const float best_dist_normed =
292  std::acos(std::min(kDistNorm * best_dist, 1.0f));
293 
294  // Check if match distance passes threshold.
295  if (best_dist_normed > max_distance) {
296  continue;
297  }
298 
299  const float second_best_dist_normed =
300  std::acos(std::min(kDistNorm * second_best_dist, 1.0f));
301 
302  // Check if match passes ratio test. Keep this comparison >= in order to
303  // ensure that the case of best == second_best is detected.
304  if (best_dist_normed >= max_ratio * second_best_dist_normed) {
305  continue;
306  }
307 
308  num_matches += 1;
309  (*matches)[d1_idx] = best_i2;
310  }
311 
312  return num_matches;
313 }
314 
315 void FindBestMatchesFLANN(
316  const Eigen::
317  Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>&
318  indices_1to2,
319  const Eigen::
320  Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>&
321  distances_1to2,
322  const Eigen::
323  Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>&
324  indices_2to1,
325  const Eigen::
326  Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>&
327  distances_2to1,
328  const float max_ratio,
329  const float max_distance,
330  const bool cross_check,
331  FeatureMatches* matches) {
332  matches->clear();
333 
334  std::vector<int> matches12;
335  const size_t num_matches12 = FindBestMatchesOneWayFLANN(
336  indices_1to2, distances_1to2, max_ratio, max_distance, &matches12);
337 
338  if (cross_check && indices_2to1.rows()) {
339  std::vector<int> matches21;
340  const size_t num_matches21 =
341  FindBestMatchesOneWayFLANN(indices_2to1, distances_2to1,
342  max_ratio, max_distance, &matches21);
343  matches->reserve(std::min(num_matches12, num_matches21));
344  for (size_t i1 = 0; i1 < matches12.size(); ++i1) {
345  if (matches12[i1] != -1 && matches21[matches12[i1]] != -1 &&
346  matches21[matches12[i1]] == static_cast<int>(i1)) {
347  FeatureMatch match;
348  match.point2D_idx1 = i1;
349  match.point2D_idx2 = matches12[i1];
350  matches->push_back(match);
351  }
352  }
353  } else {
354  matches->reserve(num_matches12);
355  for (size_t i1 = 0; i1 < matches12.size(); ++i1) {
356  if (matches12[i1] != -1) {
357  FeatureMatch match;
358  match.point2D_idx1 = i1;
359  match.point2D_idx2 = matches12[i1];
360  matches->push_back(match);
361  }
362  }
363  }
364 }
365 
366 void WarnIfMaxNumMatchesReachedGPU(const SiftMatchGPU& sift_match_gpu,
368  if (sift_match_gpu.GetMaxSift() < descriptors.rows()) {
369  std::cout << StringPrintf(
370  "WARNING: Clamping features from %d to %d - "
371  "consider "
372  "increasing the maximum number of matches.",
373  descriptors.rows(), sift_match_gpu.GetMaxSift())
374  << std::endl;
375  }
376 }
377 
378 void WarnDarknessAdaptivityNotAvailable() {
379  std::cout << "WARNING: Darkness adaptivity only available for GLSL SiftGPU."
380  << std::endl;
381 }
382 
383 } // namespace
384 
386  if (use_gpu) {
387  CHECK_OPTION_GT(CSVToVector<int>(gpu_index).size(), 0);
388  }
395  if (domain_size_pooling) {
399  }
400  return true;
401 }
402 
404  if (use_gpu) {
405  CHECK_OPTION_GT(CSVToVector<int>(gpu_index).size(), 0);
406  }
416  return true;
417 }
418 
420  const Bitmap& bitmap,
421  FeatureKeypoints* keypoints,
423  CHECK(options.Check());
424  CHECK(bitmap.IsGrey());
425  CHECK_NOTNULL(keypoints);
426 
427  CHECK(!options.estimate_affine_shape);
428  CHECK(!options.domain_size_pooling);
429 
430  if (options.darkness_adaptivity) {
431  WarnDarknessAdaptivityNotAvailable();
432  }
433 
434  // Setup SIFT extractor.
435  std::unique_ptr<VlSiftFilt, void (*)(VlSiftFilt*)> sift(
436  vl_sift_new(bitmap.Width(), bitmap.Height(), options.num_octaves,
437  options.octave_resolution, options.first_octave),
438  &vl_sift_delete);
439  if (!sift) {
440  return false;
441  }
442 
443  vl_sift_set_peak_thresh(sift.get(), options.peak_threshold);
444  vl_sift_set_edge_thresh(sift.get(), options.edge_threshold);
445 
446  // Iterate through octaves.
447  std::vector<size_t> level_num_features;
448  std::vector<FeatureKeypoints> level_keypoints;
449  std::vector<FeatureDescriptors> level_descriptors;
450  bool first_octave = true;
451  while (true) {
452  if (first_octave) {
453  const std::vector<uint8_t> data_uint8 =
454  bitmap.ConvertToRowMajorArray();
455  std::vector<float> data_float(data_uint8.size());
456  for (size_t i = 0; i < data_uint8.size(); ++i) {
457  data_float[i] = static_cast<float>(data_uint8[i]) / 255.0f;
458  }
459  if (vl_sift_process_first_octave(sift.get(), data_float.data())) {
460  break;
461  }
462  first_octave = false;
463  } else {
464  if (vl_sift_process_next_octave(sift.get())) {
465  break;
466  }
467  }
468 
469  // Detect keypoints.
470  vl_sift_detect(sift.get());
471 
472  // Extract detected keypoints.
473  const VlSiftKeypoint* vl_keypoints = vl_sift_get_keypoints(sift.get());
474  const int num_keypoints = vl_sift_get_nkeypoints(sift.get());
475  if (num_keypoints == 0) {
476  continue;
477  }
478 
479  // Extract features with different orientations per DOG level.
480  size_t level_idx = 0;
481  int prev_level = -1;
482  for (int i = 0; i < num_keypoints; ++i) {
483  if (vl_keypoints[i].is != prev_level) {
484  if (i > 0) {
485  // Resize containers of previous DOG level.
486  level_keypoints.back().resize(level_idx);
487  if (descriptors != nullptr) {
488  level_descriptors.back().conservativeResize(level_idx,
489  128);
490  }
491  }
492 
493  // Add containers for new DOG level.
494  level_idx = 0;
495  level_num_features.push_back(0);
496  level_keypoints.emplace_back(options.max_num_orientations *
497  num_keypoints);
498  if (descriptors != nullptr) {
499  level_descriptors.emplace_back(
500  options.max_num_orientations * num_keypoints, 128);
501  }
502  }
503 
504  level_num_features.back() += 1;
505  prev_level = vl_keypoints[i].is;
506 
507  // Extract feature orientations.
508  double angles[4];
509  int num_orientations;
510  if (options.upright) {
511  num_orientations = 1;
512  angles[0] = 0.0;
513  } else {
514  num_orientations = vl_sift_calc_keypoint_orientations(
515  sift.get(), angles, &vl_keypoints[i]);
516  }
517 
518  // Note that this is different from SiftGPU, which selects the top
519  // global maxima as orientations while this selects the first two
520  // local maxima. It is not clear which procedure is better.
521  const int num_used_orientations =
522  std::min(num_orientations, options.max_num_orientations);
523 
524  for (int o = 0; o < num_used_orientations; ++o) {
525  level_keypoints.back()[level_idx] = FeatureKeypoint(
526  vl_keypoints[i].x + 0.5f, vl_keypoints[i].y + 0.5f,
527  vl_keypoints[i].sigma, angles[o]);
528  if (descriptors != nullptr) {
529  Eigen::MatrixXf desc(1, 128);
530  vl_sift_calc_keypoint_descriptor(sift.get(), desc.data(),
531  &vl_keypoints[i],
532  angles[o]);
533  if (options.normalization ==
535  desc = L2NormalizeFeatureDescriptors(desc);
536  } else if (options.normalization ==
539  } else {
540  LOG(FATAL) << "Normalization type not supported";
541  }
542 
543  level_descriptors.back().row(level_idx) =
545  }
546 
547  level_idx += 1;
548  }
549  }
550 
551  // Resize containers for last DOG level in octave.
552  level_keypoints.back().resize(level_idx);
553  if (descriptors != nullptr) {
554  level_descriptors.back().conservativeResize(level_idx, 128);
555  }
556  }
557 
558  // Determine how many DOG levels to keep to satisfy max_num_features option.
559  int first_level_to_keep = 0;
560  int num_features = 0;
561  int num_features_with_orientations = 0;
562  for (int i = level_keypoints.size() - 1; i >= 0; --i) {
563  num_features += level_num_features[i];
564  num_features_with_orientations += level_keypoints[i].size();
565  if (num_features > options.max_num_features) {
566  first_level_to_keep = i;
567  break;
568  }
569  }
570 
571  // Extract the features to be kept.
572  {
573  size_t k = 0;
574  keypoints->resize(num_features_with_orientations);
575  for (size_t i = first_level_to_keep; i < level_keypoints.size(); ++i) {
576  for (size_t j = 0; j < level_keypoints[i].size(); ++j) {
577  (*keypoints)[k] = level_keypoints[i][j];
578  k += 1;
579  }
580  }
581  }
582 
583  // Compute the descriptors for the detected keypoints.
584  if (descriptors != nullptr) {
585  size_t k = 0;
586  descriptors->resize(num_features_with_orientations, 128);
587  for (size_t i = first_level_to_keep; i < level_keypoints.size(); ++i) {
588  for (size_t j = 0; j < level_keypoints[i].size(); ++j) {
589  descriptors->row(k) = level_descriptors[i].row(j);
590  k += 1;
591  }
592  }
593  *descriptors = TransformVLFeatToUBCFeatureDescriptors(*descriptors);
594  }
595 
596  return true;
597 }
598 
600  const Bitmap& bitmap,
601  FeatureKeypoints* keypoints,
603  CHECK(options.Check());
604  CHECK(bitmap.IsGrey());
605  CHECK_NOTNULL(keypoints);
606 
607  if (options.darkness_adaptivity) {
608  WarnDarknessAdaptivityNotAvailable();
609  }
610 
611  // Setup covariant SIFT detector.
612  std::unique_ptr<VlCovDet, void (*)(VlCovDet*)> covdet(
613  vl_covdet_new(VL_COVDET_METHOD_DOG), &vl_covdet_delete);
614  if (!covdet) {
615  return false;
616  }
617 
618  const int kMaxOctaveResolution = 1000;
619  CHECK_LE(options.octave_resolution, kMaxOctaveResolution);
620 
621  vl_covdet_set_first_octave(covdet.get(), options.first_octave);
622  vl_covdet_set_octave_resolution(covdet.get(), options.octave_resolution);
623  vl_covdet_set_peak_threshold(covdet.get(), options.peak_threshold);
624  vl_covdet_set_edge_threshold(covdet.get(), options.edge_threshold);
625 
626  {
627  const std::vector<uint8_t> data_uint8 = bitmap.ConvertToRowMajorArray();
628  std::vector<float> data_float(data_uint8.size());
629  for (size_t i = 0; i < data_uint8.size(); ++i) {
630  data_float[i] = static_cast<float>(data_uint8[i]) / 255.0f;
631  }
632  vl_covdet_put_image(covdet.get(), data_float.data(), bitmap.Width(),
633  bitmap.Height());
634  }
635 
636  vl_covdet_detect(covdet.get(), options.max_num_features);
637 
638  if (!options.upright) {
639  if (options.estimate_affine_shape) {
640  vl_covdet_extract_affine_shape(covdet.get());
641  } else {
642  vl_covdet_extract_orientations(covdet.get());
643  }
644  }
645 
646  const int num_features = vl_covdet_get_num_features(covdet.get());
647  VlCovDetFeature* features = vl_covdet_get_features(covdet.get());
648 
649  // Sort features according to detected octave and scale.
650  std::sort(features, features + num_features,
651  [](const VlCovDetFeature& feature1,
652  const VlCovDetFeature& feature2) {
653  if (feature1.o == feature2.o) {
654  return feature1.s > feature2.s;
655  } else {
656  return feature1.o > feature2.o;
657  }
658  });
659 
660  const size_t max_num_features =
661  static_cast<size_t>(options.max_num_features);
662 
663  // Copy detected keypoints and clamp when maximum number of features
664  // reached.
665  int prev_octave_scale_idx = std::numeric_limits<int>::max();
666  for (int i = 0; i < num_features; ++i) {
667  FeatureKeypoint keypoint;
668  keypoint.x = features[i].frame.x + 0.5;
669  keypoint.y = features[i].frame.y + 0.5;
670  keypoint.a11 = features[i].frame.a11;
671  keypoint.a12 = features[i].frame.a12;
672  keypoint.a21 = features[i].frame.a21;
673  keypoint.a22 = features[i].frame.a22;
674  keypoints->push_back(keypoint);
675 
676  const int octave_scale_idx =
677  features[i].o * kMaxOctaveResolution + features[i].s;
678  CHECK_LE(octave_scale_idx, prev_octave_scale_idx);
679 
680  if (octave_scale_idx != prev_octave_scale_idx &&
681  keypoints->size() >= max_num_features) {
682  break;
683  }
684 
685  prev_octave_scale_idx = octave_scale_idx;
686  }
687 
688  // Compute the descriptors for the detected keypoints.
689  if (descriptors != nullptr) {
690  descriptors->resize(keypoints->size(), 128);
691 
692  const size_t kPatchResolution = 15;
693  const size_t kPatchSide = 2 * kPatchResolution + 1;
694  const double kPatchRelativeExtent = 7.5;
695  const double kPatchRelativeSmoothing = 1;
696  const double kPatchStep = kPatchRelativeExtent / kPatchResolution;
697  const double kSigma =
698  kPatchRelativeExtent / (3.0 * (4 + 1) / 2) / kPatchStep;
699 
700  std::vector<float> patch(kPatchSide * kPatchSide);
701  std::vector<float> patchXY(2 * kPatchSide * kPatchSide);
702 
703  float dsp_min_scale = 1;
704  float dsp_scale_step = 0;
705  int dsp_num_scales = 1;
706  if (options.domain_size_pooling) {
707  dsp_min_scale = options.dsp_min_scale;
708  dsp_scale_step = (options.dsp_max_scale - options.dsp_min_scale) /
709  options.dsp_num_scales;
710  dsp_num_scales = options.dsp_num_scales;
711  }
712 
713  Eigen::Matrix<float, Eigen::Dynamic, 128, Eigen::RowMajor>
714  scaled_descriptors(dsp_num_scales, 128);
715 
716  std::unique_ptr<VlSiftFilt, void (*)(VlSiftFilt*)> sift(
717  vl_sift_new(16, 16, 1, 3, 0), &vl_sift_delete);
718  if (!sift) {
719  return false;
720  }
721 
722  vl_sift_set_magnif(sift.get(), 3.0);
723 
724  for (size_t i = 0; i < keypoints->size(); ++i) {
725  for (int s = 0; s < dsp_num_scales; ++s) {
726  const double dsp_scale = dsp_min_scale + s * dsp_scale_step;
727 
728  VlFrameOrientedEllipse scaled_frame = features[i].frame;
729  scaled_frame.a11 *= dsp_scale;
730  scaled_frame.a12 *= dsp_scale;
731  scaled_frame.a21 *= dsp_scale;
732  scaled_frame.a22 *= dsp_scale;
733 
734  vl_covdet_extract_patch_for_frame(
735  covdet.get(), patch.data(), kPatchResolution,
736  kPatchRelativeExtent, kPatchRelativeSmoothing,
737  scaled_frame);
738 
739  vl_imgradient_polar_f(patchXY.data(), patchXY.data() + 1, 2,
740  2 * kPatchSide, patch.data(), kPatchSide,
741  kPatchSide, kPatchSide);
742 
743  vl_sift_calc_raw_descriptor(sift.get(), patchXY.data(),
744  scaled_descriptors.row(s).data(),
745  kPatchSide, kPatchSide,
746  kPatchResolution, kPatchResolution,
747  kSigma, 0);
748  }
749 
750  Eigen::Matrix<float, 1, 128> descriptor;
751  if (options.domain_size_pooling) {
752  descriptor = scaled_descriptors.colwise().mean();
753  } else {
754  descriptor = scaled_descriptors;
755  }
756 
757  if (options.normalization ==
759  descriptor = L2NormalizeFeatureDescriptors(descriptor);
760  } else if (options.normalization ==
762  descriptor = L1RootNormalizeFeatureDescriptors(descriptor);
763  } else {
764  LOG(FATAL) << "Normalization type not supported";
765  }
766 
767  descriptors->row(i) = FeatureDescriptorsToUnsignedByte(descriptor);
768  }
769 
770  *descriptors = TransformVLFeatToUBCFeatureDescriptors(*descriptors);
771  }
772 
773  return true;
774 }
775 
777  SiftGPU* sift_gpu) {
778  CHECK(options.Check());
779  CHECK_NOTNULL(sift_gpu);
780 
781  // SiftGPU uses many global static state variables and the initialization
782  // must be thread-safe in order to work correctly. This is enforced here.
783  static std::mutex mutex;
784  std::unique_lock<std::mutex> lock(mutex);
785 
786  std::vector<int> gpu_indices = CSVToVector<int>(options.gpu_index);
787  CHECK_EQ(gpu_indices.size(), 1) << "SiftGPU can only run on one GPU";
788 
789  std::vector<std::string> sift_gpu_args;
790 
791  sift_gpu_args.push_back("./sift_gpu");
792 
793 #ifdef CUDA_ENABLED
794  // Use CUDA version by default if darkness adaptivity is disabled.
795  if (!options.darkness_adaptivity && gpu_indices[0] < 0) {
796  gpu_indices[0] = 0;
797  }
798 
799  if (gpu_indices[0] >= 0) {
800  sift_gpu_args.push_back("-cuda");
801  sift_gpu_args.push_back(std::to_string(gpu_indices[0]));
802  }
803 #endif // CUDA_ENABLED
804 
805  // Darkness adaptivity (hidden feature). Significantly improves
806  // distribution of features. Only available in GLSL version.
807  if (options.darkness_adaptivity) {
808  if (gpu_indices[0] >= 0) {
809  WarnDarknessAdaptivityNotAvailable();
810  }
811  sift_gpu_args.push_back("-da");
812  }
813 
814  // No verbose logging.
815  sift_gpu_args.push_back("-v");
816  sift_gpu_args.push_back("0");
817 
818  // Set maximum image dimension.
819  // Note the max dimension of SiftGPU is the maximum dimension of the
820  // first octave in the pyramid (which is the 'first_octave').
821  const int compensation_factor = 1 << -std::min(0, options.first_octave);
822  sift_gpu_args.push_back("-maxd");
823  sift_gpu_args.push_back(
824  std::to_string(options.max_image_size * compensation_factor));
825 
826  // Keep the highest level features.
827  sift_gpu_args.push_back("-tc2");
828  sift_gpu_args.push_back(std::to_string(options.max_num_features));
829 
830  // First octave level.
831  sift_gpu_args.push_back("-fo");
832  sift_gpu_args.push_back(std::to_string(options.first_octave));
833 
834  // Number of octave levels.
835  sift_gpu_args.push_back("-d");
836  sift_gpu_args.push_back(std::to_string(options.octave_resolution));
837 
838  // Peak threshold.
839  sift_gpu_args.push_back("-t");
840  sift_gpu_args.push_back(std::to_string(options.peak_threshold));
841 
842  // Edge threshold.
843  sift_gpu_args.push_back("-e");
844  sift_gpu_args.push_back(std::to_string(options.edge_threshold));
845 
846  if (options.upright) {
847  // Fix the orientation to 0 for upright features.
848  sift_gpu_args.push_back("-ofix");
849  // Maximum number of orientations.
850  sift_gpu_args.push_back("-mo");
851  sift_gpu_args.push_back("1");
852  } else {
853  // Maximum number of orientations.
854  sift_gpu_args.push_back("-mo");
855  sift_gpu_args.push_back(std::to_string(options.max_num_orientations));
856  }
857 
858  std::vector<const char*> sift_gpu_args_cstr;
859  sift_gpu_args_cstr.reserve(sift_gpu_args.size());
860  for (const auto& arg : sift_gpu_args) {
861  sift_gpu_args_cstr.push_back(arg.c_str());
862  }
863 
864  sift_gpu->ParseParam(sift_gpu_args_cstr.size(), sift_gpu_args_cstr.data());
865 
866  sift_gpu->gpu_index = gpu_indices[0];
867  if (sift_extraction_mutexes.count(gpu_indices[0]) == 0) {
868  sift_extraction_mutexes.emplace(
869  gpu_indices[0], std::unique_ptr<std::mutex>(new std::mutex()));
870  }
871 
872  return sift_gpu->VerifyContextGL() == SiftGPU::SIFTGPU_FULL_SUPPORTED;
873 }
874 
876  const Bitmap& bitmap,
877  SiftGPU* sift_gpu,
878  FeatureKeypoints* keypoints,
880  CHECK(options.Check());
881  CHECK(bitmap.IsGrey());
882  CHECK_NOTNULL(keypoints);
883  CHECK_NOTNULL(descriptors);
884 
885  // Note the max dimension of SiftGPU is the maximum dimension of the
886  // first octave in the pyramid (which is the 'first_octave').
887  const int compensation_factor = 1 << -std::min(0, options.first_octave);
888  CHECK_EQ(options.max_image_size * compensation_factor,
889  sift_gpu->GetMaxDimension());
890 
891  CHECK(!options.estimate_affine_shape);
892  CHECK(!options.domain_size_pooling);
893 
894  std::unique_lock<std::mutex> lock(
895  *sift_extraction_mutexes[sift_gpu->gpu_index]);
896 
897  // Note, that this produces slightly different results than using SiftGPU
898  // directly for RGB->GRAY conversion, since it uses different weights.
899  const std::vector<uint8_t> bitmap_raw_bits = bitmap.ConvertToRawBits();
900  const int code = sift_gpu->RunSIFT(bitmap.ScanWidth(), bitmap.Height(),
901  bitmap_raw_bits.data(), GL_LUMINANCE,
902  GL_UNSIGNED_BYTE);
903 
904  const int kSuccessCode = 1;
905  if (code != kSuccessCode) {
906  return false;
907  }
908 
909  const size_t num_features = static_cast<size_t>(sift_gpu->GetFeatureNum());
910 
911  std::vector<SiftKeypoint> keypoints_data(num_features);
912 
913  // Eigen's default is ColMajor, but SiftGPU stores result as RowMajor.
914  Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>
915  descriptors_float(num_features, 128);
916 
917  // Download the extracted keypoints and descriptors.
918  sift_gpu->GetFeatureVector(keypoints_data.data(), descriptors_float.data());
919 
920  keypoints->resize(num_features);
921  for (size_t i = 0; i < num_features; ++i) {
922  (*keypoints)[i] =
923  FeatureKeypoint(keypoints_data[i].x, keypoints_data[i].y,
924  keypoints_data[i].s, keypoints_data[i].o);
925  }
926 
927  // Save and normalize the descriptors.
929  descriptors_float = L2NormalizeFeatureDescriptors(descriptors_float);
930  } else if (options.normalization ==
932  descriptors_float =
933  L1RootNormalizeFeatureDescriptors(descriptors_float);
934  } else {
935  LOG(FATAL) << "Normalization type not supported";
936  }
937 
938  *descriptors = FeatureDescriptorsToUnsignedByte(descriptors_float);
939 
940  return true;
941 }
942 
943 void LoadSiftFeaturesFromTextFile(const std::string& path,
944  FeatureKeypoints* keypoints,
946  CHECK_NOTNULL(keypoints);
947  CHECK_NOTNULL(descriptors);
948 
949  std::ifstream file(path.c_str());
950  CHECK(file.is_open()) << path;
951 
952  std::string line;
953  std::string item;
954 
955  std::getline(file, line);
956  std::stringstream header_line_stream(line);
957 
958  std::getline(header_line_stream >> std::ws, item, ' ');
959  const point2D_t num_features = std::stoul(item);
960 
961  std::getline(header_line_stream >> std::ws, item, ' ');
962  const size_t dim = std::stoul(item);
963 
964  CHECK_EQ(dim, 128) << "SIFT features must have 128 dimensions";
965 
966  keypoints->resize(num_features);
967  descriptors->resize(num_features, dim);
968 
969  for (size_t i = 0; i < num_features; ++i) {
970  std::getline(file, line);
971  std::stringstream feature_line_stream(line);
972 
973  std::getline(feature_line_stream >> std::ws, item, ' ');
974  const float x = std::stold(item);
975 
976  std::getline(feature_line_stream >> std::ws, item, ' ');
977  const float y = std::stold(item);
978 
979  std::getline(feature_line_stream >> std::ws, item, ' ');
980  const float scale = std::stold(item);
981 
982  std::getline(feature_line_stream >> std::ws, item, ' ');
983  const float orientation = std::stold(item);
984 
985  (*keypoints)[i] = FeatureKeypoint(x, y, scale, orientation);
986 
987  // Descriptor
988  for (size_t j = 0; j < dim; ++j) {
989  std::getline(feature_line_stream >> std::ws, item, ' ');
990  const float value = std::stod(item);
991  CHECK_GE(value, 0);
992  CHECK_LE(value, 255);
993  (*descriptors)(i, j) = TruncateCast<float, uint8_t>(value);
994  }
995  }
996 }
997 
999  const FeatureDescriptors& descriptors1,
1000  const FeatureDescriptors& descriptors2,
1001  FeatureMatches* matches) {
1002  CHECK(match_options.Check());
1003  CHECK_NOTNULL(matches);
1004 
1005  const Eigen::MatrixXi distances = ComputeSiftDistanceMatrix(
1006  nullptr, nullptr, descriptors1, descriptors2, nullptr);
1007 
1008  FindBestMatchesBruteForce(distances, match_options.max_ratio,
1009  match_options.max_distance,
1010  match_options.cross_check, matches);
1011 }
1012 
1014  const FeatureDescriptors& descriptors1,
1015  const FeatureDescriptors& descriptors2,
1016  FeatureMatches* matches) {
1017  CHECK(match_options.Check());
1018  CHECK_NOTNULL(matches);
1019 
1020  Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>
1021  indices_1to2;
1022  Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>
1023  distances_1to2;
1024  Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>
1025  indices_2to1;
1026  Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>
1027  distances_2to1;
1028 
1029  FindNearestNeighborsFLANN(descriptors1, descriptors2, &indices_1to2,
1030  &distances_1to2);
1031  if (match_options.cross_check) {
1032  FindNearestNeighborsFLANN(descriptors2, descriptors1, &indices_2to1,
1033  &distances_2to1);
1034  }
1035 
1036  FindBestMatchesFLANN(indices_1to2, distances_1to2, indices_2to1,
1037  distances_2to1, match_options.max_ratio,
1038  match_options.max_distance, match_options.cross_check,
1039  matches);
1040 }
1041 
1042 void MatchSiftFeaturesCPU(const SiftMatchingOptions& match_options,
1043  const FeatureDescriptors& descriptors1,
1044  const FeatureDescriptors& descriptors2,
1045  FeatureMatches* matches) {
1046  MatchSiftFeaturesCPUFLANN(match_options, descriptors1, descriptors2,
1047  matches);
1048 }
1049 
1051  const FeatureKeypoints& keypoints1,
1052  const FeatureKeypoints& keypoints2,
1053  const FeatureDescriptors& descriptors1,
1054  const FeatureDescriptors& descriptors2,
1055  TwoViewGeometry* two_view_geometry) {
1056  CHECK(match_options.Check());
1057  CHECK_NOTNULL(two_view_geometry);
1058 
1059  const float max_residual =
1060  match_options.max_error * match_options.max_error;
1061 
1062  const Eigen::Matrix3f F = two_view_geometry->F.cast<float>();
1063  const Eigen::Matrix3f H = two_view_geometry->H.cast<float>();
1064 
1065  std::function<bool(float, float, float, float)> guided_filter;
1066  if (two_view_geometry->config == TwoViewGeometry::CALIBRATED ||
1067  two_view_geometry->config == TwoViewGeometry::UNCALIBRATED) {
1068  guided_filter = [&](const float x1, const float y1, const float x2,
1069  const float y2) {
1070  const Eigen::Vector3f p1(x1, y1, 1.0f);
1071  const Eigen::Vector3f p2(x2, y2, 1.0f);
1072  const Eigen::Vector3f Fx1 = F * p1;
1073  const Eigen::Vector3f Ftx2 = F.transpose() * p2;
1074  const float x2tFx1 = p2.transpose() * Fx1;
1075  return x2tFx1 * x2tFx1 /
1076  (Fx1(0) * Fx1(0) + Fx1(1) * Fx1(1) +
1077  Ftx2(0) * Ftx2(0) + Ftx2(1) * Ftx2(1)) >
1078  max_residual;
1079  };
1080  } else if (two_view_geometry->config == TwoViewGeometry::PLANAR ||
1081  two_view_geometry->config == TwoViewGeometry::PANORAMIC ||
1082  two_view_geometry->config ==
1084  guided_filter = [&](const float x1, const float y1, const float x2,
1085  const float y2) {
1086  const Eigen::Vector3f p1(x1, y1, 1.0f);
1087  const Eigen::Vector2f p2(x2, y2);
1088  return ((H * p1).hnormalized() - p2).squaredNorm() > max_residual;
1089  };
1090  } else {
1091  return;
1092  }
1093 
1094  CHECK(guided_filter);
1095 
1096  const Eigen::MatrixXi dists =
1097  ComputeSiftDistanceMatrix(&keypoints1, &keypoints2, descriptors1,
1098  descriptors2, guided_filter);
1099 
1100  Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>
1101  indices_1to2(dists.rows(), dists.cols());
1102  Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>
1103  indices_2to1(dists.cols(), dists.rows());
1104  Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>
1105  distances_1to2 = dists;
1106  Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>
1107  distances_2to1 = dists.transpose();
1108 
1109  for (int i = 0; i < indices_1to2.rows(); ++i) {
1110  indices_1to2.row(i) = Eigen::VectorXi::LinSpaced(
1111  indices_1to2.cols(), 0, indices_1to2.cols() - 1);
1112  }
1113  for (int i = 0; i < indices_2to1.rows(); ++i) {
1114  indices_2to1.row(i) = Eigen::VectorXi::LinSpaced(
1115  indices_2to1.cols(), 0, indices_2to1.cols() - 1);
1116  }
1117 
1118  FindBestMatchesFLANN(indices_1to2, distances_1to2, indices_2to1,
1119  distances_2to1, match_options.max_ratio,
1120  match_options.max_distance, match_options.cross_check,
1121  &two_view_geometry->inlier_matches);
1122 }
1123 
1124 bool CreateSiftGPUMatcher(const SiftMatchingOptions& match_options,
1125  SiftMatchGPU* sift_match_gpu) {
1126  CHECK(match_options.Check());
1127  CHECK_NOTNULL(sift_match_gpu);
1128 
1129  // SiftGPU uses many global static state variables and the initialization
1130  // must be thread-safe in order to work correctly. This is enforced here.
1131  static std::mutex mutex;
1132  std::unique_lock<std::mutex> lock(mutex);
1133 
1134  const std::vector<int> gpu_indices =
1135  CSVToVector<int>(match_options.gpu_index);
1136  CHECK_EQ(gpu_indices.size(), 1) << "SiftGPU can only run on one GPU";
1137 
1138  SiftGPU sift_gpu;
1139  sift_gpu.SetVerbose(0);
1140 
1141  // CRITICAL FIX: Don't call SetMaxSift() before VerifyContextGL()!
1142  // The SiftMatchGPU constructor already set __max_sift correctly.
1143  // SetMaxSift() only works AFTER __matcher is created by VerifyContextGL().
1144  // Calling it before can cause issues on Ubuntu 22.04 due to stricter memory
1145  // management.
1146 
1147 #ifdef CUDA_ENABLED
1148  if (gpu_indices[0] >= 0) {
1149  sift_match_gpu->SetLanguage(SiftMatchGPU::SIFTMATCH_CUDA_DEVICE0 +
1150  gpu_indices[0]);
1151  } else {
1152  sift_match_gpu->SetLanguage(SiftMatchGPU::SIFTMATCH_CUDA);
1153  }
1154 #else // CUDA_ENABLED
1155  sift_match_gpu->SetLanguage(SiftMatchGPU::SIFTMATCH_GLSL);
1156 #endif // CUDA_ENABLED
1157 
1158  if (sift_match_gpu->VerifyContextGL() == 0) {
1159  return false;
1160  }
1161 
1162  if (!sift_match_gpu->Allocate(match_options.max_num_matches,
1163  match_options.cross_check)) {
1164  std::cout
1165  << StringPrintf(
1166  "ERROR: Not enough GPU memory to match %d features. "
1167  "Reduce the maximum number of matches.",
1168  match_options.max_num_matches)
1169  << std::endl;
1170  return false;
1171  }
1172 
1173 #ifndef CUDA_ENABLED
1174  if (sift_match_gpu->GetMaxSift() < match_options.max_num_matches) {
1175  std::cout
1176  << StringPrintf(
1177  "WARNING: OpenGL version of SiftGPU only supports a "
1178  "maximum of %d matches - consider changing to "
1179  "CUDA-based "
1180  "feature matching to avoid this limitation.",
1181  sift_match_gpu->GetMaxSift())
1182  << std::endl;
1183  }
1184 #endif // CUDA_ENABLED
1185 
1186  sift_match_gpu->gpu_index = gpu_indices[0];
1187  if (sift_matching_mutexes.count(gpu_indices[0]) == 0) {
1188  sift_matching_mutexes.emplace(
1189  gpu_indices[0], std::unique_ptr<std::mutex>(new std::mutex()));
1190  }
1191 
1192  return true;
1193 }
1194 
1195 void MatchSiftFeaturesGPU(const SiftMatchingOptions& match_options,
1196  const FeatureDescriptors* descriptors1,
1197  const FeatureDescriptors* descriptors2,
1198  SiftMatchGPU* sift_match_gpu,
1199  FeatureMatches* matches) {
1200  CHECK(match_options.Check());
1201  CHECK_NOTNULL(sift_match_gpu);
1202  CHECK_NOTNULL(matches);
1203 
1204  // Verify GPU index exists in mutex map
1205  if (sift_matching_mutexes.find(sift_match_gpu->gpu_index) ==
1206  sift_matching_mutexes.end()) {
1207  std::cerr << "ERROR: GPU index " << sift_match_gpu->gpu_index
1208  << " not found in SIFT matching mutexes" << std::endl;
1209  // Don't call matches->clear() - let caller handle it
1210  // Just resize to 0 to indicate failure
1211  matches->resize(0);
1212  return;
1213  }
1214 
1215  std::unique_lock<std::mutex> lock(
1216  *sift_matching_mutexes[sift_match_gpu->gpu_index]);
1217 
1218  if (descriptors1 != nullptr) {
1219  if (descriptors1->cols() != 128) {
1220  std::cerr
1221  << "ERROR: Invalid descriptor dimensions for descriptors1: "
1222  << descriptors1->cols() << " (expected 128)" << std::endl;
1223  matches->resize(0);
1224  return;
1225  }
1226  WarnIfMaxNumMatchesReachedGPU(*sift_match_gpu, *descriptors1);
1227  sift_match_gpu->SetDescriptors(0, descriptors1->rows(),
1228  descriptors1->data());
1229  }
1230 
1231  if (descriptors2 != nullptr) {
1232  if (descriptors2->cols() != 128) {
1233  std::cerr
1234  << "ERROR: Invalid descriptor dimensions for descriptors2: "
1235  << descriptors2->cols() << " (expected 128)" << std::endl;
1236  matches->resize(0);
1237  return;
1238  }
1239  WarnIfMaxNumMatchesReachedGPU(*sift_match_gpu, *descriptors2);
1240  sift_match_gpu->SetDescriptors(1, descriptors2->rows(),
1241  descriptors2->data());
1242  }
1243 
1244  matches->resize(static_cast<size_t>(match_options.max_num_matches));
1245 
1246  const int num_matches = sift_match_gpu->GetSiftMatch(
1247  match_options.max_num_matches,
1248  reinterpret_cast<uint32_t(*)[2]>(matches->data()),
1249  static_cast<float>(match_options.max_distance),
1250  static_cast<float>(match_options.max_ratio),
1251  match_options.cross_check);
1252 
1253  if (num_matches < 0) {
1254  std::cerr
1255  << "ERROR: Feature matching failed. This is probably caused by "
1256  "insufficient GPU memory. Consider reducing the maximum "
1257  "number of features and/or matches."
1258  << std::endl;
1259  // Use resize(0) instead of clear() to avoid potential double free
1260  matches->resize(0);
1261  } else {
1262  CHECK_LE(num_matches, matches->size());
1263  matches->resize(num_matches);
1264  }
1265 }
1266 
1268  const FeatureKeypoints* keypoints1,
1269  const FeatureKeypoints* keypoints2,
1270  const FeatureDescriptors* descriptors1,
1271  const FeatureDescriptors* descriptors2,
1272  SiftMatchGPU* sift_match_gpu,
1273  TwoViewGeometry* two_view_geometry) {
1274  static_assert(offsetof(FeatureKeypoint, x) == 0 * sizeof(float),
1275  "Invalid keypoint format");
1276  static_assert(offsetof(FeatureKeypoint, y) == 1 * sizeof(float),
1277  "Invalid keypoint format");
1278  static_assert(sizeof(FeatureKeypoint) == 6 * sizeof(float),
1279  "Invalid keypoint format");
1280 
1281  CHECK(match_options.Check());
1282  CHECK_NOTNULL(sift_match_gpu);
1283  CHECK_NOTNULL(two_view_geometry);
1284 
1285  std::unique_lock<std::mutex> lock(
1286  *sift_matching_mutexes[sift_match_gpu->gpu_index]);
1287 
1288  const size_t kFeatureShapeNumElems = 4;
1289 
1290  if (descriptors1 != nullptr) {
1291  CHECK_NOTNULL(keypoints1);
1292  CHECK_EQ(descriptors1->rows(), keypoints1->size());
1293  CHECK_EQ(descriptors1->cols(), 128);
1294  WarnIfMaxNumMatchesReachedGPU(*sift_match_gpu, *descriptors1);
1295  const size_t kIndex = 0;
1296  sift_match_gpu->SetDescriptors(kIndex, descriptors1->rows(),
1297  descriptors1->data());
1298  sift_match_gpu->SetFeautreLocation(
1299  kIndex, reinterpret_cast<const float*>(keypoints1->data()),
1300  kFeatureShapeNumElems);
1301  }
1302 
1303  if (descriptors2 != nullptr) {
1304  CHECK_NOTNULL(keypoints2);
1305  CHECK_EQ(descriptors2->rows(), keypoints2->size());
1306  CHECK_EQ(descriptors2->cols(), 128);
1307  WarnIfMaxNumMatchesReachedGPU(*sift_match_gpu, *descriptors2);
1308  const size_t kIndex = 1;
1309  sift_match_gpu->SetDescriptors(kIndex, descriptors2->rows(),
1310  descriptors2->data());
1311  sift_match_gpu->SetFeautreLocation(
1312  kIndex, reinterpret_cast<const float*>(keypoints2->data()),
1313  kFeatureShapeNumElems);
1314  }
1315 
1316  Eigen::Matrix<float, 3, 3, Eigen::RowMajor> F;
1317  Eigen::Matrix<float, 3, 3, Eigen::RowMajor> H;
1318  float* F_ptr = nullptr;
1319  float* H_ptr = nullptr;
1320  if (two_view_geometry->config == TwoViewGeometry::CALIBRATED ||
1321  two_view_geometry->config == TwoViewGeometry::UNCALIBRATED) {
1322  F = two_view_geometry->F.cast<float>();
1323  F_ptr = F.data();
1324  } else if (two_view_geometry->config == TwoViewGeometry::PLANAR ||
1325  two_view_geometry->config == TwoViewGeometry::PANORAMIC ||
1326  two_view_geometry->config ==
1328  H = two_view_geometry->H.cast<float>();
1329  H_ptr = H.data();
1330  } else {
1331  return;
1332  }
1333 
1334  CHECK(F_ptr != nullptr || H_ptr != nullptr);
1335 
1336  two_view_geometry->inlier_matches.resize(
1337  static_cast<size_t>(match_options.max_num_matches));
1338 
1339  const int num_matches = sift_match_gpu->GetGuidedSiftMatch(
1340  match_options.max_num_matches,
1341  reinterpret_cast<uint32_t(*)[2]>(
1342  two_view_geometry->inlier_matches.data()),
1343  H_ptr, F_ptr, static_cast<float>(match_options.max_distance),
1344  static_cast<float>(match_options.max_ratio),
1345  static_cast<float>(match_options.max_error *
1346  match_options.max_error),
1347  static_cast<float>(match_options.max_error *
1348  match_options.max_error),
1349  match_options.cross_check);
1350 
1351  if (num_matches < 0) {
1352  std::cerr
1353  << "ERROR: Feature matching failed. This is probably caused by "
1354  "insufficient GPU memory. Consider reducing the maximum "
1355  "number of features."
1356  << std::endl;
1357  two_view_geometry->inlier_matches.clear();
1358  } else {
1359  CHECK_LE(num_matches, two_view_geometry->inlier_matches.size());
1360  two_view_geometry->inlier_matches.resize(num_matches);
1361  }
1362 }
1363 
1364 } // namespace colmap
int size
std::vector< uint8_t > ConvertToRowMajorArray() const
Definition: bitmap.cc:147
std::vector< uint8_t > ConvertToRawBits() const
Definition: bitmap.cc:136
unsigned int ScanWidth() const
Definition: bitmap.h:257
int Width() const
Definition: bitmap.h:249
bool IsGrey() const
Definition: bitmap.h:263
int Height() const
Definition: bitmap.h:250
#define CHECK_OPTION_LE(val1, val2)
Definition: logging.h:31
#define CHECK_OPTION_GT(val1, val2)
Definition: logging.h:34
#define CHECK_OPTION_GE(val1, val2)
Definition: logging.h:33
normal_z y
normal_z x
Definition: Eigen.h:103
QTextStream & endl(QTextStream &stream)
Definition: QtCompat.h:718
static const std::string path
Definition: PointCloud.cpp:59
Eigen::MatrixXf L1RootNormalizeFeatureDescriptors(const Eigen::MatrixXf &descriptors)
Definition: utils.cc:52
bool ExtractCovariantSiftFeaturesCPU(const SiftExtractionOptions &options, const Bitmap &bitmap, FeatureKeypoints *keypoints, FeatureDescriptors *descriptors)
Definition: sift.cc:599
void MatchGuidedSiftFeaturesGPU(const SiftMatchingOptions &match_options, const FeatureKeypoints *keypoints1, const FeatureKeypoints *keypoints2, const FeatureDescriptors *descriptors1, const FeatureDescriptors *descriptors2, SiftMatchGPU *sift_match_gpu, TwoViewGeometry *two_view_geometry)
Definition: sift.cc:1267
bool CreateSiftGPUExtractor(const SiftExtractionOptions &options, SiftGPU *sift_gpu)
Definition: sift.cc:776
void MatchSiftFeaturesGPU(const SiftMatchingOptions &match_options, const FeatureDescriptors *descriptors1, const FeatureDescriptors *descriptors2, SiftMatchGPU *sift_match_gpu, FeatureMatches *matches)
Definition: sift.cc:1195
uint32_t point2D_t
Definition: types.h:67
bool CreateSiftGPUMatcher(const SiftMatchingOptions &match_options, SiftMatchGPU *sift_match_gpu)
Definition: sift.cc:1124
Eigen::Matrix< uint8_t, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor > FeatureDescriptors
Definition: types.h:79
FeatureDescriptors FeatureDescriptorsToUnsignedByte(const Eigen::MatrixXf &descriptors)
Definition: utils.cc:65
Eigen::MatrixXf L2NormalizeFeatureDescriptors(const Eigen::MatrixXf &descriptors)
Definition: utils.cc:47
void MatchSiftFeaturesCPUBruteForce(const SiftMatchingOptions &match_options, const FeatureDescriptors &descriptors1, const FeatureDescriptors &descriptors2, FeatureMatches *matches)
Definition: sift.cc:998
void MatchSiftFeaturesCPU(const SiftMatchingOptions &match_options, const FeatureDescriptors &descriptors1, const FeatureDescriptors &descriptors2, FeatureMatches *matches)
Definition: sift.cc:1042
bool ExtractSiftFeaturesCPU(const SiftExtractionOptions &options, const Bitmap &bitmap, FeatureKeypoints *keypoints, FeatureDescriptors *descriptors)
Definition: sift.cc:419
void MatchGuidedSiftFeaturesCPU(const SiftMatchingOptions &match_options, const FeatureKeypoints &keypoints1, const FeatureKeypoints &keypoints2, const FeatureDescriptors &descriptors1, const FeatureDescriptors &descriptors2, TwoViewGeometry *two_view_geometry)
Definition: sift.cc:1050
std::vector< FeatureKeypoint > FeatureKeypoints
Definition: types.h:77
std::string StringPrintf(const char *format,...)
Definition: string.cc:131
std::vector< FeatureMatch > FeatureMatches
Definition: types.h:80
bool ExtractSiftFeaturesGPU(const SiftExtractionOptions &options, const Bitmap &bitmap, SiftGPU *sift_gpu, FeatureKeypoints *keypoints, FeatureDescriptors *descriptors)
Definition: sift.cc:875
void LoadSiftFeaturesFromTextFile(const std::string &path, FeatureKeypoints *keypoints, FeatureDescriptors *descriptors)
Definition: sift.cc:943
void MatchSiftFeaturesCPUFLANN(const SiftMatchingOptions &match_options, const FeatureDescriptors &descriptors1, const FeatureDescriptors &descriptors2, FeatureMatches *matches)
Definition: sift.cc:1013
Eigen::MatrixXd::Index Index
Definition: knncpp.h:26
Definition: Common.h:18
std::string to_string(const T &n)
Definition: Common.h:20
CorePointDescSet * descriptors
Normalization normalization
Definition: sift.h:91
std::string gpu_index
Definition: sift.h:32
bool Check() const
Definition: sift.cc:403
std::string gpu_index
Definition: sift.h:109