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