35 #include <unordered_set>
43 #define PrintOption(option) std::cout << #option ": " << option << std::endl
49 : options_(options), problem_(problem) {}
85 std::cout <<
"src_image_idxs: ";
97 CHECK(options_.
Check());
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);
104 CHECK_NOTNULL(problem_.
images);
119 CHECK_EQ(problem_.
src_image_idxs.size() + 1, unique_image_idxs.size());
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;
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;
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;
141 CHECK_LT(image_idx, problem_.
depth_maps->size()) << image_idx;
143 CHECK_EQ(
image.GetWidth(), depth_map.
GetWidth()) << image_idx;
163 patch_match_cuda_->Run();
167 return patch_match_cuda_->GetDepthMap();
171 return patch_match_cuda_->GetNormalMap();
175 return patch_match_cuda_->GetSelProbMap();
181 patch_match_cuda_->GetConsistentImageIdxs());
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)
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);
197 void PatchMatchController::Run() {
202 thread_pool_.reset(
new ThreadPool(gpu_indices_.size()));
207 auto photometric_options = options_;
209 photometric_options.filter =
false;
211 for (
size_t problem_idx = 0; problem_idx < problems_.size();
213 thread_pool_->AddTask(&PatchMatchController::ProcessProblem,
this,
214 photometric_options, problem_idx);
217 thread_pool_->Wait();
220 for (
size_t problem_idx = 0; problem_idx < problems_.size(); ++problem_idx) {
221 thread_pool_->AddTask(&PatchMatchController::ProcessProblem,
this, options_,
225 thread_pool_->Wait();
230 void PatchMatchController::ReadWorkspace() {
231 std::cout <<
"Reading workspace..." <<
std::endl;
233 Workspace::Options workspace_options;
235 auto workspace_format_lower_case = workspace_format_;
237 if (workspace_format_lower_case ==
"pmvs") {
238 workspace_options.stereo_folder =
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" :
"";
249 workspace_.reset(
new CachedWorkspace(workspace_options));
251 if (workspace_format_lower_case ==
"pmvs") {
252 std::cout <<
StringPrintf(
"Importing PMVS workspace (option %s)...",
253 pmvs_option_name_.c_str())
258 depth_ranges_ = workspace_->GetModel().ComputeDepthRanges();
261 void PatchMatchController::ReadProblems() {
262 std::cout <<
"Reading configuration..." <<
std::endl;
266 const auto& model = workspace_->GetModel();
268 const std::string config_path =
270 ?
JoinPaths(workspace_path_, workspace_->GetOptions().stereo_folder,
275 std::vector<std::map<int, int>> shared_num_points;
276 std::vector<std::map<int, float>> triangulation_angles;
278 const float min_triangulation_angle_rad =
281 std::string ref_image_name;
282 std::unordered_set<int> ref_image_idxs;
284 struct ProblemConfig {
285 std::string ref_image_name;
286 std::vector<std::string> src_image_names;
288 std::vector<ProblemConfig> problem_configs;
290 for (
size_t i = 0; i < config.size(); ++i) {
291 std::string& config_line = config[i];
294 if (config_line.empty() || config_line[0] ==
'#') {
298 if (ref_image_name.empty()) {
299 ref_image_name = config_line;
303 ref_image_idxs.insert(model.GetImageIdx(ref_image_name));
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);
310 ref_image_name.clear();
313 for (
const auto& problem_config : problem_configs) {
314 PatchMatch::Problem problem;
316 problem.ref_image_idx = model.GetImageIdx(problem_config.ref_image_name);
318 if (problem_config.src_image_names.size() == 1 &&
319 problem_config.src_image_names[0] ==
"__all__") {
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);
328 }
else if (problem_config.src_image_names.size() == 2 &&
329 problem_config.src_image_names[0] ==
"__auto__") {
335 if (shared_num_points.empty()) {
336 shared_num_points = model.ComputeSharedPoints();
338 if (triangulation_angles.empty()) {
339 const float kTriangulationAnglePercentile = 75;
340 triangulation_angles =
341 model.ComputeTriangulationAngles(kTriangulationAnglePercentile);
344 const size_t max_num_src_images =
345 std::stoll(problem_config.src_image_names[1]);
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);
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);
361 const size_t eff_max_num_src_images =
362 std::min(src_images.size(), max_num_src_images);
364 std::partial_sort(src_images.begin(),
365 src_images.begin() + eff_max_num_src_images,
367 [](
const std::pair<int, int>& image1,
368 const std::pair<int, int>& image2) {
369 return image1.second > image2.second;
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);
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));
383 if (problem.src_image_idxs.empty()) {
386 "WARNING: Ignoring reference image %s, because it has no "
388 problem_config.ref_image_name.c_str())
391 problems_.push_back(problem);
395 std::cout <<
StringPrintf(
"Configuration has %d problems...",
400 void PatchMatchController::ReadGpuIndices() {
401 gpu_indices_ = CSVToVector<int>(options_.
gpu_index);
402 if (gpu_indices_.size() == 1 && gpu_indices_[0] == -1) {
404 CHECK_GT(num_cuda_devices, 0);
405 gpu_indices_.resize(num_cuda_devices);
406 std::iota(gpu_indices_.begin(), gpu_indices_.end(), 0);
410 void PatchMatchController::ProcessProblem(
const PatchMatchOptions& options,
411 const size_t problem_idx) {
416 const auto& model = workspace_->GetModel();
418 auto& problem = problems_.at(problem_idx);
419 const int gpu_index = gpu_indices_.at(thread_pool_->GetThreadIndex());
420 CHECK_GE(gpu_index, -1);
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);
436 (!options.write_consistency_graph ||
442 problems_.size(), image_name.c_str()));
444 auto patch_match_options = options;
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.";
459 if (patch_match_options.sigma_spatial <= 0.0f) {
460 patch_match_options.sigma_spatial = patch_match_options.window_radius;
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());
471 problem.images = &images;
472 problem.depth_maps = &depth_maps;
473 problem.normal_maps = &normal_maps;
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);
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);
487 std::unique_lock<std::mutex> lock(workspace_mutex_);
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);
497 (options.geom_consistency && !
ExistsFile(depth_path)) ||
498 (options.geom_consistency && !
ExistsFile(normal_path))) {
499 if (options.allow_missing_files) {
501 "WARN: Skipping source image %d: %s for missing "
502 "image or depth/normal map",
503 image_idx, model.GetImageName(image_idx).c_str())
509 "ERROR: Missing image or map dependency for image %d: %s",
510 image_idx, model.GetImageName(image_idx).c_str())
515 if (image_idx != problem.ref_image_idx) {
516 src_image_idxs.push_back(image_idx);
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);
524 problem.src_image_idxs = src_image_idxs;
528 patch_match_options.Print();
530 PatchMatch patch_match(patch_match_options, problem);
534 <<
StringPrintf(
"Writing %s output for %s", output_type.c_str(),
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);
std::shared_ptr< core::Tensor > image
const Timer & GetTimer() const
void PrintMinutes() const
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="")
Mat< float > GetSelProbMap() const
ConsistencyGraph GetConsistencyGraph() const
NormalMap GetNormalMap() const
PatchMatch(const PatchMatchOptions &options, const Problem &problem)
DepthMap GetDepthMap() const
QTextStream & endl(QTextStream &stream)
void ImportPMVSWorkspace(const Workspace &workspace, const std::string &option_name)
void PrintHeading2(const std::string &heading)
void StringTrim(std::string *str)
void StringToLower(std::string *str)
std::vector< std::string > ReadTextFileLines(const std::string &path)
bool ExistsFile(const std::string &path)
std::string JoinPaths(T const &... paths)
void PrintHeading1(const std::string &heading)
std::string StringPrintf(const char *format,...)
float DegToRad(const float deg)
std::string to_string(const T &n)
#define PrintOption(option)
int filter_min_num_consistent
double min_triangulation_angle
double geom_consistency_max_cost
double filter_geom_consistency_max_cost
double geom_consistency_regularizer
double filter_min_triangulation_angle
bool write_consistency_graph
double incident_angle_sigma
std::vector< Image > * images
std::vector< NormalMap > * normal_maps
std::vector< DepthMap > * depth_maps
std::vector< int > src_image_idxs