ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
fusion.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/fusion.h"
33 
34 #include "util/misc.h"
35 
36 namespace colmap {
37 namespace mvs {
38 namespace internal {
39 
40 template <typename T>
41 float Median(std::vector<T>* elems) {
42  CHECK(!elems->empty());
43  const size_t mid_idx = elems->size() / 2;
44  std::nth_element(elems->begin(), elems->begin() + mid_idx, elems->end());
45  if (elems->size() % 2 == 0) {
46  const float mid_element1 = static_cast<float>((*elems)[mid_idx]);
47  const float mid_element2 = static_cast<float>(
48  *std::max_element(elems->begin(), elems->begin() + mid_idx));
49  return (mid_element1 + mid_element2) / 2.0f;
50  } else {
51  return static_cast<float>((*elems)[mid_idx]);
52  }
53 }
54 
55 // Use the sparse model to find most connected image that has not yet been
56 // fused. This is used as a heuristic to ensure that the workspace cache reuses
57 // already cached images as efficient as possible.
58 int FindNextImage(const std::vector<std::vector<int>>& overlapping_images,
59  const std::vector<char>& used_images,
60  const std::vector<char>& fused_images,
61  const int prev_image_idx) {
62  CHECK_EQ(used_images.size(), fused_images.size());
63 
64  if (prev_image_idx >= 0 &&
65  static_cast<size_t>(prev_image_idx) < overlapping_images.size()) {
66  for (const auto image_idx : overlapping_images[prev_image_idx]) {
67  if (image_idx >= 0 &&
68  static_cast<size_t>(image_idx) < used_images.size() &&
69  static_cast<size_t>(image_idx) < fused_images.size() &&
70  used_images[image_idx] && !fused_images[image_idx]) {
71  return image_idx;
72  }
73  }
74  }
75 
76  // If none of the overlapping images are not yet fused, simply return the
77  // first image that has not yet been fused.
78  for (size_t image_idx = 0; image_idx < fused_images.size(); ++image_idx) {
79  if (used_images[image_idx] && !fused_images[image_idx]) {
80  return image_idx;
81  }
82  }
83 
84  return -1;
85 }
86 
87 } // namespace internal
88 
90 #define PrintOption(option) std::cout << #option ": " << option << std::endl
91  PrintHeading2("StereoFusion::Options");
103  const auto& bbox_min = bounding_box.first.transpose().eval();
104  const auto& bbox_max = bounding_box.second.transpose().eval();
105  PrintOption(bbox_min);
106  PrintOption(bbox_max);
107 #undef PrintOption
108 }
109 
119  return true;
120 }
121 
123  const std::string& workspace_path,
124  const std::string& workspace_format,
125  const std::string& pmvs_option_name,
126  const std::string& input_type)
127  : options_(options),
128  workspace_path_(workspace_path),
129  workspace_format_(workspace_format),
130  pmvs_option_name_(pmvs_option_name),
131  input_type_(input_type),
132  max_squared_reproj_error_(options_.max_reproj_error *
133  options_.max_reproj_error),
134  min_cos_normal_error_(std::cos(DegToRad(options_.max_normal_error))) {
135  CHECK(options_.Check());
136 }
137 
138 const std::vector<PlyPoint>& StereoFusion::GetFusedPoints() const {
139  return fused_points_;
140 }
141 
142 const std::vector<std::vector<int>>& StereoFusion::GetFusedPointsVisibility()
143  const {
144  return fused_points_visibility_;
145 }
146 
147 void StereoFusion::Run() {
148  fused_points_.clear();
149  fused_points_visibility_.clear();
150 
151  options_.Print();
152  std::cout << std::endl;
153 
154  std::cout << "Reading workspace..." << std::endl;
155 
156  Workspace::Options workspace_options;
157 
158  auto workspace_format_lower_case = workspace_format_;
159  StringToLower(&workspace_format_lower_case);
160  if (workspace_format_lower_case == "pmvs") {
161  workspace_options.stereo_folder =
162  StringPrintf("stereo-%s", pmvs_option_name_.c_str());
163  }
164 
165  workspace_options.num_threads = options_.num_threads;
166  workspace_options.max_image_size = options_.max_image_size;
167  workspace_options.image_as_rgb = true;
168  workspace_options.cache_size = options_.cache_size;
169  workspace_options.workspace_path = workspace_path_;
170  workspace_options.workspace_format = workspace_format_;
171  workspace_options.input_type = input_type_;
172 
173  const auto image_names = ReadTextFileLines(JoinPaths(
174  workspace_path_, workspace_options.stereo_folder, "fusion.cfg"));
175  int num_threads = 1;
176  if (options_.use_cache) {
177  workspace_.reset(new CachedWorkspace(workspace_options));
178  } else {
179  workspace_.reset(new Workspace(workspace_options));
180  workspace_->Load(image_names);
181  num_threads = GetEffectiveNumThreads(options_.num_threads);
182  }
183 
184  if (IsStopped()) {
186  return;
187  }
188 
189  std::cout << "Reading configuration..." << std::endl;
190 
191  const auto& model = workspace_->GetModel();
192 
193  const double kMinTriangulationAngle = 0;
194  if (model.GetMaxOverlappingImagesFromPMVS().empty()) {
195  overlapping_images_ = model.GetMaxOverlappingImages(
196  options_.check_num_images, kMinTriangulationAngle);
197  } else {
198  overlapping_images_ = model.GetMaxOverlappingImagesFromPMVS();
199  }
200 
201  task_fused_points_.resize(num_threads);
202  task_fused_points_visibility_.resize(num_threads);
203 
204  used_images_.resize(model.images.size(), false);
205  fused_images_.resize(model.images.size(), false);
206  fused_pixel_masks_.resize(model.images.size());
207  depth_map_sizes_.resize(model.images.size());
208  bitmap_scales_.resize(model.images.size());
209  P_.resize(model.images.size());
210  inv_P_.resize(model.images.size());
211  inv_R_.resize(model.images.size());
212 
213  for (const auto& image_name : image_names) {
214  const int image_idx = model.GetImageIdx(image_name);
215 
216  if (!workspace_->HasBitmap(image_idx) ||
217  !workspace_->HasDepthMap(image_idx) ||
218  !workspace_->HasNormalMap(image_idx)) {
219  std::cout << StringPrintf(
220  "WARNING: Ignoring image %s, because input "
221  "does not exist.",
222  image_name.c_str())
223  << std::endl;
224  continue;
225  }
226 
227  const auto& image = model.images.at(image_idx);
228  const auto& depth_map = workspace_->GetDepthMap(image_idx);
229 
230  used_images_.at(image_idx) = true;
231 
232  InitFusedPixelMask(image_idx, depth_map.GetWidth(),
233  depth_map.GetHeight());
234 
235  depth_map_sizes_.at(image_idx) =
236  std::make_pair(depth_map.GetWidth(), depth_map.GetHeight());
237 
238  bitmap_scales_.at(image_idx) = std::make_pair(
239  static_cast<float>(depth_map.GetWidth()) / image.GetWidth(),
240  static_cast<float>(depth_map.GetHeight()) / image.GetHeight());
241 
242  Eigen::Matrix<float, 3, 3, Eigen::RowMajor> K =
243  Eigen::Map<const Eigen::Matrix<float, 3, 3, Eigen::RowMajor>>(
244  image.GetK());
245  K(0, 0) *= bitmap_scales_.at(image_idx).first;
246  K(0, 2) *= bitmap_scales_.at(image_idx).first;
247  K(1, 1) *= bitmap_scales_.at(image_idx).second;
248  K(1, 2) *= bitmap_scales_.at(image_idx).second;
249 
250  ComposeProjectionMatrix(K.data(), image.GetR(), image.GetT(),
251  P_.at(image_idx).data());
252  ComposeInverseProjectionMatrix(K.data(), image.GetR(), image.GetT(),
253  inv_P_.at(image_idx).data());
254  inv_R_.at(image_idx) =
255  Eigen::Map<const Eigen::Matrix<float, 3, 3, Eigen::RowMajor>>(
256  image.GetR())
257  .transpose();
258  }
259 
260  std::cout << StringPrintf("Starting fusion with %d threads", num_threads)
261  << std::endl;
262  ThreadPool thread_pool(num_threads);
263 
264  // Using a row stride of 10 to avoid starting parallel processing in rows
265  // that are too close to each other which may lead to duplicated work, since
266  // nearby pixels are likely to get fused into the same point.
267  const int kRowStride = 10;
268  auto ProcessImageRows = [&, this](const int row_start, const int height,
269  const int width, const int image_idx,
270  const Mat<char>& fused_pixel_mask) {
271  const int row_end = std::min(height, row_start + kRowStride);
272  for (int row = row_start; row < row_end; ++row) {
273  for (int col = 0; col < width; ++col) {
274  if (fused_pixel_mask.Get(row, col) > 0) {
275  continue;
276  }
277  const int thread_id = thread_pool.GetThreadIndex();
278  Fuse(thread_id, image_idx, row, col);
279  }
280  }
281  };
282 
283  size_t num_fused_images = 0;
284  size_t total_fused_points = 0;
285  for (int image_idx = 0; image_idx >= 0;
286  image_idx = internal::FindNextImage(overlapping_images_, used_images_,
287  fused_images_, image_idx)) {
288  if (IsStopped()) {
289  break;
290  }
291 
292  Timer timer;
293  timer.Start();
294 
295  std::cout << StringPrintf("Fusing image [%d/%d] with index %d",
296  num_fused_images + 1, model.images.size(),
297  image_idx)
298  << std::flush;
299 
300  const int width = depth_map_sizes_.at(image_idx).first;
301  const int height = depth_map_sizes_.at(image_idx).second;
302  const auto& fused_pixel_mask = fused_pixel_masks_.at(image_idx);
303 
304  for (int row_start = 0; row_start < height; row_start += kRowStride) {
305  thread_pool.AddTask(ProcessImageRows, row_start, height, width,
306  image_idx, fused_pixel_mask);
307  }
308  thread_pool.Wait();
309 
310  num_fused_images += 1;
311  fused_images_.at(image_idx) = true;
312 
313  total_fused_points = 0;
314  for (const auto& task_fused_points : task_fused_points_) {
315  total_fused_points += task_fused_points.size();
316  }
317  std::cout << StringPrintf(" in %.3fs (%d points)",
318  timer.ElapsedSeconds(), total_fused_points)
319  << std::endl;
320  }
321 
322  fused_points_.reserve(total_fused_points);
323  fused_points_visibility_.reserve(total_fused_points);
324  for (size_t thread_id = 0; thread_id < task_fused_points_.size();
325  ++thread_id) {
326  fused_points_.insert(fused_points_.end(),
327  task_fused_points_[thread_id].begin(),
328  task_fused_points_[thread_id].end());
329  task_fused_points_[thread_id].clear();
330 
331  fused_points_visibility_.insert(
332  fused_points_visibility_.end(),
333  task_fused_points_visibility_[thread_id].begin(),
334  task_fused_points_visibility_[thread_id].end());
335  task_fused_points_visibility_[thread_id].clear();
336  }
337 
338  if (fused_points_.empty()) {
339  std::cout << "WARNING: Could not fuse any points. This is likely "
340  "caused by "
341  "incorrect settings - filtering must be enabled for the "
342  "last "
343  "call to patch match stereo."
344  << std::endl;
345  }
346 
347  std::cout << "Number of fused points: " << fused_points_.size()
348  << std::endl;
350 }
351 
352 void StereoFusion::InitFusedPixelMask(int image_idx,
353  size_t width,
354  size_t height) {
355  Bitmap mask;
356  Mat<char>& fused_pixel_mask = fused_pixel_masks_.at(image_idx);
357  const std::string mask_path =
358  JoinPaths(options_.mask_path,
359  workspace_->GetModel().GetImageName(image_idx) + ".png");
360  fused_pixel_mask = Mat<char>(width, height, 1);
361  if (!options_.mask_path.empty() && ExistsFile(mask_path) &&
362  mask.Read(mask_path, false)) {
363  BitmapColor<uint8_t> color;
364  mask.Rescale(static_cast<int>(width), static_cast<int>(height),
365  FILTER_BOX);
366  for (size_t row = 0; row < height; ++row) {
367  for (size_t col = 0; col < width; ++col) {
368  mask.GetPixel(col, row, &color);
369  fused_pixel_mask.Set(row, col, color.r == 0 ? 1 : 0);
370  }
371  }
372  } else {
373  fused_pixel_mask.Fill(0);
374  }
375 }
376 
377 void StereoFusion::Fuse(const int thread_id,
378  const int image_idx,
379  const int row,
380  const int col) {
381  // Next points to fuse.
382  std::vector<FusionData> fusion_queue;
383  fusion_queue.emplace_back(image_idx, row, col, 0);
384 
385  Eigen::Vector4f fused_ref_point = Eigen::Vector4f::Zero();
386  Eigen::Vector3f fused_ref_normal = Eigen::Vector3f::Zero();
387 
388  // Points of different pixels of the currently point to be fused.
389  std::vector<float> fused_point_x;
390  std::vector<float> fused_point_y;
391  std::vector<float> fused_point_z;
392  std::vector<float> fused_point_nx;
393  std::vector<float> fused_point_ny;
394  std::vector<float> fused_point_nz;
395  std::vector<uint8_t> fused_point_r;
396  std::vector<uint8_t> fused_point_g;
397  std::vector<uint8_t> fused_point_b;
398  std::unordered_set<int> fused_point_visibility;
399 
400  while (!fusion_queue.empty()) {
401  const auto data = fusion_queue.back();
402  const int image_idx = data.image_idx;
403  const int row = data.row;
404  const int col = data.col;
405  const int traversal_depth = data.traversal_depth;
406 
407  fusion_queue.pop_back();
408 
409  // Check if pixel already fused.
410  auto& fused_pixel_mask = fused_pixel_masks_.at(image_idx);
411  if (fused_pixel_mask.Get(row, col) > 0) {
412  continue;
413  }
414 
415  const auto& depth_map = workspace_->GetDepthMap(image_idx);
416  const float depth = depth_map.Get(row, col);
417 
418  // Pixels with negative depth are filtered.
419  if (depth <= 0.0f) {
420  continue;
421  }
422 
423  // If the traversal depth is greater than zero, the initial reference
424  // pixel has already been added and we need to check for consistency.
425  if (traversal_depth > 0) {
426  // Project reference point into current view.
427  const Eigen::Vector3f proj = P_.at(image_idx) * fused_ref_point;
428 
429  // Check for valid projection depth to avoid division by zero
430  if (std::abs(proj(2)) < std::numeric_limits<float>::epsilon()) {
431  continue;
432  }
433 
434  // Depth error of reference depth with current depth.
435  const float depth_error = std::abs((proj(2) - depth) / depth);
436  if (depth_error > options_.max_depth_error) {
437  continue;
438  }
439 
440  // Reprojection error reference point in the current view.
441  const float col_diff = proj(0) / proj(2) - col;
442  const float row_diff = proj(1) / proj(2) - row;
443  const float squared_reproj_error =
444  col_diff * col_diff + row_diff * row_diff;
445  if (squared_reproj_error > max_squared_reproj_error_) {
446  continue;
447  }
448  }
449 
450  // Determine normal direction in global reference frame.
451  const auto& normal_map = workspace_->GetNormalMap(image_idx);
452  const Eigen::Vector3f normal =
453  inv_R_.at(image_idx) *
454  Eigen::Vector3f(normal_map.Get(row, col, 0),
455  normal_map.Get(row, col, 1),
456  normal_map.Get(row, col, 2));
457 
458  // Check for consistent normal direction with reference normal.
459  if (traversal_depth > 0) {
460  const float cos_normal_error = fused_ref_normal.dot(normal);
461  if (cos_normal_error < min_cos_normal_error_) {
462  continue;
463  }
464  }
465 
466  // Determine 3D location of current depth value.
467  const Eigen::Vector3f xyz =
468  inv_P_.at(image_idx) *
469  Eigen::Vector4f(col * depth, row * depth, depth, 1.0f);
470 
471  // Read the color of the pixel.
472  BitmapColor<uint8_t> color;
473  const auto& bitmap_scale = bitmap_scales_.at(image_idx);
474  workspace_->GetBitmap(image_idx).InterpolateNearestNeighbor(
475  col / bitmap_scale.first, row / bitmap_scale.second, &color);
476 
477  // Set the current pixel as visited.
478  fused_pixel_mask.Set(row, col, 1);
479 
480  // Pixels out of bounds are filtered
481  if (xyz(0) < options_.bounding_box.first(0) ||
482  xyz(1) < options_.bounding_box.first(1) ||
483  xyz(2) < options_.bounding_box.first(2) ||
484  xyz(0) > options_.bounding_box.second(0) ||
485  xyz(1) > options_.bounding_box.second(1) ||
486  xyz(2) > options_.bounding_box.second(2)) {
487  continue;
488  }
489 
490  // Accumulate statistics for fused point.
491  fused_point_x.push_back(xyz(0));
492  fused_point_y.push_back(xyz(1));
493  fused_point_z.push_back(xyz(2));
494  fused_point_nx.push_back(normal(0));
495  fused_point_ny.push_back(normal(1));
496  fused_point_nz.push_back(normal(2));
497  fused_point_r.push_back(color.r);
498  fused_point_g.push_back(color.g);
499  fused_point_b.push_back(color.b);
500  fused_point_visibility.insert(image_idx);
501 
502  // Remember the first pixel as the reference.
503  if (traversal_depth == 0) {
504  fused_ref_point = Eigen::Vector4f(xyz(0), xyz(1), xyz(2), 1.0f);
505  fused_ref_normal = normal;
506  }
507 
508  if (fused_point_x.size() >=
509  static_cast<size_t>(options_.max_num_pixels)) {
510  break;
511  }
512 
513  if (traversal_depth >= options_.max_traversal_depth - 1) {
514  continue;
515  }
516 
517  if (static_cast<size_t>(image_idx) < overlapping_images_.size()) {
518  for (const auto next_image_idx : overlapping_images_[image_idx]) {
519  if (next_image_idx < 0 ||
520  static_cast<size_t>(next_image_idx) >=
521  used_images_.size() ||
522  static_cast<size_t>(next_image_idx) >=
523  fused_images_.size() ||
524  !used_images_[next_image_idx] ||
525  fused_images_[next_image_idx]) {
526  continue;
527  }
528 
529  if (static_cast<size_t>(next_image_idx) >= P_.size()) {
530  std::cout << "P_ size: " << P_.size() << std::endl;
531  std::cout << "next_image_idx: " << next_image_idx
532  << std::endl;
533  continue;
534  }
535 
536  const Eigen::Vector3f next_proj =
537  P_[next_image_idx] * xyz.homogeneous();
538 
539  // Check for valid projection depth to avoid division by zero
540  if (std::abs(next_proj(2)) <
541  std::numeric_limits<float>::epsilon()) {
542  std::cout << "next_proj(2) is zero: " << next_proj(2)
543  << std::endl;
544  continue;
545  }
546 
547  const int next_col = static_cast<int>(
548  std::round(next_proj(0) / next_proj(2)));
549  const int next_row = static_cast<int>(
550  std::round(next_proj(1) / next_proj(2)));
551 
552  if (static_cast<size_t>(next_image_idx) >=
553  depth_map_sizes_.size()) {
554  std::cout << "depth_map_sizes_ size: "
555  << depth_map_sizes_.size() << std::endl;
556  std::cout << "next_image_idx: " << next_image_idx
557  << std::endl;
558  continue;
559  }
560 
561  const auto& depth_map_size = depth_map_sizes_[next_image_idx];
562  if (next_col < 0 || next_row < 0 ||
563  next_col >= depth_map_size.first ||
564  next_row >= depth_map_size.second) {
565  continue;
566  }
567  fusion_queue.emplace_back(next_image_idx, next_row, next_col,
568  traversal_depth + 1);
569  }
570  }
571  }
572 
573  const size_t num_pixels = fused_point_x.size();
574  if (num_pixels >= static_cast<size_t>(options_.min_num_pixels)) {
575  // Validate that all required data vectors have the same size
576  if (fused_point_x.empty() || fused_point_y.empty() ||
577  fused_point_z.empty() || fused_point_nx.empty() ||
578  fused_point_ny.empty() || fused_point_nz.empty() ||
579  fused_point_r.empty() || fused_point_g.empty() ||
580  fused_point_b.empty()) {
581  std::cout << "fused_point_x.empty(): " << fused_point_x.empty()
582  << std::endl;
583  std::cout << "fused_point_y.empty(): " << fused_point_y.empty()
584  << std::endl;
585  std::cout << "fused_point_z.empty(): " << fused_point_z.empty()
586  << std::endl;
587  std::cout << "fused_point_nx.empty(): " << fused_point_nx.empty()
588  << std::endl;
589  std::cout << "fused_point_ny.empty(): " << fused_point_ny.empty()
590  << std::endl;
591  std::cout << "fused_point_nz.empty(): " << fused_point_nz.empty()
592  << std::endl;
593  std::cout << "fused_point_r.empty(): " << fused_point_r.empty()
594  << std::endl;
595  return;
596  }
597 
598  PlyPoint fused_point;
599 
600  Eigen::Vector3f fused_normal;
601  fused_normal.x() = internal::Median(&fused_point_nx);
602  fused_normal.y() = internal::Median(&fused_point_ny);
603  fused_normal.z() = internal::Median(&fused_point_nz);
604  const float fused_normal_norm = fused_normal.norm();
605  if (fused_normal_norm < std::numeric_limits<float>::epsilon()) {
606  return;
607  }
608 
609  fused_point.x = internal::Median(&fused_point_x);
610  fused_point.y = internal::Median(&fused_point_y);
611  fused_point.z = internal::Median(&fused_point_z);
612 
613  fused_point.nx = fused_normal.x() / fused_normal_norm;
614  fused_point.ny = fused_normal.y() / fused_normal_norm;
615  fused_point.nz = fused_normal.z() / fused_normal_norm;
616 
617  fused_point.r = TruncateCast<float, uint8_t>(
618  std::round(internal::Median(&fused_point_r)));
619  fused_point.g = TruncateCast<float, uint8_t>(
620  std::round(internal::Median(&fused_point_g)));
621  fused_point.b = TruncateCast<float, uint8_t>(
622  std::round(internal::Median(&fused_point_b)));
623 
624  if (static_cast<size_t>(thread_id) < task_fused_points_.size() &&
625  static_cast<size_t>(thread_id) <
626  task_fused_points_visibility_.size()) {
627  task_fused_points_[thread_id].push_back(fused_point);
628  task_fused_points_visibility_[thread_id].emplace_back(
629  fused_point_visibility.begin(),
630  fused_point_visibility.end());
631  }
632  }
633 }
634 
636  const std::string& path,
637  const std::vector<std::vector<int>>& points_visibility) {
638  std::fstream file(path, std::ios::out | std::ios::binary);
639  CHECK(file.is_open()) << path;
640 
641  WriteBinaryLittleEndian<uint64_t>(&file, points_visibility.size());
642 
643  for (const auto& visibility : points_visibility) {
644  WriteBinaryLittleEndian<uint32_t>(&file, visibility.size());
645  for (const auto& image_idx : visibility) {
646  WriteBinaryLittleEndian<uint32_t>(&file, image_idx);
647  }
648  }
649 }
650 
651 } // namespace mvs
652 } // namespace colmap
std::shared_ptr< core::Tensor > image
double normal[3]
int width
int height
math::float4 color
const Timer & GetTimer() const
Definition: threading.cc:154
bool IsStopped()
Definition: threading.cc:97
void PrintMinutes() const
Definition: timer.cc:93
const std::vector< std::vector< int > > & GetFusedPointsVisibility() const
Definition: fusion.cc:142
const std::vector< PlyPoint > & GetFusedPoints() const
Definition: fusion.cc:138
StereoFusion(const StereoFusionOptions &options, const std::string &workspace_path, const std::string &workspace_format, const std::string &pmvs_option_name, const std::string &input_type)
Definition: fusion.cc:122
#define PrintOption(option)
GraphType data
Definition: graph_cut.cc:138
#define CHECK_OPTION_LE(val1, val2)
Definition: logging.h:31
#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
CLOUDVIEWER_HOST_DEVICE Pair< First, Second > make_pair(const First &_first, const Second &_second)
Definition: SlabTraits.h:49
static const std::string path
Definition: PointCloud.cpp:59
float Median(std::vector< T > *elems)
Definition: fusion.cc:41
int FindNextImage(const std::vector< std::vector< int >> &overlapping_images, const std::vector< char > &used_images, const std::vector< char > &fused_images, const int prev_image_idx)
Definition: fusion.cc:58
void WritePointsVisibility(const std::string &path, const std::vector< std::vector< int >> &points_visibility)
Definition: fusion.cc:635
void ComposeInverseProjectionMatrix(const float K[9], const float R[9], const float T[3], float inv_P[12])
Definition: image.cc:115
void ComposeProjectionMatrix(const float K[9], const float R[9], const float T[3], float P[12])
Definition: image.cc:106
void PrintHeading2(const std::string &heading)
Definition: misc.cc:231
void StringToLower(std::string *str)
Definition: string.cc:193
std::vector< std::string > ReadTextFileLines(const std::string &path)
Definition: misc.cc:308
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
float DegToRad(const float deg)
Definition: math.h:171
int GetEffectiveNumThreads(const int num_threads)
Definition: threading.cc:269
Definition: Eigen.h:85
std::pair< Eigen::Vector3f, Eigen::Vector3f > bounding_box
Definition: fusion.h:71