ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
patch_match.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/patch_match.h"
33 
34 #include <numeric>
35 #include <unordered_set>
36 
37 #include "mvs/consistency_graph.h"
38 #include "mvs/patch_match_cuda.h"
39 #include "mvs/workspace.h"
40 #include "util/math.h"
41 #include "util/misc.h"
42 
43 #define PrintOption(option) std::cout << #option ": " << option << std::endl
44 
45 namespace colmap {
46 namespace mvs {
47 
48 PatchMatch::PatchMatch(const PatchMatchOptions& options, const Problem& problem)
49  : options_(options), problem_(problem) {}
50 
52 
54  PrintHeading2("PatchMatchOptions");
78 }
79 
81  PrintHeading2("PatchMatch::Problem");
82 
84 
85  std::cout << "src_image_idxs: ";
86  if (!src_image_idxs.empty()) {
87  for (size_t i = 0; i < src_image_idxs.size() - 1; ++i) {
88  std::cout << src_image_idxs[i] << " ";
89  }
90  std::cout << src_image_idxs.back() << std::endl;
91  } else {
92  std::cout << std::endl;
93  }
94 }
95 
96 void PatchMatch::Check() const {
97  CHECK(options_.Check());
98 
99  CHECK(!options_.gpu_index.empty());
100  const std::vector<int> gpu_indices = CSVToVector<int>(options_.gpu_index);
101  CHECK_EQ(gpu_indices.size(), 1);
102  CHECK_GE(gpu_indices[0], -1);
103 
104  CHECK_NOTNULL(problem_.images);
105  if (options_.geom_consistency) {
106  CHECK_NOTNULL(problem_.depth_maps);
107  CHECK_NOTNULL(problem_.normal_maps);
108  CHECK_EQ(problem_.depth_maps->size(), problem_.images->size());
109  CHECK_EQ(problem_.normal_maps->size(), problem_.images->size());
110  }
111 
112  CHECK_GT(problem_.src_image_idxs.size(), 0);
113 
114  // Check that there are no duplicate images and that the reference image
115  // is not defined as a source image.
116  std::set<int> unique_image_idxs(problem_.src_image_idxs.begin(),
117  problem_.src_image_idxs.end());
118  unique_image_idxs.insert(problem_.ref_image_idx);
119  CHECK_EQ(problem_.src_image_idxs.size() + 1, unique_image_idxs.size());
120 
121  // Check that input data is well-formed.
122  for (const int image_idx : unique_image_idxs) {
123  CHECK_GE(image_idx, 0) << image_idx;
124  CHECK_LT(image_idx, problem_.images->size()) << image_idx;
125 
126  const Image& image = problem_.images->at(image_idx);
127  CHECK_GT(image.GetBitmap().Width(), 0) << image_idx;
128  CHECK_GT(image.GetBitmap().Height(), 0) << image_idx;
129  CHECK(image.GetBitmap().IsGrey()) << image_idx;
130  CHECK_EQ(image.GetWidth(), image.GetBitmap().Width()) << image_idx;
131  CHECK_EQ(image.GetHeight(), image.GetBitmap().Height()) << image_idx;
132 
133  // Make sure, the calibration matrix only contains fx, fy, cx, cy.
134  CHECK_LT(std::abs(image.GetK()[1] - 0.0f), 1e-6f) << image_idx;
135  CHECK_LT(std::abs(image.GetK()[3] - 0.0f), 1e-6f) << image_idx;
136  CHECK_LT(std::abs(image.GetK()[6] - 0.0f), 1e-6f) << image_idx;
137  CHECK_LT(std::abs(image.GetK()[7] - 0.0f), 1e-6f) << image_idx;
138  CHECK_LT(std::abs(image.GetK()[8] - 1.0f), 1e-6f) << image_idx;
139 
140  if (options_.geom_consistency) {
141  CHECK_LT(image_idx, problem_.depth_maps->size()) << image_idx;
142  const DepthMap& depth_map = problem_.depth_maps->at(image_idx);
143  CHECK_EQ(image.GetWidth(), depth_map.GetWidth()) << image_idx;
144  CHECK_EQ(image.GetHeight(), depth_map.GetHeight()) << image_idx;
145  }
146  }
147 
148  if (options_.geom_consistency) {
149  const Image& ref_image = problem_.images->at(problem_.ref_image_idx);
150  const NormalMap& ref_normal_map =
151  problem_.normal_maps->at(problem_.ref_image_idx);
152  CHECK_EQ(ref_image.GetWidth(), ref_normal_map.GetWidth());
153  CHECK_EQ(ref_image.GetHeight(), ref_normal_map.GetHeight());
154  }
155 }
156 
158  PrintHeading2("PatchMatch::Run");
159 
160  Check();
161 
162  patch_match_cuda_.reset(new PatchMatchCuda(options_, problem_));
163  patch_match_cuda_->Run();
164 }
165 
167  return patch_match_cuda_->GetDepthMap();
168 }
169 
171  return patch_match_cuda_->GetNormalMap();
172 }
173 
175  return patch_match_cuda_->GetSelProbMap();
176 }
177 
179  const auto& ref_image = problem_.images->at(problem_.ref_image_idx);
180  return ConsistencyGraph(ref_image.GetWidth(), ref_image.GetHeight(),
181  patch_match_cuda_->GetConsistentImageIdxs());
182 }
183 
185  const std::string& workspace_path,
186  const std::string& workspace_format,
187  const std::string& pmvs_option_name,
188  const std::string& config_path)
189  : options_(options),
190  workspace_path_(workspace_path),
191  workspace_format_(workspace_format),
192  pmvs_option_name_(pmvs_option_name),
193  config_path_(config_path) {
194  std::vector<int> gpu_indices = CSVToVector<int>(options_.gpu_index);
195 }
196 
197 void PatchMatchController::Run() {
198  ReadWorkspace();
199  ReadProblems();
200  ReadGpuIndices();
201 
202  thread_pool_.reset(new ThreadPool(gpu_indices_.size()));
203 
204  // If geometric consistency is enabled, then photometric output must be
205  // computed first for all images without filtering.
206  if (options_.geom_consistency) {
207  auto photometric_options = options_;
208  photometric_options.geom_consistency = false;
209  photometric_options.filter = false;
210 
211  for (size_t problem_idx = 0; problem_idx < problems_.size();
212  ++problem_idx) {
213  thread_pool_->AddTask(&PatchMatchController::ProcessProblem, this,
214  photometric_options, problem_idx);
215  }
216 
217  thread_pool_->Wait();
218  }
219 
220  for (size_t problem_idx = 0; problem_idx < problems_.size(); ++problem_idx) {
221  thread_pool_->AddTask(&PatchMatchController::ProcessProblem, this, options_,
222  problem_idx);
223  }
224 
225  thread_pool_->Wait();
226 
228 }
229 
230 void PatchMatchController::ReadWorkspace() {
231  std::cout << "Reading workspace..." << std::endl;
232 
233  Workspace::Options workspace_options;
234 
235  auto workspace_format_lower_case = workspace_format_;
236  StringToLower(&workspace_format_lower_case);
237  if (workspace_format_lower_case == "pmvs") {
238  workspace_options.stereo_folder =
239  StringPrintf("stereo-%s", pmvs_option_name_.c_str());
240  }
241 
242  workspace_options.max_image_size = options_.max_image_size;
243  workspace_options.image_as_rgb = false;
244  workspace_options.cache_size = options_.cache_size;
245  workspace_options.workspace_path = workspace_path_;
246  workspace_options.workspace_format = workspace_format_;
247  workspace_options.input_type = options_.geom_consistency ? "photometric" : "";
248 
249  workspace_.reset(new CachedWorkspace(workspace_options));
250 
251  if (workspace_format_lower_case == "pmvs") {
252  std::cout << StringPrintf("Importing PMVS workspace (option %s)...",
253  pmvs_option_name_.c_str())
254  << std::endl;
255  ImportPMVSWorkspace(*workspace_, pmvs_option_name_);
256  }
257 
258  depth_ranges_ = workspace_->GetModel().ComputeDepthRanges();
259 }
260 
261 void PatchMatchController::ReadProblems() {
262  std::cout << "Reading configuration..." << std::endl;
263 
264  problems_.clear();
265 
266  const auto& model = workspace_->GetModel();
267 
268  const std::string config_path =
269  config_path_.empty()
270  ? JoinPaths(workspace_path_, workspace_->GetOptions().stereo_folder,
271  "patch-match.cfg")
272  : config_path_;
273  std::vector<std::string> config = ReadTextFileLines(config_path);
274 
275  std::vector<std::map<int, int>> shared_num_points;
276  std::vector<std::map<int, float>> triangulation_angles;
277 
278  const float min_triangulation_angle_rad =
280 
281  std::string ref_image_name;
282  std::unordered_set<int> ref_image_idxs;
283 
284  struct ProblemConfig {
285  std::string ref_image_name;
286  std::vector<std::string> src_image_names;
287  };
288  std::vector<ProblemConfig> problem_configs;
289 
290  for (size_t i = 0; i < config.size(); ++i) {
291  std::string& config_line = config[i];
292  StringTrim(&config_line);
293 
294  if (config_line.empty() || config_line[0] == '#') {
295  continue;
296  }
297 
298  if (ref_image_name.empty()) {
299  ref_image_name = config_line;
300  continue;
301  }
302 
303  ref_image_idxs.insert(model.GetImageIdx(ref_image_name));
304 
305  ProblemConfig problem_config;
306  problem_config.ref_image_name = ref_image_name;
307  problem_config.src_image_names = CSVToVector<std::string>(config_line);
308  problem_configs.push_back(problem_config);
309 
310  ref_image_name.clear();
311  }
312 
313  for (const auto& problem_config : problem_configs) {
314  PatchMatch::Problem problem;
315 
316  problem.ref_image_idx = model.GetImageIdx(problem_config.ref_image_name);
317 
318  if (problem_config.src_image_names.size() == 1 &&
319  problem_config.src_image_names[0] == "__all__") {
320  // Use all images as source images.
321  problem.src_image_idxs.clear();
322  problem.src_image_idxs.reserve(model.images.size() - 1);
323  for (size_t image_idx = 0; image_idx < model.images.size(); ++image_idx) {
324  if (static_cast<int>(image_idx) != problem.ref_image_idx) {
325  problem.src_image_idxs.push_back(image_idx);
326  }
327  }
328  } else if (problem_config.src_image_names.size() == 2 &&
329  problem_config.src_image_names[0] == "__auto__") {
330  // Use maximum number of overlapping images as source images. Overlapping
331  // will be sorted based on the number of shared points to the reference
332  // image and the top ranked images are selected. Note that images are only
333  // selected if some points have a sufficient triangulation angle.
334 
335  if (shared_num_points.empty()) {
336  shared_num_points = model.ComputeSharedPoints();
337  }
338  if (triangulation_angles.empty()) {
339  const float kTriangulationAnglePercentile = 75;
340  triangulation_angles =
341  model.ComputeTriangulationAngles(kTriangulationAnglePercentile);
342  }
343 
344  const size_t max_num_src_images =
345  std::stoll(problem_config.src_image_names[1]);
346 
347  const auto& overlapping_images =
348  shared_num_points.at(problem.ref_image_idx);
349  const auto& overlapping_triangulation_angles =
350  triangulation_angles.at(problem.ref_image_idx);
351 
352  std::vector<std::pair<int, int>> src_images;
353  src_images.reserve(overlapping_images.size());
354  for (const auto& image : overlapping_images) {
355  if (overlapping_triangulation_angles.at(image.first) >=
356  min_triangulation_angle_rad) {
357  src_images.emplace_back(image.first, image.second);
358  }
359  }
360 
361  const size_t eff_max_num_src_images =
362  std::min(src_images.size(), max_num_src_images);
363 
364  std::partial_sort(src_images.begin(),
365  src_images.begin() + eff_max_num_src_images,
366  src_images.end(),
367  [](const std::pair<int, int>& image1,
368  const std::pair<int, int>& image2) {
369  return image1.second > image2.second;
370  });
371 
372  problem.src_image_idxs.reserve(eff_max_num_src_images);
373  for (size_t i = 0; i < eff_max_num_src_images; ++i) {
374  problem.src_image_idxs.push_back(src_images[i].first);
375  }
376  } else {
377  problem.src_image_idxs.reserve(problem_config.src_image_names.size());
378  for (const auto& src_image_name : problem_config.src_image_names) {
379  problem.src_image_idxs.push_back(model.GetImageIdx(src_image_name));
380  }
381  }
382 
383  if (problem.src_image_idxs.empty()) {
384  std::cout
385  << StringPrintf(
386  "WARNING: Ignoring reference image %s, because it has no "
387  "source images.",
388  problem_config.ref_image_name.c_str())
389  << std::endl;
390  } else {
391  problems_.push_back(problem);
392  }
393  }
394 
395  std::cout << StringPrintf("Configuration has %d problems...",
396  problems_.size())
397  << std::endl;
398 }
399 
400 void PatchMatchController::ReadGpuIndices() {
401  gpu_indices_ = CSVToVector<int>(options_.gpu_index);
402  if (gpu_indices_.size() == 1 && gpu_indices_[0] == -1) {
403  const int num_cuda_devices = GetNumCudaDevices();
404  CHECK_GT(num_cuda_devices, 0);
405  gpu_indices_.resize(num_cuda_devices);
406  std::iota(gpu_indices_.begin(), gpu_indices_.end(), 0);
407  }
408 }
409 
410 void PatchMatchController::ProcessProblem(const PatchMatchOptions& options,
411  const size_t problem_idx) {
412  if (IsStopped()) {
413  return;
414  }
415 
416  const auto& model = workspace_->GetModel();
417 
418  auto& problem = problems_.at(problem_idx);
419  const int gpu_index = gpu_indices_.at(thread_pool_->GetThreadIndex());
420  CHECK_GE(gpu_index, -1);
421 
422  const std::string& stereo_folder = workspace_->GetOptions().stereo_folder;
423  const std::string output_type =
424  options.geom_consistency ? "geometric" : "photometric";
425  const std::string image_name = model.GetImageName(problem.ref_image_idx);
426  const std::string file_name =
427  StringPrintf("%s.%s.bin", image_name.c_str(), output_type.c_str());
428  const std::string depth_map_path =
429  JoinPaths(workspace_path_, stereo_folder, "depth_maps", file_name);
430  const std::string normal_map_path =
431  JoinPaths(workspace_path_, stereo_folder, "normal_maps", file_name);
432  const std::string consistency_graph_path = JoinPaths(
433  workspace_path_, stereo_folder, "consistency_graphs", file_name);
434 
435  if (ExistsFile(depth_map_path) && ExistsFile(normal_map_path) &&
436  (!options.write_consistency_graph ||
437  ExistsFile(consistency_graph_path))) {
438  return;
439  }
440 
441  PrintHeading1(StringPrintf("Processing view %d / %d for %s", problem_idx + 1,
442  problems_.size(), image_name.c_str()));
443 
444  auto patch_match_options = options;
445 
446  if (patch_match_options.depth_min < 0 || patch_match_options.depth_max < 0) {
447  patch_match_options.depth_min =
448  depth_ranges_.at(problem.ref_image_idx).first;
449  patch_match_options.depth_max =
450  depth_ranges_.at(problem.ref_image_idx).second;
451  CHECK(patch_match_options.depth_min > 0 &&
452  patch_match_options.depth_max > 0)
453  << " - You must manually set the minimum and maximum depth, since no "
454  "sparse model is provided in the workspace.";
455  }
456 
457  patch_match_options.gpu_index = std::to_string(gpu_index);
458 
459  if (patch_match_options.sigma_spatial <= 0.0f) {
460  patch_match_options.sigma_spatial = patch_match_options.window_radius;
461  }
462 
463  std::vector<Image> images = model.images;
464  std::vector<DepthMap> depth_maps;
465  std::vector<NormalMap> normal_maps;
466  if (options.geom_consistency) {
467  depth_maps.resize(model.images.size());
468  normal_maps.resize(model.images.size());
469  }
470 
471  problem.images = &images;
472  problem.depth_maps = &depth_maps;
473  problem.normal_maps = &normal_maps;
474 
475  {
476  // Collect all used images in current problem.
477  std::unordered_set<int> used_image_idxs(problem.src_image_idxs.begin(),
478  problem.src_image_idxs.end());
479  used_image_idxs.insert(problem.ref_image_idx);
480 
481  patch_match_options.filter_min_num_consistent =
482  std::min(static_cast<int>(used_image_idxs.size()) - 1,
483  patch_match_options.filter_min_num_consistent);
484 
485  // Only access workspace from one thread at a time and only spawn resample
486  // threads from one master thread at a time.
487  std::unique_lock<std::mutex> lock(workspace_mutex_);
488 
489  std::cout << "Reading inputs..." << std::endl;
490  std::vector<int> src_image_idxs;
491  for (const auto image_idx : used_image_idxs) {
492  std::string image_path = workspace_->GetBitmapPath(image_idx);
493  std::string depth_path = workspace_->GetDepthMapPath(image_idx);
494  std::string normal_path = workspace_->GetNormalMapPath(image_idx);
495 
496  if (!ExistsFile(image_path) ||
497  (options.geom_consistency && !ExistsFile(depth_path)) ||
498  (options.geom_consistency && !ExistsFile(normal_path))) {
499  if (options.allow_missing_files) {
500  std::cout << StringPrintf(
501  "WARN: Skipping source image %d: %s for missing "
502  "image or depth/normal map",
503  image_idx, model.GetImageName(image_idx).c_str())
504  << std::endl;
505  continue;
506  } else {
507  std::cout
508  << StringPrintf(
509  "ERROR: Missing image or map dependency for image %d: %s",
510  image_idx, model.GetImageName(image_idx).c_str())
511  << std::endl;
512  }
513  }
514 
515  if (image_idx != problem.ref_image_idx) {
516  src_image_idxs.push_back(image_idx);
517  }
518  images.at(image_idx).SetBitmap(workspace_->GetBitmap(image_idx));
519  if (options.geom_consistency) {
520  depth_maps.at(image_idx) = workspace_->GetDepthMap(image_idx);
521  normal_maps.at(image_idx) = workspace_->GetNormalMap(image_idx);
522  }
523  }
524  problem.src_image_idxs = src_image_idxs;
525  }
526 
527  problem.Print();
528  patch_match_options.Print();
529 
530  PatchMatch patch_match(patch_match_options, problem);
531  patch_match.Run();
532 
533  std::cout << std::endl
534  << StringPrintf("Writing %s output for %s", output_type.c_str(),
535  image_name.c_str())
536  << std::endl;
537 
538  patch_match.GetDepthMap().Write(depth_map_path);
539  patch_match.GetNormalMap().Write(normal_map_path);
540  if (options.write_consistency_graph) {
541  patch_match.GetConsistencyGraph().Write(consistency_graph_path);
542  }
543 }
544 
545 } // namespace mvs
546 } // namespace colmap
std::shared_ptr< core::Tensor > image
const Timer & GetTimer() const
Definition: threading.cc:154
bool IsStopped()
Definition: threading.cc:97
void PrintMinutes() const
Definition: timer.cc:93
size_t GetHeight() const
Definition: image.h:89
size_t GetWidth() const
Definition: image.h:87
size_t GetWidth() const
Definition: mat.h:71
size_t GetHeight() const
Definition: mat.h:76
PatchMatchController(const PatchMatchOptions &options, const std::string &workspace_path, const std::string &workspace_format, const std::string &pmvs_option_name, const std::string &config_path="")
Definition: patch_match.cc:184
Mat< float > GetSelProbMap() const
Definition: patch_match.cc:174
ConsistencyGraph GetConsistencyGraph() const
Definition: patch_match.cc:178
NormalMap GetNormalMap() const
Definition: patch_match.cc:170
PatchMatch(const PatchMatchOptions &options, const Problem &problem)
Definition: patch_match.cc:48
DepthMap GetDepthMap() const
Definition: patch_match.cc:166
QTextStream & endl(QTextStream &stream)
Definition: QtCompat.h:718
void ImportPMVSWorkspace(const Workspace &workspace, const std::string &option_name)
Definition: workspace.cc:227
void PrintHeading2(const std::string &heading)
Definition: misc.cc:231
void StringTrim(std::string *str)
Definition: string.cc:188
int GetNumCudaDevices()
Definition: cuda.cc:57
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
void PrintHeading1(const std::string &heading)
Definition: misc.cc:225
std::string StringPrintf(const char *format,...)
Definition: string.cc:131
float DegToRad(const float deg)
Definition: math.h:171
std::string to_string(const T &n)
Definition: Common.h:20
#define PrintOption(option)
Definition: patch_match.cc:43
std::vector< Image > * images
Definition: patch_match.h:163
std::vector< NormalMap > * normal_maps
Definition: patch_match.h:169
std::vector< DepthMap > * depth_maps
Definition: patch_match.h:166
std::vector< int > src_image_idxs
Definition: patch_match.h:160