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 "mvs/model.h"
33 
34 #include "base/camera_models.h"
35 #include "base/pose.h"
36 #include "base/projection.h"
37 #include "base/reconstruction.h"
38 #include "base/triangulation.h"
39 #include "util/misc.h"
40 
41 namespace colmap {
42 namespace mvs {
43 
44 void Model::Read(const std::string& path, const std::string& format) {
45  auto format_lower_case = format;
46  StringToLower(&format_lower_case);
47  if (format_lower_case == "colmap") {
49  } else if (format_lower_case == "pmvs") {
51  } else {
52  LOG(FATAL) << "Invalid input format";
53  }
54 }
55 
56 void Model::ReadFromCOLMAP(const std::string& path,
57  const std::string& sparse_path,
58  const std::string& images_path) {
59  Reconstruction reconstruction;
60  reconstruction.Read(JoinPaths(path, sparse_path));
61 
62  images.reserve(reconstruction.NumRegImages());
63  std::unordered_map<image_t, size_t> image_id_to_idx;
64  for (size_t i = 0; i < reconstruction.NumRegImages(); ++i) {
65  const auto image_id = reconstruction.RegImageIds()[i];
66  const auto& image = reconstruction.Image(image_id);
67  const auto& camera = reconstruction.Camera(image.CameraId());
68 
69  const std::string image_path = JoinPaths(path, images_path, image.Name());
70  const Eigen::Matrix<float, 3, 3, Eigen::RowMajor> K =
71  camera.CalibrationMatrix().cast<float>();
72  const Eigen::Matrix<float, 3, 3, Eigen::RowMajor> R =
73  QuaternionToRotationMatrix(image.Qvec()).cast<float>();
74  const Eigen::Vector3f T = image.Tvec().cast<float>();
75 
76  images.emplace_back(image_path, camera.Width(), camera.Height(), K.data(),
77  R.data(), T.data());
78  image_id_to_idx.emplace(image_id, i);
79  image_names_.push_back(image.Name());
80  image_name_to_idx_.emplace(image.Name(), i);
81  }
82 
83  points.reserve(reconstruction.NumPoints3D());
84  for (const auto& point3D : reconstruction.Points3D()) {
85  Point point;
86  point.x = point3D.second.X();
87  point.y = point3D.second.Y();
88  point.z = point3D.second.Z();
89  point.track.reserve(point3D.second.Track().Length());
90  for (const auto& track_el : point3D.second.Track().Elements()) {
91  point.track.push_back(image_id_to_idx.at(track_el.image_id));
92  }
93  points.push_back(point);
94  }
95 
96  // Fix for Reconstruction::~Reconstruction() crash issue:
97  // The crash may be caused by memory alignment issues when Image objects
98  // (which contain Eigen::Vector4d and Eigen::Vector3d requiring 16-byte
99  // alignment) are stored in std::unordered_map without proper alignment.
100  // Explicitly clear data structures before destruction to avoid potential
101  // alignment-related crashes, double-free, or memory corruption issues
102  // during automatic destruction.
103  //
104  // Cleanup order is critical:
105  // 1. First, tear down the reconstruction to clean up correspondence graph
106  // and compress tracks (this may reallocate memory with proper alignment)
107  reconstruction.TearDown();
108  // 2. Then, explicitly clear all points and 2D observations to break
109  // cross-references between points3D_ and images_ before destruction.
110  // This ensures Image objects with Eigen-aligned members are destroyed
111  // in a controlled manner, avoiding alignment issues in unordered_map.
112  reconstruction.DeleteAllPoints2DAndPoints3D();
113  // Note: After these operations, the reconstruction object will be safely
114  // destroyed without accessing potentially misaligned Eigen members.
115 }
116 
117 void Model::ReadFromPMVS(const std::string& path) {
118  if (ReadFromBundlerPMVS(path)) {
119  return;
120  } else if (ReadFromRawPMVS(path)) {
121  return;
122  } else {
123  LOG(FATAL) << "Invalid PMVS format";
124  }
125 }
126 
127 int Model::GetImageIdx(const std::string& name) const {
128  CHECK_GT(image_name_to_idx_.count(name), 0)
129  << "Image with name `" << name << "` does not exist";
130  return image_name_to_idx_.at(name);
131 }
132 
133 std::string Model::GetImageName(const int image_idx) const {
134  CHECK_GE(image_idx, 0);
135  CHECK_LT(image_idx, image_names_.size());
136  return image_names_.at(image_idx);
137 }
138 
139 std::vector<std::vector<int>> Model::GetMaxOverlappingImages(
140  const size_t num_images, const double min_triangulation_angle) const {
141  std::vector<std::vector<int>> overlapping_images(images.size());
142 
143  const float min_triangulation_angle_rad = DegToRad(min_triangulation_angle);
144 
145  const auto shared_num_points = ComputeSharedPoints();
146 
147  const float kTriangulationAnglePercentile = 75;
148  const auto triangulation_angles =
149  ComputeTriangulationAngles(kTriangulationAnglePercentile);
150 
151  for (size_t image_idx = 0; image_idx < images.size(); ++image_idx) {
152  const auto& shared_images = shared_num_points.at(image_idx);
153  const auto& overlapping_triangulation_angles =
154  triangulation_angles.at(image_idx);
155 
156  std::vector<std::pair<int, int>> ordered_images;
157  ordered_images.reserve(shared_images.size());
158  for (const auto& image : shared_images) {
159  if (overlapping_triangulation_angles.at(image.first) >=
160  min_triangulation_angle_rad) {
161  ordered_images.emplace_back(image.first, image.second);
162  }
163  }
164 
165  const size_t eff_num_images = std::min(ordered_images.size(), num_images);
166  if (eff_num_images < shared_images.size()) {
167  std::partial_sort(ordered_images.begin(),
168  ordered_images.begin() + eff_num_images,
169  ordered_images.end(),
170  [](const std::pair<int, int> image1,
171  const std::pair<int, int> image2) {
172  return image1.second > image2.second;
173  });
174  } else {
175  std::sort(ordered_images.begin(), ordered_images.end(),
176  [](const std::pair<int, int> image1,
177  const std::pair<int, int> image2) {
178  return image1.second > image2.second;
179  });
180  }
181 
182  overlapping_images[image_idx].reserve(eff_num_images);
183  for (size_t i = 0; i < eff_num_images; ++i) {
184  overlapping_images[image_idx].push_back(ordered_images[i].first);
185  }
186  }
187 
188  return overlapping_images;
189 }
190 
191 const std::vector<std::vector<int>>& Model::GetMaxOverlappingImagesFromPMVS()
192  const {
193  return pmvs_vis_dat_;
194 }
195 
196 std::vector<std::pair<float, float>> Model::ComputeDepthRanges() const {
197  std::vector<std::vector<float>> depths(images.size());
198  for (const auto& point : points) {
199  const Eigen::Vector3f X(point.x, point.y, point.z);
200  for (const auto& image_idx : point.track) {
201  const auto& image = images.at(image_idx);
202  const float depth =
203  Eigen::Map<const Eigen::Vector3f>(&image.GetR()[6]).dot(X) +
204  image.GetT()[2];
205  if (depth > 0) {
206  depths[image_idx].push_back(depth);
207  }
208  }
209  }
210 
211  std::vector<std::pair<float, float>> depth_ranges(depths.size());
212  for (size_t image_idx = 0; image_idx < depth_ranges.size(); ++image_idx) {
213  auto& depth_range = depth_ranges[image_idx];
214 
215  auto& image_depths = depths[image_idx];
216 
217  if (image_depths.empty()) {
218  depth_range.first = -1.0f;
219  depth_range.second = -1.0f;
220  continue;
221  }
222 
223  std::sort(image_depths.begin(), image_depths.end());
224 
225  const float kMinPercentile = 0.01f;
226  const float kMaxPercentile = 0.99f;
227  depth_range.first = image_depths[image_depths.size() * kMinPercentile];
228  depth_range.second = image_depths[image_depths.size() * kMaxPercentile];
229 
230  const float kStretchRatio = 0.25f;
231  depth_range.first *= (1.0f - kStretchRatio);
232  depth_range.second *= (1.0f + kStretchRatio);
233  }
234 
235  return depth_ranges;
236 }
237 
238 std::vector<std::map<int, int>> Model::ComputeSharedPoints() const {
239  std::vector<std::map<int, int>> shared_points(images.size());
240  for (const auto& point : points) {
241  for (size_t i = 0; i < point.track.size(); ++i) {
242  const int image_idx1 = point.track[i];
243  for (size_t j = 0; j < i; ++j) {
244  const int image_idx2 = point.track[j];
245  if (image_idx1 != image_idx2) {
246  shared_points.at(image_idx1)[image_idx2] += 1;
247  shared_points.at(image_idx2)[image_idx1] += 1;
248  }
249  }
250  }
251  }
252  return shared_points;
253 }
254 
255 std::vector<std::map<int, float>> Model::ComputeTriangulationAngles(
256  const float percentile) const {
257  std::vector<Eigen::Vector3d> proj_centers(images.size());
258  for (size_t image_idx = 0; image_idx < images.size(); ++image_idx) {
259  const auto& image = images[image_idx];
260  Eigen::Vector3f C;
261  ComputeProjectionCenter(image.GetR(), image.GetT(), C.data());
262  proj_centers[image_idx] = C.cast<double>();
263  }
264 
265  std::vector<std::map<int, std::vector<float>>> all_triangulation_angles(
266  images.size());
267  for (const auto& point : points) {
268  for (size_t i = 0; i < point.track.size(); ++i) {
269  const int image_idx1 = point.track[i];
270  for (size_t j = 0; j < i; ++j) {
271  const int image_idx2 = point.track[j];
272  if (image_idx1 != image_idx2) {
273  const float angle = CalculateTriangulationAngle(
274  proj_centers.at(image_idx1), proj_centers.at(image_idx2),
275  Eigen::Vector3d(point.x, point.y, point.z));
276  all_triangulation_angles.at(image_idx1)[image_idx2].push_back(angle);
277  all_triangulation_angles.at(image_idx2)[image_idx1].push_back(angle);
278  }
279  }
280  }
281  }
282 
283  std::vector<std::map<int, float>> triangulation_angles(images.size());
284  for (size_t image_idx = 0; image_idx < all_triangulation_angles.size();
285  ++image_idx) {
286  const auto& overlapping_images = all_triangulation_angles[image_idx];
287  for (const auto& image : overlapping_images) {
288  triangulation_angles[image_idx].emplace(
289  image.first, Percentile(image.second, percentile));
290  }
291  }
292 
293  return triangulation_angles;
294 }
295 
296 bool Model::ReadFromBundlerPMVS(const std::string& path) {
297  const std::string bundle_file_path = JoinPaths(path, "bundle.rd.out");
298 
299  if (!ExistsFile(bundle_file_path)) {
300  return false;
301  }
302 
303  std::ifstream file(bundle_file_path);
304  CHECK(file.is_open()) << bundle_file_path;
305 
306  // Header line.
307  std::string header;
308  std::getline(file, header);
309 
310  int num_images, num_points;
311  file >> num_images >> num_points;
312 
313  images.reserve(num_images);
314  for (int image_idx = 0; image_idx < num_images; ++image_idx) {
315  const std::string image_name = StringPrintf("%08d.jpg", image_idx);
316  const std::string image_path = JoinPaths(path, "visualize", image_name);
317 
318  float K[9] = {1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f};
319  file >> K[0];
320  K[4] = K[0];
321 
322  Bitmap bitmap;
323  CHECK(bitmap.Read(image_path));
324  K[2] = bitmap.Width() / 2.0f;
325  K[5] = bitmap.Height() / 2.0f;
326 
327  float k1, k2;
328  file >> k1 >> k2;
329  CHECK_EQ(k1, 0.0f);
330  CHECK_EQ(k2, 0.0f);
331 
332  float R[9];
333  for (size_t i = 0; i < 9; ++i) {
334  file >> R[i];
335  }
336  for (size_t i = 3; i < 9; ++i) {
337  R[i] = -R[i];
338  }
339 
340  float T[3];
341  file >> T[0] >> T[1] >> T[2];
342  T[1] = -T[1];
343  T[2] = -T[2];
344 
345  images.emplace_back(image_path, bitmap.Width(), bitmap.Height(), K, R, T);
346  image_names_.push_back(image_name);
347  image_name_to_idx_.emplace(image_name, image_idx);
348  }
349 
350  points.resize(num_points);
351  for (int point_id = 0; point_id < num_points; ++point_id) {
352  auto& point = points[point_id];
353 
354  file >> point.x >> point.y >> point.z;
355 
356  int color[3];
357  file >> color[0] >> color[1] >> color[2];
358 
359  int track_len;
360  file >> track_len;
361  point.track.resize(track_len);
362 
363  for (int i = 0; i < track_len; ++i) {
364  int feature_idx;
365  float imx, imy;
366  file >> point.track[i] >> feature_idx >> imx >> imy;
367  CHECK_LT(point.track[i], images.size());
368  }
369  }
370 
371  return true;
372 }
373 
374 bool Model::ReadFromRawPMVS(const std::string& path) {
375  const std::string vis_dat_path = JoinPaths(path, "vis.dat");
376  if (!ExistsFile(vis_dat_path)) {
377  return false;
378  }
379 
380  for (int image_idx = 0;; ++image_idx) {
381  const std::string image_name = StringPrintf("%08d.jpg", image_idx);
382  const std::string image_path = JoinPaths(path, "visualize", image_name);
383 
384  if (!ExistsFile(image_path)) {
385  break;
386  }
387 
388  Bitmap bitmap;
389  CHECK(bitmap.Read(image_path));
390 
391  const std::string proj_matrix_path =
392  JoinPaths(path, "txt", StringPrintf("%08d.txt", image_idx));
393 
394  std::ifstream proj_matrix_file(proj_matrix_path);
395  CHECK(proj_matrix_file.is_open()) << proj_matrix_path;
396 
397  std::string contour;
398  proj_matrix_file >> contour;
399  CHECK_EQ(contour, "CONTOUR");
400 
402  for (int i = 0; i < 3; ++i) {
403  proj_matrix_file >> P(i, 0) >> P(i, 1) >> P(i, 2) >> P(i, 3);
404  }
405 
406  Eigen::Matrix3d K;
407  Eigen::Matrix3d R;
408  Eigen::Vector3d T;
409  DecomposeProjectionMatrix(P, &K, &R, &T);
410 
411  // The COLMAP patch match algorithm requires that there is no skew.
412  K(0, 1) = 0.0f;
413  K(1, 0) = 0.0f;
414  K(2, 0) = 0.0f;
415  K(2, 1) = 0.0f;
416  K(2, 2) = 1.0f;
417 
418  const Eigen::Matrix<float, 3, 3, Eigen::RowMajor> K_float = K.cast<float>();
419  const Eigen::Matrix<float, 3, 3, Eigen::RowMajor> R_float = R.cast<float>();
420  const Eigen::Vector3f T_float = T.cast<float>();
421 
422  images.emplace_back(image_path, bitmap.Width(), bitmap.Height(),
423  K_float.data(), R_float.data(), T_float.data());
424  image_names_.push_back(image_name);
425  image_name_to_idx_.emplace(image_name, image_idx);
426  }
427 
428  std::ifstream vis_dat_file(vis_dat_path);
429  CHECK(vis_dat_file.is_open()) << vis_dat_path;
430 
431  std::string visdata;
432  vis_dat_file >> visdata;
433  CHECK_EQ(visdata, "VISDATA");
434 
435  int num_images;
436  vis_dat_file >> num_images;
437  CHECK_GE(num_images, 0);
438  CHECK_EQ(num_images, images.size());
439 
440  pmvs_vis_dat_.resize(num_images);
441  for (int i = 0; i < num_images; ++i) {
442  int image_idx;
443  vis_dat_file >> image_idx;
444  CHECK_GE(image_idx, 0);
445  CHECK_LT(image_idx, num_images);
446 
447  int num_visible_images;
448  vis_dat_file >> num_visible_images;
449 
450  auto& visible_image_idxs = pmvs_vis_dat_[image_idx];
451  visible_image_idxs.reserve(num_visible_images);
452 
453  for (int j = 0; j < num_visible_images; ++j) {
454  int visible_image_idx;
455  vis_dat_file >> visible_image_idx;
456  CHECK_GE(visible_image_idx, 0);
457  CHECK_LT(visible_image_idx, num_images);
458  if (visible_image_idx != image_idx) {
459  visible_image_idxs.push_back(visible_image_idx);
460  }
461  }
462  }
463 
464  return true;
465 }
466 
467 } // namespace mvs
468 } // namespace colmap
std::shared_ptr< core::Tensor > image
filament::Texture::InternalFormat format
std::string name
math::float4 color
void * X
Definition: SmallVector.cpp:45
size_t NumRegImages() const
const class Image & Image(const image_t image_id) const
size_t NumPoints3D() const
const class Camera & Camera(const camera_t camera_id) const
const std::unordered_map< point3D_t, class Point3D > & Points3D() const
const std::vector< image_t > & RegImageIds() const
void Read(const std::string &path)
Matrix< double, 3, 4 > Matrix3x4d
Definition: types.h:39
static const std::string path
Definition: PointCloud.cpp:59
void ComputeProjectionCenter(const float R[9], const float T[3], float C[3])
Definition: image.cc:125
void StringToLower(std::string *str)
Definition: string.cc:193
bool DecomposeProjectionMatrix(const Eigen::Matrix3x4d &P, Eigen::Matrix3d *K, Eigen::Matrix3d *R, Eigen::Vector3d *T)
Definition: projection.cc:72
bool ExistsFile(const std::string &path)
Definition: misc.cc:100
std::string JoinPaths(T const &... paths)
Definition: misc.h:128
std::string StringPrintf(const char *format,...)
Definition: string.cc:131
double CalculateTriangulationAngle(const Eigen::Vector3d &proj_center1, const Eigen::Vector3d &proj_center2, const Eigen::Vector3d &point3D)
float DegToRad(const float deg)
Definition: math.h:171
T Percentile(const std::vector< T > &elems, const double p)
Definition: math.h:209
Eigen::Matrix3d QuaternionToRotationMatrix(const Eigen::Vector4d &qvec)
Definition: pose.cc:75
std::vector< int > track
Definition: model.h:31
std::vector< Point > points
Definition: model.h:73
std::string GetImageName(const int image_idx) const
Definition: model.cc:133
int GetImageIdx(const std::string &name) const
Definition: model.cc:127
std::vector< std::pair< float, float > > ComputeDepthRanges() const
Definition: model.cc:196
void ReadFromCOLMAP(const std::string &path, const std::string &sparse_path="sparse", const std::string &images_path="images")
Definition: model.cc:56
std::vector< Image > images
Definition: model.h:72
std::vector< std::vector< int > > GetMaxOverlappingImages(const size_t num_images, const double min_triangulation_angle) const
Definition: model.cc:139
std::vector< std::map< int, float > > ComputeTriangulationAngles(const float percentile=50) const
Definition: model.cc:255
void Read(const std::string &path, const std::string &format)
Definition: model.cc:44
void ReadFromPMVS(const std::string &path)
Definition: model.cc:117
const std::vector< std::vector< int > > & GetMaxOverlappingImagesFromPMVS() const
Definition: model.cc:191
std::vector< std::map< int, int > > ComputeSharedPoints() const
Definition: model.cc:238