ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
incremental_mapper.cc
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
9 
10 #include "util/misc.h"
11 
12 namespace colmap {
13 namespace {
14 
15 size_t TriangulateImage(const IncrementalMapperOptions& options,
16  const Image& image,
17  IncrementalMapper* mapper) {
18  std::cout << " => Continued observations: " << image.NumPoints3D()
19  << std::endl;
20  const size_t num_tris =
21  mapper->TriangulateImage(options.Triangulation(), image.ImageId());
22  std::cout << " => Added observations: " << num_tris << std::endl;
23  return num_tris;
24 }
25 
26 void AdjustGlobalBundle(const IncrementalMapperOptions& options,
27  IncrementalMapper* mapper) {
28  BundleAdjustmentOptions custom_ba_options =
29  options.GlobalBundleAdjustment();
30 
31  const size_t num_reg_images = mapper->GetReconstruction().NumRegImages();
32 
33  // Use stricter convergence criteria for first registered images.
34  const size_t kMinNumRegImagesForFastBA = 10;
35  if (num_reg_images < kMinNumRegImagesForFastBA) {
36  custom_ba_options.solver_options.function_tolerance /= 10;
37  custom_ba_options.solver_options.gradient_tolerance /= 10;
38  custom_ba_options.solver_options.parameter_tolerance /= 10;
39  custom_ba_options.solver_options.max_num_iterations *= 2;
40  custom_ba_options.solver_options.max_linear_solver_iterations = 200;
41  }
42 
43  PrintHeading1("Global bundle adjustment");
44 #ifdef PBA_ENABLED
45  if (options.ba_global_use_pba && !options.fix_existing_images &&
46  num_reg_images >= kMinNumRegImagesForFastBA &&
47  ParallelBundleAdjuster::IsSupported(custom_ba_options,
48  mapper->GetReconstruction())) {
49  mapper->AdjustParallelGlobalBundle(
50  custom_ba_options, options.ParallelGlobalBundleAdjustment());
51  } else {
52  mapper->AdjustGlobalBundle(options.Mapper(), custom_ba_options);
53  }
54 #else
55  mapper->AdjustGlobalBundle(options.Mapper(), custom_ba_options);
56 #endif
57 }
58 
59 void IterativeLocalRefinement(const IncrementalMapperOptions& options,
60  const image_t image_id,
61  IncrementalMapper* mapper) {
62  auto ba_options = options.LocalBundleAdjustment();
63  for (int i = 0; i < options.ba_local_max_refinements; ++i) {
64  const auto report = mapper->AdjustLocalBundle(
65  options.Mapper(), ba_options, options.Triangulation(), image_id,
66  mapper->GetModifiedPoints3D());
67  std::cout << " => Merged observations: "
68  << report.num_merged_observations << std::endl;
69  std::cout << " => Completed observations: "
70  << report.num_completed_observations << std::endl;
71  std::cout << " => Filtered observations: "
72  << report.num_filtered_observations << std::endl;
73  const double changed =
74  (report.num_merged_observations +
75  report.num_completed_observations +
76  report.num_filtered_observations) /
77  static_cast<double>(report.num_adjusted_observations);
78  std::cout << StringPrintf(" => Changed observations: %.6f", changed)
79  << std::endl;
80  if (changed < options.ba_local_max_refinement_change) {
81  break;
82  }
83  // Only use robust cost function for first iteration.
84  ba_options.loss_function_type =
86  }
87  mapper->ClearModifiedPoints3D();
88 }
89 
90 void IterativeGlobalRefinement(const IncrementalMapperOptions& options,
91  IncrementalMapper* mapper) {
92  PrintHeading1("Retriangulation");
93  CompleteAndMergeTracks(options, mapper);
94  std::cout << " => Retriangulated observations: "
95  << mapper->Retriangulate(options.Triangulation()) << std::endl;
96 
97  for (int i = 0; i < options.ba_global_max_refinements; ++i) {
98  const size_t num_observations =
99  mapper->GetReconstruction().ComputeNumObservations();
100  size_t num_changed_observations = 0;
101  AdjustGlobalBundle(options, mapper);
102  num_changed_observations += CompleteAndMergeTracks(options, mapper);
103  num_changed_observations += FilterPoints(options, mapper);
104  const double changed = static_cast<double>(num_changed_observations) /
105  num_observations;
106  std::cout << StringPrintf(" => Changed observations: %.6f", changed)
107  << std::endl;
108  if (changed < options.ba_global_max_refinement_change) {
109  break;
110  }
111  }
112 
113  FilterImages(options, mapper);
114 }
115 
116 void ExtractColors(const std::string& image_path,
117  const image_t image_id,
118  Reconstruction* reconstruction) {
119  if (!reconstruction->ExtractColorsForImage(image_id, image_path)) {
120  std::cout << StringPrintf(
121  "WARNING: Could not read image %s at path %s.",
122  reconstruction->Image(image_id).Name().c_str(),
123  image_path.c_str())
124  << std::endl;
125  }
126 }
127 
128 void WriteSnapshot(const Reconstruction& reconstruction,
129  const std::string& snapshot_path) {
130  PrintHeading1("Creating snapshot");
131  // Get the current timestamp in milliseconds.
132  const size_t timestamp =
133  std::chrono::duration_cast<std::chrono::milliseconds>(
134  std::chrono::high_resolution_clock::now()
135  .time_since_epoch())
136  .count();
137  // Write reconstruction to unique path with current timestamp.
138  const std::string path =
139  JoinPaths(snapshot_path, StringPrintf("%010d", timestamp));
141  std::cout << " => Writing to " << path << std::endl;
142  reconstruction.Write(path);
143 }
144 
145 } // namespace
146 
148  IncrementalMapper* mapper) {
149  const size_t num_filtered_observations =
150  mapper->FilterPoints(options.Mapper());
151  std::cout << " => Filtered observations: " << num_filtered_observations
152  << std::endl;
153  return num_filtered_observations;
154 }
155 
157  IncrementalMapper* mapper) {
158  const size_t num_filtered_images = mapper->FilterImages(options.Mapper());
159  std::cout << " => Filtered images: " << num_filtered_images << std::endl;
160  return num_filtered_images;
161 }
162 
164  IncrementalMapper* mapper) {
165  const size_t num_completed_observations =
166  mapper->CompleteTracks(options.Triangulation());
167  std::cout << " => Completed observations: " << num_completed_observations
168  << std::endl;
169  const size_t num_merged_observations =
170  mapper->MergeTracks(options.Triangulation());
171  std::cout << " => Merged observations: " << num_merged_observations
172  << std::endl;
173  return num_completed_observations + num_merged_observations;
174 }
175 
183  options.num_threads = num_threads;
186  return options;
187 }
188 
190  const {
195  return options;
196 }
197 
199  const {
200  BundleAdjustmentOptions options;
201  options.solver_options.function_tolerance = ba_local_function_tolerance;
202  options.solver_options.gradient_tolerance = 10.0;
203  options.solver_options.parameter_tolerance = 0.0;
204  options.solver_options.max_num_iterations = ba_local_max_num_iterations;
205  options.solver_options.max_linear_solver_iterations = 100;
206  options.solver_options.minimizer_progress_to_stdout = false;
207  options.solver_options.num_threads = num_threads;
208 #if CERES_VERSION_MAJOR < 2
209  options.solver_options.num_linear_solver_threads = num_threads;
210 #endif // CERES_VERSION_MAJOR
211  options.print_summary = true;
217  options.loss_function_scale = 1.0;
218  options.loss_function_type =
220  options.use_gpu = ba_use_gpu;
221  options.gpu_index = ba_gpu_index;
222  return options;
223 }
224 
226  const {
227  BundleAdjustmentOptions options;
228  options.solver_options.function_tolerance = ba_global_function_tolerance;
229  options.solver_options.gradient_tolerance = 1.0;
230  options.solver_options.parameter_tolerance = 0.0;
231  options.solver_options.max_num_iterations = ba_global_max_num_iterations;
232  options.solver_options.max_linear_solver_iterations = 100;
233  options.solver_options.minimizer_progress_to_stdout = true;
234  options.solver_options.num_threads = num_threads;
235 #if CERES_VERSION_MAJOR < 2
236  options.solver_options.num_linear_solver_threads = num_threads;
237 #endif // CERES_VERSION_MAJOR
238  options.print_summary = true;
244  options.loss_function_type =
246  options.use_gpu = ba_use_gpu;
247  options.gpu_index = ba_gpu_index;
248  return options;
249 }
250 
251 #ifdef PBA_ENABLED
252 ParallelBundleAdjuster::Options
253 IncrementalMapperOptions::ParallelGlobalBundleAdjustment() const {
254  ParallelBundleAdjuster::Options options;
255  options.max_num_iterations = ba_global_max_num_iterations;
256  options.print_summary = true;
257  options.gpu_index = ba_global_pba_gpu_index;
258  options.num_threads = num_threads;
259  options.min_num_residuals_for_cpu_multi_threading =
261  return options;
262 }
263 #endif
264 
288  return true;
289 }
290 
292  const IncrementalMapperOptions* options,
293  const std::string& image_path,
294  const std::string& database_path,
295  ReconstructionManager* reconstruction_manager)
296  : options_(options),
297  image_path_(image_path),
298  database_path_(database_path),
299  reconstruction_manager_(reconstruction_manager) {
300  CHECK(options_->Check());
304 }
305 
306 void IncrementalMapperController::Run() {
307  if (!LoadDatabase()) {
308  return;
309  }
310 
311  IncrementalMapper::Options init_mapper_options = options_->Mapper();
312  Reconstruct(init_mapper_options);
313 
314  const size_t kNumInitRelaxations = 2;
315  for (size_t i = 0; i < kNumInitRelaxations; ++i) {
316  if (reconstruction_manager_->Size() > 0 || IsStopped()) {
317  break;
318  }
319 
320  std::cout << " => Relaxing the initialization constraints."
321  << std::endl;
322  init_mapper_options.init_min_num_inliers /= 2;
323  Reconstruct(init_mapper_options);
324 
325  if (reconstruction_manager_->Size() > 0 || IsStopped()) {
326  break;
327  }
328 
329  std::cout << " => Relaxing the initialization constraints."
330  << std::endl;
331  init_mapper_options.init_min_tri_angle /= 2;
332  Reconstruct(init_mapper_options);
333  }
334 
335  std::cout << std::endl;
337 }
338 
339 bool IncrementalMapperController::LoadDatabase() {
340  PrintHeading1("Loading database");
341 
342  // Make sure images of the given reconstruction are also included when
343  // manually specifying images for the reconstrunstruction procedure.
344  std::unordered_set<std::string> image_names = options_->image_names;
345  if (reconstruction_manager_->Size() == 1 &&
346  !options_->image_names.empty()) {
347  const Reconstruction& reconstruction = reconstruction_manager_->Get(0);
348  for (const image_t image_id : reconstruction.RegImageIds()) {
349  const auto& image = reconstruction.Image(image_id);
350  image_names.insert(image.Name());
351  }
352  }
353 
354  Database database(database_path_);
355  Timer timer;
356  timer.Start();
357  const size_t min_num_matches =
358  static_cast<size_t>(options_->min_num_matches);
359  database_cache_.Load(database, min_num_matches, options_->ignore_watermarks,
360  image_names);
361  std::cout << std::endl;
362  timer.PrintMinutes();
363 
364  std::cout << std::endl;
365 
366  if (database_cache_.NumImages() == 0) {
367  std::cout << "WARNING: No images with matches found in the database."
368  << std::endl
369  << std::endl;
370  return false;
371  }
372 
373  return true;
374 }
375 
376 void IncrementalMapperController::Reconstruct(
377  const IncrementalMapper::Options& init_mapper_options) {
378  const bool kDiscardReconstruction = true;
379 
381  // Main loop
383 
384  IncrementalMapper mapper(&database_cache_);
385 
386  // Is there a sub-model before we start the reconstruction? I.e. the user
387  // has imported an existing reconstruction.
388  const bool initial_reconstruction_given =
389  reconstruction_manager_->Size() > 0;
390  CHECK_LE(reconstruction_manager_->Size(), 1)
391  << "Can only resume from a "
392  "single reconstruction, but "
393  "multiple are given.";
394 
395  for (int num_trials = 0; num_trials < options_->init_num_trials;
396  ++num_trials) {
397  BlockIfPaused();
398  if (IsStopped()) {
399  break;
400  }
401 
402  size_t reconstruction_idx;
403  if (!initial_reconstruction_given || num_trials > 0) {
404  reconstruction_idx = reconstruction_manager_->Add();
405  } else {
406  reconstruction_idx = 0;
407  }
408 
409  Reconstruction& reconstruction =
410  reconstruction_manager_->Get(reconstruction_idx);
411 
412  mapper.BeginReconstruction(&reconstruction);
413 
415  // Register initial pair
417 
418  if (reconstruction.NumRegImages() == 0) {
419  image_t image_id1 = static_cast<image_t>(options_->init_image_id1);
420  image_t image_id2 = static_cast<image_t>(options_->init_image_id2);
421 
422  // Try to find good initial pair.
423  if (options_->init_image_id1 == -1 ||
424  options_->init_image_id2 == -1) {
425  PrintHeading1("Finding good initial image pair");
426  const bool find_init_success = mapper.FindInitialImagePair(
427  init_mapper_options, &image_id1, &image_id2);
428  if (!find_init_success) {
429  std::cout << " => No good initial image pair found."
430  << std::endl;
431  mapper.EndReconstruction(kDiscardReconstruction);
432  reconstruction_manager_->Delete(reconstruction_idx);
433  break;
434  }
435  } else {
436  if (!reconstruction.ExistsImage(image_id1) ||
437  !reconstruction.ExistsImage(image_id2)) {
438  std::cout << StringPrintf(
439  " => Initial image pair #%d and #%d "
440  "do not exist.",
441  image_id1, image_id2)
442  << std::endl;
443  mapper.EndReconstruction(kDiscardReconstruction);
444  reconstruction_manager_->Delete(reconstruction_idx);
445  return;
446  }
447  }
448 
450  StringPrintf("Initializing with image pair #%d and #%d",
451  image_id1, image_id2));
452  const bool reg_init_success = mapper.RegisterInitialImagePair(
453  init_mapper_options, image_id1, image_id2);
454  if (!reg_init_success) {
455  std::cout
456  << " => Initialization failed - possible solutions:"
457  << std::endl
458  << " - try to relax the initialization constraints"
459  << std::endl
460  << " - manually select an initial image pair"
461  << std::endl;
462  mapper.EndReconstruction(kDiscardReconstruction);
463  reconstruction_manager_->Delete(reconstruction_idx);
464  break;
465  }
466 
467  AdjustGlobalBundle(*options_, &mapper);
468  FilterPoints(*options_, &mapper);
469  FilterImages(*options_, &mapper);
470 
471  // Initial image pair failed to register.
472  if (reconstruction.NumRegImages() == 0 ||
473  reconstruction.NumPoints3D() == 0) {
474  mapper.EndReconstruction(kDiscardReconstruction);
475  reconstruction_manager_->Delete(reconstruction_idx);
476  // If both initial images are manually specified, there is no
477  // need for further initialization trials.
478  if (options_->init_image_id1 != -1 &&
479  options_->init_image_id2 != -1) {
480  break;
481  } else {
482  continue;
483  }
484  }
485 
486  if (options_->extract_colors) {
487  ExtractColors(image_path_, image_id1, &reconstruction);
488  }
489  }
490 
492 
494  // Incremental mapping
496 
497  size_t snapshot_prev_num_reg_images = reconstruction.NumRegImages();
498  size_t ba_prev_num_reg_images = reconstruction.NumRegImages();
499  size_t ba_prev_num_points = reconstruction.NumPoints3D();
500 
501  bool reg_next_success = true;
502  bool prev_reg_next_success = true;
503  while (reg_next_success) {
504  BlockIfPaused();
505  if (IsStopped()) {
506  break;
507  }
508 
509  reg_next_success = false;
510 
511  const std::vector<image_t> next_images =
512  mapper.FindNextImages(options_->Mapper());
513 
514  if (next_images.empty()) {
515  break;
516  }
517 
518  for (size_t reg_trial = 0; reg_trial < next_images.size();
519  ++reg_trial) {
520  const image_t next_image_id = next_images[reg_trial];
521  const Image& next_image = reconstruction.Image(next_image_id);
522 
523  PrintHeading1(StringPrintf("Registering image #%d (%d)",
524  next_image_id,
525  reconstruction.NumRegImages() + 1));
526 
527  std::cout << StringPrintf(" => Image sees %d / %d points",
528  next_image.NumVisiblePoints3D(),
529  next_image.NumObservations())
530  << std::endl;
531 
532  reg_next_success = mapper.RegisterNextImage(options_->Mapper(),
533  next_image_id);
534 
535  if (reg_next_success) {
536  TriangulateImage(*options_, next_image, &mapper);
537  IterativeLocalRefinement(*options_, next_image_id, &mapper);
538 
539  if (reconstruction.NumRegImages() >=
540  options_->ba_global_images_ratio *
541  ba_prev_num_reg_images ||
542  reconstruction.NumRegImages() >=
543  options_->ba_global_images_freq +
544  ba_prev_num_reg_images ||
545  reconstruction.NumPoints3D() >=
546  options_->ba_global_points_ratio *
547  ba_prev_num_points ||
548  reconstruction.NumPoints3D() >=
549  options_->ba_global_points_freq +
550  ba_prev_num_points) {
551  IterativeGlobalRefinement(*options_, &mapper);
552  ba_prev_num_points = reconstruction.NumPoints3D();
553  ba_prev_num_reg_images = reconstruction.NumRegImages();
554  }
555 
556  if (options_->extract_colors) {
557  ExtractColors(image_path_, next_image_id,
558  &reconstruction);
559  }
560 
561  if (options_->snapshot_images_freq > 0 &&
562  reconstruction.NumRegImages() >=
563  options_->snapshot_images_freq +
564  snapshot_prev_num_reg_images) {
565  snapshot_prev_num_reg_images =
566  reconstruction.NumRegImages();
567  WriteSnapshot(reconstruction, options_->snapshot_path);
568  }
569 
571 
572  break;
573  } else {
574  std::cout
575  << " => Could not register, trying another image."
576  << std::endl;
577 
578  // If initial pair fails to continue for some time,
579  // abort and try different initial pair.
580  const size_t kMinNumInitialRegTrials = 30;
581  if (reg_trial >= kMinNumInitialRegTrials &&
582  reconstruction.NumRegImages() <
583  static_cast<size_t>(options_->min_model_size)) {
584  break;
585  }
586  }
587  }
588 
589  const size_t max_model_overlap =
590  static_cast<size_t>(options_->max_model_overlap);
591  if (mapper.NumSharedRegImages() >= max_model_overlap) {
592  break;
593  }
594 
595  // If no image could be registered, try a single final global
596  // iterative bundle adjustment and try again to register one image.
597  // If this fails once, then exit the incremental mapping.
598  if (!reg_next_success && prev_reg_next_success) {
599  reg_next_success = true;
600  prev_reg_next_success = false;
601  IterativeGlobalRefinement(*options_, &mapper);
602  } else {
603  prev_reg_next_success = reg_next_success;
604  }
605  }
606 
607  if (IsStopped()) {
608  const bool kDiscardReconstruction = false;
609  mapper.EndReconstruction(kDiscardReconstruction);
610  break;
611  }
612 
613  // Only run final global BA, if last incremental BA was not global.
614  if (reconstruction.NumRegImages() >= 2 &&
615  reconstruction.NumRegImages() != ba_prev_num_reg_images &&
616  reconstruction.NumPoints3D() != ba_prev_num_points) {
617  IterativeGlobalRefinement(*options_, &mapper);
618  }
619 
620  // If the total number of images is small then do not enforce the
621  // minimum model size so that we can reconstruct small image
622  // collections.
623  const size_t min_model_size =
624  std::min(database_cache_.NumImages(),
625  static_cast<size_t>(options_->min_model_size));
626  if ((options_->multiple_models &&
627  reconstruction.NumRegImages() < min_model_size) ||
628  reconstruction.NumRegImages() == 0) {
629  mapper.EndReconstruction(kDiscardReconstruction);
630  reconstruction_manager_->Delete(reconstruction_idx);
631  } else {
632  const bool kDiscardReconstruction = false;
633  mapper.EndReconstruction(kDiscardReconstruction);
634  }
635 
637 
638  const size_t max_num_models =
639  static_cast<size_t>(options_->max_num_models);
640  if (initial_reconstruction_given || !options_->multiple_models ||
641  reconstruction_manager_->Size() >= max_num_models ||
642  mapper.NumTotalRegImages() >= database_cache_.NumImages() - 1) {
643  break;
644  }
645  }
646 }
647 
648 } // namespace colmap
std::shared_ptr< core::Tensor > image
size_t NumImages() const
void Load(const Database &database, const size_t min_num_matches, const bool ignore_watermarks, const std::unordered_set< std::string > &image_names)
IncrementalMapperController(const IncrementalMapperOptions *options, const std::string &image_path, const std::string &database_path, ReconstructionManager *reconstruction_manager)
size_t MergeTracks(const IncrementalTriangulator::Options &tri_options)
size_t FilterImages(const Options &options)
size_t CompleteTracks(const IncrementalTriangulator::Options &tri_options)
size_t FilterPoints(const Options &options)
const Reconstruction & Get(const size_t idx) const
void Callback(const int id) const
Definition: threading.cc:127
void BlockIfPaused()
Definition: threading.cc:156
void RegisterCallback(const int id)
Definition: threading.cc:123
const Timer & GetTimer() const
Definition: threading.cc:154
bool IsStopped()
Definition: threading.cc:97
void PrintMinutes() const
Definition: timer.cc:93
#define CHECK_OPTION(expr)
Definition: logging.h:20
#define CHECK_OPTION_GT(val1, val2)
Definition: logging.h:34
#define CHECK_OPTION_GE(val1, val2)
Definition: logging.h:33
QTextStream & endl(QTextStream &stream)
Definition: QtCompat.h:718
static const std::string path
Definition: PointCloud.cpp:59
colmap::IncrementalMapperOptions IncrementalMapperOptions
size_t FilterImages(const IncrementalMapperOptions &options, IncrementalMapper *mapper)
size_t CompleteAndMergeTracks(const IncrementalMapperOptions &options, IncrementalMapper *mapper)
void CreateDirIfNotExists(const std::string &path)
Definition: misc.cc:112
std::string JoinPaths(T const &... paths)
Definition: misc.h:128
uint32_t image_t
Definition: types.h:61
void PrintHeading1(const std::string &heading)
Definition: misc.cc:225
std::string StringPrintf(const char *format,...)
Definition: string.cc:131
size_t FilterPoints(const IncrementalMapperOptions &options, IncrementalMapper *mapper)
ceres::Solver::Options solver_options
IncrementalTriangulator::Options triangulation
IncrementalTriangulator::Options Triangulation() const
IncrementalMapper::Options mapper
BundleAdjustmentOptions LocalBundleAdjustment() const
IncrementalMapper::Options Mapper() const
BundleAdjustmentOptions GlobalBundleAdjustment() const
std::unordered_set< std::string > image_names