ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
model.cc
Go to the documentation of this file.
1 // Copyright (c) 2018, ETH Zurich and UNC Chapel Hill.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are met:
6 //
7 // * Redistributions of source code must retain the above copyright
8 // notice, this list of conditions and the following disclaimer.
9 //
10 // * Redistributions in binary form must reproduce the above copyright
11 // notice, this list of conditions and the following disclaimer in the
12 // documentation and/or other materials provided with the distribution.
13 //
14 // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of
15 // its contributors may be used to endorse or promote products derived
16 // from this software without specific prior written permission.
17 //
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
22 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 // POSSIBILITY OF SUCH DAMAGE.
29 //
30 // Author: Johannes L. Schoenberger (jsch-at-demuc-dot-de)
31 
32 #include "exe/model.h"
33 
34 #include "base/gps.h"
35 #include "base/pose.h"
38 #include "util/misc.h"
39 #include "util/option_manager.h"
40 #include "util/threading.h"
41 
42 namespace colmap {
43 namespace {
44 
45 std::vector<std::pair<Eigen::Vector3d, Eigen::Vector3d>>
46 ComputeEqualPartsBounds(const Reconstruction& reconstruction,
47  const Eigen::Vector3i& split) {
48  std::vector<std::pair<Eigen::Vector3d, Eigen::Vector3d>> bounds;
49  const auto bbox = reconstruction.ComputeBoundingBox();
50  const Eigen::Vector3d extent = bbox.second - bbox.first;
51  const Eigen::Vector3d offset(extent(0) / split(0), extent(1) / split(1),
52  extent(2) / split(2));
53 
54  for (int k = 0; k < split(2); ++k) {
55  for (int j = 0; j < split(1); ++j) {
56  for (int i = 0; i < split(0); ++i) {
57  Eigen::Vector3d min_bound(bbox.first(0) + i * offset(0),
58  bbox.first(1) + j * offset(1),
59  bbox.first(2) + k * offset(2));
60  bounds.emplace_back(min_bound, min_bound + offset);
61  }
62  }
63  }
64 
65  return bounds;
66 }
67 
68 Eigen::Vector3d TransformLatLonAltToModelCoords(
69  const SimilarityTransform3& tform, double lat, double lon, double alt) {
70  // Since this is intended for use in ENU aligned models we want to define the
71  // altitude along the ENU frame z axis and not the Earth's radius. Thus, we
72  // set the altitude to 0 when converting from LLA to ECEF and then we use the
73  // altitude at the end, after scaling, to set it as the z coordinate in the
74  // ENU frame.
75  Eigen::Vector3d xyz = GPSTransform(GPSTransform::WGS84)
76  .EllToXYZ({Eigen::Vector3d(lat, lon, 0.0)})[0];
77  tform.TransformPoint(&xyz);
78  xyz(2) = tform.Scale() * alt;
79  return xyz;
80 }
81 
82 void WriteBoundingBox(const std::string& reconstruction_path,
83  const std::pair<Eigen::Vector3d, Eigen::Vector3d>& bounds,
84  const std::string& suffix = "") {
85  const Eigen::Vector3d extent = bounds.second - bounds.first;
86  // write axis-aligned bounding box
87  {
88  const std::string path =
89  JoinPaths(reconstruction_path, "bbox_aligned" + suffix + ".txt");
90  std::ofstream file(path, std::ios::trunc);
91  CHECK(file.is_open()) << path;
92 
93  // Ensure that we don't loose any precision by storing in text.
94  file.precision(17);
95  file << bounds.first.transpose() << std::endl;
96  file << bounds.second.transpose() << std::endl;
97  }
98  // write oriented bounding box
99  {
100  const std::string path =
101  JoinPaths(reconstruction_path, "bbox_oriented" + suffix + ".txt");
102  std::ofstream file(path, std::ios::trunc);
103  CHECK(file.is_open()) << path;
104 
105  // Ensure that we don't loose any precision by storing in text.
106  file.precision(17);
107  const Eigen::Vector3d center = (bounds.first + bounds.second) * 0.5;
108  file << center.transpose() << std::endl << std::endl;
109  file << "1 0 0\n0 1 0\n0 0 1" << std::endl << std::endl;
110  file << extent.transpose() << std::endl;
111  }
112 }
113 
114 void ReadFileCameraLocations(const std::string& ref_images_path,
115  std::vector<std::string>& ref_image_names,
116  std::vector<Eigen::Vector3d>& ref_locations) {
117  const auto lines = ReadTextFileLines(ref_images_path);
118  for (const auto& line : lines) {
119  std::stringstream line_parser(line);
120  std::string image_name;
121  Eigen::Vector3d camera_position;
122  line_parser >> image_name >> camera_position[0] >> camera_position[1] >>
123  camera_position[2];
124  ref_image_names.push_back(image_name);
125  ref_locations.push_back(camera_position);
126  }
127 }
128 
129 void ReadDatabaseCameraLocations(const std::string& database_path,
130  std::vector<std::string>& ref_image_names,
131  std::vector<Eigen::Vector3d>& ref_locations) {
132  Database database(database_path);
133  auto images = database.ReadAllImages();
134  std::vector<Eigen::Vector3d> gps_locations;
135  GPSTransform gps_transform(GPSTransform::WGS84);
136  for (const auto& image : images) {
137  if (image.HasTvecPrior()) {
138  ref_image_names.push_back(image.Name());
139  gps_locations.push_back(image.TvecPrior());
140  }
141  }
142  ref_locations = gps_transform.EllToXYZ(gps_locations);
143 }
144 
145 void WriteComparisonErrorsCSV(const std::string& path,
146  const std::vector<double>& rotation_errors,
147  const std::vector<double>& translation_errors,
148  const std::vector<double>& proj_center_errors) {
149  CHECK_EQ(rotation_errors.size(), translation_errors.size());
150  CHECK_EQ(rotation_errors.size(), proj_center_errors.size());
151 
152  std::ofstream file(path, std::ios::trunc);
153  CHECK(file.is_open()) << path;
154 
155  file.precision(17);
156  file << "# Model comparison pose errors: one entry per common image"
157  << std::endl;
158  file << "# <rotation error (deg)>, <translation error>, <proj center error>"
159  << std::endl;
160  for (size_t i = 0; i < rotation_errors.size(); ++i) {
161  file << rotation_errors[i] << ", " << translation_errors[i] << ", "
162  << proj_center_errors[i] << std::endl;
163  }
164 }
165 
166 void PrintErrorStats(std::ostream& out, std::vector<double>& vals) {
167  const size_t len = vals.size();
168  if (len == 0) {
169  out << "Cannot extract error statistics from empty input" << std::endl;
170  return;
171  }
172  std::sort(vals.begin(), vals.end());
173  out << "Min: " << vals.front() << std::endl;
174  out << "Max: " << vals.back() << std::endl;
175  out << "Mean: " << Mean(vals) << std::endl;
176  out << "Median: " << Median(vals) << std::endl;
177  out << "P90: " << vals[size_t(0.9 * len)] << std::endl;
178  out << "P99: " << vals[size_t(0.99 * len)] << std::endl;
179 }
180 
181 void PrintComparisonSummary(std::ostream& out,
182  std::vector<double>& rotation_errors,
183  std::vector<double>& translation_errors,
184  std::vector<double>& proj_center_errors) {
185  out << "# Image pose error summary" << std::endl;
186  out << std::endl << "Rotation angular errors (degrees)" << std::endl;
187  PrintErrorStats(out, rotation_errors);
188  out << std::endl << "Translation distance errors" << std::endl;
189  PrintErrorStats(out, translation_errors);
190  out << std::endl << "Projection center distance errors" << std::endl;
191  PrintErrorStats(out, proj_center_errors);
192 }
193 
194 } // namespace
195 
196 int RunModelAligner(int argc, char** argv) {
197  std::string input_path;
198  std::string output_path;
199  std::string database_path;
200  std::string ref_images_path;
201  std::string transform_path;
202  std::string alignment_type = "custom";
203  int min_common_images = 3;
204  bool robust_alignment = true;
205  bool estimate_scale = true;
206  RANSACOptions ransac_options;
207 
208  OptionManager options;
209  options.AddRequiredOption("input_path", &input_path);
210  options.AddRequiredOption("output_path", &output_path);
211  options.AddDefaultOption("database_path", &database_path);
212  options.AddDefaultOption("ref_images_path", &ref_images_path);
213  options.AddDefaultOption("transform_path", &transform_path);
214  options.AddDefaultOption("alignment_type", &alignment_type,
215  "{plane, ecef, enu, enu-unscaled, custom}");
216  options.AddDefaultOption("min_common_images", &min_common_images);
217  options.AddDefaultOption("robust_alignment", &robust_alignment);
218  options.AddDefaultOption("estimate_scale", &estimate_scale);
219  options.AddDefaultOption("robust_alignment_max_error",
220  &ransac_options.max_error);
221  options.Parse(argc, argv);
222 
223  StringToLower(&alignment_type);
224  const std::unordered_set<std::string> alignment_options{
225  "plane", "ecef", "enu", "enu-unscaled", "custom"};
226  if (alignment_options.count(alignment_type) == 0) {
227  std::cerr << "ERROR: Invalid `alignment_type` - supported values are "
228  "{'plane', 'ecef', 'enu', 'enu-unscaled', 'custom'}"
229  << std::endl;
230  return EXIT_FAILURE;
231  }
232 
233  if (robust_alignment && ransac_options.max_error <= 0) {
234  std::cout << "WARNING: You must provide a maximum alignment error > 0"
235  << std::endl;
236  return EXIT_FAILURE;
237  }
238 
239  if (alignment_type != "plane" && database_path.empty() &&
240  ref_images_path.empty()) {
241  std::cerr << "ERROR: Location alignment requires either database or "
242  "location file path."
243  << std::endl;
244  return EXIT_FAILURE;
245  }
246 
247  std::vector<std::string> ref_image_names;
248  std::vector<Eigen::Vector3d> ref_locations;
249  if (!ref_images_path.empty() && database_path.empty()) {
250  ReadFileCameraLocations(ref_images_path, ref_image_names, ref_locations);
251  } else if (!database_path.empty() && ref_images_path.empty()) {
252  ReadDatabaseCameraLocations(database_path, ref_image_names, ref_locations);
253  } else {
254  std::cerr << "ERROR: Use location file or database, not both" << std::endl;
255  return EXIT_FAILURE;
256  }
257 
258  if (alignment_type != "plane" &&
259  static_cast<int>(ref_locations.size()) < min_common_images) {
260  std::cout << "WARNING: Cannot align with insufficient reference locations."
261  << std::endl;
262  return EXIT_FAILURE;
263  }
264 
265  Reconstruction reconstruction;
266  reconstruction.Read(input_path);
267  SimilarityTransform3 tform;
268  bool alignment_success = true;
269 
270  if (alignment_type == "plane") {
271  PrintHeading2("Aligning reconstruction to principal plane");
272  AlignToPrincipalPlane(&reconstruction, &tform);
273  } else {
274  PrintHeading2("Aligning reconstruction to ECEF");
275  std::cout << StringPrintf(" => Using %d reference images",
276  ref_image_names.size())
277  << std::endl;
278 
279  if (estimate_scale) {
280  if (robust_alignment) {
281  alignment_success = reconstruction.AlignRobust(
282  ref_image_names, ref_locations, min_common_images, ransac_options,
283  &tform);
284  } else {
285  alignment_success = reconstruction.Align(ref_image_names, ref_locations,
286  min_common_images, &tform);
287  }
288  } else {
289  if (robust_alignment) {
290  alignment_success = reconstruction.AlignRobust<false>(
291  ref_image_names, ref_locations, min_common_images, ransac_options,
292  &tform);
293  } else {
294  alignment_success = reconstruction.Align<false>(
295  ref_image_names, ref_locations, min_common_images, &tform);
296  }
297  }
298 
299  std::vector<double> errors;
300  errors.reserve(ref_image_names.size());
301 
302  for (size_t i = 0; i < ref_image_names.size(); ++i) {
303  const Image* image = reconstruction.FindImageWithName(ref_image_names[i]);
304  if (image != nullptr) {
305  errors.push_back((image->ProjectionCenter() - ref_locations[i]).norm());
306  }
307  }
308  std::cout << StringPrintf(" => Alignment error: %f (mean), %f (median)",
309  Mean(errors), Median(errors))
310  << std::endl;
311 
312  if (alignment_success && StringStartsWith(alignment_type, "enu")) {
313  PrintHeading2("Aligning reconstruction to ENU");
314  AlignToENUPlane(&reconstruction, &tform,
315  alignment_type == "enu-unscaled");
316  }
317  }
318 
319  if (alignment_success) {
320  std::cout << " => Alignment succeeded" << std::endl;
321  reconstruction.Write(output_path);
322  if (!transform_path.empty()) {
323  tform.Write(transform_path);
324  }
325  return EXIT_SUCCESS;
326  } else {
327  std::cout << " => Alignment failed" << std::endl;
328  return EXIT_FAILURE;
329  }
330 }
331 
332 int RunModelAnalyzer(int argc, char** argv) {
333  std::string path;
334 
335  OptionManager options;
336  options.AddRequiredOption("path", &path);
337  options.Parse(argc, argv);
338 
339  Reconstruction reconstruction;
340  reconstruction.Read(path);
341 
342  std::cout << StringPrintf("Cameras: %d", reconstruction.NumCameras())
343  << std::endl;
344  std::cout << StringPrintf("Images: %d", reconstruction.NumImages())
345  << std::endl;
346  std::cout << StringPrintf("Registered images: %d",
347  reconstruction.NumRegImages())
348  << std::endl;
349  std::cout << StringPrintf("Points: %d", reconstruction.NumPoints3D())
350  << std::endl;
351  std::cout << StringPrintf("Observations: %d",
352  reconstruction.ComputeNumObservations())
353  << std::endl;
354  std::cout << StringPrintf("Mean track length: %f",
355  reconstruction.ComputeMeanTrackLength())
356  << std::endl;
357  std::cout << StringPrintf("Mean observations per image: %f",
358  reconstruction.ComputeMeanObservationsPerRegImage())
359  << std::endl;
360  std::cout << StringPrintf("Mean reprojection error: %fpx",
361  reconstruction.ComputeMeanReprojectionError())
362  << std::endl;
363 
364  return EXIT_SUCCESS;
365 }
366 
367 int RunModelComparer(int argc, char** argv) {
368  std::string input_path1;
369  std::string input_path2;
370  std::string output_path;
371  double min_inlier_observations = 0.3;
372  double max_reproj_error = 8.0;
373 
374  OptionManager options;
375  options.AddRequiredOption("input_path1", &input_path1);
376  options.AddRequiredOption("input_path2", &input_path2);
377  options.AddDefaultOption("output_path", &output_path);
378  options.AddDefaultOption("min_inlier_observations", &min_inlier_observations);
379  options.AddDefaultOption("max_reproj_error", &max_reproj_error);
380  options.Parse(argc, argv);
381 
382  if (!output_path.empty() && !ExistsDir(output_path)) {
383  std::cerr << "ERROR: Provided output path is not a valid directory"
384  << std::endl;
385  return EXIT_FAILURE;
386  }
387 
388  Reconstruction reconstruction1;
389  reconstruction1.Read(input_path1);
390  PrintHeading1("Reconstruction 1");
391  std::cout << StringPrintf("Images: %d", reconstruction1.NumRegImages())
392  << std::endl;
393  std::cout << StringPrintf("Points: %d", reconstruction1.NumPoints3D())
394  << std::endl;
395 
396  Reconstruction reconstruction2;
397  reconstruction2.Read(input_path2);
398  PrintHeading1("Reconstruction 2");
399  std::cout << StringPrintf("Images: %d", reconstruction2.NumRegImages())
400  << std::endl;
401  std::cout << StringPrintf("Points: %d", reconstruction2.NumPoints3D())
402  << std::endl;
403 
404  PrintHeading1("Comparing reconstructed image poses");
405  const auto common_image_ids =
406  reconstruction1.FindCommonRegImageIds(reconstruction2);
407  std::cout << StringPrintf("Common images: %d", common_image_ids.size())
408  << std::endl;
409 
410  Eigen::Matrix3x4d alignment;
411  if (!ComputeAlignmentBetweenReconstructions(reconstruction2, reconstruction1,
412  min_inlier_observations,
413  max_reproj_error, &alignment)) {
414  std::cout << "=> Reconstruction alignment failed" << std::endl;
415  return EXIT_FAILURE;
416  }
417 
418  const SimilarityTransform3 tform(alignment);
419  std::cout << "Computed alignment transform:" << std::endl
420  << tform.Matrix() << std::endl;
421 
422  const size_t num_images = common_image_ids.size();
423  std::vector<double> rotation_errors(num_images, 0.0);
424  std::vector<double> translation_errors(num_images, 0.0);
425  std::vector<double> proj_center_errors(num_images, 0.0);
426  for (size_t i = 0; i < num_images; ++i) {
427  const image_t image_id = common_image_ids[i];
428  const Image& image1 = reconstruction1.Image(image_id);
429  Image& image2 = reconstruction2.Image(image_id);
430  tform.TransformPose(&image2.Qvec(), &image2.Tvec());
431 
432  const Eigen::Vector4d normalized_qvec1 = NormalizeQuaternion(image1.Qvec());
433  const Eigen::Quaterniond quat1(normalized_qvec1(0), normalized_qvec1(1),
434  normalized_qvec1(2), normalized_qvec1(3));
435  const Eigen::Vector4d normalized_qvec2 = NormalizeQuaternion(image2.Qvec());
436  const Eigen::Quaterniond quat2(normalized_qvec2(0), normalized_qvec2(1),
437  normalized_qvec2(2), normalized_qvec2(3));
438 
439  rotation_errors[i] = RadToDeg(quat1.angularDistance(quat2));
440  translation_errors[i] = (image1.Tvec() - image2.Tvec()).norm();
441  proj_center_errors[i] =
442  (image1.ProjectionCenter() - image2.ProjectionCenter()).norm();
443  }
444 
445  if (output_path.empty()) {
446  PrintComparisonSummary(std::cout, rotation_errors, translation_errors,
447  proj_center_errors);
448  } else {
449  const std::string errors_path = JoinPaths(output_path, "errors.csv");
450  WriteComparisonErrorsCSV(errors_path, rotation_errors, translation_errors,
451  proj_center_errors);
452  const std::string summary_path =
453  JoinPaths(output_path, "errors_summary.txt");
454  std::ofstream file(summary_path, std::ios::trunc);
455  CHECK(file.is_open()) << summary_path;
456  PrintComparisonSummary(file, rotation_errors, translation_errors,
457  proj_center_errors);
458  }
459 
460  return EXIT_SUCCESS;
461 }
462 
463 int RunModelConverter(int argc, char** argv) {
464  std::string input_path;
465  std::string output_path;
466  std::string output_type;
467  bool skip_distortion = false;
468 
469  OptionManager options;
470  options.AddRequiredOption("input_path", &input_path);
471  options.AddRequiredOption("output_path", &output_path);
472  options.AddRequiredOption("output_type", &output_type,
473  "{BIN, TXT, NVM, Bundler, VRML, PLY, R3D, CAM}");
474  options.AddDefaultOption("skip_distortion", &skip_distortion);
475  options.Parse(argc, argv);
476 
477  Reconstruction reconstruction;
478  reconstruction.Read(input_path);
479 
480  StringToLower(&output_type);
481  if (output_type == "bin") {
482  reconstruction.WriteBinary(output_path);
483  } else if (output_type == "txt") {
484  reconstruction.WriteText(output_path);
485  } else if (output_type == "nvm") {
486  reconstruction.ExportNVM(output_path, skip_distortion);
487  } else if (output_type == "bundler") {
488  reconstruction.ExportBundler(output_path + ".bundle.out",
489  output_path + ".list.txt", skip_distortion);
490  } else if (output_type == "r3d") {
491  reconstruction.ExportRecon3D(output_path, skip_distortion);
492  } else if (output_type == "cam") {
493  reconstruction.ExportCam(output_path, skip_distortion);
494  } else if (output_type == "ply") {
495  reconstruction.ExportPLY(output_path);
496  } else if (output_type == "vrml") {
497  const auto base_path = output_path.substr(0, output_path.find_last_of("."));
498  reconstruction.ExportVRML(base_path + ".images.wrl",
499  base_path + ".points3D.wrl", 1,
500  Eigen::Vector3d(1, 0, 0));
501  } else {
502  std::cerr << "ERROR: Invalid `output_type`" << std::endl;
503  return EXIT_FAILURE;
504  }
505 
506  return EXIT_SUCCESS;
507 }
508 
509 int RunModelCropper(int argc, char** argv) {
510  Timer timer;
511  timer.Start();
512 
513  std::string input_path;
514  std::string output_path;
515  std::string boundary;
516  std::string gps_transform_path;
517  bool is_gps = false;
518 
519  OptionManager options;
520  options.AddRequiredOption("input_path", &input_path);
521  options.AddRequiredOption("output_path", &output_path);
522  options.AddRequiredOption("boundary", &boundary);
523  options.AddDefaultOption("gps_transform_path", &gps_transform_path);
524  options.Parse(argc, argv);
525 
526  if (!ExistsDir(input_path)) {
527  std::cerr << "ERROR: `input_path` is not a directory" << std::endl;
528  return EXIT_FAILURE;
529  }
530 
531  if (!ExistsDir(output_path)) {
532  std::cerr << "ERROR: `output_path` is not a directory" << std::endl;
533  return EXIT_FAILURE;
534  }
535 
536  std::vector<double> boundary_elements = CSVToVector<double>(boundary);
537  if (boundary_elements.size() != 2 && boundary_elements.size() != 6) {
538  std::cerr << "ERROR: Invalid `boundary` - supported values are "
539  "'x1,y1,z1,x2,y2,z2' or 'p1,p2'."
540  << std::endl;
541  return EXIT_FAILURE;
542  }
543 
544  Reconstruction reconstruction;
545  reconstruction.Read(input_path);
546 
547  PrintHeading2("Calculating boundary coordinates");
548  std::pair<Eigen::Vector3d, Eigen::Vector3d> bounding_box;
549  if (boundary_elements.size() == 6) {
550  SimilarityTransform3 tform;
551  if (!gps_transform_path.empty()) {
552  PrintHeading2("Reading model to ECEF transform");
553  is_gps = true;
554  tform = SimilarityTransform3::FromFile(gps_transform_path).Inverse();
555  }
556  bounding_box.first =
557  is_gps ? TransformLatLonAltToModelCoords(tform, boundary_elements[0],
558  boundary_elements[1],
559  boundary_elements[2])
560  : Eigen::Vector3d(boundary_elements[0], boundary_elements[1],
561  boundary_elements[2]);
562  bounding_box.second =
563  is_gps ? TransformLatLonAltToModelCoords(tform, boundary_elements[3],
564  boundary_elements[4],
565  boundary_elements[5])
566  : Eigen::Vector3d(boundary_elements[3], boundary_elements[4],
567  boundary_elements[5]);
568  } else {
569  bounding_box = reconstruction.ComputeBoundingBox(boundary_elements[0],
570  boundary_elements[1]);
571  }
572 
573  PrintHeading2("Cropping reconstruction");
574  reconstruction.Crop(bounding_box).Write(output_path);
575  WriteBoundingBox(output_path, bounding_box);
576 
577  std::cout << "=> Cropping succeeded" << std::endl;
578  timer.PrintMinutes();
579  return EXIT_SUCCESS;
580 }
581 
582 int RunModelMerger(int argc, char** argv) {
583  std::string input_path1;
584  std::string input_path2;
585  std::string output_path;
586  double max_reproj_error = 64.0;
587 
588  OptionManager options;
589  options.AddRequiredOption("input_path1", &input_path1);
590  options.AddRequiredOption("input_path2", &input_path2);
591  options.AddRequiredOption("output_path", &output_path);
592  options.AddDefaultOption("max_reproj_error", &max_reproj_error);
593  options.Parse(argc, argv);
594 
595  Reconstruction reconstruction1;
596  reconstruction1.Read(input_path1);
597  PrintHeading2("Reconstruction 1");
598  std::cout << StringPrintf("Images: %d", reconstruction1.NumRegImages())
599  << std::endl;
600  std::cout << StringPrintf("Points: %d", reconstruction1.NumPoints3D())
601  << std::endl;
602 
603  Reconstruction reconstruction2;
604  reconstruction2.Read(input_path2);
605  PrintHeading2("Reconstruction 2");
606  std::cout << StringPrintf("Images: %d", reconstruction2.NumRegImages())
607  << std::endl;
608  std::cout << StringPrintf("Points: %d", reconstruction2.NumPoints3D())
609  << std::endl;
610 
611  PrintHeading2("Merging reconstructions");
612  if (reconstruction1.Merge(reconstruction2, max_reproj_error)) {
613  std::cout << "=> Merge succeeded" << std::endl;
614  PrintHeading2("Merged reconstruction");
615  std::cout << StringPrintf("Images: %d", reconstruction1.NumRegImages())
616  << std::endl;
617  std::cout << StringPrintf("Points: %d", reconstruction1.NumPoints3D())
618  << std::endl;
619  } else {
620  std::cout << "=> Merge failed" << std::endl;
621  }
622 
623  reconstruction1.Write(output_path);
624 
625  return EXIT_SUCCESS;
626 }
627 
628 int RunModelOrientationAligner(int argc, char** argv) {
629  std::string input_path;
630  std::string output_path;
631  std::string method = "MANHATTAN-WORLD";
632 
633  ManhattanWorldFrameEstimationOptions frame_estimation_options;
634 
635  OptionManager options;
636  options.AddImageOptions();
637  options.AddRequiredOption("input_path", &input_path);
638  options.AddRequiredOption("output_path", &output_path);
639  options.AddDefaultOption("method", &method,
640  "{MANHATTAN-WORLD, IMAGE-ORIENTATION}");
641  options.AddDefaultOption("max_image_size",
642  &frame_estimation_options.max_image_size);
643  options.Parse(argc, argv);
644 
645  StringToLower(&method);
646  if (method != "manhattan-world" && method != "image-orientation") {
647  std::cout << "WARNING: Invalid `method` - supported values are "
648  "'MANHATTAN-WORLD' or 'IMAGE-ORIENTATION'."
649  << std::endl;
650  return EXIT_FAILURE;
651  }
652 
653  Reconstruction reconstruction;
654  reconstruction.Read(input_path);
655 
656  PrintHeading1("Aligning Reconstruction");
657 
658  Eigen::Matrix3d tform;
659 
660  if (method == "manhattan-world") {
661  const Eigen::Matrix3d frame = EstimateManhattanWorldFrame(
662  frame_estimation_options, reconstruction, *options.image_path);
663 
664  if (frame.col(0).nonZeros() == 0) {
665  std::cout << "Only aligning vertical axis" << std::endl;
666  tform = RotationFromUnitVectors(frame.col(1), Eigen::Vector3d(0, 1, 0));
667  } else if (frame.col(1).nonZeros() == 0) {
668  tform = RotationFromUnitVectors(frame.col(0), Eigen::Vector3d(1, 0, 0));
669  std::cout << "Only aligning horizontal axis" << std::endl;
670  } else {
671  tform = frame.transpose();
672  std::cout << "Aligning horizontal and vertical axes" << std::endl;
673  }
674  } else if (method == "image-orientation") {
675  const Eigen::Vector3d gravity_axis =
677  tform = RotationFromUnitVectors(gravity_axis, Eigen::Vector3d(0, 1, 0));
678  } else {
679  LOG(FATAL) << "Alignment method not supported";
680  }
681 
682  std::cout << "Using the rotation matrix:" << std::endl;
683  std::cout << tform << std::endl;
684 
685  reconstruction.Transform(SimilarityTransform3(
686  1, RotationMatrixToQuaternion(tform), Eigen::Vector3d(0, 0, 0)));
687 
688  std::cout << "Writing aligned reconstruction..." << std::endl;
689  reconstruction.Write(output_path);
690 
691  return EXIT_SUCCESS;
692 }
693 
694 int RunModelSplitter(int argc, char** argv) {
695  Timer timer;
696  timer.Start();
697 
698  std::string input_path;
699  std::string output_path;
700  std::string split_type;
701  std::string split_params;
702  std::string gps_transform_path;
703  int num_threads = -1;
704  int min_reg_images = 10;
705  int min_num_points = 100;
706  double overlap_ratio = 0.0;
707  double min_area_ratio = 0.0;
708  bool is_gps = false;
709 
710  OptionManager options;
711  options.AddRequiredOption("input_path", &input_path);
712  options.AddRequiredOption("output_path", &output_path);
713  options.AddRequiredOption("split_type", &split_type,
714  "{tiles, extent, parts}");
715  options.AddRequiredOption("split_params", &split_params);
716  options.AddDefaultOption("gps_transform_path", &gps_transform_path);
717  options.AddDefaultOption("num_threads", &num_threads);
718  options.AddDefaultOption("min_reg_images", &min_reg_images);
719  options.AddDefaultOption("min_num_points", &min_num_points);
720  options.AddDefaultOption("overlap_ratio", &overlap_ratio);
721  options.AddDefaultOption("min_area_ratio", &min_area_ratio);
722  options.Parse(argc, argv);
723 
724  if (!ExistsDir(input_path)) {
725  std::cerr << "ERROR: `input_path` is not a directory" << std::endl;
726  return EXIT_FAILURE;
727  }
728 
729  if (!ExistsDir(output_path)) {
730  std::cerr << "ERROR: `output_path` is not a directory" << std::endl;
731  return EXIT_FAILURE;
732  }
733 
734  if (overlap_ratio < 0) {
735  std::cout << "WARN: Invalid `overlap_ratio`; resetting to 0" << std::endl;
736  overlap_ratio = 0.0;
737  }
738 
739  PrintHeading1("Splitting sparse model");
740  std::cout << StringPrintf(" => Using \"%s\" split type", split_type.c_str())
741  << std::endl;
742 
743  Reconstruction reconstruction;
744  reconstruction.Read(input_path);
745 
746  SimilarityTransform3 tform;
747  if (!gps_transform_path.empty()) {
748  PrintHeading2("Reading model to ECEF transform");
749  is_gps = true;
750  tform = SimilarityTransform3::FromFile(gps_transform_path).Inverse();
751  }
752  const double scale = tform.Scale();
753 
754  // Create the necessary number of reconstructions based on the split method
755  // and get the bounding boxes for each sub-reconstruction
756  PrintHeading2("Computing bound_coords");
757  std::vector<std::string> tile_keys;
758  std::vector<std::pair<Eigen::Vector3d, Eigen::Vector3d>> exact_bounds;
759  StringToLower(&split_type);
760  if (split_type == "tiles") {
761  std::ifstream file(split_params);
762  CHECK(file.is_open()) << split_params;
763 
764  double x1, y1, z1, x2, y2, z2;
765  std::string tile_key;
766  std::vector<std::pair<Eigen::Vector3d, Eigen::Vector3d>> bounds;
767  tile_keys.clear();
768  file >> tile_key >> x1 >> y1 >> z1 >> x2 >> y2 >> z2;
769  while (!file.fail()) {
770  tile_keys.push_back(tile_key);
771  if (is_gps) {
772  exact_bounds.emplace_back(
773  TransformLatLonAltToModelCoords(tform, x1, y1, z1),
774  TransformLatLonAltToModelCoords(tform, x2, y2, z2));
775  } else {
776  exact_bounds.emplace_back(Eigen::Vector3d(x1, y1, z1),
777  Eigen::Vector3d(x2, y2, z2));
778  }
779  file >> tile_key >> x1 >> y1 >> z1 >> x2 >> y2 >> z2;
780  }
781  } else if (split_type == "extent") {
782  std::vector<double> parts = CSVToVector<double>(split_params);
783  Eigen::Vector3d extent(std::numeric_limits<double>::max(),
784  std::numeric_limits<double>::max(),
785  std::numeric_limits<double>::max());
786  for (size_t i = 0; i < parts.size(); ++i) {
787  extent(i) = parts[i] * scale;
788  }
789 
790  const auto bbox = reconstruction.ComputeBoundingBox();
791  const Eigen::Vector3d full_extent = bbox.second - bbox.first;
792  const Eigen::Vector3i split(
793  static_cast<int>(full_extent(0) / extent(0)) + 1,
794  static_cast<int>(full_extent(1) / extent(1)) + 1,
795  static_cast<int>(full_extent(2) / extent(2)) + 1);
796 
797  exact_bounds = ComputeEqualPartsBounds(reconstruction, split);
798 
799  } else if (split_type == "parts") {
800  auto parts = CSVToVector<int>(split_params);
801  Eigen::Vector3i split(1, 1, 1);
802  for (size_t i = 0; i < parts.size(); ++i) {
803  split(i) = parts[i];
804  if (split(i) < 1) {
805  std::cerr << "ERROR: Cannot split in less than 1 parts for dim " << i
806  << std::endl;
807  return EXIT_FAILURE;
808  }
809  }
810  exact_bounds = ComputeEqualPartsBounds(reconstruction, split);
811  } else {
812  std::cout << "WARNING: Invalid split type: " << split_type << std::endl;
813  return EXIT_FAILURE;
814  }
815 
816  std::vector<std::pair<Eigen::Vector3d, Eigen::Vector3d>> bounds;
817  for (const auto& bbox : exact_bounds) {
818  const Eigen::Vector3d padding =
819  (overlap_ratio * (bbox.second - bbox.first));
820  bounds.emplace_back(bbox.first - padding, bbox.second + padding);
821  }
822 
823  PrintHeading2("Applying split and writing reconstructions");
824  const size_t num_parts = bounds.size();
825  std::cout << StringPrintf(" => Splitting to %d parts", num_parts)
826  << std::endl;
827 
828  const bool use_tile_keys = split_type == "tiles";
829 
830  auto SplitReconstruction = [&](const int idx) {
831  Reconstruction tile_recon = reconstruction.Crop(bounds[idx]);
832  // calculate area covered by model as proportion of box area
833  auto bbox_extent = bounds[idx].second - bounds[idx].first;
834  auto model_bbox = tile_recon.ComputeBoundingBox();
835  auto model_extent = model_bbox.second - model_bbox.first;
836  double area_ratio =
837  (model_extent(0) * model_extent(1)) / (bbox_extent(0) * bbox_extent(1));
838  int tile_num_points = tile_recon.NumPoints3D();
839 
840  std::string name = use_tile_keys ? tile_keys[idx] : std::to_string(idx);
841  const bool include_tile =
842  area_ratio >= min_area_ratio && //
843  tile_num_points >= min_num_points && //
844  tile_recon.NumRegImages() >= static_cast<size_t>(min_reg_images);
845 
846  if (include_tile) {
847  std::cout << StringPrintf(
848  "Writing reconstruction %s with %d images, %d points, "
849  "and %.2f%% area coverage",
850  name.c_str(), tile_recon.NumRegImages(), tile_num_points,
851  100.0 * area_ratio)
852  << std::endl;
853  const std::string reconstruction_path = JoinPaths(output_path, name);
854  CreateDirIfNotExists(reconstruction_path);
855  reconstruction.Write(reconstruction_path);
856  WriteBoundingBox(reconstruction_path, bounds[idx]);
857  WriteBoundingBox(reconstruction_path, exact_bounds[idx], "_exact");
858 
859  } else {
860  std::cout << StringPrintf(
861  "Skipping reconstruction %s with %d images, %d points, "
862  "and %.2f%% area coverage",
863  name.c_str(), tile_recon.NumRegImages(), tile_num_points,
864  100.0 * area_ratio)
865  << std::endl;
866  }
867  };
868 
869  ThreadPool thread_pool(GetEffectiveNumThreads(num_threads));
870  for (size_t idx = 0; idx < num_parts; ++idx) {
871  thread_pool.AddTask(SplitReconstruction, idx);
872  }
873  thread_pool.Wait();
874 
875  timer.PrintMinutes();
876  return EXIT_SUCCESS;
877 }
878 
879 int RunModelTransformer(int argc, char** argv) {
880  std::string input_path;
881  std::string output_path;
882  std::string transform_path;
883  bool is_inverse = false;
884 
885  OptionManager options;
886  options.AddRequiredOption("input_path", &input_path);
887  options.AddRequiredOption("output_path", &output_path);
888  options.AddRequiredOption("transform_path", &transform_path);
889  options.AddDefaultOption("is_inverse", &is_inverse);
890  options.Parse(argc, argv);
891 
892  std::cout << "Reading points input: " << input_path << std::endl;
893  Reconstruction recon;
894  bool is_dense = false;
895  if (HasFileExtension(input_path, ".ply")) {
896  is_dense = true;
897  recon.ImportPLY(input_path);
898  } else if (ExistsDir(input_path)) {
899  recon.Read(input_path);
900  } else {
901  std::cerr << "Invalid model input; not a PLY file or sparse reconstruction "
902  "directory."
903  << std::endl;
904  return EXIT_FAILURE;
905  }
906 
907  std::cout << "Reading transform input: " << transform_path << std::endl;
909  if (is_inverse) {
910  tform = tform.Inverse();
911  }
912 
913  std::cout << "Applying transform to recon with " << recon.NumPoints3D()
914  << " points" << std::endl;
915  recon.Transform(tform);
916 
917  std::cout << "Writing output: " << output_path << std::endl;
918  if (is_dense) {
919  recon.ExportPLY(output_path);
920  } else {
921  recon.Write(output_path);
922  }
923 
924  return EXIT_SUCCESS;
925 }
926 
927 } // namespace colmap
Rect frame
std::shared_ptr< core::Tensor > image
std::string name
int offset
const Eigen::Vector3d & Tvec() const
Definition: image.h:325
const Eigen::Vector4d & Qvec() const
Definition: image.h:301
Eigen::Vector3d ProjectionCenter() const
Definition: image.cc:152
void AddRequiredOption(const std::string &name, T *option, const std::string &help_text="")
void AddDefaultOption(const std::string &name, T *option, const std::string &help_text="")
void Parse(const int argc, char **argv)
std::shared_ptr< std::string > image_path
bool Align(const std::vector< std::string > &image_names, const std::vector< Eigen::Vector3d > &locations, const int min_common_images, SimilarityTransform3 *tform=nullptr)
size_t NumImages() const
bool ExportCam(const std::string &path, bool skip_distortion=false) const
Reconstruction Crop(const std::pair< Eigen::Vector3d, Eigen::Vector3d > &bbox) const
double ComputeMeanTrackLength() const
size_t NumRegImages() const
void Transform(const SimilarityTransform3 &tform)
std::pair< Eigen::Vector3d, Eigen::Vector3d > ComputeBoundingBox(const double p0=0.0, const double p1=1.0) const
void Write(const std::string &path) const
std::vector< image_t > FindCommonRegImageIds(const Reconstruction &reconstruction) const
bool ExportNVM(const std::string &path, bool skip_distortion=false) const
size_t ComputeNumObservations() const
const class Image & Image(const image_t image_id) const
bool ExportRecon3D(const std::string &path, bool skip_distortion=false) const
void ExportPLY(const std::string &path) const
size_t NumPoints3D() const
void WriteBinary(const std::string &path) const
bool Merge(const Reconstruction &reconstruction, const double max_reproj_error)
void ExportVRML(const std::string &images_path, const std::string &points3D_path, const double image_scale, const Eigen::Vector3d &image_rgb) const
bool ExportBundler(const std::string &path, const std::string &list_path, bool skip_distortion=false) const
bool AlignRobust(const std::vector< std::string > &image_names, const std::vector< Eigen::Vector3d > &locations, const int min_common_images, const RANSACOptions &ransac_options, SimilarityTransform3 *tform=nullptr)
const class Image * FindImageWithName(const std::string &name) const
double ComputeMeanReprojectionError() const
double ComputeMeanObservationsPerRegImage() const
void WriteText(const std::string &path) const
size_t NumCameras() const
void ImportPLY(const std::string &path)
void Read(const std::string &path)
void Write(const std::string &path)
static SimilarityTransform3 FromFile(const std::string &path)
void TransformPose(Eigen::Vector4d *qvec, Eigen::Vector3d *tvec) const
Eigen::Matrix4d Matrix() const
SimilarityTransform3 Inverse() const
auto AddTask(func_t &&f, args_t &&... args) -> std::future< typename std::result_of< func_t(args_t...)>::type >
Definition: threading.h:299
void Start()
Definition: timer.cc:43
void PrintMinutes() const
Definition: timer.cc:93
Matrix< double, 3, 4 > Matrix3x4d
Definition: types.h:39
QTextStream & endl(QTextStream &stream)
Definition: QtCompat.h:718
static const std::string path
Definition: PointCloud.cpp:59
bool ComputeAlignmentBetweenReconstructions(const Reconstruction &src_reconstruction, const Reconstruction &ref_reconstruction, const double min_inlier_observations, const double max_reproj_error, Eigen::Matrix3x4d *alignment)
float RadToDeg(const float rad)
Definition: math.h:180
int RunModelAligner(int argc, char **argv)
Definition: model.cc:196
Eigen::Matrix3d RotationFromUnitVectors(const Eigen::Vector3d &vector1, const Eigen::Vector3d &vector2)
Definition: pose.cc:145
void PrintHeading2(const std::string &heading)
Definition: misc.cc:231
int RunModelOrientationAligner(int argc, char **argv)
Definition: model.cc:628
int RunModelConverter(int argc, char **argv)
Definition: model.cc:463
int RunModelComparer(int argc, char **argv)
Definition: model.cc:367
Eigen::Vector4d NormalizeQuaternion(const Eigen::Vector4d &qvec)
Definition: pose.cc:82
bool HasFileExtension(const std::string &file_name, const std::string &ext)
Definition: misc.cc:51
bool StringStartsWith(const std::string &str, const std::string &prefix)
Definition: string.cc:173
void StringToLower(std::string *str)
Definition: string.cc:193
double Median(const std::vector< T > &elems)
Definition: math.h:189
Eigen::Vector4d RotationMatrixToQuaternion(const Eigen::Matrix3d &rot_mat)
Definition: pose.cc:70
int RunModelCropper(int argc, char **argv)
Definition: model.cc:509
int RunModelMerger(int argc, char **argv)
Definition: model.cc:582
std::vector< std::string > ReadTextFileLines(const std::string &path)
Definition: misc.cc:308
bool ExistsDir(const std::string &path)
Definition: misc.cc:104
Eigen::Matrix3d EstimateManhattanWorldFrame(const ManhattanWorldFrameEstimationOptions &options, const Reconstruction &reconstruction, const std::string &image_path)
void CreateDirIfNotExists(const std::string &path)
Definition: misc.cc:112
std::string JoinPaths(T const &... paths)
Definition: misc.h:128
Eigen::Vector3d EstimateGravityVectorFromImageOrientation(const Reconstruction &reconstruction, const double max_axis_distance)
uint32_t image_t
Definition: types.h:61
void PrintHeading1(const std::string &heading)
Definition: misc.cc:225
int RunModelSplitter(int argc, char **argv)
Definition: model.cc:694
double Mean(const std::vector< T > &elems)
Definition: math.h:227
std::string StringPrintf(const char *format,...)
Definition: string.cc:131
int RunModelAnalyzer(int argc, char **argv)
Definition: model.cc:332
void AlignToENUPlane(Reconstruction *recon, SimilarityTransform3 *tform, bool unscaled)
int RunModelTransformer(int argc, char **argv)
Definition: model.cc:879
void AlignToPrincipalPlane(Reconstruction *recon, SimilarityTransform3 *tform)
int GetEffectiveNumThreads(const int num_threads)
Definition: threading.cc:269
Eigen::Matrix< Index, 3, 1 > Vector3i
Definition: knncpp.h:30
std::string to_string(const T &n)
Definition: Common.h:20