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