// 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) #ifndef COLMAP_SRC_OPTIM_BUNDLE_ADJUSTMENT_H_ #define COLMAP_SRC_OPTIM_BUNDLE_ADJUSTMENT_H_ #include #include #include #include #include "PBA/pba.h" #include "base/camera_rig.h" #include "base/reconstruction.h" #include "util/alignment.h" namespace colmap { struct BundleAdjustmentOptions { // Loss function types: Trivial (non-robust) and Cauchy (robust) loss. enum class LossFunctionType { TRIVIAL, SOFT_L1, CAUCHY }; LossFunctionType loss_function_type = LossFunctionType::TRIVIAL; // Scaling factor determines residual at which robustification takes place. double loss_function_scale = 1.0; // Whether to refine the focal length parameter group. bool refine_focal_length = true; // Whether to refine the principal point parameter group. bool refine_principal_point = false; // Whether to refine the extra parameter group. bool refine_extra_params = true; // Whether to refine the extrinsic parameter group. bool refine_extrinsics = true; // Whether to print a final summary. bool print_summary = true; // Minimum number of residuals to enable multi-threading. Note that // single-threaded is typically better for small bundle adjustment problems // due to the overhead of threading. int min_num_residuals_for_multi_threading = 50000; // Ceres-Solver options. ceres::Solver::Options solver_options; BundleAdjustmentOptions() { solver_options.function_tolerance = 0.0; solver_options.gradient_tolerance = 0.0; solver_options.parameter_tolerance = 0.0; solver_options.minimizer_progress_to_stdout = false; solver_options.max_num_iterations = 100; solver_options.max_linear_solver_iterations = 200; solver_options.max_num_consecutive_invalid_steps = 10; solver_options.max_consecutive_nonmonotonic_steps = 10; solver_options.num_threads = -1; #if CERES_VERSION_MAJOR < 2 solver_options.num_linear_solver_threads = -1; #endif // CERES_VERSION_MAJOR } // Create a new loss function based on the specified options. The caller // takes ownership of the loss function. ceres::LossFunction* CreateLossFunction() const; bool Check() const; }; // Configuration container to setup bundle adjustment problems. class BundleAdjustmentConfig { public: BundleAdjustmentConfig(); size_t NumImages() const; size_t NumPoints() const; size_t NumConstantCameras() const; size_t NumConstantPoses() const; size_t NumConstantTvecs() const; size_t NumVariablePoints() const; size_t NumConstantPoints() const; // Determine the number of residuals for the given reconstruction. The number // of residuals equals the number of observations times two. size_t NumResiduals(const Reconstruction& reconstruction) const; // Add / remove images from the configuration. void AddImage(const image_t image_id); bool HasImage(const image_t image_id) const; void RemoveImage(const image_t image_id); // Set cameras of added images as constant or variable. By default all // cameras of added images are variable. Note that the corresponding images // have to be added prior to calling these methods. void SetConstantCamera(const camera_t camera_id); void SetVariableCamera(const camera_t camera_id); bool IsConstantCamera(const camera_t camera_id) const; // Set the pose of added images as constant. The pose is defined as the // rotational and translational part of the projection matrix. void SetConstantPose(const image_t image_id); void SetVariablePose(const image_t image_id); bool HasConstantPose(const image_t image_id) const; // Set the translational part of the pose, hence the constant pose // indices may be in [0, 1, 2] and must be unique. Note that the // corresponding images have to be added prior to calling these methods. void SetConstantTvec(const image_t image_id, const std::vector& idxs); void RemoveConstantTvec(const image_t image_id); bool HasConstantTvec(const image_t image_id) const; // Add / remove points from the configuration. Note that points can either // be variable or constant but not both at the same time. void AddVariablePoint(const point3D_t point3D_id); void AddConstantPoint(const point3D_t point3D_id); bool HasPoint(const point3D_t point3D_id) const; bool HasVariablePoint(const point3D_t point3D_id) const; bool HasConstantPoint(const point3D_t point3D_id) const; void RemoveVariablePoint(const point3D_t point3D_id); void RemoveConstantPoint(const point3D_t point3D_id); // Access configuration data. const std::unordered_set& Images() const; const std::unordered_set& VariablePoints() const; const std::unordered_set& ConstantPoints() const; const std::vector& ConstantTvec(const image_t image_id) const; private: std::unordered_set constant_camera_ids_; std::unordered_set image_ids_; std::unordered_set variable_point3D_ids_; std::unordered_set constant_point3D_ids_; std::unordered_set constant_poses_; std::unordered_map> constant_tvecs_; }; // Bundle adjustment based on Ceres-Solver. Enables most flexible configurations // and provides best solution quality. class BundleAdjuster { public: BundleAdjuster(const BundleAdjustmentOptions& options, const BundleAdjustmentConfig& config); bool Solve(Reconstruction* reconstruction); // Get the Ceres solver summary for the last call to `Solve`. const ceres::Solver::Summary& Summary() const; private: void SetUp(Reconstruction* reconstruction, ceres::LossFunction* loss_function); void TearDown(Reconstruction* reconstruction); void AddImageToProblem(const image_t image_id, Reconstruction* reconstruction, ceres::LossFunction* loss_function); void AddPointToProblem(const point3D_t point3D_id, Reconstruction* reconstruction, ceres::LossFunction* loss_function); protected: void ParameterizeCameras(Reconstruction* reconstruction); void ParameterizePoints(Reconstruction* reconstruction); const BundleAdjustmentOptions options_; BundleAdjustmentConfig config_; std::unique_ptr problem_; ceres::Solver::Summary summary_; std::unordered_set camera_ids_; std::unordered_map point3D_num_observations_; }; // Bundle adjustment using PBA (GPU or CPU). Less flexible and accurate than // Ceres-Solver bundle adjustment but much faster. Only supports SimpleRadial // camera model. class ParallelBundleAdjuster { public: struct Options { // Whether to print a final summary. bool print_summary = true; // Maximum number of iterations. int max_num_iterations = 50; // Index of the GPU used for bundle adjustment. int gpu_index = -1; // Number of threads for CPU based bundle adjustment. int num_threads = -1; // Minimum number of residuals to enable multi-threading. Note that // single-threaded is typically better for small bundle adjustment problems // due to the overhead of threading. int min_num_residuals_for_multi_threading = 50000; bool Check() const; }; ParallelBundleAdjuster(const Options& options, const BundleAdjustmentOptions& ba_options, const BundleAdjustmentConfig& config); bool Solve(Reconstruction* reconstruction); // Get the Ceres solver summary for the last call to `Solve`. const ceres::Solver::Summary& Summary() const; // Check whether PBA is supported for the given reconstruction. If the // reconstruction is not supported, the PBA solver will exit ungracefully. static bool IsSupported(const BundleAdjustmentOptions& options, const Reconstruction& reconstruction); private: void SetUp(Reconstruction* reconstruction); void TearDown(Reconstruction* reconstruction); void AddImagesToProblem(Reconstruction* reconstruction); void AddPointsToProblem(Reconstruction* reconstruction); const Options options_; const BundleAdjustmentOptions ba_options_; BundleAdjustmentConfig config_; ceres::Solver::Summary summary_; size_t num_measurements_; std::vector cameras_; std::vector points3D_; std::vector measurements_; std::unordered_set camera_ids_; std::unordered_set point3D_ids_; std::vector camera_idxs_; std::vector point3D_idxs_; std::vector ordered_image_ids_; std::vector ordered_point3D_ids_; std::unordered_map image_id_to_camera_idx_; }; class RigBundleAdjuster : public BundleAdjuster { public: struct Options { // Whether to optimize the relative poses of the camera rigs. bool refine_relative_poses = true; // The maximum allowed reprojection error for an observation to be // considered in the bundle adjustment. Some observations might have large // reprojection errors due to the concatenation of the absolute and relative // rig poses, which might be different from the absolute pose of the image // in the reconstruction. double max_reproj_error = 1000.0; }; RigBundleAdjuster(const BundleAdjustmentOptions& options, const Options& rig_options, const BundleAdjustmentConfig& config); bool Solve(Reconstruction* reconstruction, std::vector* camera_rigs); private: void SetUp(Reconstruction* reconstruction, std::vector* camera_rigs, ceres::LossFunction* loss_function); void TearDown(Reconstruction* reconstruction, const std::vector& camera_rigs); void AddImageToProblem(const image_t image_id, Reconstruction* reconstruction, std::vector* camera_rigs, ceres::LossFunction* loss_function); void AddPointToProblem(const point3D_t point3D_id, Reconstruction* reconstruction, ceres::LossFunction* loss_function); void ComputeCameraRigPoses(const Reconstruction& reconstruction, const std::vector& camera_rigs); void ParameterizeCameraRigs(Reconstruction* reconstruction); const Options rig_options_; // Mapping from images to camera rigs. std::unordered_map image_id_to_camera_rig_; // Mapping from images to the absolute camera rig poses. std::unordered_map image_id_to_rig_qvec_; std::unordered_map image_id_to_rig_tvec_; // For each camera rig, the absolute camera rig poses. std::vector> camera_rig_qvecs_; std::vector> camera_rig_tvecs_; // The Quaternions added to the problem, used to set the local // parameterization once after setting up the problem. std::unordered_set parameterized_qvec_data_; }; void PrintSolverSummary(const ceres::Solver::Summary& summary); } // namespace colmap #endif // COLMAP_SRC_OPTIM_BUNDLE_ADJUSTMENT_H_