| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | #include "mvs/patch_match.h" |
| |
|
| | #include <numeric> |
| | #include <unordered_set> |
| |
|
| | #include "mvs/consistency_graph.h" |
| | #include "mvs/patch_match_cuda.h" |
| | #include "mvs/workspace.h" |
| | #include "util/math.h" |
| | #include "util/misc.h" |
| |
|
| | #define PrintOption(option) std::cout << #option ": " << option << std::endl |
| |
|
| | namespace colmap { |
| | namespace mvs { |
| |
|
| | PatchMatch::PatchMatch(const PatchMatchOptions& options, const Problem& problem) |
| | : options_(options), problem_(problem) {} |
| |
|
| | PatchMatch::~PatchMatch() {} |
| |
|
| | void PatchMatchOptions::Print() const { |
| | PrintHeading2("PatchMatchOptions"); |
| | PrintOption(max_image_size); |
| | PrintOption(gpu_index); |
| | PrintOption(depth_min); |
| | PrintOption(depth_max); |
| | PrintOption(window_radius); |
| | PrintOption(window_step); |
| | PrintOption(sigma_spatial); |
| | PrintOption(sigma_color); |
| | PrintOption(num_samples); |
| | PrintOption(ncc_sigma); |
| | PrintOption(min_triangulation_angle); |
| | PrintOption(incident_angle_sigma); |
| | PrintOption(num_iterations); |
| | PrintOption(geom_consistency); |
| | PrintOption(geom_consistency_regularizer); |
| | PrintOption(geom_consistency_max_cost); |
| | PrintOption(filter); |
| | PrintOption(filter_min_ncc); |
| | PrintOption(filter_min_triangulation_angle); |
| | PrintOption(filter_min_num_consistent); |
| | PrintOption(filter_geom_consistency_max_cost); |
| | PrintOption(write_consistency_graph); |
| | PrintOption(allow_missing_files); |
| | } |
| |
|
| | void PatchMatch::Problem::Print() const { |
| | PrintHeading2("PatchMatch::Problem"); |
| |
|
| | PrintOption(ref_image_idx); |
| |
|
| | std::cout << "src_image_idxs: "; |
| | if (!src_image_idxs.empty()) { |
| | for (size_t i = 0; i < src_image_idxs.size() - 1; ++i) { |
| | std::cout << src_image_idxs[i] << " "; |
| | } |
| | std::cout << src_image_idxs.back() << std::endl; |
| | } else { |
| | std::cout << std::endl; |
| | } |
| | } |
| |
|
| | void PatchMatch::Check() const { |
| | CHECK(options_.Check()); |
| |
|
| | CHECK(!options_.gpu_index.empty()); |
| | const std::vector<int> gpu_indices = CSVToVector<int>(options_.gpu_index); |
| | CHECK_EQ(gpu_indices.size(), 1); |
| | CHECK_GE(gpu_indices[0], -1); |
| |
|
| | CHECK_NOTNULL(problem_.images); |
| | if (options_.geom_consistency) { |
| | CHECK_NOTNULL(problem_.depth_maps); |
| | CHECK_NOTNULL(problem_.normal_maps); |
| | CHECK_EQ(problem_.depth_maps->size(), problem_.images->size()); |
| | CHECK_EQ(problem_.normal_maps->size(), problem_.images->size()); |
| | } |
| |
|
| | CHECK_GT(problem_.src_image_idxs.size(), 0); |
| |
|
| | |
| | |
| | std::set<int> unique_image_idxs(problem_.src_image_idxs.begin(), |
| | problem_.src_image_idxs.end()); |
| | unique_image_idxs.insert(problem_.ref_image_idx); |
| | CHECK_EQ(problem_.src_image_idxs.size() + 1, unique_image_idxs.size()); |
| |
|
| | |
| | for (const int image_idx : unique_image_idxs) { |
| | CHECK_GE(image_idx, 0) << image_idx; |
| | CHECK_LT(image_idx, problem_.images->size()) << image_idx; |
| |
|
| | const Image& image = problem_.images->at(image_idx); |
| | CHECK_GT(image.GetBitmap().Width(), 0) << image_idx; |
| | CHECK_GT(image.GetBitmap().Height(), 0) << image_idx; |
| | CHECK(image.GetBitmap().IsGrey()) << image_idx; |
| | CHECK_EQ(image.GetWidth(), image.GetBitmap().Width()) << image_idx; |
| | CHECK_EQ(image.GetHeight(), image.GetBitmap().Height()) << image_idx; |
| |
|
| | |
| | CHECK_LT(std::abs(image.GetK()[1] - 0.0f), 1e-6f) << image_idx; |
| | CHECK_LT(std::abs(image.GetK()[3] - 0.0f), 1e-6f) << image_idx; |
| | CHECK_LT(std::abs(image.GetK()[6] - 0.0f), 1e-6f) << image_idx; |
| | CHECK_LT(std::abs(image.GetK()[7] - 0.0f), 1e-6f) << image_idx; |
| | CHECK_LT(std::abs(image.GetK()[8] - 1.0f), 1e-6f) << image_idx; |
| |
|
| | if (options_.geom_consistency) { |
| | CHECK_LT(image_idx, problem_.depth_maps->size()) << image_idx; |
| | const DepthMap& depth_map = problem_.depth_maps->at(image_idx); |
| | CHECK_EQ(image.GetWidth(), depth_map.GetWidth()) << image_idx; |
| | CHECK_EQ(image.GetHeight(), depth_map.GetHeight()) << image_idx; |
| | } |
| | } |
| |
|
| | if (options_.geom_consistency) { |
| | const Image& ref_image = problem_.images->at(problem_.ref_image_idx); |
| | const NormalMap& ref_normal_map = |
| | problem_.normal_maps->at(problem_.ref_image_idx); |
| | CHECK_EQ(ref_image.GetWidth(), ref_normal_map.GetWidth()); |
| | CHECK_EQ(ref_image.GetHeight(), ref_normal_map.GetHeight()); |
| | } |
| | } |
| |
|
| | void PatchMatch::Run() { |
| | PrintHeading2("PatchMatch::Run"); |
| |
|
| | Check(); |
| |
|
| | patch_match_cuda_ = std::make_unique<PatchMatchCuda>(options_, problem_); |
| | patch_match_cuda_->Run(); |
| | } |
| |
|
| | DepthMap PatchMatch::GetDepthMap() const { |
| | return patch_match_cuda_->GetDepthMap(); |
| | } |
| |
|
| | NormalMap PatchMatch::GetNormalMap() const { |
| | return patch_match_cuda_->GetNormalMap(); |
| | } |
| |
|
| | Mat<float> PatchMatch::GetSelProbMap() const { |
| | return patch_match_cuda_->GetSelProbMap(); |
| | } |
| |
|
| | ConsistencyGraph PatchMatch::GetConsistencyGraph() const { |
| | const auto& ref_image = problem_.images->at(problem_.ref_image_idx); |
| | return ConsistencyGraph(ref_image.GetWidth(), ref_image.GetHeight(), |
| | patch_match_cuda_->GetConsistentImageIdxs()); |
| | } |
| |
|
| | PatchMatchController::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) |
| | : options_(options), |
| | workspace_path_(workspace_path), |
| | workspace_format_(workspace_format), |
| | pmvs_option_name_(pmvs_option_name), |
| | config_path_(config_path) { |
| | std::vector<int> gpu_indices = CSVToVector<int>(options_.gpu_index); |
| | } |
| |
|
| | void PatchMatchController::Run() { |
| | ReadWorkspace(); |
| | ReadProblems(); |
| | ReadGpuIndices(); |
| |
|
| | thread_pool_ = std::make_unique<ThreadPool>(gpu_indices_.size()); |
| |
|
| | |
| | |
| | if (options_.geom_consistency) { |
| | auto photometric_options = options_; |
| | photometric_options.geom_consistency = false; |
| | photometric_options.filter = false; |
| |
|
| | for (size_t problem_idx = 0; problem_idx < problems_.size(); |
| | ++problem_idx) { |
| | thread_pool_->AddTask(&PatchMatchController::ProcessProblem, this, |
| | photometric_options, problem_idx); |
| | } |
| |
|
| | thread_pool_->Wait(); |
| | } |
| |
|
| | for (size_t problem_idx = 0; problem_idx < problems_.size(); ++problem_idx) { |
| | thread_pool_->AddTask(&PatchMatchController::ProcessProblem, this, options_, |
| | problem_idx); |
| | } |
| |
|
| | thread_pool_->Wait(); |
| |
|
| | GetTimer().PrintMinutes(); |
| | } |
| |
|
| | void PatchMatchController::ReadWorkspace() { |
| | std::cout << "Reading workspace..." << std::endl; |
| |
|
| | Workspace::Options workspace_options; |
| |
|
| | auto workspace_format_lower_case = workspace_format_; |
| | StringToLower(&workspace_format_lower_case); |
| | if (workspace_format_lower_case == "pmvs") { |
| | workspace_options.stereo_folder = |
| | StringPrintf("stereo-%s", pmvs_option_name_.c_str()); |
| | } |
| |
|
| | workspace_options.max_image_size = options_.max_image_size; |
| | workspace_options.image_as_rgb = false; |
| | workspace_options.cache_size = options_.cache_size; |
| | workspace_options.workspace_path = workspace_path_; |
| | workspace_options.workspace_format = workspace_format_; |
| | workspace_options.input_type = options_.geom_consistency ? "photometric" : ""; |
| |
|
| | workspace_ = std::make_unique<CachedWorkspace>(workspace_options); |
| |
|
| | if (workspace_format_lower_case == "pmvs") { |
| | std::cout << StringPrintf("Importing PMVS workspace (option %s)...", |
| | pmvs_option_name_.c_str()) |
| | << std::endl; |
| | ImportPMVSWorkspace(*workspace_, pmvs_option_name_); |
| | } |
| |
|
| | depth_ranges_ = workspace_->GetModel().ComputeDepthRanges(); |
| | } |
| |
|
| | void PatchMatchController::ReadProblems() { |
| | std::cout << "Reading configuration..." << std::endl; |
| |
|
| | problems_.clear(); |
| |
|
| | const auto& model = workspace_->GetModel(); |
| |
|
| | const std::string config_path = |
| | config_path_.empty() |
| | ? JoinPaths(workspace_path_, workspace_->GetOptions().stereo_folder, |
| | "patch-match.cfg") |
| | : config_path_; |
| | std::vector<std::string> config = ReadTextFileLines(config_path); |
| |
|
| | std::vector<std::map<int, int>> shared_num_points; |
| | std::vector<std::map<int, float>> triangulation_angles; |
| |
|
| | const float min_triangulation_angle_rad = |
| | DegToRad(options_.min_triangulation_angle); |
| |
|
| | std::string ref_image_name; |
| | std::unordered_set<int> ref_image_idxs; |
| |
|
| | struct ProblemConfig { |
| | std::string ref_image_name; |
| | std::vector<std::string> src_image_names; |
| | }; |
| | std::vector<ProblemConfig> problem_configs; |
| |
|
| | for (size_t i = 0; i < config.size(); ++i) { |
| | std::string& config_line = config[i]; |
| | StringTrim(&config_line); |
| |
|
| | if (config_line.empty() || config_line[0] == '#') { |
| | continue; |
| | } |
| |
|
| | if (ref_image_name.empty()) { |
| | ref_image_name = config_line; |
| | continue; |
| | } |
| |
|
| | ref_image_idxs.insert(model.GetImageIdx(ref_image_name)); |
| |
|
| | ProblemConfig problem_config; |
| | problem_config.ref_image_name = ref_image_name; |
| | problem_config.src_image_names = CSVToVector<std::string>(config_line); |
| | problem_configs.push_back(problem_config); |
| |
|
| | ref_image_name.clear(); |
| | } |
| |
|
| | for (const auto& problem_config : problem_configs) { |
| | PatchMatch::Problem problem; |
| |
|
| | problem.ref_image_idx = model.GetImageIdx(problem_config.ref_image_name); |
| |
|
| | if (problem_config.src_image_names.size() == 1 && |
| | problem_config.src_image_names[0] == "__all__") { |
| | |
| | problem.src_image_idxs.clear(); |
| | problem.src_image_idxs.reserve(model.images.size() - 1); |
| | for (size_t image_idx = 0; image_idx < model.images.size(); ++image_idx) { |
| | if (static_cast<int>(image_idx) != problem.ref_image_idx) { |
| | problem.src_image_idxs.push_back(image_idx); |
| | } |
| | } |
| | } else if (problem_config.src_image_names.size() == 2 && |
| | problem_config.src_image_names[0] == "__auto__") { |
| | |
| | |
| | |
| | |
| |
|
| | if (shared_num_points.empty()) { |
| | shared_num_points = model.ComputeSharedPoints(); |
| | } |
| | if (triangulation_angles.empty()) { |
| | const float kTriangulationAnglePercentile = 75; |
| | triangulation_angles = |
| | model.ComputeTriangulationAngles(kTriangulationAnglePercentile); |
| | } |
| |
|
| | const size_t max_num_src_images = |
| | std::stoll(problem_config.src_image_names[1]); |
| |
|
| | const auto& overlapping_images = |
| | shared_num_points.at(problem.ref_image_idx); |
| | const auto& overlapping_triangulation_angles = |
| | triangulation_angles.at(problem.ref_image_idx); |
| |
|
| | std::vector<std::pair<int, int>> src_images; |
| | src_images.reserve(overlapping_images.size()); |
| | for (const auto& image : overlapping_images) { |
| | if (overlapping_triangulation_angles.at(image.first) >= |
| | min_triangulation_angle_rad) { |
| | src_images.emplace_back(image.first, image.second); |
| | } |
| | } |
| |
|
| | const size_t eff_max_num_src_images = |
| | std::min(src_images.size(), max_num_src_images); |
| |
|
| | std::partial_sort(src_images.begin(), |
| | src_images.begin() + eff_max_num_src_images, |
| | src_images.end(), |
| | [](const std::pair<int, int>& image1, |
| | const std::pair<int, int>& image2) { |
| | return image1.second > image2.second; |
| | }); |
| |
|
| | problem.src_image_idxs.reserve(eff_max_num_src_images); |
| | for (size_t i = 0; i < eff_max_num_src_images; ++i) { |
| | problem.src_image_idxs.push_back(src_images[i].first); |
| | } |
| | } else { |
| | problem.src_image_idxs.reserve(problem_config.src_image_names.size()); |
| | for (const auto& src_image_name : problem_config.src_image_names) { |
| | problem.src_image_idxs.push_back(model.GetImageIdx(src_image_name)); |
| | } |
| | } |
| |
|
| | if (problem.src_image_idxs.empty()) { |
| | std::cout |
| | << StringPrintf( |
| | "WARNING: Ignoring reference image %s, because it has no " |
| | "source images.", |
| | problem_config.ref_image_name.c_str()) |
| | << std::endl; |
| | } else { |
| | problems_.push_back(problem); |
| | } |
| | } |
| |
|
| | std::cout << StringPrintf("Configuration has %d problems...", |
| | problems_.size()) |
| | << std::endl; |
| | } |
| |
|
| | void PatchMatchController::ReadGpuIndices() { |
| | gpu_indices_ = CSVToVector<int>(options_.gpu_index); |
| | if (gpu_indices_.size() == 1 && gpu_indices_[0] == -1) { |
| | const int num_cuda_devices = GetNumCudaDevices(); |
| | CHECK_GT(num_cuda_devices, 0); |
| | gpu_indices_.resize(num_cuda_devices); |
| | std::iota(gpu_indices_.begin(), gpu_indices_.end(), 0); |
| | } |
| | } |
| |
|
| | void PatchMatchController::ProcessProblem(const PatchMatchOptions& options, |
| | const size_t problem_idx) { |
| | if (IsStopped()) { |
| | return; |
| | } |
| |
|
| | const auto& model = workspace_->GetModel(); |
| |
|
| | auto& problem = problems_.at(problem_idx); |
| | const int gpu_index = gpu_indices_.at(thread_pool_->GetThreadIndex()); |
| | CHECK_GE(gpu_index, -1); |
| |
|
| | const std::string& stereo_folder = workspace_->GetOptions().stereo_folder; |
| | const std::string output_type = |
| | options.geom_consistency ? "geometric" : "photometric"; |
| | const std::string image_name = model.GetImageName(problem.ref_image_idx); |
| | const std::string file_name = |
| | StringPrintf("%s.%s.bin", image_name.c_str(), output_type.c_str()); |
| | const std::string depth_map_path = |
| | JoinPaths(workspace_path_, stereo_folder, "depth_maps", file_name); |
| | const std::string normal_map_path = |
| | JoinPaths(workspace_path_, stereo_folder, "normal_maps", file_name); |
| | const std::string consistency_graph_path = JoinPaths( |
| | workspace_path_, stereo_folder, "consistency_graphs", file_name); |
| |
|
| | if (ExistsFile(depth_map_path) && ExistsFile(normal_map_path) && |
| | (!options.write_consistency_graph || |
| | ExistsFile(consistency_graph_path))) { |
| | return; |
| | } |
| |
|
| | PrintHeading1(StringPrintf("Processing view %d / %d for %s", problem_idx + 1, |
| | problems_.size(), image_name.c_str())); |
| |
|
| | auto patch_match_options = options; |
| |
|
| | if (patch_match_options.depth_min < 0 || patch_match_options.depth_max < 0) { |
| | patch_match_options.depth_min = |
| | depth_ranges_.at(problem.ref_image_idx).first; |
| | patch_match_options.depth_max = |
| | depth_ranges_.at(problem.ref_image_idx).second; |
| | CHECK(patch_match_options.depth_min > 0 && |
| | patch_match_options.depth_max > 0) |
| | << " - You must manually set the minimum and maximum depth, since no " |
| | "sparse model is provided in the workspace."; |
| | } |
| |
|
| | patch_match_options.gpu_index = std::to_string(gpu_index); |
| |
|
| | if (patch_match_options.sigma_spatial <= 0.0f) { |
| | patch_match_options.sigma_spatial = patch_match_options.window_radius; |
| | } |
| |
|
| | std::vector<Image> images = model.images; |
| | std::vector<DepthMap> depth_maps; |
| | std::vector<NormalMap> normal_maps; |
| | if (options.geom_consistency) { |
| | depth_maps.resize(model.images.size()); |
| | normal_maps.resize(model.images.size()); |
| | } |
| |
|
| | problem.images = &images; |
| | problem.depth_maps = &depth_maps; |
| | problem.normal_maps = &normal_maps; |
| |
|
| | { |
| | |
| | std::unordered_set<int> used_image_idxs(problem.src_image_idxs.begin(), |
| | problem.src_image_idxs.end()); |
| | used_image_idxs.insert(problem.ref_image_idx); |
| |
|
| | patch_match_options.filter_min_num_consistent = |
| | std::min(static_cast<int>(used_image_idxs.size()) - 1, |
| | patch_match_options.filter_min_num_consistent); |
| |
|
| | |
| | |
| | std::unique_lock<std::mutex> lock(workspace_mutex_); |
| |
|
| | std::cout << "Reading inputs..." << std::endl; |
| | std::vector<int> src_image_idxs; |
| | for (const auto image_idx : used_image_idxs) { |
| | std::string image_path = workspace_->GetBitmapPath(image_idx); |
| | std::string depth_path = workspace_->GetDepthMapPath(image_idx); |
| | std::string normal_path = workspace_->GetNormalMapPath(image_idx); |
| |
|
| | if (!ExistsFile(image_path) || |
| | (options.geom_consistency && !ExistsFile(depth_path)) || |
| | (options.geom_consistency && !ExistsFile(normal_path))) { |
| | if (options.allow_missing_files) { |
| | std::cout << StringPrintf( |
| | "WARN: Skipping source image %d: %s for missing " |
| | "image or depth/normal map", |
| | image_idx, model.GetImageName(image_idx).c_str()) |
| | << std::endl; |
| | continue; |
| | } else { |
| | std::cout |
| | << StringPrintf( |
| | "ERROR: Missing image or map dependency for image %d: %s", |
| | image_idx, model.GetImageName(image_idx).c_str()) |
| | << std::endl; |
| | } |
| | } |
| |
|
| | if (image_idx != problem.ref_image_idx) { |
| | src_image_idxs.push_back(image_idx); |
| | } |
| | images.at(image_idx).SetBitmap(workspace_->GetBitmap(image_idx)); |
| | if (options.geom_consistency) { |
| | depth_maps.at(image_idx) = workspace_->GetDepthMap(image_idx); |
| | normal_maps.at(image_idx) = workspace_->GetNormalMap(image_idx); |
| | } |
| | } |
| | problem.src_image_idxs = src_image_idxs; |
| | } |
| |
|
| | problem.Print(); |
| | patch_match_options.Print(); |
| |
|
| | PatchMatch patch_match(patch_match_options, problem); |
| | patch_match.Run(); |
| |
|
| | std::cout << std::endl |
| | << StringPrintf("Writing %s output for %s", output_type.c_str(), |
| | image_name.c_str()) |
| | << std::endl; |
| |
|
| | patch_match.GetDepthMap().Write(depth_map_path); |
| | patch_match.GetNormalMap().Write(normal_map_path); |
| | if (options.write_consistency_graph) { |
| | patch_match.GetConsistencyGraph().Write(consistency_graph_path); |
| | } |
| | } |
| |
|
| | } |
| | } |
| |
|