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