// Copyright (c) 2022, ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. // // Author: Johannes L. Schoenberger (jsch-at-demuc-dot-de) #include "mvs/model.h" #include "base/camera_models.h" #include "base/pose.h" #include "base/projection.h" #include "base/reconstruction.h" #include "base/triangulation.h" #include "util/misc.h" namespace colmap { namespace mvs { void Model::Read(const std::string& path, const std::string& format) { auto format_lower_case = format; StringToLower(&format_lower_case); if (format_lower_case == "colmap") { ReadFromCOLMAP(path); } else if (format_lower_case == "pmvs") { ReadFromPMVS(path); } else { LOG(FATAL) << "Invalid input format"; } } void Model::ReadFromCOLMAP(const std::string& path, const std::string& sparse_path, const std::string& images_path) { Reconstruction reconstruction; reconstruction.Read(JoinPaths(path, sparse_path)); images.reserve(reconstruction.NumRegImages()); std::unordered_map image_id_to_idx; for (size_t i = 0; i < reconstruction.NumRegImages(); ++i) { const auto image_id = reconstruction.RegImageIds()[i]; const auto& image = reconstruction.Image(image_id); const auto& camera = reconstruction.Camera(image.CameraId()); const std::string image_path = JoinPaths(path, images_path, image.Name()); const Eigen::Matrix K = camera.CalibrationMatrix().cast(); const Eigen::Matrix R = QuaternionToRotationMatrix(image.Qvec()).cast(); const Eigen::Vector3f T = image.Tvec().cast(); images.emplace_back(image_path, camera.Width(), camera.Height(), K.data(), R.data(), T.data()); image_id_to_idx.emplace(image_id, i); image_names_.push_back(image.Name()); image_name_to_idx_.emplace(image.Name(), i); } points.reserve(reconstruction.NumPoints3D()); for (const auto& point3D : reconstruction.Points3D()) { Point point; point.x = point3D.second.X(); point.y = point3D.second.Y(); point.z = point3D.second.Z(); point.track.reserve(point3D.second.Track().Length()); for (const auto& track_el : point3D.second.Track().Elements()) { point.track.push_back(image_id_to_idx.at(track_el.image_id)); } points.push_back(point); } } void Model::ReadFromPMVS(const std::string& path) { if (ReadFromBundlerPMVS(path)) { return; } else if (ReadFromRawPMVS(path)) { return; } else { LOG(FATAL) << "Invalid PMVS format"; } } int Model::GetImageIdx(const std::string& name) const { CHECK_GT(image_name_to_idx_.count(name), 0) << "Image with name `" << name << "` does not exist"; return image_name_to_idx_.at(name); } std::string Model::GetImageName(const int image_idx) const { CHECK_GE(image_idx, 0); CHECK_LT(image_idx, image_names_.size()); return image_names_.at(image_idx); } std::vector> Model::GetMaxOverlappingImages( const size_t num_images, const double min_triangulation_angle) const { std::vector> overlapping_images(images.size()); const float min_triangulation_angle_rad = DegToRad(min_triangulation_angle); const auto shared_num_points = ComputeSharedPoints(); const float kTriangulationAnglePercentile = 75; const auto triangulation_angles = ComputeTriangulationAngles(kTriangulationAnglePercentile); for (size_t image_idx = 0; image_idx < images.size(); ++image_idx) { const auto& shared_images = shared_num_points.at(image_idx); const auto& overlapping_triangulation_angles = triangulation_angles.at(image_idx); std::vector> ordered_images; ordered_images.reserve(shared_images.size()); for (const auto& image : shared_images) { if (overlapping_triangulation_angles.at(image.first) >= min_triangulation_angle_rad) { ordered_images.emplace_back(image.first, image.second); } } const size_t eff_num_images = std::min(ordered_images.size(), num_images); if (eff_num_images < shared_images.size()) { std::partial_sort(ordered_images.begin(), ordered_images.begin() + eff_num_images, ordered_images.end(), [](const std::pair image1, const std::pair image2) { return image1.second > image2.second; }); } else { std::sort(ordered_images.begin(), ordered_images.end(), [](const std::pair image1, const std::pair image2) { return image1.second > image2.second; }); } overlapping_images[image_idx].reserve(eff_num_images); for (size_t i = 0; i < eff_num_images; ++i) { overlapping_images[image_idx].push_back(ordered_images[i].first); } } return overlapping_images; } const std::vector>& Model::GetMaxOverlappingImagesFromPMVS() const { return pmvs_vis_dat_; } std::vector> Model::ComputeDepthRanges() const { std::vector> depths(images.size()); for (const auto& point : points) { const Eigen::Vector3f X(point.x, point.y, point.z); for (const auto& image_idx : point.track) { const auto& image = images.at(image_idx); const float depth = Eigen::Map(&image.GetR()[6]).dot(X) + image.GetT()[2]; if (depth > 0) { depths[image_idx].push_back(depth); } } } std::vector> depth_ranges(depths.size()); for (size_t image_idx = 0; image_idx < depth_ranges.size(); ++image_idx) { auto& depth_range = depth_ranges[image_idx]; auto& image_depths = depths[image_idx]; if (image_depths.empty()) { depth_range.first = -1.0f; depth_range.second = -1.0f; continue; } std::sort(image_depths.begin(), image_depths.end()); const float kMinPercentile = 0.01f; const float kMaxPercentile = 0.99f; depth_range.first = image_depths[image_depths.size() * kMinPercentile]; depth_range.second = image_depths[image_depths.size() * kMaxPercentile]; const float kStretchRatio = 0.25f; depth_range.first *= (1.0f - kStretchRatio); depth_range.second *= (1.0f + kStretchRatio); } return depth_ranges; } std::vector> Model::ComputeSharedPoints() const { std::vector> shared_points(images.size()); for (const auto& point : points) { for (size_t i = 0; i < point.track.size(); ++i) { const int image_idx1 = point.track[i]; for (size_t j = 0; j < i; ++j) { const int image_idx2 = point.track[j]; if (image_idx1 != image_idx2) { shared_points.at(image_idx1)[image_idx2] += 1; shared_points.at(image_idx2)[image_idx1] += 1; } } } } return shared_points; } std::vector> Model::ComputeTriangulationAngles( const float percentile) const { std::vector proj_centers(images.size()); for (size_t image_idx = 0; image_idx < images.size(); ++image_idx) { const auto& image = images[image_idx]; Eigen::Vector3f C; ComputeProjectionCenter(image.GetR(), image.GetT(), C.data()); proj_centers[image_idx] = C.cast(); } std::vector>> all_triangulation_angles( images.size()); for (const auto& point : points) { for (size_t i = 0; i < point.track.size(); ++i) { const int image_idx1 = point.track[i]; for (size_t j = 0; j < i; ++j) { const int image_idx2 = point.track[j]; if (image_idx1 != image_idx2) { const float angle = CalculateTriangulationAngle( proj_centers.at(image_idx1), proj_centers.at(image_idx2), Eigen::Vector3d(point.x, point.y, point.z)); all_triangulation_angles.at(image_idx1)[image_idx2].push_back(angle); all_triangulation_angles.at(image_idx2)[image_idx1].push_back(angle); } } } } std::vector> triangulation_angles(images.size()); for (size_t image_idx = 0; image_idx < all_triangulation_angles.size(); ++image_idx) { const auto& overlapping_images = all_triangulation_angles[image_idx]; for (const auto& image : overlapping_images) { triangulation_angles[image_idx].emplace( image.first, Percentile(image.second, percentile)); } } return triangulation_angles; } bool Model::ReadFromBundlerPMVS(const std::string& path) { const std::string bundle_file_path = JoinPaths(path, "bundle.rd.out"); if (!ExistsFile(bundle_file_path)) { return false; } std::ifstream file(bundle_file_path); CHECK(file.is_open()) << bundle_file_path; // Header line. std::string header; std::getline(file, header); int num_images, num_points; file >> num_images >> num_points; images.reserve(num_images); for (int image_idx = 0; image_idx < num_images; ++image_idx) { const std::string image_name = StringPrintf("%08d.jpg", image_idx); const std::string image_path = JoinPaths(path, "visualize", image_name); float K[9] = {1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f}; file >> K[0]; K[4] = K[0]; Bitmap bitmap; CHECK(bitmap.Read(image_path)); K[2] = bitmap.Width() / 2.0f; K[5] = bitmap.Height() / 2.0f; float k1, k2; file >> k1 >> k2; CHECK_EQ(k1, 0.0f); CHECK_EQ(k2, 0.0f); float R[9]; for (size_t i = 0; i < 9; ++i) { file >> R[i]; } for (size_t i = 3; i < 9; ++i) { R[i] = -R[i]; } float T[3]; file >> T[0] >> T[1] >> T[2]; T[1] = -T[1]; T[2] = -T[2]; images.emplace_back(image_path, bitmap.Width(), bitmap.Height(), K, R, T); image_names_.push_back(image_name); image_name_to_idx_.emplace(image_name, image_idx); } points.resize(num_points); for (int point_id = 0; point_id < num_points; ++point_id) { auto& point = points[point_id]; file >> point.x >> point.y >> point.z; int color[3]; file >> color[0] >> color[1] >> color[2]; int track_len; file >> track_len; point.track.resize(track_len); for (int i = 0; i < track_len; ++i) { int feature_idx; float imx, imy; file >> point.track[i] >> feature_idx >> imx >> imy; CHECK_LT(point.track[i], images.size()); } } return true; } bool Model::ReadFromRawPMVS(const std::string& path) { const std::string vis_dat_path = JoinPaths(path, "vis.dat"); if (!ExistsFile(vis_dat_path)) { return false; } for (int image_idx = 0;; ++image_idx) { const std::string image_name = StringPrintf("%08d.jpg", image_idx); const std::string image_path = JoinPaths(path, "visualize", image_name); if (!ExistsFile(image_path)) { break; } Bitmap bitmap; CHECK(bitmap.Read(image_path)); const std::string proj_matrix_path = JoinPaths(path, "txt", StringPrintf("%08d.txt", image_idx)); std::ifstream proj_matrix_file(proj_matrix_path); CHECK(proj_matrix_file.is_open()) << proj_matrix_path; std::string contour; proj_matrix_file >> contour; CHECK_EQ(contour, "CONTOUR"); Eigen::Matrix3x4d P; for (int i = 0; i < 3; ++i) { proj_matrix_file >> P(i, 0) >> P(i, 1) >> P(i, 2) >> P(i, 3); } Eigen::Matrix3d K; Eigen::Matrix3d R; Eigen::Vector3d T; DecomposeProjectionMatrix(P, &K, &R, &T); // The COLMAP patch match algorithm requires that there is no skew. K(0, 1) = 0.0f; K(1, 0) = 0.0f; K(2, 0) = 0.0f; K(2, 1) = 0.0f; K(2, 2) = 1.0f; const Eigen::Matrix K_float = K.cast(); const Eigen::Matrix R_float = R.cast(); const Eigen::Vector3f T_float = T.cast(); images.emplace_back(image_path, bitmap.Width(), bitmap.Height(), K_float.data(), R_float.data(), T_float.data()); image_names_.push_back(image_name); image_name_to_idx_.emplace(image_name, image_idx); } std::ifstream vis_dat_file(vis_dat_path); CHECK(vis_dat_file.is_open()) << vis_dat_path; std::string visdata; vis_dat_file >> visdata; CHECK_EQ(visdata, "VISDATA"); int num_images; vis_dat_file >> num_images; CHECK_GE(num_images, 0); CHECK_EQ(num_images, images.size()); pmvs_vis_dat_.resize(num_images); for (int i = 0; i < num_images; ++i) { int image_idx; vis_dat_file >> image_idx; CHECK_GE(image_idx, 0); CHECK_LT(image_idx, num_images); int num_visible_images; vis_dat_file >> num_visible_images; auto& visible_image_idxs = pmvs_vis_dat_[image_idx]; visible_image_idxs.reserve(num_visible_images); for (int j = 0; j < num_visible_images; ++j) { int visible_image_idx; vis_dat_file >> visible_image_idx; CHECK_GE(visible_image_idx, 0); CHECK_LT(visible_image_idx, num_images); if (visible_image_idx != image_idx) { visible_image_idxs.push_back(visible_image_idx); } } } return true; } } // namespace mvs } // namespace colmap