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>
Median(std::vector<T> * elems)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.
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)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   for (const auto image_idx : overlapping_images.at(prev_image_idx)) {
65     if (used_images.at(image_idx) && !fused_images.at(image_idx)) {
66       return image_idx;
67     }
68   }
69 
70   // If none of the overlapping images are not yet fused, simply return the
71   // first image that has not yet been fused.
72   for (size_t image_idx = 0; image_idx < fused_images.size(); ++image_idx) {
73     if (used_images[image_idx] && !fused_images[image_idx]) {
74       return image_idx;
75     }
76   }
77 
78   return -1;
79 }
80 
81 }  // namespace internal
82 
Print() const83 void StereoFusionOptions::Print() const {
84 #define PrintOption(option) std::cout << #option ": " << option << std::endl
85   PrintHeading2("StereoFusion::Options");
86   PrintOption(max_image_size);
87   PrintOption(min_num_pixels);
88   PrintOption(max_num_pixels);
89   PrintOption(max_traversal_depth);
90   PrintOption(max_reproj_error);
91   PrintOption(max_depth_error);
92   PrintOption(max_normal_error);
93   PrintOption(check_num_images);
94   PrintOption(cache_size);
95 #undef PrintOption
96 }
97 
Check() const98 bool StereoFusionOptions::Check() const {
99   CHECK_OPTION_GE(min_num_pixels, 0);
100   CHECK_OPTION_LE(min_num_pixels, max_num_pixels);
101   CHECK_OPTION_GT(max_traversal_depth, 0);
102   CHECK_OPTION_GE(max_reproj_error, 0);
103   CHECK_OPTION_GE(max_depth_error, 0);
104   CHECK_OPTION_GE(max_normal_error, 0);
105   CHECK_OPTION_GT(check_num_images, 0);
106   CHECK_OPTION_GT(cache_size, 0);
107   return true;
108 }
109 
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)110 StereoFusion::StereoFusion(const StereoFusionOptions& options,
111                            const std::string& workspace_path,
112                            const std::string& workspace_format,
113                            const std::string& pmvs_option_name,
114                            const std::string& input_type)
115     : options_(options),
116       workspace_path_(workspace_path),
117       workspace_format_(workspace_format),
118       pmvs_option_name_(pmvs_option_name),
119       input_type_(input_type),
120       max_squared_reproj_error_(options_.max_reproj_error *
121                                 options_.max_reproj_error),
122       min_cos_normal_error_(std::cos(DegToRad(options_.max_normal_error))) {
123   CHECK(options_.Check());
124 }
125 
GetFusedPoints() const126 const std::vector<PlyPoint>& StereoFusion::GetFusedPoints() const {
127   return fused_points_;
128 }
129 
GetFusedPointsVisibility() const130 const std::vector<std::vector<int>>& StereoFusion::GetFusedPointsVisibility()
131     const {
132   return fused_points_visibility_;
133 }
134 
Run()135 void StereoFusion::Run() {
136   fused_points_.clear();
137   fused_points_visibility_.clear();
138 
139   options_.Print();
140   std::cout << std::endl;
141 
142   std::cout << "Reading workspace..." << std::endl;
143 
144   Workspace::Options workspace_options;
145 
146   auto workspace_format_lower_case = workspace_format_;
147   StringToLower(&workspace_format_lower_case);
148   if (workspace_format_lower_case == "pmvs") {
149     workspace_options.stereo_folder =
150         StringPrintf("stereo-%s", pmvs_option_name_.c_str());
151   }
152 
153   workspace_options.max_image_size = options_.max_image_size;
154   workspace_options.image_as_rgb = true;
155   workspace_options.cache_size = options_.cache_size;
156   workspace_options.workspace_path = workspace_path_;
157   workspace_options.workspace_format = workspace_format_;
158   workspace_options.input_type = input_type_;
159 
160   workspace_.reset(new Workspace(workspace_options));
161 
162   if (IsStopped()) {
163     GetTimer().PrintMinutes();
164     return;
165   }
166 
167   std::cout << "Reading configuration..." << std::endl;
168 
169   const auto& model = workspace_->GetModel();
170 
171   const double kMinTriangulationAngle = 0;
172   if (model.GetMaxOverlappingImagesFromPMVS().empty()) {
173     overlapping_images_ = model.GetMaxOverlappingImages(
174         options_.check_num_images, kMinTriangulationAngle);
175   } else {
176     overlapping_images_ = model.GetMaxOverlappingImagesFromPMVS();
177   }
178 
179   used_images_.resize(model.images.size(), false);
180   fused_images_.resize(model.images.size(), false);
181   fused_pixel_masks_.resize(model.images.size());
182   depth_map_sizes_.resize(model.images.size());
183   bitmap_scales_.resize(model.images.size());
184   P_.resize(model.images.size());
185   inv_P_.resize(model.images.size());
186   inv_R_.resize(model.images.size());
187 
188   const auto image_names = ReadTextFileLines(JoinPaths(
189       workspace_path_, workspace_options.stereo_folder, "fusion.cfg"));
190   for (const auto& image_name : image_names) {
191     const int image_idx = model.GetImageIdx(image_name);
192 
193     if (!workspace_->HasBitmap(image_idx) ||
194         !workspace_->HasDepthMap(image_idx) ||
195         !workspace_->HasNormalMap(image_idx)) {
196       std::cout
197           << StringPrintf(
198                  "WARNING: Ignoring image %s, because input does not exist.",
199                  image_name.c_str())
200           << std::endl;
201       continue;
202     }
203 
204     const auto& image = model.images.at(image_idx);
205     const auto& depth_map = workspace_->GetDepthMap(image_idx);
206 
207     used_images_.at(image_idx) = true;
208 
209     fused_pixel_masks_.at(image_idx) =
210         Mat<bool>(depth_map.GetWidth(), depth_map.GetHeight(), 1);
211     fused_pixel_masks_.at(image_idx).Fill(false);
212 
213     depth_map_sizes_.at(image_idx) =
214         std::make_pair(depth_map.GetWidth(), depth_map.GetHeight());
215 
216     bitmap_scales_.at(image_idx) = std::make_pair(
217         static_cast<float>(depth_map.GetWidth()) / image.GetWidth(),
218         static_cast<float>(depth_map.GetHeight()) / image.GetHeight());
219 
220     Eigen::Matrix<float, 3, 3, Eigen::RowMajor> K =
221         Eigen::Map<const Eigen::Matrix<float, 3, 3, Eigen::RowMajor>>(
222             image.GetK());
223     K(0, 0) *= bitmap_scales_.at(image_idx).first;
224     K(0, 2) *= bitmap_scales_.at(image_idx).first;
225     K(1, 1) *= bitmap_scales_.at(image_idx).second;
226     K(1, 2) *= bitmap_scales_.at(image_idx).second;
227 
228     ComposeProjectionMatrix(K.data(), image.GetR(), image.GetT(),
229                             P_.at(image_idx).data());
230     ComposeInverseProjectionMatrix(K.data(), image.GetR(), image.GetT(),
231                                    inv_P_.at(image_idx).data());
232     inv_R_.at(image_idx) =
233         Eigen::Map<const Eigen::Matrix<float, 3, 3, Eigen::RowMajor>>(
234             image.GetR())
235             .transpose();
236   }
237 
238   size_t num_fused_images = 0;
239   for (int image_idx = 0; image_idx >= 0;
240        image_idx = internal::FindNextImage(overlapping_images_, used_images_,
241                                            fused_images_, image_idx)) {
242     if (IsStopped()) {
243       break;
244     }
245 
246     Timer timer;
247     timer.Start();
248 
249     std::cout << StringPrintf("Fusing image [%d/%d]", num_fused_images + 1,
250                               model.images.size())
251               << std::flush;
252 
253     const int width = depth_map_sizes_.at(image_idx).first;
254     const int height = depth_map_sizes_.at(image_idx).second;
255     const auto& fused_pixel_mask = fused_pixel_masks_.at(image_idx);
256 
257     FusionData data;
258     data.image_idx = image_idx;
259     data.traversal_depth = 0;
260 
261     for (data.row = 0; data.row < height; ++data.row) {
262       for (data.col = 0; data.col < width; ++data.col) {
263         if (fused_pixel_mask.Get(data.row, data.col)) {
264           continue;
265         }
266 
267         fusion_queue_.push_back(data);
268 
269         Fuse();
270       }
271     }
272 
273     num_fused_images += 1;
274     fused_images_.at(image_idx) = true;
275 
276     std::cout << StringPrintf(" in %.3fs (%d points)", timer.ElapsedSeconds(),
277                               fused_points_.size())
278               << std::endl;
279   }
280 
281   fused_points_.shrink_to_fit();
282   fused_points_visibility_.shrink_to_fit();
283 
284   if (fused_points_.empty()) {
285     std::cout << "WARNING: Could not fuse any points. This is likely caused by "
286                  "incorrect settings - filtering must be enabled for the last "
287                  "call to patch match stereo."
288               << std::endl;
289   }
290 
291   std::cout << "Number of fused points: " << fused_points_.size() << std::endl;
292   GetTimer().PrintMinutes();
293 }
294 
Fuse()295 void StereoFusion::Fuse() {
296   CHECK_EQ(fusion_queue_.size(), 1);
297 
298   Eigen::Vector4f fused_ref_point = Eigen::Vector4f::Zero();
299   Eigen::Vector3f fused_ref_normal = Eigen::Vector3f::Zero();
300 
301   fused_point_x_.clear();
302   fused_point_y_.clear();
303   fused_point_z_.clear();
304   fused_point_nx_.clear();
305   fused_point_ny_.clear();
306   fused_point_nz_.clear();
307   fused_point_r_.clear();
308   fused_point_g_.clear();
309   fused_point_b_.clear();
310   fused_point_visibility_.clear();
311 
312   while (!fusion_queue_.empty()) {
313     const auto data = fusion_queue_.back();
314     const int image_idx = data.image_idx;
315     const int row = data.row;
316     const int col = data.col;
317     const int traversal_depth = data.traversal_depth;
318 
319     fusion_queue_.pop_back();
320 
321     // Check if pixel already fused.
322     auto& fused_pixel_mask = fused_pixel_masks_.at(image_idx);
323     if (fused_pixel_mask.Get(row, col)) {
324       continue;
325     }
326 
327     const auto& depth_map = workspace_->GetDepthMap(image_idx);
328     const float depth = depth_map.Get(row, col);
329 
330     // Pixels with negative depth are filtered.
331     if (depth <= 0.0f) {
332       continue;
333     }
334 
335     // If the traversal depth is greater than zero, the initial reference
336     // pixel has already been added and we need to check for consistency.
337     if (traversal_depth > 0) {
338       // Project reference point into current view.
339       const Eigen::Vector3f proj = P_.at(image_idx) * fused_ref_point;
340 
341       // Depth error of reference depth with current depth.
342       const float depth_error = std::abs((proj(2) - depth) / depth);
343       if (depth_error > options_.max_depth_error) {
344         continue;
345       }
346 
347       // Reprojection error reference point in the current view.
348       const float col_diff = proj(0) / proj(2) - col;
349       const float row_diff = proj(1) / proj(2) - row;
350       const float squared_reproj_error =
351           col_diff * col_diff + row_diff * row_diff;
352       if (squared_reproj_error > max_squared_reproj_error_) {
353         continue;
354       }
355     }
356 
357     // Determine normal direction in global reference frame.
358     const auto& normal_map = workspace_->GetNormalMap(image_idx);
359     const Eigen::Vector3f normal =
360         inv_R_.at(image_idx) * Eigen::Vector3f(normal_map.Get(row, col, 0),
361                                                normal_map.Get(row, col, 1),
362                                                normal_map.Get(row, col, 2));
363 
364     // Check for consistent normal direction with reference normal.
365     if (traversal_depth > 0) {
366       const float cos_normal_error = fused_ref_normal.dot(normal);
367       if (cos_normal_error < min_cos_normal_error_) {
368         continue;
369       }
370     }
371 
372     // Determine 3D location of current depth value.
373     const Eigen::Vector3f xyz =
374         inv_P_.at(image_idx) *
375         Eigen::Vector4f(col * depth, row * depth, depth, 1.0f);
376 
377     // Read the color of the pixel.
378     BitmapColor<uint8_t> color;
379     const auto& bitmap_scale = bitmap_scales_.at(image_idx);
380     workspace_->GetBitmap(image_idx).InterpolateNearestNeighbor(
381         col / bitmap_scale.first, row / bitmap_scale.second, &color);
382 
383     // Set the current pixel as visited.
384     fused_pixel_mask.Set(row, col, true);
385 
386     // Accumulate statistics for fused point.
387     fused_point_x_.push_back(xyz(0));
388     fused_point_y_.push_back(xyz(1));
389     fused_point_z_.push_back(xyz(2));
390     fused_point_nx_.push_back(normal(0));
391     fused_point_ny_.push_back(normal(1));
392     fused_point_nz_.push_back(normal(2));
393     fused_point_r_.push_back(color.r);
394     fused_point_g_.push_back(color.g);
395     fused_point_b_.push_back(color.b);
396     fused_point_visibility_.insert(image_idx);
397 
398     // Remember the first pixel as the reference.
399     if (traversal_depth == 0) {
400       fused_ref_point = Eigen::Vector4f(xyz(0), xyz(1), xyz(2), 1.0f);
401       fused_ref_normal = normal;
402     }
403 
404     if (fused_point_x_.size() >= static_cast<size_t>(options_.max_num_pixels)) {
405       break;
406     }
407 
408     FusionData next_data;
409     next_data.traversal_depth = traversal_depth + 1;
410 
411     if (next_data.traversal_depth >= options_.max_traversal_depth) {
412       continue;
413     }
414 
415     for (const auto next_image_idx : overlapping_images_.at(image_idx)) {
416       if (!used_images_.at(next_image_idx) ||
417           fused_images_.at(next_image_idx)) {
418         continue;
419       }
420 
421       next_data.image_idx = next_image_idx;
422 
423       const Eigen::Vector3f next_proj =
424           P_.at(next_image_idx) * xyz.homogeneous();
425       next_data.col = static_cast<int>(std::round(next_proj(0) / next_proj(2)));
426       next_data.row = static_cast<int>(std::round(next_proj(1) / next_proj(2)));
427 
428       const auto& depth_map_size = depth_map_sizes_.at(next_image_idx);
429       if (next_data.col < 0 || next_data.row < 0 ||
430           next_data.col >= depth_map_size.first ||
431           next_data.row >= depth_map_size.second) {
432         continue;
433       }
434 
435       fusion_queue_.push_back(next_data);
436     }
437   }
438 
439   fusion_queue_.clear();
440 
441   const size_t num_pixels = fused_point_x_.size();
442   if (num_pixels >= static_cast<size_t>(options_.min_num_pixels)) {
443     PlyPoint fused_point;
444 
445     Eigen::Vector3f fused_normal;
446     fused_normal.x() = internal::Median(&fused_point_nx_);
447     fused_normal.y() = internal::Median(&fused_point_ny_);
448     fused_normal.z() = internal::Median(&fused_point_nz_);
449     const float fused_normal_norm = fused_normal.norm();
450     if (fused_normal_norm < std::numeric_limits<float>::epsilon()) {
451       return;
452     }
453 
454     fused_point.x = internal::Median(&fused_point_x_);
455     fused_point.y = internal::Median(&fused_point_y_);
456     fused_point.z = internal::Median(&fused_point_z_);
457 
458     fused_point.nx = fused_normal.x() / fused_normal_norm;
459     fused_point.ny = fused_normal.y() / fused_normal_norm;
460     fused_point.nz = fused_normal.z() / fused_normal_norm;
461 
462     fused_point.r = TruncateCast<float, uint8_t>(
463         std::round(internal::Median(&fused_point_r_)));
464     fused_point.g = TruncateCast<float, uint8_t>(
465         std::round(internal::Median(&fused_point_g_)));
466     fused_point.b = TruncateCast<float, uint8_t>(
467         std::round(internal::Median(&fused_point_b_)));
468 
469     fused_points_.push_back(fused_point);
470     fused_points_visibility_.emplace_back(fused_point_visibility_.begin(),
471                                           fused_point_visibility_.end());
472   }
473 }
474 
WritePointsVisibility(const std::string & path,const std::vector<std::vector<int>> & points_visibility)475 void WritePointsVisibility(
476     const std::string& path,
477     const std::vector<std::vector<int>>& points_visibility) {
478   std::fstream file(path, std::ios::out | std::ios::binary);
479   CHECK(file.is_open()) << path;
480 
481   WriteBinaryLittleEndian<uint64_t>(&file, points_visibility.size());
482 
483   for (const auto& visibility : points_visibility) {
484     WriteBinaryLittleEndian<uint32_t>(&file, visibility.size());
485     for (const auto& image_idx : visibility) {
486       WriteBinaryLittleEndian<uint32_t>(&file, image_idx);
487     }
488   }
489 }
490 
491 }  // namespace mvs
492 }  // namespace colmap
493