From 9bc9e20bc3895eb27f21d7a36252af10bd597ff1 Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Mon, 8 Apr 2024 20:53:16 +0200 Subject: [PATCH 01/42] remove unused teardown for raw bundle adjustment. add some getters. --- src/colmap/estimators/bundle_adjustment.cc | 26 +++++++++++++++++----- src/colmap/estimators/bundle_adjustment.h | 15 +++++++++---- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/src/colmap/estimators/bundle_adjustment.cc b/src/colmap/estimators/bundle_adjustment.cc index b4fd3b4977..b81fba2eee 100644 --- a/src/colmap/estimators/bundle_adjustment.cc +++ b/src/colmap/estimators/bundle_adjustment.cc @@ -194,6 +194,10 @@ bool BundleAdjustmentConfig::HasConstantCamPositions( constant_cam_positions_.end(); } +const std::unordered_set BundleAdjustmentConfig::ConstantIntrinsics() const { + return constant_intrinsics_; +} + const std::unordered_set& BundleAdjustmentConfig::Images() const { return image_ids_; } @@ -208,6 +212,10 @@ const std::unordered_set& BundleAdjustmentConfig::ConstantPoints() return constant_point3D_ids_; } +const std::unordered_set& BundleAdjustmentConfig::ConstantCamPoses() const { + return constant_cam_poses_; +} + const std::vector& BundleAdjustmentConfig::ConstantCamPositions( const image_t image_id) const { return constant_cam_positions_.at(image_id); @@ -312,11 +320,21 @@ bool BundleAdjuster::Solve(Reconstruction* reconstruction) { PrintSolverSummary(summary_, "Bundle adjustment report"); } - TearDown(reconstruction); - return true; } +const BundleAdjustmentOptions& BundleAdjuster::Options() const { + return options_; +} + +const BundleAdjustmentConfig& BundleAdjuster::Config() const { + return config_; +} + +const std::unique_ptr& BundleAdjuster::Problem() const { + return *problem_; +} + const ceres::Solver::Summary& BundleAdjuster::Summary() const { return summary_; } @@ -339,10 +357,6 @@ void BundleAdjuster::SetUp(Reconstruction* reconstruction, ParameterizePoints(reconstruction); } -void BundleAdjuster::TearDown(Reconstruction*) { - // Nothing to do -} - void BundleAdjuster::AddImageToProblem(const image_t image_id, Reconstruction* reconstruction, ceres::LossFunction* loss_function) { diff --git a/src/colmap/estimators/bundle_adjustment.h b/src/colmap/estimators/bundle_adjustment.h index 3a6c62dd1d..3f0b0d1e3a 100644 --- a/src/colmap/estimators/bundle_adjustment.h +++ b/src/colmap/estimators/bundle_adjustment.h @@ -147,9 +147,11 @@ class BundleAdjustmentConfig { void RemoveConstantPoint(point3D_t point3D_id); // Access configuration data. + const std::unordered_set ConstantIntrinsics() const; const std::unordered_set& Images() const; const std::unordered_set& VariablePoints() const; const std::unordered_set& ConstantPoints() const; + const std::unordered_set& ConstantCamPoses() const; const std::vector& ConstantCamPositions(image_t image_id) const; private: @@ -170,14 +172,19 @@ class BundleAdjuster { bool Solve(Reconstruction* reconstruction); + // Set up the problem + void SetUp(Reconstruction* reconstruction, + ceres::LossFunction* loss_function); + + // Getter functions below + const BundleAdjustmentOptions& Options() const; + const BundleAdjustmentConfig& Config() const; + // Get the Ceres problem after the last call to "set_up" + const ceres::Problem& Problem() const; // 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(image_t image_id, Reconstruction* reconstruction, ceres::LossFunction* loss_function); From 9e72fb07e8364fa19f1082b97aa5ac71141f1790 Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Mon, 8 Apr 2024 20:57:44 +0200 Subject: [PATCH 02/42] remove unused line. --- src/colmap/estimators/bundle_adjustment.cc | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/colmap/estimators/bundle_adjustment.cc b/src/colmap/estimators/bundle_adjustment.cc index b81fba2eee..cd14f79664 100644 --- a/src/colmap/estimators/bundle_adjustment.cc +++ b/src/colmap/estimators/bundle_adjustment.cc @@ -553,8 +553,6 @@ bool RigBundleAdjuster::Solve(Reconstruction* reconstruction, } } - problem_ = std::make_unique(); - ceres::Problem::Options problem_options; problem_options.loss_function_ownership = ceres::DO_NOT_TAKE_OWNERSHIP; problem_ = std::make_unique(problem_options); From ad5dccb315e7de2295a307ee1a0ef980a5148c74 Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Mon, 8 Apr 2024 21:30:00 +0200 Subject: [PATCH 03/42] restructure bundle adjustment. --- src/colmap/estimators/bundle_adjustment.cc | 173 ++++++++++----------- src/colmap/estimators/bundle_adjustment.h | 11 +- 2 files changed, 86 insertions(+), 98 deletions(-) diff --git a/src/colmap/estimators/bundle_adjustment.cc b/src/colmap/estimators/bundle_adjustment.cc index cd14f79664..ec7eab9759 100644 --- a/src/colmap/estimators/bundle_adjustment.cc +++ b/src/colmap/estimators/bundle_adjustment.cc @@ -264,55 +264,15 @@ BundleAdjuster::BundleAdjuster(const BundleAdjustmentOptions& options, } bool BundleAdjuster::Solve(Reconstruction* reconstruction) { - THROW_CHECK_NOTNULL(reconstruction); - THROW_CHECK(!problem_) << "Cannot use the same BundleAdjuster multiple times"; - - ceres::Problem::Options problem_options; - problem_options.loss_function_ownership = ceres::DO_NOT_TAKE_OWNERSHIP; - problem_ = std::make_unique(problem_options); - const auto loss_function = std::unique_ptr(options_.CreateLossFunction()); - SetUp(reconstruction, loss_function.get()); + SetUpProblem(reconstruction, loss_function.get()); if (problem_->NumResiduals() == 0) { return false; } - ceres::Solver::Options solver_options = options_.solver_options; - const bool has_sparse = - solver_options.sparse_linear_algebra_library_type != ceres::NO_SPARSE; - - // Empirical choice. - const size_t kMaxNumImagesDirectDenseSolver = 50; - const size_t kMaxNumImagesDirectSparseSolver = 1000; - const size_t num_images = config_.NumImages(); - if (num_images <= kMaxNumImagesDirectDenseSolver) { - solver_options.linear_solver_type = ceres::DENSE_SCHUR; - } else if (num_images <= kMaxNumImagesDirectSparseSolver && has_sparse) { - solver_options.linear_solver_type = ceres::SPARSE_SCHUR; - } else { // Indirect sparse (preconditioned CG) solver. - solver_options.linear_solver_type = ceres::ITERATIVE_SCHUR; - solver_options.preconditioner_type = ceres::SCHUR_JACOBI; - } - - if (problem_->NumResiduals() < - options_.min_num_residuals_for_multi_threading) { - solver_options.num_threads = 1; -#if CERES_VERSION_MAJOR < 2 - solver_options.num_linear_solver_threads = 1; -#endif // CERES_VERSION_MAJOR - } else { - solver_options.num_threads = - GetEffectiveNumThreads(solver_options.num_threads); -#if CERES_VERSION_MAJOR < 2 - solver_options.num_linear_solver_threads = - GetEffectiveNumThreads(solver_options.num_linear_solver_threads); -#endif // CERES_VERSION_MAJOR - } - - std::string solver_error; - THROW_CHECK(solver_options.IsValid(&solver_error)) << solver_error; + ceres::Solver::Options solver_options = SetUpSolverOptions(*problem_, options_.solver_options); ceres::Solve(solver_options, problem_.get(), &summary_); @@ -331,7 +291,7 @@ const BundleAdjustmentConfig& BundleAdjuster::Config() const { return config_; } -const std::unique_ptr& BundleAdjuster::Problem() const { +const ceres::Problem& BundleAdjuster::Problem() const { return *problem_; } @@ -339,8 +299,17 @@ const ceres::Solver::Summary& BundleAdjuster::Summary() const { return summary_; } -void BundleAdjuster::SetUp(Reconstruction* reconstruction, +void BundleAdjuster::SetUpProblem(Reconstruction* reconstruction, ceres::LossFunction* loss_function) { + THROW_CHECK_NOTNULL(reconstruction); + THROW_CHECK(!problem_) << "Cannot set up problem from the same BundleAdjuster multiple times"; + + // Initialize an empty problem + ceres::Problem::Options problem_options; + problem_options.loss_function_ownership = ceres::DO_NOT_TAKE_OWNERSHIP; + problem_ = std::make_unique(problem_options); + + // Set up problem // Warning: AddPointsToProblem assumes that AddImageToProblem is called first. // Do not change order of instructions! for (const image_t image_id : config_.Images()) { @@ -357,6 +326,45 @@ void BundleAdjuster::SetUp(Reconstruction* reconstruction, ParameterizePoints(reconstruction); } +ceres::Solver::Options BundleAdjuster::SetUpSolverOptions(const ceres::Problem& problem, + const ceres::Solver::Options& input_solver_options) const { + ceres::Solver::Options solver_options = input_solver_options; + const bool has_sparse = + solver_options.sparse_linear_algebra_library_type != ceres::NO_SPARSE; + + // Empirical choice. + const size_t kMaxNumImagesDirectDenseSolver = 50; + const size_t kMaxNumImagesDirectSparseSolver = 1000; + const size_t num_images = config_.NumImages(); + if (num_images <= kMaxNumImagesDirectDenseSolver) { + solver_options.linear_solver_type = ceres::DENSE_SCHUR; + } else if (num_images <= kMaxNumImagesDirectSparseSolver && has_sparse) { + solver_options.linear_solver_type = ceres::SPARSE_SCHUR; + } else { // Indirect sparse (preconditioned CG) solver. + solver_options.linear_solver_type = ceres::ITERATIVE_SCHUR; + solver_options.preconditioner_type = ceres::SCHUR_JACOBI; + } + + if (problem.NumResiduals() < + options_.min_num_residuals_for_multi_threading) { + solver_options.num_threads = 1; +#if CERES_VERSION_MAJOR < 2 + solver_options.num_linear_solver_threads = 1; +#endif // CERES_VERSION_MAJOR + } else { + solver_options.num_threads = + GetEffectiveNumThreads(solver_options.num_threads); +#if CERES_VERSION_MAJOR < 2 + solver_options.num_linear_solver_threads = + GetEffectiveNumThreads(solver_options.num_linear_solver_threads); +#endif // CERES_VERSION_MAJOR + } + + std::string solver_error; + THROW_CHECK(solver_options.IsValid(&solver_error)) << solver_error; + return solver_options; +} + void BundleAdjuster::AddImageToProblem(const image_t image_id, Reconstruction* reconstruction, ceres::LossFunction* loss_function) { @@ -530,9 +538,33 @@ RigBundleAdjuster::RigBundleAdjuster(const BundleAdjustmentOptions& options, bool RigBundleAdjuster::Solve(Reconstruction* reconstruction, std::vector* camera_rigs) { + const auto loss_function = + std::unique_ptr(options_.CreateLossFunction()); + SetUpProblem(reconstruction, camera_rigs, loss_function.get()); + + if (problem_->NumResiduals() == 0) { + return false; + } + + ceres::Solver::Options solver_options = SetUpSolverOptions(*problem_, options_.solver_options); + + ceres::Solve(solver_options, problem_.get(), &summary_); + + if (options_.print_summary || VLOG_IS_ON(1)) { + PrintSolverSummary(summary_, "Rig Bundle adjustment report"); + } + + TearDown(reconstruction, *camera_rigs); + + return true; +} + +void RigBundleAdjuster::SetUpProblem(Reconstruction* reconstruction, + std::vector* camera_rigs, + ceres::LossFunction* loss_function) { THROW_CHECK_NOTNULL(reconstruction); THROW_CHECK_NOTNULL(camera_rigs); - THROW_CHECK(!problem_) << "Cannot use the same BundleAdjuster multiple times"; + THROW_CHECK(!problem_) << "Cannot set up problem from the same BundleAdjuster multiple times"; // Check the validity of the provided camera rigs. std::unordered_set rig_camera_ids; @@ -553,59 +585,12 @@ bool RigBundleAdjuster::Solve(Reconstruction* reconstruction, } } + // Initialize an empty problem ceres::Problem::Options problem_options; problem_options.loss_function_ownership = ceres::DO_NOT_TAKE_OWNERSHIP; problem_ = std::make_unique(problem_options); - const auto loss_function = - std::unique_ptr(options_.CreateLossFunction()); - SetUp(reconstruction, camera_rigs, loss_function.get()); - - if (problem_->NumResiduals() == 0) { - return false; - } - - ceres::Solver::Options solver_options = options_.solver_options; - const bool has_sparse = - solver_options.sparse_linear_algebra_library_type != ceres::NO_SPARSE; - - // Empirical choice. - const size_t kMaxNumImagesDirectDenseSolver = 50; - const size_t kMaxNumImagesDirectSparseSolver = 1000; - const size_t num_images = config_.NumImages(); - if (num_images <= kMaxNumImagesDirectDenseSolver) { - solver_options.linear_solver_type = ceres::DENSE_SCHUR; - } else if (num_images <= kMaxNumImagesDirectSparseSolver && has_sparse) { - solver_options.linear_solver_type = ceres::SPARSE_SCHUR; - } else { // Indirect sparse (preconditioned CG) solver. - solver_options.linear_solver_type = ceres::ITERATIVE_SCHUR; - solver_options.preconditioner_type = ceres::SCHUR_JACOBI; - } - - solver_options.num_threads = - GetEffectiveNumThreads(solver_options.num_threads); -#if CERES_VERSION_MAJOR < 2 - solver_options.num_linear_solver_threads = - GetEffectiveNumThreads(solver_options.num_linear_solver_threads); -#endif // CERES_VERSION_MAJOR - - std::string solver_error; - THROW_CHECK(solver_options.IsValid(&solver_error)) << solver_error; - - ceres::Solve(solver_options, problem_.get(), &summary_); - - if (options_.print_summary || VLOG_IS_ON(1)) { - PrintSolverSummary(summary_, "Rig Bundle adjustment report"); - } - - TearDown(reconstruction, *camera_rigs); - - return true; -} - -void RigBundleAdjuster::SetUp(Reconstruction* reconstruction, - std::vector* camera_rigs, - ceres::LossFunction* loss_function) { + // Set up problem ComputeCameraRigPoses(*reconstruction, *camera_rigs); for (const image_t image_id : config_.Images()) { diff --git a/src/colmap/estimators/bundle_adjustment.h b/src/colmap/estimators/bundle_adjustment.h index 3f0b0d1e3a..ea29d40288 100644 --- a/src/colmap/estimators/bundle_adjustment.h +++ b/src/colmap/estimators/bundle_adjustment.h @@ -173,8 +173,10 @@ class BundleAdjuster { bool Solve(Reconstruction* reconstruction); // Set up the problem - void SetUp(Reconstruction* reconstruction, - ceres::LossFunction* loss_function); + void SetUpProblem(Reconstruction* reconstruction, + ceres::LossFunction* loss_function); + ceres::Solver::Options SetUpSolverOptions(const ceres::Problem& problem, + const ceres::Solver::Options& input_solver_options) const; // Getter functions below const BundleAdjustmentOptions& Options() const; @@ -226,13 +228,14 @@ class RigBundleAdjuster : public BundleAdjuster { bool Solve(Reconstruction* reconstruction, std::vector* camera_rigs); - private: - void SetUp(Reconstruction* reconstruction, + void SetUpProblem(Reconstruction* reconstruction, std::vector* camera_rigs, ceres::LossFunction* loss_function); + void TearDown(Reconstruction* reconstruction, const std::vector& camera_rigs); + private: void AddImageToProblem(image_t image_id, Reconstruction* reconstruction, std::vector* camera_rigs, From 3f5ec5cea629ba739174b7ebfdb9c06c9ab721b4 Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Mon, 8 Apr 2024 21:43:06 +0200 Subject: [PATCH 04/42] add return value (ceres::Problem) for SetUpProblem. --- src/colmap/estimators/bundle_adjustment.cc | 6 ++++-- src/colmap/estimators/bundle_adjustment.h | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/colmap/estimators/bundle_adjustment.cc b/src/colmap/estimators/bundle_adjustment.cc index ec7eab9759..65111c8389 100644 --- a/src/colmap/estimators/bundle_adjustment.cc +++ b/src/colmap/estimators/bundle_adjustment.cc @@ -299,7 +299,7 @@ const ceres::Solver::Summary& BundleAdjuster::Summary() const { return summary_; } -void BundleAdjuster::SetUpProblem(Reconstruction* reconstruction, +const ceres::Problem& BundleAdjuster::SetUpProblem(Reconstruction* reconstruction, ceres::LossFunction* loss_function) { THROW_CHECK_NOTNULL(reconstruction); THROW_CHECK(!problem_) << "Cannot set up problem from the same BundleAdjuster multiple times"; @@ -324,6 +324,7 @@ void BundleAdjuster::SetUpProblem(Reconstruction* reconstruction, ParameterizeCameras(reconstruction); ParameterizePoints(reconstruction); + return *problem_; } ceres::Solver::Options BundleAdjuster::SetUpSolverOptions(const ceres::Problem& problem, @@ -559,7 +560,7 @@ bool RigBundleAdjuster::Solve(Reconstruction* reconstruction, return true; } -void RigBundleAdjuster::SetUpProblem(Reconstruction* reconstruction, +const ceres::Problem& RigBundleAdjuster::SetUpProblem(Reconstruction* reconstruction, std::vector* camera_rigs, ceres::LossFunction* loss_function) { THROW_CHECK_NOTNULL(reconstruction); @@ -606,6 +607,7 @@ void RigBundleAdjuster::SetUpProblem(Reconstruction* reconstruction, ParameterizeCameras(reconstruction); ParameterizePoints(reconstruction); ParameterizeCameraRigs(reconstruction); + return *problem_; } void RigBundleAdjuster::TearDown(Reconstruction* reconstruction, diff --git a/src/colmap/estimators/bundle_adjustment.h b/src/colmap/estimators/bundle_adjustment.h index ea29d40288..1f151ad5ed 100644 --- a/src/colmap/estimators/bundle_adjustment.h +++ b/src/colmap/estimators/bundle_adjustment.h @@ -173,7 +173,7 @@ class BundleAdjuster { bool Solve(Reconstruction* reconstruction); // Set up the problem - void SetUpProblem(Reconstruction* reconstruction, + const ceres::Problem& SetUpProblem(Reconstruction* reconstruction, ceres::LossFunction* loss_function); ceres::Solver::Options SetUpSolverOptions(const ceres::Problem& problem, const ceres::Solver::Options& input_solver_options) const; @@ -228,7 +228,7 @@ class RigBundleAdjuster : public BundleAdjuster { bool Solve(Reconstruction* reconstruction, std::vector* camera_rigs); - void SetUpProblem(Reconstruction* reconstruction, + const ceres::Problem& SetUpProblem(Reconstruction* reconstruction, std::vector* camera_rigs, ceres::LossFunction* loss_function); From e7021756af4276d27f91a66c56a50e716862fd82 Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Mon, 8 Apr 2024 21:43:54 +0200 Subject: [PATCH 05/42] fix format. --- src/colmap/estimators/bundle_adjustment.cc | 45 ++++++++++++---------- src/colmap/estimators/bundle_adjustment.h | 11 +++--- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/colmap/estimators/bundle_adjustment.cc b/src/colmap/estimators/bundle_adjustment.cc index 65111c8389..e272e7a6ef 100644 --- a/src/colmap/estimators/bundle_adjustment.cc +++ b/src/colmap/estimators/bundle_adjustment.cc @@ -194,7 +194,8 @@ bool BundleAdjustmentConfig::HasConstantCamPositions( constant_cam_positions_.end(); } -const std::unordered_set BundleAdjustmentConfig::ConstantIntrinsics() const { +const std::unordered_set BundleAdjustmentConfig::ConstantIntrinsics() + const { return constant_intrinsics_; } @@ -212,7 +213,8 @@ const std::unordered_set& BundleAdjustmentConfig::ConstantPoints() return constant_point3D_ids_; } -const std::unordered_set& BundleAdjustmentConfig::ConstantCamPoses() const { +const std::unordered_set& BundleAdjustmentConfig::ConstantCamPoses() + const { return constant_cam_poses_; } @@ -272,7 +274,8 @@ bool BundleAdjuster::Solve(Reconstruction* reconstruction) { return false; } - ceres::Solver::Options solver_options = SetUpSolverOptions(*problem_, options_.solver_options); + ceres::Solver::Options solver_options = + SetUpSolverOptions(*problem_, options_.solver_options); ceres::Solve(solver_options, problem_.get(), &summary_); @@ -287,22 +290,19 @@ const BundleAdjustmentOptions& BundleAdjuster::Options() const { return options_; } -const BundleAdjustmentConfig& BundleAdjuster::Config() const { - return config_; -} +const BundleAdjustmentConfig& BundleAdjuster::Config() const { return config_; } -const ceres::Problem& BundleAdjuster::Problem() const { - return *problem_; -} +const ceres::Problem& BundleAdjuster::Problem() const { return *problem_; } const ceres::Solver::Summary& BundleAdjuster::Summary() const { return summary_; } -const ceres::Problem& BundleAdjuster::SetUpProblem(Reconstruction* reconstruction, - ceres::LossFunction* loss_function) { +const ceres::Problem& BundleAdjuster::SetUpProblem( + Reconstruction* reconstruction, ceres::LossFunction* loss_function) { THROW_CHECK_NOTNULL(reconstruction); - THROW_CHECK(!problem_) << "Cannot set up problem from the same BundleAdjuster multiple times"; + THROW_CHECK(!problem_) + << "Cannot set up problem from the same BundleAdjuster multiple times"; // Initialize an empty problem ceres::Problem::Options problem_options; @@ -327,8 +327,9 @@ const ceres::Problem& BundleAdjuster::SetUpProblem(Reconstruction* reconstructio return *problem_; } -ceres::Solver::Options BundleAdjuster::SetUpSolverOptions(const ceres::Problem& problem, - const ceres::Solver::Options& input_solver_options) const { +ceres::Solver::Options BundleAdjuster::SetUpSolverOptions( + const ceres::Problem& problem, + const ceres::Solver::Options& input_solver_options) const { ceres::Solver::Options solver_options = input_solver_options; const bool has_sparse = solver_options.sparse_linear_algebra_library_type != ceres::NO_SPARSE; @@ -346,8 +347,7 @@ ceres::Solver::Options BundleAdjuster::SetUpSolverOptions(const ceres::Problem& solver_options.preconditioner_type = ceres::SCHUR_JACOBI; } - if (problem.NumResiduals() < - options_.min_num_residuals_for_multi_threading) { + if (problem.NumResiduals() < options_.min_num_residuals_for_multi_threading) { solver_options.num_threads = 1; #if CERES_VERSION_MAJOR < 2 solver_options.num_linear_solver_threads = 1; @@ -547,7 +547,8 @@ bool RigBundleAdjuster::Solve(Reconstruction* reconstruction, return false; } - ceres::Solver::Options solver_options = SetUpSolverOptions(*problem_, options_.solver_options); + ceres::Solver::Options solver_options = + SetUpSolverOptions(*problem_, options_.solver_options); ceres::Solve(solver_options, problem_.get(), &summary_); @@ -560,12 +561,14 @@ bool RigBundleAdjuster::Solve(Reconstruction* reconstruction, return true; } -const ceres::Problem& RigBundleAdjuster::SetUpProblem(Reconstruction* reconstruction, - std::vector* camera_rigs, - ceres::LossFunction* loss_function) { +const ceres::Problem& RigBundleAdjuster::SetUpProblem( + Reconstruction* reconstruction, + std::vector* camera_rigs, + ceres::LossFunction* loss_function) { THROW_CHECK_NOTNULL(reconstruction); THROW_CHECK_NOTNULL(camera_rigs); - THROW_CHECK(!problem_) << "Cannot set up problem from the same BundleAdjuster multiple times"; + THROW_CHECK(!problem_) + << "Cannot set up problem from the same BundleAdjuster multiple times"; // Check the validity of the provided camera rigs. std::unordered_set rig_camera_ids; diff --git a/src/colmap/estimators/bundle_adjustment.h b/src/colmap/estimators/bundle_adjustment.h index 1f151ad5ed..969f23ccef 100644 --- a/src/colmap/estimators/bundle_adjustment.h +++ b/src/colmap/estimators/bundle_adjustment.h @@ -174,9 +174,10 @@ class BundleAdjuster { // Set up the problem const ceres::Problem& SetUpProblem(Reconstruction* reconstruction, - ceres::LossFunction* loss_function); - ceres::Solver::Options SetUpSolverOptions(const ceres::Problem& problem, - const ceres::Solver::Options& input_solver_options) const; + ceres::LossFunction* loss_function); + ceres::Solver::Options SetUpSolverOptions( + const ceres::Problem& problem, + const ceres::Solver::Options& input_solver_options) const; // Getter functions below const BundleAdjustmentOptions& Options() const; @@ -229,8 +230,8 @@ class RigBundleAdjuster : public BundleAdjuster { std::vector* camera_rigs); const ceres::Problem& SetUpProblem(Reconstruction* reconstruction, - std::vector* camera_rigs, - ceres::LossFunction* loss_function); + std::vector* camera_rigs, + ceres::LossFunction* loss_function); void TearDown(Reconstruction* reconstruction, const std::vector& camera_rigs); From 31227c2089550d595e72c1793b76beb25a081db2 Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Tue, 9 Apr 2024 02:38:18 +0200 Subject: [PATCH 06/42] bind bundle adjustment draft. --- pycolmap/custom_bundle_adjustment.py | 179 +++++++++++++++++++ pycolmap/custom_incremental_mapping.py | 15 +- src/colmap/estimators/bundle_adjustment.cc | 16 +- src/colmap/estimators/bundle_adjustment.h | 12 +- src/colmap/sfm/incremental_mapper.cc | 16 +- src/colmap/sfm/incremental_mapper.h | 11 +- src/pycolmap/estimators/bindings.cc | 2 + src/pycolmap/estimators/bundle_adjustment.cc | 64 +++++++ src/pycolmap/sfm/incremental_mapper.cc | 13 +- 9 files changed, 305 insertions(+), 23 deletions(-) create mode 100644 pycolmap/custom_bundle_adjustment.py create mode 100644 src/pycolmap/estimators/bundle_adjustment.cc diff --git a/pycolmap/custom_bundle_adjustment.py b/pycolmap/custom_bundle_adjustment.py new file mode 100644 index 0000000000..0541e3ff5f --- /dev/null +++ b/pycolmap/custom_bundle_adjustment.py @@ -0,0 +1,179 @@ +""" +Python reimplementation of the bundle adjustment for the incremental mapper of C++ with equivalent logic. +As a result, one can add customized residuals on top of the exposed ceres problem from conventional bundle adjustment. +""" +import pyceres +import pycolmap +from pycolmap import logging +import copy + +def solve_bundle_adjustment(reconstruction, ba_options, ba_config): + import pdb + bundle_adjuster = pycolmap.BundleAdjuster(ba_options, ba_config) + bundle_adjuster.solve(reconstruction) + return bundle_adjuster.summary +""" + problem = bundle_adjuster.set_up_problem(reconstruction, pyceres.TrivialLoss()) # TODO + if problem.num_residuals() == 0: + return None + solver_options = bundle_adjuster.set_up_solver_options(problem, bundle_adjuster.options.solver_options) + summary = pyceres.SolverSummary() + pdb.set_trace() + pyceres.solve(solver_options, problem, summary) + return summary +""" + +def adjust_global_bundle(mapper, mapper_options, ba_options): + """Equivalent to mapper.adjust_global_bundle(...)""" + reconstruction = mapper.get_reconstruction() + assert reconstruction is not None + reg_image_ids = reconstruction.reg_image_ids() + if len(reg_image_ids) < 2: + logging.fatal("At least two images must be registered for global bundle-adjustment") + ba_options_tmp = copy.deepcopy(ba_options) + + # Use stricter convergence criteria for first registered images + if len(reg_image_ids) < 10: # kMinNumRegImagesForFastBA = 10 + ba_options_tmp.solver_options.function_tolerance /= 10; + ba_options_tmp.solver_options.gradient_tolerance /= 10; + ba_options_tmp.solver_options.parameter_tolerance /= 10; + ba_options_tmp.solver_options.max_num_iterations *= 2; + ba_options_tmp.solver_options.max_linear_solver_iterations = 200; + + # Avoid degeneracies in bundle adjustment + reconstruction.filter_observations_with_negative_depth() + + # Configure bundle adjustment + ba_config = pycolmap.BundleAdjustmentConfig() + for image_id in reg_image_ids: + ba_config.add_image(image_id) + + # Fix the existing images, if option specified + if mapper_options.fix_existing_images: + for image_id in reg_image_ids: + if image_id in mapper.existing_image_ids: + ba_config.set_constant_cam_pose(image_id) + + # Fix 7-DOFs of the bundle adjustment problem + ba_config.set_constant_cam_pose(reg_image_ids[0]) + if (not mapper_options.fix_existing_images) or (reg_image_ids[1] not in mapper.existing_image_ids): + ba_config.set_constant_cam_positions(reg_image_ids[1], [0]) + + # Run bundle adjustment + summary = solve_bundle_adjustment(reconstruction, ba_options_tmp, ba_config) + logging.info("Global Bundle Adjustment") + logging.info(summary.BriefReport()) + return True + +def iterative_global_refinement(mapper, max_num_refinements, max_refinement_change, mapper_options, ba_options, tri_options, normalize_reconstruction = True): + """Equivalent to mapper.iterative_global_refinement(...)""" + reconstruction = mapper.get_reconstruction() + mapper.complete_and_merge_tracks(tri_options) + logging.verbose(1, "=> Retriangulated observations: {0}".format(mapper.retriangulate(tri_options))) + for i in range(max_num_refinements): + num_observations = reconstruction.compute_num_observations() + # mapper.adjust_global_bundle(mapper_options, ba_options) + adjust_global_bundle(mapper, mapper_options, ba_options) + if normalize_reconstruction: + reconstruction.normalize() + num_changed_observations = mapper.complete_and_merge_tracks(tri_options) + num_changed_observations += mapper.filter_points(mapper_options) + changed = 0 if num_observations == 0 else num_changed_observations / num_observations + logging.verbose(1, "=> Changed observations: {0:.6f}".format(changed)) + if changed < max_refinement_change: + break + +def adjust_local_bundle(mapper, mapper_options, ba_options, tri_options, image_id, point3D_ids): + """Equivalent to mapper.adjust_local_bundle(...)""" + reconstruction = mapper.get_reconstruction() + assert reconstruction is not None + report = pycolmap.LocalBundleAdjustmentReport() + + # Find images that have most 3D points with given image in common + local_bundle = mapper.find_local_bundle(mapper_options, image_id) + + # Do the bundle adjustment only if there is any connected images + if len(local_bundle) > 0: + ba_config = pycolmap.BundleAdjustmentConfig() + ba_config.add_image(image_id) + for local_image_id in local_bundle: + ba_config.add_image(local_image_id) + + # Fix the existing images, if options specified + if mapper_options.fix_existing_images: + for local_image_id in local_bundle: + if local_image_id in mapper.existing_image_ids: + ba_config.set_constant_cam_pose(local_image_id) + + # Determine which cameras to fix, when not all the registered images are within the current local bundle. + num_images_per_camera = {} + for image_id in ba_config.image_ids: + image = reconstruction.images[image_id] + num_images_per_camera[image.camera_id] += 1 + for camera_id, num_images_local in num_images_per_camera.items(): + if num_images_local < reconstruction.num_reg_images_per_camera[camera_id]: + ba_config.set_constant_cam_intrinsics(camera_id) + + # Fix 7 DOF to avoid scale/rotation/translation drift in bundle adjustment + if len(local_bundle) == 1: + ba_config.set_constant_cam_pose(local_bundle[0]) + ba_config.set_constant_cam_positions(image_id, [0]) + elif len(local_bundle) > 1: + image_id1, image_id2 = local_bundle[-1], local_bundle[-2] + ba_config.set_constant_cam_pose(image_id1) + if (not mapper_options.fix_existing_images) or (image_id2 not in mapper.existing_image_ids): + ba_config.set_constant_cam_positions(image_id2, [0]) + + # Make sure, we refine all new and short-track 3D points, no matter if + # they are fully contained in the local image set or not. Do not include + # long track 3D points as they are usually already very stable and adding + # to them to bundle adjustment and track merging/completion would slow + # down the local bundle adjustment significantly. + varialbe_point3D_ids = [] + for point3D_id in point3D_ids: + point3D = reconstruction.points3D[point3D_id] + kMaxTrackLength = 15 + if (not point3D.error != -1.) or point3D.track.length() <= kMaxTrackLength: + ba_config.add_variable_point(point3D_id) + variable_point3D_ids.push_back(point3D_id) + + # Adjust the local bundle + summary = solve_bundle_adjustment(reconstruction, ba_options, ba_config) + logging.info("Local Bundle Adjustment") + logging.info(summary.BriefReport()) + + report.num_adjusted_observations = summary.num_residuals / 2 + # Merge refined tracks with other existing points + report.num_merged_observations = mapper.get_triangulator().merge_tracks(tri_options, variable_point3D_ids) + # Complete tracks that may have failed to triangulate before refinement + # of camera pose and calibration in bundle adjustment. This may avoid that some points are filtered and it helps for subsequent image registrations + report.num_completed_observations = mapper.get_triangulator().complete_tracks(tri_options, variable_point3D_ids) + report.num_completed_observations += mapper.get_triangulator().complete_image(tri_options, image_id) + + filter_image_ids = [] + filter_image_ids.push_back(image_id) + filter_image_ids.extend(local_bundle) + report.num_filtered_observations = reconstruction.filter_points3D_in_images(mapper_options.filter_max_reproj_error, mapper_options.filter_min_tri_angle, filter_image_ids) + report.num_filtered_observations += reconstruction.filter_points3D(mapper_options.filter_max_reproj_error, mapper_options.filter_min_tri_angle, point3D_ids) + return report + +def iterative_local_refinement(mapper, max_num_refinements, max_refinement_change, mapper_options, ba_options, tri_options, image_id): + """Equivalent to mapper.iterative_local_refinement(...)""" + reconstruction = mapper.get_reconstruction() + ba_options_tmp = copy.deepcopy(ba_options) + for i in range(max_num_refinements): + report = mapper.adjust_local_bundle(mapper_options, ba_options_tmp, tri_options, image_id, mapper.get_modified_points3D()) + logging.verbose(1, "=> Merged observations: {0}".format(report.num_merged_observations)) + logging.verbose(1, "=> Completed observations: {0}".format(report.num_completed_observations)) + logging.verbose(1, "=> Filtered observations: {0}".format(report.num_filtered_observations)) + changed = 0 + if report.num_adjusted_observations != 0: + changed = (report.num_merged_observations + report.num_completed_observations + report.num_filtered_observations) / report.num_adjusted_observations + logging.verbose(1, "=> Changed observations: {0:.6f}".format(changed)) + if changed < max_refinement_change: + break + + # Only use robust cost function for first iteration + ba_options_tmp.loss_function_type = pycolmap.LossFunctionType.TRIVIAL + mapper.clear_modified_points3D() + diff --git a/pycolmap/custom_incremental_mapping.py b/pycolmap/custom_incremental_mapping.py index 881502bda0..053da90a62 100644 --- a/pycolmap/custom_incremental_mapping.py +++ b/pycolmap/custom_incremental_mapping.py @@ -12,6 +12,7 @@ import pycolmap from pycolmap import logging +import custom_bundle_adjustment def extract_colors(image_path, image_id, reconstruction): @@ -30,7 +31,9 @@ def write_snapshot(reconstruction, snapshot_path): def iterative_global_refinement(options, mapper_options, mapper): logging.info("Retriangulation and Global bundle adjustment") - mapper.iterative_global_refinement( + # mapper.iterative_global_refinement( + custom_bundle_adjustment.iterative_global_refinement( + mapper, options.ba_global_max_refinements, options.ba_global_max_refinement_change, mapper_options, @@ -70,8 +73,11 @@ def initialize_reconstruction( mapper_options, two_view_geometry, *init_pair ) logging.info("Global bundle adjustment") - mapper.adjust_global_bundle( - mapper_options, options.get_global_bundle_adjustment() + # mapper.adjust_global_bundle( + # mapper_options, options.get_global_bundle_adjustment() + # ) + custom_bundle_adjustment.adjust_global_bundle( + mapper, mapper_options, options.get_global_bundle_adjustment() ) reconstruction.normalize() mapper.filter_points(mapper_options) @@ -144,7 +150,8 @@ def reconstruct_sub_model(controller, mapper, mapper_options, reconstruction): break if reg_next_success: mapper.triangulate_image(options.get_triangulation(), next_image_id) - mapper.iterative_local_refinement( + custom_bundle_adjustment.iterative_local_refinement( + mapper, options.ba_local_max_refinements, options.ba_local_max_refinement_change, mapper_options, diff --git a/src/colmap/estimators/bundle_adjustment.cc b/src/colmap/estimators/bundle_adjustment.cc index e272e7a6ef..5a4bc1aef5 100644 --- a/src/colmap/estimators/bundle_adjustment.cc +++ b/src/colmap/estimators/bundle_adjustment.cc @@ -298,16 +298,16 @@ const ceres::Solver::Summary& BundleAdjuster::Summary() const { return summary_; } -const ceres::Problem& BundleAdjuster::SetUpProblem( +ceres::Problem* BundleAdjuster::SetUpProblem( Reconstruction* reconstruction, ceres::LossFunction* loss_function) { THROW_CHECK_NOTNULL(reconstruction); - THROW_CHECK(!problem_) - << "Cannot set up problem from the same BundleAdjuster multiple times"; + // THROW_CHECK(!problem_) + // << "Cannot set up problem from the same BundleAdjuster multiple times"; // Initialize an empty problem ceres::Problem::Options problem_options; problem_options.loss_function_ownership = ceres::DO_NOT_TAKE_OWNERSHIP; - problem_ = std::make_unique(problem_options); + problem_ = std::make_shared(problem_options); // Set up problem // Warning: AddPointsToProblem assumes that AddImageToProblem is called first. @@ -324,7 +324,7 @@ const ceres::Problem& BundleAdjuster::SetUpProblem( ParameterizeCameras(reconstruction); ParameterizePoints(reconstruction); - return *problem_; + return problem_.get(); } ceres::Solver::Options BundleAdjuster::SetUpSolverOptions( @@ -561,7 +561,7 @@ bool RigBundleAdjuster::Solve(Reconstruction* reconstruction, return true; } -const ceres::Problem& RigBundleAdjuster::SetUpProblem( +ceres::Problem* RigBundleAdjuster::SetUpProblem( Reconstruction* reconstruction, std::vector* camera_rigs, ceres::LossFunction* loss_function) { @@ -592,7 +592,7 @@ const ceres::Problem& RigBundleAdjuster::SetUpProblem( // Initialize an empty problem ceres::Problem::Options problem_options; problem_options.loss_function_ownership = ceres::DO_NOT_TAKE_OWNERSHIP; - problem_ = std::make_unique(problem_options); + problem_ = std::make_shared(problem_options); // Set up problem ComputeCameraRigPoses(*reconstruction, *camera_rigs); @@ -610,7 +610,7 @@ const ceres::Problem& RigBundleAdjuster::SetUpProblem( ParameterizeCameras(reconstruction); ParameterizePoints(reconstruction); ParameterizeCameraRigs(reconstruction); - return *problem_; + return problem_.get(); } void RigBundleAdjuster::TearDown(Reconstruction* reconstruction, diff --git a/src/colmap/estimators/bundle_adjustment.h b/src/colmap/estimators/bundle_adjustment.h index 969f23ccef..736799529d 100644 --- a/src/colmap/estimators/bundle_adjustment.h +++ b/src/colmap/estimators/bundle_adjustment.h @@ -173,8 +173,8 @@ class BundleAdjuster { bool Solve(Reconstruction* reconstruction); // Set up the problem - const ceres::Problem& SetUpProblem(Reconstruction* reconstruction, - ceres::LossFunction* loss_function); + ceres::Problem* SetUpProblem(Reconstruction* reconstruction, + ceres::LossFunction* loss_function); ceres::Solver::Options SetUpSolverOptions( const ceres::Problem& problem, const ceres::Solver::Options& input_solver_options) const; @@ -202,7 +202,7 @@ class BundleAdjuster { const BundleAdjustmentOptions options_; BundleAdjustmentConfig config_; - std::unique_ptr problem_; + std::shared_ptr problem_; ceres::Solver::Summary summary_; std::unordered_set camera_ids_; std::unordered_map point3D_num_observations_; @@ -229,9 +229,9 @@ class RigBundleAdjuster : public BundleAdjuster { bool Solve(Reconstruction* reconstruction, std::vector* camera_rigs); - const ceres::Problem& SetUpProblem(Reconstruction* reconstruction, - std::vector* camera_rigs, - ceres::LossFunction* loss_function); + ceres::Problem* SetUpProblem(Reconstruction* reconstruction, + std::vector* camera_rigs, + ceres::LossFunction* loss_function); void TearDown(Reconstruction* reconstruction, const std::vector& camera_rigs); diff --git a/src/colmap/sfm/incremental_mapper.cc b/src/colmap/sfm/incremental_mapper.cc index e48899874b..c44afbee8f 100644 --- a/src/colmap/sfm/incremental_mapper.cc +++ b/src/colmap/sfm/incremental_mapper.cc @@ -834,11 +834,21 @@ size_t IncrementalMapper::FilterPoints(const Options& options) { return num_filtered_observations; } -const Reconstruction& IncrementalMapper::GetReconstruction() const { - THROW_CHECK_NOTNULL(reconstruction_); - return *reconstruction_; +const std::unordered_set& IncrementalMapper::GetFilteredImages() + const { + return filtered_images_; +} + +const std::unordered_set& IncrementalMapper::GetExistingImageIds() + const { + return existing_image_ids_; } +const std::unordered_map& +IncrementalMapper::GetNumRegImagesPerCamera() const { + return num_reg_images_per_camera_; +}; + size_t IncrementalMapper::NumTotalRegImages() const { return num_total_reg_images_; } diff --git a/src/colmap/sfm/incremental_mapper.h b/src/colmap/sfm/incremental_mapper.h index b61343e21e..2f125c42c4 100644 --- a/src/colmap/sfm/incremental_mapper.h +++ b/src/colmap/sfm/incremental_mapper.h @@ -242,7 +242,16 @@ class IncrementalMapper { size_t FilterImages(const Options& options); size_t FilterPoints(const Options& options); - const Reconstruction& GetReconstruction() const; + // Getter functions + const Reconstruction* GetReconstruction() const { + return reconstruction_.get(); + }; + const IncrementalTriangulator* GetTriangulator() const { + return triangulator_.get(); + }; + const std::unordered_set& GetFilteredImages() const; + const std::unordered_set& GetExistingImageIds() const; + const std::unordered_map& GetNumRegImagesPerCamera() const; // Number of images that are registered in at least on reconstruction. size_t NumTotalRegImages() const; diff --git a/src/pycolmap/estimators/bindings.cc b/src/pycolmap/estimators/bindings.cc index 6b036e4a13..1ec642199e 100644 --- a/src/pycolmap/estimators/bindings.cc +++ b/src/pycolmap/estimators/bindings.cc @@ -4,6 +4,7 @@ namespace py = pybind11; void BindAbsolutePoseEstimator(py::module& m); void BindAlignmentEstimator(py::module& m); +void BindBundleAdjuster(py::module& m); void BindCostFunctions(py::module& m); void BindEssentialMatrixEstimator(py::module& m); void BindFundamentalMatrixEstimator(py::module& m); @@ -15,6 +16,7 @@ void BindTwoViewGeometryEstimator(py::module& m); void BindEstimators(py::module& m) { BindAbsolutePoseEstimator(m); BindAlignmentEstimator(m); + BindBundleAdjuster(m); BindCostFunctions(m); BindEssentialMatrixEstimator(m); BindFundamentalMatrixEstimator(m); diff --git a/src/pycolmap/estimators/bundle_adjustment.cc b/src/pycolmap/estimators/bundle_adjustment.cc new file mode 100644 index 0000000000..e5f8f6054b --- /dev/null +++ b/src/pycolmap/estimators/bundle_adjustment.cc @@ -0,0 +1,64 @@ +#include "colmap/estimators/bundle_adjustment.h" + +#include "pycolmap/helpers.h" +#include "pycolmap/pybind11_extension.h" +#include "pycolmap/utils.h" + +#include +#include +#include + +using namespace colmap; +using namespace pybind11::literals; +namespace py = pybind11; + +void BindBundleAdjuster(py::module& m) { + using BACfg = BundleAdjustmentConfig; + auto PyBundleAdjustmentConfig = + py::class_(m, "BundleAdjustmentConfig") + .def(py::init<>()) + .def("num_images", &BACfg::NumImages) + .def("num_points", &BACfg::NumPoints) + .def("num_constant_cam_intrinsics", &BACfg::NumConstantCamIntrinsics) + .def("num_constant_cam_poses", &BACfg::NumConstantCamPoses) + .def("num_constant_cam_positions", &BACfg::NumConstantCamPositions) + .def("num_variable_points", &BACfg::NumVariablePoints) + .def("num_constant_points", &BACfg::NumConstantPoints) + .def("num_residuals", &BACfg::NumResiduals, "reconstruction"_a) + .def("add_image", &BACfg::AddImage, "image_id"_a) + .def("has_image", &BACfg::HasImage, "image_id"_a) + .def("remove_image", &BACfg::RemoveImage, "image_id"_a) + .def("set_constant_cam_intrinsics", &BACfg::SetConstantCamIntrinsics, "camera_id"_a) + .def("set_variable_cam_intrinsics", &BACfg::SetVariableCamIntrinsics, "camera_id"_a) + .def("has_constant_cam_intrinsics", &BACfg::HasConstantCamIntrinsics, "camera_id"_a) + .def("set_constant_cam_pose", &BACfg::SetConstantCamPose, "image_id"_a) + .def("set_variable_cam_pose", &BACfg::SetVariableCamPose, "image_id"_a) + .def("has_constant_cam_pose", &BACfg::HasConstantCamPose, "image_id"_a) + .def("set_constant_cam_positions", &BACfg::SetConstantCamPositions, "image_id"_a, "idxs"_a) + .def("remove_variable_cam_positions", &BACfg::RemoveConstantCamPositions, "image_id"_a) + .def("has_constant_cam_positions", &BACfg::HasConstantCamPositions, "image_id"_a) + .def("add_variable_point", &BACfg::AddVariablePoint, "point3D_id"_a) + .def("add_constant_point", &BACfg::AddConstantPoint, "point3D_id"_a) + .def("has_point", &BACfg::HasPoint, "point3D_id"_a) + .def("has_variable_point", &BACfg::HasVariablePoint, "point3D_id"_a) + .def("has_constant_point", &BACfg::HasConstantPoint, "point3D_id"_a) + .def("remove_variable_point", &BACfg::RemoveVariablePoint, "point3D_id"_a) + .def("remove_constant_point", &BACfg::RemoveConstantPoint, "point3D_id"_a) + .def_property_readonly("constant_intrinsics", &BACfg::ConstantIntrinsics) + .def_property_readonly("image_ids", &BACfg::Images) + .def_property_readonly("variable_point3D_ids", &BACfg::VariablePoints) + .def_property_readonly("constant_point3D_ids", &BACfg::ConstantPoints) + .def_property_readonly("constant_cam_poses", &BACfg::ConstantCamPoses) + .def("constant_cam_positions", &BACfg::ConstantCamPositions, "image_id"_a); + MakeDataclass(PyBundleAdjustmentConfig); + + py::class_(m, "BundleAdjuster") + .def(py::init()) + .def("solve", &BundleAdjuster::Solve, "reconstruction"_a) + .def("set_up_problem", &BundleAdjuster::SetUpProblem, "reconstruction"_a, "loss_function"_a, py::return_value_policy::reference) + .def("set_up_solver_options", &BundleAdjuster::SetUpSolverOptions, "problem"_a, "input_solver_options"_a) + .def("get_problem", &BundleAdjuster::Problem, py::return_value_policy::reference) + .def_property_readonly("options", &BundleAdjuster::Options) + .def_property_readonly("config", &BundleAdjuster::Config) + .def_property_readonly("summary", &BundleAdjuster::Summary, py::return_value_policy::reference); +} diff --git a/src/pycolmap/sfm/incremental_mapper.cc b/src/pycolmap/sfm/incremental_mapper.cc index d58ee36347..2e6c6e380a 100644 --- a/src/pycolmap/sfm/incremental_mapper.cc +++ b/src/pycolmap/sfm/incremental_mapper.cc @@ -296,7 +296,18 @@ void BindIncrementalMapperImpl(py::module& m) { "normalize_reconstruction"_a = true) .def("filter_images", &IncrementalMapper::FilterImages, "options"_a) .def("filter_points", &IncrementalMapper::FilterPoints, "options"_a) - .def("get_reconstruction", &IncrementalMapper::GetReconstruction) + .def("get_reconstruction", + &IncrementalMapper::GetReconstruction, + py::return_value_policy::reference) + .def("get_triangulator", + &IncrementalMapper::GetTriangulator, + py::return_value_policy::reference_internal) + .def_property_readonly("filtered_images", + &IncrementalMapper::GetFilteredImages) + .def_property_readonly("existing_image_ids", + &IncrementalMapper::GetExistingImageIds) + .def_property_readonly("num_reg_images_per_camera", + &IncrementalMapper::GetNumRegImagesPerCamera) .def("num_total_reg_images", &IncrementalMapper::NumTotalRegImages) .def("num_shared_reg_images", &IncrementalMapper::NumSharedRegImages) .def("get_modified_points3D", &IncrementalMapper::GetModifiedPoints3D) From 0361bb7f01b4eeb147b0e0bdfee7c09f24736ffa Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Tue, 9 Apr 2024 21:41:22 +0200 Subject: [PATCH 07/42] update. fix local ba. --- pycolmap/custom_bundle_adjustment.py | 28 +++---- pycolmap/custom_incremental_mapping.py | 1 - src/colmap/estimators/bundle_adjustment.cc | 4 +- src/colmap/estimators/bundle_adjustment.h | 2 +- src/colmap/sfm/incremental_mapper.cc | 2 +- src/colmap/sfm/incremental_mapper.h | 22 +++--- src/pycolmap/estimators/bundle_adjustment.cc | 77 ++++++++++++++------ src/pycolmap/scene/reconstruction.cc | 7 ++ src/pycolmap/sfm/incremental_mapper.cc | 6 +- 9 files changed, 98 insertions(+), 51 deletions(-) diff --git a/pycolmap/custom_bundle_adjustment.py b/pycolmap/custom_bundle_adjustment.py index 0541e3ff5f..66ef612aba 100644 --- a/pycolmap/custom_bundle_adjustment.py +++ b/pycolmap/custom_bundle_adjustment.py @@ -8,7 +8,6 @@ import copy def solve_bundle_adjustment(reconstruction, ba_options, ba_config): - import pdb bundle_adjuster = pycolmap.BundleAdjuster(ba_options, ba_config) bundle_adjuster.solve(reconstruction) return bundle_adjuster.summary @@ -18,7 +17,6 @@ def solve_bundle_adjustment(reconstruction, ba_options, ba_config): return None solver_options = bundle_adjuster.set_up_solver_options(problem, bundle_adjuster.options.solver_options) summary = pyceres.SolverSummary() - pdb.set_trace() pyceres.solve(solver_options, problem, summary) return summary """ @@ -109,9 +107,11 @@ def adjust_local_bundle(mapper, mapper_options, ba_options, tri_options, image_i num_images_per_camera = {} for image_id in ba_config.image_ids: image = reconstruction.images[image_id] + if image.camera_id not in num_images_per_camera: + num_images_per_camera[image.camera_id] = 0 num_images_per_camera[image.camera_id] += 1 for camera_id, num_images_local in num_images_per_camera.items(): - if num_images_local < reconstruction.num_reg_images_per_camera[camera_id]: + if num_images_local < mapper.num_reg_images_per_camera[camera_id]: ba_config.set_constant_cam_intrinsics(camera_id) # Fix 7 DOF to avoid scale/rotation/translation drift in bundle adjustment @@ -129,20 +129,20 @@ def adjust_local_bundle(mapper, mapper_options, ba_options, tri_options, image_i # long track 3D points as they are usually already very stable and adding # to them to bundle adjustment and track merging/completion would slow # down the local bundle adjustment significantly. - varialbe_point3D_ids = [] - for point3D_id in point3D_ids: + variable_point3D_ids = set() + for point3D_id in list(point3D_ids): point3D = reconstruction.points3D[point3D_id] kMaxTrackLength = 15 - if (not point3D.error != -1.) or point3D.track.length() <= kMaxTrackLength: + if (point3D.error == -1.) or point3D.track.length() <= kMaxTrackLength: ba_config.add_variable_point(point3D_id) - variable_point3D_ids.push_back(point3D_id) + variable_point3D_ids.add(point3D_id) # Adjust the local bundle - summary = solve_bundle_adjustment(reconstruction, ba_options, ba_config) + summary = solve_bundle_adjustment(mapper.get_reconstruction(), ba_options, ba_config) logging.info("Local Bundle Adjustment") logging.info(summary.BriefReport()) - report.num_adjusted_observations = summary.num_residuals / 2 + report.num_adjusted_observations = int(summary.num_residuals / 2) # Merge refined tracks with other existing points report.num_merged_observations = mapper.get_triangulator().merge_tracks(tri_options, variable_point3D_ids) # Complete tracks that may have failed to triangulate before refinement @@ -150,19 +150,19 @@ def adjust_local_bundle(mapper, mapper_options, ba_options, tri_options, image_i report.num_completed_observations = mapper.get_triangulator().complete_tracks(tri_options, variable_point3D_ids) report.num_completed_observations += mapper.get_triangulator().complete_image(tri_options, image_id) - filter_image_ids = [] - filter_image_ids.push_back(image_id) - filter_image_ids.extend(local_bundle) + filter_image_ids = set() + filter_image_ids.add(image_id) + filter_image_ids.update(local_bundle) report.num_filtered_observations = reconstruction.filter_points3D_in_images(mapper_options.filter_max_reproj_error, mapper_options.filter_min_tri_angle, filter_image_ids) report.num_filtered_observations += reconstruction.filter_points3D(mapper_options.filter_max_reproj_error, mapper_options.filter_min_tri_angle, point3D_ids) return report def iterative_local_refinement(mapper, max_num_refinements, max_refinement_change, mapper_options, ba_options, tri_options, image_id): """Equivalent to mapper.iterative_local_refinement(...)""" - reconstruction = mapper.get_reconstruction() ba_options_tmp = copy.deepcopy(ba_options) for i in range(max_num_refinements): - report = mapper.adjust_local_bundle(mapper_options, ba_options_tmp, tri_options, image_id, mapper.get_modified_points3D()) + # report = mapper.adjust_local_bundle(mapper_options, ba_options_tmp, tri_options, image_id, mapper.get_modified_points3D()) + report = adjust_local_bundle(mapper, mapper_options, ba_options_tmp, tri_options, image_id, mapper.get_modified_points3D()) logging.verbose(1, "=> Merged observations: {0}".format(report.num_merged_observations)) logging.verbose(1, "=> Completed observations: {0}".format(report.num_completed_observations)) logging.verbose(1, "=> Filtered observations: {0}".format(report.num_filtered_observations)) diff --git a/pycolmap/custom_incremental_mapping.py b/pycolmap/custom_incremental_mapping.py index 053da90a62..6bd9180014 100644 --- a/pycolmap/custom_incremental_mapping.py +++ b/pycolmap/custom_incremental_mapping.py @@ -14,7 +14,6 @@ from pycolmap import logging import custom_bundle_adjustment - def extract_colors(image_path, image_id, reconstruction): if not reconstruction.extract_colors_for_image(image_id, image_path): logging.warning(f"Could not read image {image_id} at path {image_path}") diff --git a/src/colmap/estimators/bundle_adjustment.cc b/src/colmap/estimators/bundle_adjustment.cc index 5a4bc1aef5..bfda5f23f8 100644 --- a/src/colmap/estimators/bundle_adjustment.cc +++ b/src/colmap/estimators/bundle_adjustment.cc @@ -292,7 +292,9 @@ const BundleAdjustmentOptions& BundleAdjuster::Options() const { const BundleAdjustmentConfig& BundleAdjuster::Config() const { return config_; } -const ceres::Problem& BundleAdjuster::Problem() const { return *problem_; } +const std::shared_ptr& BundleAdjuster::Problem() const { + return problem_; +} const ceres::Solver::Summary& BundleAdjuster::Summary() const { return summary_; diff --git a/src/colmap/estimators/bundle_adjustment.h b/src/colmap/estimators/bundle_adjustment.h index 736799529d..70bce22b6d 100644 --- a/src/colmap/estimators/bundle_adjustment.h +++ b/src/colmap/estimators/bundle_adjustment.h @@ -183,7 +183,7 @@ class BundleAdjuster { const BundleAdjustmentOptions& Options() const; const BundleAdjustmentConfig& Config() const; // Get the Ceres problem after the last call to "set_up" - const ceres::Problem& Problem() const; + const std::shared_ptr& Problem() const; // Get the Ceres solver summary for the last call to `Solve`. const ceres::Solver::Summary& Summary() const; diff --git a/src/colmap/sfm/incremental_mapper.cc b/src/colmap/sfm/incremental_mapper.cc index c44afbee8f..871abf01a0 100644 --- a/src/colmap/sfm/incremental_mapper.cc +++ b/src/colmap/sfm/incremental_mapper.cc @@ -110,7 +110,7 @@ void IncrementalMapper::BeginReconstruction( reconstruction_ = reconstruction; reconstruction_->Load(*database_cache_); reconstruction_->SetUp(database_cache_->CorrespondenceGraph()); - triangulator_ = std::make_unique( + triangulator_ = std::make_shared( database_cache_->CorrespondenceGraph(), reconstruction); num_shared_reg_images_ = 0; diff --git a/src/colmap/sfm/incremental_mapper.h b/src/colmap/sfm/incremental_mapper.h index 2f125c42c4..24f1c11504 100644 --- a/src/colmap/sfm/incremental_mapper.h +++ b/src/colmap/sfm/incremental_mapper.h @@ -243,11 +243,11 @@ class IncrementalMapper { size_t FilterPoints(const Options& options); // Getter functions - const Reconstruction* GetReconstruction() const { - return reconstruction_.get(); + const std::shared_ptr& GetReconstruction() const { + return reconstruction_; }; - const IncrementalTriangulator* GetTriangulator() const { - return triangulator_.get(); + const std::shared_ptr& GetTriangulator() const { + return triangulator_; }; const std::unordered_set& GetFilteredImages() const; const std::unordered_set& GetExistingImageIds() const; @@ -272,6 +272,12 @@ class IncrementalMapper { image_t image_id1, image_t image_id2); + // Find local bundle for given image in the reconstruction. The local bundle + // is defined as the images that are most connected, i.e. maximum number of + // shared 3D points, to the given image. + std::vector FindLocalBundle(const Options& options, + image_t image_id) const; + private: // Find seed images for incremental reconstruction. Suitable seed images have // a large number of correspondences and have camera calibration priors. The @@ -285,12 +291,6 @@ class IncrementalMapper { std::vector FindSecondInitialImage(const Options& options, image_t image_id1) const; - // Find local bundle for given image in the reconstruction. The local bundle - // is defined as the images that are most connected, i.e. maximum number of - // shared 3D points, to the given image. - std::vector FindLocalBundle(const Options& options, - image_t image_id) const; - // Register / De-register image in current reconstruction and update // the number of shared images between all reconstructions. void RegisterImageEvent(image_t image_id); @@ -303,7 +303,7 @@ class IncrementalMapper { std::shared_ptr reconstruction_; // Class that is responsible for incremental triangulation. - std::unique_ptr triangulator_; + std::shared_ptr triangulator_; // Number of images that are registered in at least on reconstruction. size_t num_total_reg_images_; diff --git a/src/pycolmap/estimators/bundle_adjustment.cc b/src/pycolmap/estimators/bundle_adjustment.cc index e5f8f6054b..6510fba1cc 100644 --- a/src/pycolmap/estimators/bundle_adjustment.cc +++ b/src/pycolmap/estimators/bundle_adjustment.cc @@ -28,37 +28,72 @@ void BindBundleAdjuster(py::module& m) { .def("add_image", &BACfg::AddImage, "image_id"_a) .def("has_image", &BACfg::HasImage, "image_id"_a) .def("remove_image", &BACfg::RemoveImage, "image_id"_a) - .def("set_constant_cam_intrinsics", &BACfg::SetConstantCamIntrinsics, "camera_id"_a) - .def("set_variable_cam_intrinsics", &BACfg::SetVariableCamIntrinsics, "camera_id"_a) - .def("has_constant_cam_intrinsics", &BACfg::HasConstantCamIntrinsics, "camera_id"_a) - .def("set_constant_cam_pose", &BACfg::SetConstantCamPose, "image_id"_a) - .def("set_variable_cam_pose", &BACfg::SetVariableCamPose, "image_id"_a) - .def("has_constant_cam_pose", &BACfg::HasConstantCamPose, "image_id"_a) - .def("set_constant_cam_positions", &BACfg::SetConstantCamPositions, "image_id"_a, "idxs"_a) - .def("remove_variable_cam_positions", &BACfg::RemoveConstantCamPositions, "image_id"_a) - .def("has_constant_cam_positions", &BACfg::HasConstantCamPositions, "image_id"_a) + .def("set_constant_cam_intrinsics", + &BACfg::SetConstantCamIntrinsics, + "camera_id"_a) + .def("set_variable_cam_intrinsics", + &BACfg::SetVariableCamIntrinsics, + "camera_id"_a) + .def("has_constant_cam_intrinsics", + &BACfg::HasConstantCamIntrinsics, + "camera_id"_a) + .def( + "set_constant_cam_pose", &BACfg::SetConstantCamPose, "image_id"_a) + .def( + "set_variable_cam_pose", &BACfg::SetVariableCamPose, "image_id"_a) + .def( + "has_constant_cam_pose", &BACfg::HasConstantCamPose, "image_id"_a) + .def("set_constant_cam_positions", + &BACfg::SetConstantCamPositions, + "image_id"_a, + "idxs"_a) + .def("remove_variable_cam_positions", + &BACfg::RemoveConstantCamPositions, + "image_id"_a) + .def("has_constant_cam_positions", + &BACfg::HasConstantCamPositions, + "image_id"_a) .def("add_variable_point", &BACfg::AddVariablePoint, "point3D_id"_a) .def("add_constant_point", &BACfg::AddConstantPoint, "point3D_id"_a) .def("has_point", &BACfg::HasPoint, "point3D_id"_a) .def("has_variable_point", &BACfg::HasVariablePoint, "point3D_id"_a) .def("has_constant_point", &BACfg::HasConstantPoint, "point3D_id"_a) - .def("remove_variable_point", &BACfg::RemoveVariablePoint, "point3D_id"_a) - .def("remove_constant_point", &BACfg::RemoveConstantPoint, "point3D_id"_a) - .def_property_readonly("constant_intrinsics", &BACfg::ConstantIntrinsics) + .def("remove_variable_point", + &BACfg::RemoveVariablePoint, + "point3D_id"_a) + .def("remove_constant_point", + &BACfg::RemoveConstantPoint, + "point3D_id"_a) + .def_property_readonly("constant_intrinsics", + &BACfg::ConstantIntrinsics) .def_property_readonly("image_ids", &BACfg::Images) .def_property_readonly("variable_point3D_ids", &BACfg::VariablePoints) .def_property_readonly("constant_point3D_ids", &BACfg::ConstantPoints) .def_property_readonly("constant_cam_poses", &BACfg::ConstantCamPoses) - .def("constant_cam_positions", &BACfg::ConstantCamPositions, "image_id"_a); + .def("constant_cam_positions", + &BACfg::ConstantCamPositions, + "image_id"_a); MakeDataclass(PyBundleAdjustmentConfig); py::class_(m, "BundleAdjuster") - .def(py::init()) - .def("solve", &BundleAdjuster::Solve, "reconstruction"_a) - .def("set_up_problem", &BundleAdjuster::SetUpProblem, "reconstruction"_a, "loss_function"_a, py::return_value_policy::reference) - .def("set_up_solver_options", &BundleAdjuster::SetUpSolverOptions, "problem"_a, "input_solver_options"_a) - .def("get_problem", &BundleAdjuster::Problem, py::return_value_policy::reference) - .def_property_readonly("options", &BundleAdjuster::Options) - .def_property_readonly("config", &BundleAdjuster::Config) - .def_property_readonly("summary", &BundleAdjuster::Summary, py::return_value_policy::reference); + .def(py::init()) + .def("solve", &BundleAdjuster::Solve, "reconstruction"_a) + .def("set_up_problem", + &BundleAdjuster::SetUpProblem, + "reconstruction"_a, + "loss_function"_a, + py::return_value_policy::take_ownership) + .def("set_up_solver_options", + &BundleAdjuster::SetUpSolverOptions, + "problem"_a, + "input_solver_options"_a) + .def("get_problem", + &BundleAdjuster::Problem, + py::return_value_policy::take_ownership) + .def_property_readonly("options", &BundleAdjuster::Options) + .def_property_readonly("config", &BundleAdjuster::Config) + .def_property_readonly("summary", + &BundleAdjuster::Summary, + py::return_value_policy::reference_internal); } diff --git a/src/pycolmap/scene/reconstruction.cc b/src/pycolmap/scene/reconstruction.cc index 7c50c03484..a3674312f8 100644 --- a/src/pycolmap/scene/reconstruction.cc +++ b/src/pycolmap/scene/reconstruction.cc @@ -22,6 +22,9 @@ using namespace colmap; using namespace pybind11::literals; namespace py = pybind11; +using Point3DMap = std::unordered_map; +PYBIND11_MAKE_OPAQUE(Point3DMap); + bool ExistsReconstructionText(const std::string& path) { return (ExistsFile(JoinPaths(path, "cameras.txt")) && ExistsFile(JoinPaths(path, "images.txt")) && @@ -77,13 +80,17 @@ void BindReconstruction(py::module& m) { .def_property_readonly("images", &Reconstruction::Images, py::return_value_policy::reference_internal) + .def("image", py::overload_cast(&Reconstruction::Image)) .def_property_readonly("image_pairs", &Reconstruction::ImagePairs) + .def("image_pair", py::overload_cast(&Reconstruction::ImagePair)) .def_property_readonly("cameras", &Reconstruction::Cameras, py::return_value_policy::reference_internal) + .def("camera", py::overload_cast(&Reconstruction::Camera)) .def_property_readonly("points3D", &Reconstruction::Points3D, py::return_value_policy::reference_internal) + .def("point3D", py::overload_cast(&Reconstruction::Point3D)) .def("point3D_ids", &Reconstruction::Point3DIds) .def("reg_image_ids", &Reconstruction::RegImageIds) .def("exists_camera", &Reconstruction::ExistsCamera) diff --git a/src/pycolmap/sfm/incremental_mapper.cc b/src/pycolmap/sfm/incremental_mapper.cc index 2e6c6e380a..4a4de6069b 100644 --- a/src/pycolmap/sfm/incremental_mapper.cc +++ b/src/pycolmap/sfm/incremental_mapper.cc @@ -282,6 +282,10 @@ void BindIncrementalMapperImpl(py::module& m) { "ba_options"_a, "tri_options"_a, "image_id"_a) + .def("find_local_bundle", + &IncrementalMapper::FindLocalBundle, + "options"_a, + "image_id"_a) .def("adjust_global_bundle", &IncrementalMapper::AdjustGlobalBundle, "options"_a, @@ -298,7 +302,7 @@ void BindIncrementalMapperImpl(py::module& m) { .def("filter_points", &IncrementalMapper::FilterPoints, "options"_a) .def("get_reconstruction", &IncrementalMapper::GetReconstruction, - py::return_value_policy::reference) + py::return_value_policy::reference_internal) .def("get_triangulator", &IncrementalMapper::GetTriangulator, py::return_value_policy::reference_internal) From 09b5b1f0ad905bd21cbf6c57688d26a72794796a Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Tue, 9 Apr 2024 21:47:52 +0200 Subject: [PATCH 08/42] revert. --- src/pycolmap/scene/reconstruction.cc | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/pycolmap/scene/reconstruction.cc b/src/pycolmap/scene/reconstruction.cc index a3674312f8..3e408fa640 100644 --- a/src/pycolmap/scene/reconstruction.cc +++ b/src/pycolmap/scene/reconstruction.cc @@ -22,9 +22,6 @@ using namespace colmap; using namespace pybind11::literals; namespace py = pybind11; -using Point3DMap = std::unordered_map; -PYBIND11_MAKE_OPAQUE(Point3DMap); - bool ExistsReconstructionText(const std::string& path) { return (ExistsFile(JoinPaths(path, "cameras.txt")) && ExistsFile(JoinPaths(path, "images.txt")) && From 9c4b8500fbdd88550bd8e4800a012d96da82b3c6 Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Tue, 9 Apr 2024 22:11:09 +0200 Subject: [PATCH 09/42] update pybind to v2.12. fix py::bind_map. --- pycolmap/CMakeLists.txt | 2 +- src/pycolmap/pybind11_extension.h | 164 ------------------------------ src/pycolmap/scene/point3D.cc | 2 +- 3 files changed, 2 insertions(+), 166 deletions(-) diff --git a/pycolmap/CMakeLists.txt b/pycolmap/CMakeLists.txt index 5de94a4f79..407a6d2dab 100644 --- a/pycolmap/CMakeLists.txt +++ b/pycolmap/CMakeLists.txt @@ -17,7 +17,7 @@ find_package(colmap REQUIRED) find_package(Python REQUIRED COMPONENTS Interpreter Development.Module) -find_package(pybind11 2.11.1 REQUIRED) +find_package(pybind11 2.12.0 REQUIRED) file(GLOB_RECURSE SOURCE_FILES "${PROJECT_SOURCE_DIR}/../src/pycolmap/*.cc") pybind11_add_module(pycolmap ${SOURCE_FILES}) diff --git a/src/pycolmap/pybind11_extension.h b/src/pycolmap/pybind11_extension.h index d7516c7fd1..8756275061 100644 --- a/src/pycolmap/pybind11_extension.h +++ b/src/pycolmap/pybind11_extension.h @@ -128,168 +128,4 @@ class class_ext_ : public class_ { } }; -// Fix long-standing bug https://github.com/pybind/pybind11/issues/4529 -// TODO(sarlinpe): remove when https://github.com/pybind/pybind11/pull/4972 -// appears in the next release of pybind11. -template , - typename... Args> -class_ bind_map_fix(handle scope, - const std::string& name, - Args&&... args) { - using KeyType = typename Map::key_type; - using MappedType = typename Map::mapped_type; - using StrippedKeyType = detail::remove_cvref_t; - using StrippedMappedType = detail::remove_cvref_t; - using KeysView = detail::keys_view; - using ValuesView = detail::values_view; - using ItemsView = detail::items_view; - using Class_ = class_; - - // If either type is a non-module-local bound type then make the map binding - // non-local as well; otherwise (e.g. both types are either module-local or - // converting) the map will be module-local. - auto* tinfo = detail::get_type_info(typeid(MappedType)); - bool local = !tinfo || tinfo->module_local; - if (local) { - tinfo = detail::get_type_info(typeid(KeyType)); - local = !tinfo || tinfo->module_local; - } - - Class_ cl(scope, - name.c_str(), - pybind11::module_local(local), - std::forward(args)...); - std::string key_type_name(detail::type_info_description(typeid(KeyType))); - std::string mapped_type_name( - detail::type_info_description(typeid(MappedType))); - - // Wrap KeysView[KeyType] if it wasn't already wrapped - if (!detail::get_type_info(typeid(KeysView))) { - class_ keys_view(scope, - ("KeysView[" + key_type_name + "]").c_str(), - pybind11::module_local(local)); - keys_view.def("__len__", &KeysView::len); - keys_view.def("__iter__", - &KeysView::iter, - keep_alive<0, 1>() /* Essential: keep view alive while - iterator exists */ - ); - keys_view.def( - "__contains__", - static_cast(&KeysView::contains)); - // Fallback for when the object is not of the key type - keys_view.def( - "__contains__", - static_cast(&KeysView::contains)); - } - // Similarly for ValuesView: - if (!detail::get_type_info(typeid(ValuesView))) { - class_ values_view( - scope, - ("ValuesView[" + mapped_type_name + "]").c_str(), - pybind11::module_local(local)); - values_view.def("__len__", &ValuesView::len); - values_view.def("__iter__", - &ValuesView::iter, - keep_alive<0, 1>() /* Essential: keep view alive while - iterator exists */ - ); - } - // Similarly for ItemsView: - if (!detail::get_type_info(typeid(ItemsView))) { - class_ items_view(scope, - ("ItemsView[" + key_type_name + ", ") - .append(mapped_type_name + "]") - .c_str(), - pybind11::module_local(local)); - items_view.def("__len__", &ItemsView::len); - items_view.def("__iter__", - &ItemsView::iter, - keep_alive<0, 1>() /* Essential: keep view alive while - iterator exists */ - ); - } - - cl.def(init<>()); - - // Register stream insertion operator (if possible) - detail::map_if_insertion_operator(cl, name); - - cl.def( - "__bool__", - [](const Map& m) -> bool { return !m.empty(); }, - "Check whether the map is nonempty"); - - cl.def( - "__iter__", - [](Map& m) { return make_key_iterator(m.begin(), m.end()); }, - keep_alive<0, 1>() /* Essential: keep map alive while iterator exists */ - ); - - cl.def( - "keys", - [](Map& m) { - return std::unique_ptr( - new detail::KeysViewImpl(m)); - }, - keep_alive<0, 1>() /* Essential: keep map alive while view exists */ - ); - - cl.def( - "values", - [](Map& m) { - return std::unique_ptr( - new detail::ValuesViewImpl(m)); - }, - keep_alive<0, 1>() /* Essential: keep map alive while view exists */ - ); - - cl.def( - "items", - [](Map& m) { - return std::unique_ptr( - new detail::ItemsViewImpl(m)); - }, - keep_alive<0, 1>() /* Essential: keep map alive while view exists */ - ); - - cl.def( - "__getitem__", - [](Map& m, const KeyType& k) -> MappedType& { - auto it = m.find(k); - if (it == m.end()) { - throw key_error(); - } - return it->second; - }, - return_value_policy::reference_internal // ref + keepalive - ); - - cl.def("__contains__", [](Map& m, const KeyType& k) -> bool { - auto it = m.find(k); - if (it == m.end()) { - return false; - } - return true; - }); - // Fallback for when the object is not of the key type - cl.def("__contains__", [](Map&, const object&) -> bool { return false; }); - - // Assignment provided only if the type is copyable - detail::map_assignment(cl); - - cl.def("__delitem__", [](Map& m, const KeyType& k) { - auto it = m.find(k); - if (it == m.end()) { - throw key_error(); - } - m.erase(it); - }); - - // Always use a lambda in case of `using` declaration - cl.def("__len__", [](const Map& m) { return m.size(); }); - - return cl; -} } // namespace PYBIND11_NAMESPACE diff --git a/src/pycolmap/scene/point3D.cc b/src/pycolmap/scene/point3D.cc index 65acafd5e9..8b3e1c995b 100644 --- a/src/pycolmap/scene/point3D.cc +++ b/src/pycolmap/scene/point3D.cc @@ -21,7 +21,7 @@ using Point3DMap = std::unordered_map; PYBIND11_MAKE_OPAQUE(Point3DMap); void BindPoint3D(py::module& m) { - py::bind_map_fix(m, "MapPoint3DIdToPoint3D") + py::bind_map(m, "MapPoint3DIdToPoint3D") .def("__repr__", [](const Point3DMap& self) { return "MapPoint3DIdToPoint3D(num_points3D=" + std::to_string(self.size()) + ")"; From e54a1583abc6cca0734f19e488d26c8400af36dc Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Tue, 9 Apr 2024 22:23:33 +0200 Subject: [PATCH 10/42] move the make_opaque for Point3DMap to reconstruction.cc to fix reconstruction.points3D performance --- src/pycolmap/scene/point3D.cc | 4 +--- src/pycolmap/scene/reconstruction.cc | 3 +++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/pycolmap/scene/point3D.cc b/src/pycolmap/scene/point3D.cc index 8b3e1c995b..e8f3fa6603 100644 --- a/src/pycolmap/scene/point3D.cc +++ b/src/pycolmap/scene/point3D.cc @@ -17,10 +17,8 @@ using namespace colmap; namespace py = pybind11; -using Point3DMap = std::unordered_map; -PYBIND11_MAKE_OPAQUE(Point3DMap); - void BindPoint3D(py::module& m) { + using Point3DMap = std::unordered_map; py::bind_map(m, "MapPoint3DIdToPoint3D") .def("__repr__", [](const Point3DMap& self) { return "MapPoint3DIdToPoint3D(num_points3D=" + diff --git a/src/pycolmap/scene/reconstruction.cc b/src/pycolmap/scene/reconstruction.cc index 7c50c03484..4736a1788c 100644 --- a/src/pycolmap/scene/reconstruction.cc +++ b/src/pycolmap/scene/reconstruction.cc @@ -22,6 +22,9 @@ using namespace colmap; using namespace pybind11::literals; namespace py = pybind11; +using Point3DMap = std::unordered_map; +PYBIND11_MAKE_OPAQUE(Point3DMap); + bool ExistsReconstructionText(const std::string& path) { return (ExistsFile(JoinPaths(path, "cameras.txt")) && ExistsFile(JoinPaths(path, "images.txt")) && From 26f61b67c1125708cdf00c4f7c08b944d1e3a5e7 Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Tue, 9 Apr 2024 22:24:59 +0200 Subject: [PATCH 11/42] add reconstruction bindings for accessing members directly by id. --- src/pycolmap/scene/reconstruction.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/pycolmap/scene/reconstruction.cc b/src/pycolmap/scene/reconstruction.cc index 4736a1788c..a3674312f8 100644 --- a/src/pycolmap/scene/reconstruction.cc +++ b/src/pycolmap/scene/reconstruction.cc @@ -80,13 +80,17 @@ void BindReconstruction(py::module& m) { .def_property_readonly("images", &Reconstruction::Images, py::return_value_policy::reference_internal) + .def("image", py::overload_cast(&Reconstruction::Image)) .def_property_readonly("image_pairs", &Reconstruction::ImagePairs) + .def("image_pair", py::overload_cast(&Reconstruction::ImagePair)) .def_property_readonly("cameras", &Reconstruction::Cameras, py::return_value_policy::reference_internal) + .def("camera", py::overload_cast(&Reconstruction::Camera)) .def_property_readonly("points3D", &Reconstruction::Points3D, py::return_value_policy::reference_internal) + .def("point3D", py::overload_cast(&Reconstruction::Point3D)) .def("point3D_ids", &Reconstruction::Point3DIds) .def("reg_image_ids", &Reconstruction::RegImageIds) .def("exists_camera", &Reconstruction::ExistsCamera) From b6dde462371c2bb1bf65588d6fc501a1155b5f6a Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Tue, 9 Apr 2024 22:56:32 +0200 Subject: [PATCH 12/42] fix format. --- src/pycolmap/scene/reconstruction.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pycolmap/scene/reconstruction.cc b/src/pycolmap/scene/reconstruction.cc index a3674312f8..aae8691d27 100644 --- a/src/pycolmap/scene/reconstruction.cc +++ b/src/pycolmap/scene/reconstruction.cc @@ -82,7 +82,8 @@ void BindReconstruction(py::module& m) { py::return_value_policy::reference_internal) .def("image", py::overload_cast(&Reconstruction::Image)) .def_property_readonly("image_pairs", &Reconstruction::ImagePairs) - .def("image_pair", py::overload_cast(&Reconstruction::ImagePair)) + .def("image_pair", + py::overload_cast(&Reconstruction::ImagePair)) .def_property_readonly("cameras", &Reconstruction::Cameras, py::return_value_policy::reference_internal) From 8daee6b57085794b4ba4c35dc6ccf53bc5105ea9 Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Thu, 11 Apr 2024 20:31:47 +0200 Subject: [PATCH 13/42] minor. --- pycolmap/custom_bundle_adjustment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycolmap/custom_bundle_adjustment.py b/pycolmap/custom_bundle_adjustment.py index 66ef612aba..b9da7858db 100644 --- a/pycolmap/custom_bundle_adjustment.py +++ b/pycolmap/custom_bundle_adjustment.py @@ -131,7 +131,7 @@ def adjust_local_bundle(mapper, mapper_options, ba_options, tri_options, image_i # down the local bundle adjustment significantly. variable_point3D_ids = set() for point3D_id in list(point3D_ids): - point3D = reconstruction.points3D[point3D_id] + point3D = reconstruction.point3D(point3D_id) kMaxTrackLength = 15 if (point3D.error == -1.) or point3D.track.length() <= kMaxTrackLength: ba_config.add_variable_point(point3D_id) From 962a77674946219f268ddbd53e9ae6e7c61c5095 Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Thu, 11 Apr 2024 21:43:40 +0200 Subject: [PATCH 14/42] useless fix. --- src/colmap/estimators/bundle_adjustment.cc | 8 ++++---- src/colmap/estimators/bundle_adjustment.h | 4 ++-- src/pycolmap/estimators/bundle_adjustment.cc | 4 +--- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/colmap/estimators/bundle_adjustment.cc b/src/colmap/estimators/bundle_adjustment.cc index bfda5f23f8..285c7367e8 100644 --- a/src/colmap/estimators/bundle_adjustment.cc +++ b/src/colmap/estimators/bundle_adjustment.cc @@ -292,8 +292,8 @@ const BundleAdjustmentOptions& BundleAdjuster::Options() const { const BundleAdjustmentConfig& BundleAdjuster::Config() const { return config_; } -const std::shared_ptr& BundleAdjuster::Problem() const { - return problem_; +ceres::Problem* BundleAdjuster::Problem() { + return problem_.get(); } const ceres::Solver::Summary& BundleAdjuster::Summary() const { @@ -309,7 +309,7 @@ ceres::Problem* BundleAdjuster::SetUpProblem( // Initialize an empty problem ceres::Problem::Options problem_options; problem_options.loss_function_ownership = ceres::DO_NOT_TAKE_OWNERSHIP; - problem_ = std::make_shared(problem_options); + problem_ = std::make_unique(problem_options); // Set up problem // Warning: AddPointsToProblem assumes that AddImageToProblem is called first. @@ -594,7 +594,7 @@ ceres::Problem* RigBundleAdjuster::SetUpProblem( // Initialize an empty problem ceres::Problem::Options problem_options; problem_options.loss_function_ownership = ceres::DO_NOT_TAKE_OWNERSHIP; - problem_ = std::make_shared(problem_options); + problem_ = std::make_unique(problem_options); // Set up problem ComputeCameraRigPoses(*reconstruction, *camera_rigs); diff --git a/src/colmap/estimators/bundle_adjustment.h b/src/colmap/estimators/bundle_adjustment.h index 70bce22b6d..1bc0c685d8 100644 --- a/src/colmap/estimators/bundle_adjustment.h +++ b/src/colmap/estimators/bundle_adjustment.h @@ -183,7 +183,7 @@ class BundleAdjuster { const BundleAdjustmentOptions& Options() const; const BundleAdjustmentConfig& Config() const; // Get the Ceres problem after the last call to "set_up" - const std::shared_ptr& Problem() const; + ceres::Problem* Problem(); // Get the Ceres solver summary for the last call to `Solve`. const ceres::Solver::Summary& Summary() const; @@ -202,7 +202,7 @@ class BundleAdjuster { const BundleAdjustmentOptions options_; BundleAdjustmentConfig config_; - std::shared_ptr problem_; + std::unique_ptr problem_; ceres::Solver::Summary summary_; std::unordered_set camera_ids_; std::unordered_map point3D_num_observations_; diff --git a/src/pycolmap/estimators/bundle_adjustment.cc b/src/pycolmap/estimators/bundle_adjustment.cc index 6510fba1cc..fae66cb1f9 100644 --- a/src/pycolmap/estimators/bundle_adjustment.cc +++ b/src/pycolmap/estimators/bundle_adjustment.cc @@ -88,9 +88,7 @@ void BindBundleAdjuster(py::module& m) { &BundleAdjuster::SetUpSolverOptions, "problem"_a, "input_solver_options"_a) - .def("get_problem", - &BundleAdjuster::Problem, - py::return_value_policy::take_ownership) + .def_property_readonly("problem", &BundleAdjuster::Problem) .def_property_readonly("options", &BundleAdjuster::Options) .def_property_readonly("config", &BundleAdjuster::Config) .def_property_readonly("summary", From 9d80081760a038cd114f6c411fe61596b1adfaf3 Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Thu, 11 Apr 2024 21:48:01 +0200 Subject: [PATCH 15/42] push debug code. --- pycolmap/custom_bundle_adjustment.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pycolmap/custom_bundle_adjustment.py b/pycolmap/custom_bundle_adjustment.py index b9da7858db..0aa0490eee 100644 --- a/pycolmap/custom_bundle_adjustment.py +++ b/pycolmap/custom_bundle_adjustment.py @@ -12,12 +12,17 @@ def solve_bundle_adjustment(reconstruction, ba_options, ba_config): bundle_adjuster.solve(reconstruction) return bundle_adjuster.summary """ +def solve_bundle_adjustment(reconstruction, ba_options, ba_config): + bundle_adjuster = pycolmap.BundleAdjuster(ba_options, ba_config) problem = bundle_adjuster.set_up_problem(reconstruction, pyceres.TrivialLoss()) # TODO if problem.num_residuals() == 0: return None solver_options = bundle_adjuster.set_up_solver_options(problem, bundle_adjuster.options.solver_options) summary = pyceres.SolverSummary() + import pdb + pdb.set_trace() pyceres.solve(solver_options, problem, summary) + # pyceres.solve(solver_options, bundle_adjuster.problem, summary) return summary """ From 78ccc9b75053fc949a545f5eacf31e80794a74e5 Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Thu, 11 Apr 2024 21:48:32 +0200 Subject: [PATCH 16/42] fix format. --- src/colmap/estimators/bundle_adjustment.cc | 4 +--- src/pycolmap/scene/reconstruction.cc | 3 ++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/colmap/estimators/bundle_adjustment.cc b/src/colmap/estimators/bundle_adjustment.cc index 285c7367e8..474f707bf7 100644 --- a/src/colmap/estimators/bundle_adjustment.cc +++ b/src/colmap/estimators/bundle_adjustment.cc @@ -292,9 +292,7 @@ const BundleAdjustmentOptions& BundleAdjuster::Options() const { const BundleAdjustmentConfig& BundleAdjuster::Config() const { return config_; } -ceres::Problem* BundleAdjuster::Problem() { - return problem_.get(); -} +ceres::Problem* BundleAdjuster::Problem() { return problem_.get(); } const ceres::Solver::Summary& BundleAdjuster::Summary() const { return summary_; diff --git a/src/pycolmap/scene/reconstruction.cc b/src/pycolmap/scene/reconstruction.cc index 3e408fa640..453de2df3b 100644 --- a/src/pycolmap/scene/reconstruction.cc +++ b/src/pycolmap/scene/reconstruction.cc @@ -79,7 +79,8 @@ void BindReconstruction(py::module& m) { py::return_value_policy::reference_internal) .def("image", py::overload_cast(&Reconstruction::Image)) .def_property_readonly("image_pairs", &Reconstruction::ImagePairs) - .def("image_pair", py::overload_cast(&Reconstruction::ImagePair)) + .def("image_pair", + py::overload_cast(&Reconstruction::ImagePair)) .def_property_readonly("cameras", &Reconstruction::Cameras, py::return_value_policy::reference_internal) From c4793d9a9485a4f39ed52f9456a2335e545b3c3b Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Thu, 18 Apr 2024 09:00:40 +0200 Subject: [PATCH 17/42] merge with main. --- src/pycolmap/scene/point3D.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pycolmap/scene/point3D.cc b/src/pycolmap/scene/point3D.cc index e8f3fa6603..8b3e1c995b 100644 --- a/src/pycolmap/scene/point3D.cc +++ b/src/pycolmap/scene/point3D.cc @@ -17,8 +17,10 @@ using namespace colmap; namespace py = pybind11; +using Point3DMap = std::unordered_map; +PYBIND11_MAKE_OPAQUE(Point3DMap); + void BindPoint3D(py::module& m) { - using Point3DMap = std::unordered_map; py::bind_map(m, "MapPoint3DIdToPoint3D") .def("__repr__", [](const Point3DMap& self) { return "MapPoint3DIdToPoint3D(num_points3D=" + From 7f168a6b5bd6fd0e95b9dd042d1a014af01464e9 Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Sat, 20 Apr 2024 15:17:28 +0200 Subject: [PATCH 18/42] update. --- pycolmap/custom_bundle_adjustment.py | 135 ++++++++++++++++++++++++++- 1 file changed, 134 insertions(+), 1 deletion(-) diff --git a/pycolmap/custom_bundle_adjustment.py b/pycolmap/custom_bundle_adjustment.py index 0aa0490eee..528aecc6d3 100644 --- a/pycolmap/custom_bundle_adjustment.py +++ b/pycolmap/custom_bundle_adjustment.py @@ -7,10 +7,143 @@ from pycolmap import logging import copy +class PyBundleAdjuster(object): + # Python implementation of COLMAP bundle adjuster with pyceres + def __init__(self, + options: pycolmap.BundleAdjustmentOptions, + config: pycolmap.BundleAdjustmentConfig): + self.options = options + self.config = config + self.problem = pyceres.Problem() + self.summary = pyceres.SolverSummary() + self.camera_ids = set() + self.point3D_num_observations = dict() + + def solve(self, reconstruction: pycolmap.Reconstruction): + loss = pyceres.TrivialLoss() # TODO: use loss from config + self.set_up_problem(reconstruction, loss) + if self.problem.num_residuals == 0: + return False + solver_options = self.set_up_solver_options(self.problem, self.options.solver_options) + pyceres.solve(solver_options, self.problem, self.summary) + if self.options.print_summary: + logging.verbose(1, self.summary) # TODO + return True + + def set_up_problem(self, + reconstruction: pycolmap.Reconstruction, + loss: pyceres.LossFunction): + assert reconstruction is not None + self.problem = pyceres.Problem() + for image_id in self.config.image_ids: + self.add_image_to_problem(image_id, reconstruction, loss) + for point3D_id in self.config.variable_point3D_ids: + self.add_point_to_problem(point3D_id, reconstruction, loss) + for point3D_id in self.config.constant_point3D_ids: + self.add_point_to_problem(point3D_id, reconstruction, loss) + self.parameterize_cameras(reconstruction) + self.parameterize_points(reconstruction) + return self.problem + + def set_up_solver_options(self, + problem: pyceres.Problem, + solver_options: pyceres.SolverOptions): + bundle_adjuster = pycolmap.BundleAdjuster(self.options, self.config) + return bundle_adjuster.set_up_solver_options(problem, solver_options) + + def add_image_to_problem(self, + image_id: int, + reconstruction: pycolmap.Reconstruction, + loss: pyceres.LossFunction): + image = reconstruction.images[image_id] + pose = image.cam_from_world + camera = reconstruction.cameras[image.image_id] + constant_cam_pose = (not self.options.refine_extrinsics) or self.config.has_constant_cam_pose(image.image_id) + num_observations = 0 + for point2D in image.points2D: + if not point2D.has_point3D(): + continue + num_observations += 1 + if point2D.point3D_id not in self.point3D_num_observations: + self.point3D_num_observations[point2D.point3D_id] = 0 + self.point3D_num_observations[point2D.point3D_id] += 1 + point3D = reconstruction.points3D[point2D.point3D_id] + assert point3D.track.length() > 1 + if constant_cam_pose: + cost = pycolmap.cost_functions.ReprojErrorCost(camera.model, pose, point2D.xy) + self.problem.add_residual_block(cost, loss, [point3D.xyz, camera.params]) + else: + cost = pycolmap.cost_functions.ReprojErrorCost(camera.model, point2D.xy) + self.problem.add_residual_block(cost, loss, [pose.rotation.quat, pose.translation, point3D.xyz, camera.params]) + if num_observations > 0: + self.camera_ids.add(image.camera_id) + # Set pose parameterization + if not constant_cam_pose: + self.problem.set_manifold(pose.rotation.quat, pyceres.QuaternionManifold()) + if self.config.has_constant_cam_positions(image_id): + constant_position_idxs = self.config.constant_cam_positions(image_id) + self.problem.set_manifold(pose.translation, pyceres.SubsetManifold(3, constant_position_idxs)) + + + def add_point_to_problem(self, + point3D_id: int, + reconstruction: pycolmap.Reconstruction, + loss: pyceres.LossFunction): + point3D = reconstruction.points3D[point3D_id] + if self.point3D_num_observations[point3D_id] == point3D.track.length(): + return + for track_el in point3D.track.elements: + if self.config.has_image(track_el.image_id): + continue + self.point3D_num_observations[point3D_id] += 1 + image = reconstruction.images[track_el.image_id] + camera = reconstruction.cameras[image.camera_id] + point2D = image.points2D[track_el.point2D_idx] + if image.camera_id not in self.camera_ids: + self.camera_ids.add(image.camera_id) + self.config.set_constant_cam_intrinsics(image.camera_id) + cost = pycolmap.cost_functions.ReprojErrorCost(camera.model, image.cam_from_world, point2D.xy) + self.problem.add_residual_block(cost, loss, [point3D.xyz, camera.params]) + + def parameterize_cameras(self, reconstruction: pycolmap.Reconstruction): + constant_camera = (not self.options.refine_focal_length) and (not self.options.refine_principal_point) and (not options.refine_extra_params) + for camera_id in self.camera_ids: + camera = reconstruction.cameras[camera_id] + if constant_camera or self.config.has_constant_cam_intrinsics(camera_id): + self.problem.set_parameter_block_constant(camera.params) + continue + const_camera_params = [] + if not self.options.refine_focal_length: + const_camera_params.extend(camera.focal_length_idxs()) + if not self.options.refine_principal_point: + const_camera_params.extend(camera.principal_point_idxs()) + if not self.options.refine_extra_params: + const_camera_params.extend(camera.extra_point_idxs()) + if len(const_camera_params) > 0: + self.problem.set_manifold(camera.params, pyceres.SubsetManifold(len(camera.params), const_camera_params)) + + def parameterize_points(self, reconstruction: pycolmap.Reconstruction): + for point3D_id, num_observations in self.point3D_num_observations.items(): + point3D = reconstruction.points3D[point3D_id] + if point3D.track.length() > num_observations: + self.problem.set_parameter_block_constant(point3D.xyz) + for point3D_id in self.config.constant_point3D_ids: + point3D = reconstruction.points3D[point3D_id] + self.problem.set_parameter_block_constant(point3D.xyz) + +""" def solve_bundle_adjustment(reconstruction, ba_options, ba_config): - bundle_adjuster = pycolmap.BundleAdjuster(ba_options, ba_config) + bundle_adjuster = PyBundleAdjuster(ba_options, ba_config) + bundle_adjuster.solve(reconstruction) + return bundle_adjuster.summary +""" + +def solve_bundle_adjustment(reconstruction, ba_options, ba_config): + # bundle_adjuster = pycolmap.BundleAdjuster(ba_options, ba_config) + bundle_adjuster = PyBundleAdjuster(ba_options, ba_config) bundle_adjuster.solve(reconstruction) return bundle_adjuster.summary + """ def solve_bundle_adjustment(reconstruction, ba_options, ba_config): bundle_adjuster = pycolmap.BundleAdjuster(ba_options, ba_config) From 07c676a82fd80e3a93b94337c75f588474b3cebf Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Sat, 20 Apr 2024 16:17:19 +0200 Subject: [PATCH 19/42] update. --- pycolmap/custom_bundle_adjustment.py | 31 ++++++---------------------- src/pycolmap/scene/image.cc | 1 + 2 files changed, 7 insertions(+), 25 deletions(-) diff --git a/pycolmap/custom_bundle_adjustment.py b/pycolmap/custom_bundle_adjustment.py index 528aecc6d3..2f17c3630e 100644 --- a/pycolmap/custom_bundle_adjustment.py +++ b/pycolmap/custom_bundle_adjustment.py @@ -90,15 +90,18 @@ def add_point_to_problem(self, reconstruction: pycolmap.Reconstruction, loss: pyceres.LossFunction): point3D = reconstruction.points3D[point3D_id] - if self.point3D_num_observations[point3D_id] == point3D.track.length(): - return + if point3D_id in self.point3D_num_observations: + if self.point3D_num_observations[point3D_id] == point3D.track.length(): + return + else: + self.point3D_num_observations[point3D_id] = 0 for track_el in point3D.track.elements: if self.config.has_image(track_el.image_id): continue self.point3D_num_observations[point3D_id] += 1 image = reconstruction.images[track_el.image_id] camera = reconstruction.cameras[image.camera_id] - point2D = image.points2D[track_el.point2D_idx] + point2D = image.point2D(track_el.point2D_idx) if image.camera_id not in self.camera_ids: self.camera_ids.add(image.camera_id) self.config.set_constant_cam_intrinsics(image.camera_id) @@ -131,34 +134,12 @@ def parameterize_points(self, reconstruction: pycolmap.Reconstruction): point3D = reconstruction.points3D[point3D_id] self.problem.set_parameter_block_constant(point3D.xyz) -""" -def solve_bundle_adjustment(reconstruction, ba_options, ba_config): - bundle_adjuster = PyBundleAdjuster(ba_options, ba_config) - bundle_adjuster.solve(reconstruction) - return bundle_adjuster.summary -""" - def solve_bundle_adjustment(reconstruction, ba_options, ba_config): # bundle_adjuster = pycolmap.BundleAdjuster(ba_options, ba_config) bundle_adjuster = PyBundleAdjuster(ba_options, ba_config) bundle_adjuster.solve(reconstruction) return bundle_adjuster.summary -""" -def solve_bundle_adjustment(reconstruction, ba_options, ba_config): - bundle_adjuster = pycolmap.BundleAdjuster(ba_options, ba_config) - problem = bundle_adjuster.set_up_problem(reconstruction, pyceres.TrivialLoss()) # TODO - if problem.num_residuals() == 0: - return None - solver_options = bundle_adjuster.set_up_solver_options(problem, bundle_adjuster.options.solver_options) - summary = pyceres.SolverSummary() - import pdb - pdb.set_trace() - pyceres.solve(solver_options, problem, summary) - # pyceres.solve(solver_options, bundle_adjuster.problem, summary) - return summary -""" - def adjust_global_bundle(mapper, mapper_options, ba_options): """Equivalent to mapper.adjust_global_bundle(...)""" reconstruction = mapper.get_reconstruction() diff --git a/src/pycolmap/scene/image.cc b/src/pycolmap/scene/image.cc index 966f720830..37850222f5 100644 --- a/src/pycolmap/scene/image.cc +++ b/src/pycolmap/scene/image.cc @@ -110,6 +110,7 @@ void BindImage(py::module& m) { self.CamFromWorldPrior() = cam_from_world; }, "The pose prior of the image, e.g. extracted from EXIF tags.") + .def("point2D", py::overload_cast(&Image::Point2D)) .def_property("points2D", py::overload_cast<>(&Image::Points2D), py::overload_cast&>( From 49020ca4a64f1fbfb48058a3837a6d70190bc08f Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Sat, 20 Apr 2024 16:24:12 +0200 Subject: [PATCH 20/42] fix formatting. --- pycolmap/custom_bundle_adjustment.py | 265 +++++++++++++++++++------ pycolmap/custom_incremental_mapping.py | 1 + 2 files changed, 203 insertions(+), 63 deletions(-) diff --git a/pycolmap/custom_bundle_adjustment.py b/pycolmap/custom_bundle_adjustment.py index 2f17c3630e..0bf79c211f 100644 --- a/pycolmap/custom_bundle_adjustment.py +++ b/pycolmap/custom_bundle_adjustment.py @@ -2,16 +2,20 @@ Python reimplementation of the bundle adjustment for the incremental mapper of C++ with equivalent logic. As a result, one can add customized residuals on top of the exposed ceres problem from conventional bundle adjustment. """ + import pyceres import pycolmap from pycolmap import logging import copy + class PyBundleAdjuster(object): # Python implementation of COLMAP bundle adjuster with pyceres - def __init__(self, - options: pycolmap.BundleAdjustmentOptions, - config: pycolmap.BundleAdjustmentConfig): + def __init__( + self, + options: pycolmap.BundleAdjustmentOptions, + config: pycolmap.BundleAdjustmentConfig, + ): self.options = options self.config = config self.problem = pyceres.Problem() @@ -20,19 +24,23 @@ def __init__(self, self.point3D_num_observations = dict() def solve(self, reconstruction: pycolmap.Reconstruction): - loss = pyceres.TrivialLoss() # TODO: use loss from config + loss = pyceres.TrivialLoss() # TODO: use loss from config self.set_up_problem(reconstruction, loss) if self.problem.num_residuals == 0: return False - solver_options = self.set_up_solver_options(self.problem, self.options.solver_options) + solver_options = self.set_up_solver_options( + self.problem, self.options.solver_options + ) pyceres.solve(solver_options, self.problem, self.summary) if self.options.print_summary: - logging.verbose(1, self.summary) # TODO + logging.verbose(1, self.summary) # TODO return True - def set_up_problem(self, - reconstruction: pycolmap.Reconstruction, - loss: pyceres.LossFunction): + def set_up_problem( + self, + reconstruction: pycolmap.Reconstruction, + loss: pyceres.LossFunction, + ): assert reconstruction is not None self.problem = pyceres.Problem() for image_id in self.config.image_ids: @@ -45,20 +53,24 @@ def set_up_problem(self, self.parameterize_points(reconstruction) return self.problem - def set_up_solver_options(self, - problem: pyceres.Problem, - solver_options: pyceres.SolverOptions): + def set_up_solver_options( + self, problem: pyceres.Problem, solver_options: pyceres.SolverOptions + ): bundle_adjuster = pycolmap.BundleAdjuster(self.options, self.config) return bundle_adjuster.set_up_solver_options(problem, solver_options) - def add_image_to_problem(self, - image_id: int, - reconstruction: pycolmap.Reconstruction, - loss: pyceres.LossFunction): + def add_image_to_problem( + self, + image_id: int, + reconstruction: pycolmap.Reconstruction, + loss: pyceres.LossFunction, + ): image = reconstruction.images[image_id] pose = image.cam_from_world camera = reconstruction.cameras[image.image_id] - constant_cam_pose = (not self.options.refine_extrinsics) or self.config.has_constant_cam_pose(image.image_id) + constant_cam_pose = ( + not self.options.refine_extrinsics + ) or self.config.has_constant_cam_pose(image.image_id) num_observations = 0 for point2D in image.points2D: if not point2D.has_point3D(): @@ -70,28 +82,54 @@ def add_image_to_problem(self, point3D = reconstruction.points3D[point2D.point3D_id] assert point3D.track.length() > 1 if constant_cam_pose: - cost = pycolmap.cost_functions.ReprojErrorCost(camera.model, pose, point2D.xy) - self.problem.add_residual_block(cost, loss, [point3D.xyz, camera.params]) + cost = pycolmap.cost_functions.ReprojErrorCost( + camera.model, pose, point2D.xy + ) + self.problem.add_residual_block( + cost, loss, [point3D.xyz, camera.params] + ) else: - cost = pycolmap.cost_functions.ReprojErrorCost(camera.model, point2D.xy) - self.problem.add_residual_block(cost, loss, [pose.rotation.quat, pose.translation, point3D.xyz, camera.params]) + cost = pycolmap.cost_functions.ReprojErrorCost( + camera.model, point2D.xy + ) + self.problem.add_residual_block( + cost, + loss, + [ + pose.rotation.quat, + pose.translation, + point3D.xyz, + camera.params, + ], + ) if num_observations > 0: self.camera_ids.add(image.camera_id) # Set pose parameterization if not constant_cam_pose: - self.problem.set_manifold(pose.rotation.quat, pyceres.QuaternionManifold()) + self.problem.set_manifold( + pose.rotation.quat, pyceres.QuaternionManifold() + ) if self.config.has_constant_cam_positions(image_id): - constant_position_idxs = self.config.constant_cam_positions(image_id) - self.problem.set_manifold(pose.translation, pyceres.SubsetManifold(3, constant_position_idxs)) - + constant_position_idxs = self.config.constant_cam_positions( + image_id + ) + self.problem.set_manifold( + pose.translation, + pyceres.SubsetManifold(3, constant_position_idxs), + ) - def add_point_to_problem(self, - point3D_id: int, - reconstruction: pycolmap.Reconstruction, - loss: pyceres.LossFunction): + def add_point_to_problem( + self, + point3D_id: int, + reconstruction: pycolmap.Reconstruction, + loss: pyceres.LossFunction, + ): point3D = reconstruction.points3D[point3D_id] if point3D_id in self.point3D_num_observations: - if self.point3D_num_observations[point3D_id] == point3D.track.length(): + if ( + self.point3D_num_observations[point3D_id] + == point3D.track.length() + ): return else: self.point3D_num_observations[point3D_id] = 0 @@ -105,14 +143,24 @@ def add_point_to_problem(self, if image.camera_id not in self.camera_ids: self.camera_ids.add(image.camera_id) self.config.set_constant_cam_intrinsics(image.camera_id) - cost = pycolmap.cost_functions.ReprojErrorCost(camera.model, image.cam_from_world, point2D.xy) - self.problem.add_residual_block(cost, loss, [point3D.xyz, camera.params]) + cost = pycolmap.cost_functions.ReprojErrorCost( + camera.model, image.cam_from_world, point2D.xy + ) + self.problem.add_residual_block( + cost, loss, [point3D.xyz, camera.params] + ) def parameterize_cameras(self, reconstruction: pycolmap.Reconstruction): - constant_camera = (not self.options.refine_focal_length) and (not self.options.refine_principal_point) and (not options.refine_extra_params) + constant_camera = ( + (not self.options.refine_focal_length) + and (not self.options.refine_principal_point) + and (not options.refine_extra_params) + ) for camera_id in self.camera_ids: camera = reconstruction.cameras[camera_id] - if constant_camera or self.config.has_constant_cam_intrinsics(camera_id): + if constant_camera or self.config.has_constant_cam_intrinsics( + camera_id + ): self.problem.set_parameter_block_constant(camera.params) continue const_camera_params = [] @@ -123,10 +171,18 @@ def parameterize_cameras(self, reconstruction: pycolmap.Reconstruction): if not self.options.refine_extra_params: const_camera_params.extend(camera.extra_point_idxs()) if len(const_camera_params) > 0: - self.problem.set_manifold(camera.params, pyceres.SubsetManifold(len(camera.params), const_camera_params)) + self.problem.set_manifold( + camera.params, + pyceres.SubsetManifold( + len(camera.params), const_camera_params + ), + ) def parameterize_points(self, reconstruction: pycolmap.Reconstruction): - for point3D_id, num_observations in self.point3D_num_observations.items(): + for ( + point3D_id, + num_observations, + ) in self.point3D_num_observations.items(): point3D = reconstruction.points3D[point3D_id] if point3D.track.length() > num_observations: self.problem.set_parameter_block_constant(point3D.xyz) @@ -134,28 +190,32 @@ def parameterize_points(self, reconstruction: pycolmap.Reconstruction): point3D = reconstruction.points3D[point3D_id] self.problem.set_parameter_block_constant(point3D.xyz) + def solve_bundle_adjustment(reconstruction, ba_options, ba_config): # bundle_adjuster = pycolmap.BundleAdjuster(ba_options, ba_config) bundle_adjuster = PyBundleAdjuster(ba_options, ba_config) bundle_adjuster.solve(reconstruction) return bundle_adjuster.summary + def adjust_global_bundle(mapper, mapper_options, ba_options): """Equivalent to mapper.adjust_global_bundle(...)""" reconstruction = mapper.get_reconstruction() assert reconstruction is not None reg_image_ids = reconstruction.reg_image_ids() if len(reg_image_ids) < 2: - logging.fatal("At least two images must be registered for global bundle-adjustment") + logging.fatal( + "At least two images must be registered for global bundle-adjustment" + ) ba_options_tmp = copy.deepcopy(ba_options) # Use stricter convergence criteria for first registered images - if len(reg_image_ids) < 10: # kMinNumRegImagesForFastBA = 10 - ba_options_tmp.solver_options.function_tolerance /= 10; - ba_options_tmp.solver_options.gradient_tolerance /= 10; - ba_options_tmp.solver_options.parameter_tolerance /= 10; - ba_options_tmp.solver_options.max_num_iterations *= 2; - ba_options_tmp.solver_options.max_linear_solver_iterations = 200; + if len(reg_image_ids) < 10: # kMinNumRegImagesForFastBA = 10 + ba_options_tmp.solver_options.function_tolerance /= 10 + ba_options_tmp.solver_options.gradient_tolerance /= 10 + ba_options_tmp.solver_options.parameter_tolerance /= 10 + ba_options_tmp.solver_options.max_num_iterations *= 2 + ba_options_tmp.solver_options.max_linear_solver_iterations = 200 # Avoid degeneracies in bundle adjustment reconstruction.filter_observations_with_negative_depth() @@ -173,7 +233,9 @@ def adjust_global_bundle(mapper, mapper_options, ba_options): # Fix 7-DOFs of the bundle adjustment problem ba_config.set_constant_cam_pose(reg_image_ids[0]) - if (not mapper_options.fix_existing_images) or (reg_image_ids[1] not in mapper.existing_image_ids): + if (not mapper_options.fix_existing_images) or ( + reg_image_ids[1] not in mapper.existing_image_ids + ): ba_config.set_constant_cam_positions(reg_image_ids[1], [0]) # Run bundle adjustment @@ -182,11 +244,25 @@ def adjust_global_bundle(mapper, mapper_options, ba_options): logging.info(summary.BriefReport()) return True -def iterative_global_refinement(mapper, max_num_refinements, max_refinement_change, mapper_options, ba_options, tri_options, normalize_reconstruction = True): + +def iterative_global_refinement( + mapper, + max_num_refinements, + max_refinement_change, + mapper_options, + ba_options, + tri_options, + normalize_reconstruction=True, +): """Equivalent to mapper.iterative_global_refinement(...)""" reconstruction = mapper.get_reconstruction() mapper.complete_and_merge_tracks(tri_options) - logging.verbose(1, "=> Retriangulated observations: {0}".format(mapper.retriangulate(tri_options))) + logging.verbose( + 1, + "=> Retriangulated observations: {0}".format( + mapper.retriangulate(tri_options) + ), + ) for i in range(max_num_refinements): num_observations = reconstruction.compute_num_observations() # mapper.adjust_global_bundle(mapper_options, ba_options) @@ -195,12 +271,19 @@ def iterative_global_refinement(mapper, max_num_refinements, max_refinement_chan reconstruction.normalize() num_changed_observations = mapper.complete_and_merge_tracks(tri_options) num_changed_observations += mapper.filter_points(mapper_options) - changed = 0 if num_observations == 0 else num_changed_observations / num_observations + changed = ( + 0 + if num_observations == 0 + else num_changed_observations / num_observations + ) logging.verbose(1, "=> Changed observations: {0:.6f}".format(changed)) if changed < max_refinement_change: break -def adjust_local_bundle(mapper, mapper_options, ba_options, tri_options, image_id, point3D_ids): + +def adjust_local_bundle( + mapper, mapper_options, ba_options, tri_options, image_id, point3D_ids +): """Equivalent to mapper.adjust_local_bundle(...)""" reconstruction = mapper.get_reconstruction() assert reconstruction is not None @@ -240,7 +323,9 @@ def adjust_local_bundle(mapper, mapper_options, ba_options, tri_options, image_i elif len(local_bundle) > 1: image_id1, image_id2 = local_bundle[-1], local_bundle[-2] ba_config.set_constant_cam_pose(image_id1) - if (not mapper_options.fix_existing_images) or (image_id2 not in mapper.existing_image_ids): + if (not mapper_options.fix_existing_images) or ( + image_id2 not in mapper.existing_image_ids + ): ba_config.set_constant_cam_positions(image_id2, [0]) # Make sure, we refine all new and short-track 3D points, no matter if @@ -252,42 +337,97 @@ def adjust_local_bundle(mapper, mapper_options, ba_options, tri_options, image_i for point3D_id in list(point3D_ids): point3D = reconstruction.point3D(point3D_id) kMaxTrackLength = 15 - if (point3D.error == -1.) or point3D.track.length() <= kMaxTrackLength: + if ( + point3D.error == -1.0 + ) or point3D.track.length() <= kMaxTrackLength: ba_config.add_variable_point(point3D_id) variable_point3D_ids.add(point3D_id) # Adjust the local bundle - summary = solve_bundle_adjustment(mapper.get_reconstruction(), ba_options, ba_config) + summary = solve_bundle_adjustment( + mapper.get_reconstruction(), ba_options, ba_config + ) logging.info("Local Bundle Adjustment") logging.info(summary.BriefReport()) report.num_adjusted_observations = int(summary.num_residuals / 2) # Merge refined tracks with other existing points - report.num_merged_observations = mapper.get_triangulator().merge_tracks(tri_options, variable_point3D_ids) + report.num_merged_observations = mapper.get_triangulator().merge_tracks( + tri_options, variable_point3D_ids + ) # Complete tracks that may have failed to triangulate before refinement # of camera pose and calibration in bundle adjustment. This may avoid that some points are filtered and it helps for subsequent image registrations - report.num_completed_observations = mapper.get_triangulator().complete_tracks(tri_options, variable_point3D_ids) - report.num_completed_observations += mapper.get_triangulator().complete_image(tri_options, image_id) + report.num_completed_observations = ( + mapper.get_triangulator().complete_tracks( + tri_options, variable_point3D_ids + ) + ) + report.num_completed_observations += ( + mapper.get_triangulator().complete_image(tri_options, image_id) + ) filter_image_ids = set() filter_image_ids.add(image_id) filter_image_ids.update(local_bundle) - report.num_filtered_observations = reconstruction.filter_points3D_in_images(mapper_options.filter_max_reproj_error, mapper_options.filter_min_tri_angle, filter_image_ids) - report.num_filtered_observations += reconstruction.filter_points3D(mapper_options.filter_max_reproj_error, mapper_options.filter_min_tri_angle, point3D_ids) + report.num_filtered_observations = reconstruction.filter_points3D_in_images( + mapper_options.filter_max_reproj_error, + mapper_options.filter_min_tri_angle, + filter_image_ids, + ) + report.num_filtered_observations += reconstruction.filter_points3D( + mapper_options.filter_max_reproj_error, + mapper_options.filter_min_tri_angle, + point3D_ids, + ) return report -def iterative_local_refinement(mapper, max_num_refinements, max_refinement_change, mapper_options, ba_options, tri_options, image_id): + +def iterative_local_refinement( + mapper, + max_num_refinements, + max_refinement_change, + mapper_options, + ba_options, + tri_options, + image_id, +): """Equivalent to mapper.iterative_local_refinement(...)""" ba_options_tmp = copy.deepcopy(ba_options) for i in range(max_num_refinements): # report = mapper.adjust_local_bundle(mapper_options, ba_options_tmp, tri_options, image_id, mapper.get_modified_points3D()) - report = adjust_local_bundle(mapper, mapper_options, ba_options_tmp, tri_options, image_id, mapper.get_modified_points3D()) - logging.verbose(1, "=> Merged observations: {0}".format(report.num_merged_observations)) - logging.verbose(1, "=> Completed observations: {0}".format(report.num_completed_observations)) - logging.verbose(1, "=> Filtered observations: {0}".format(report.num_filtered_observations)) + report = adjust_local_bundle( + mapper, + mapper_options, + ba_options_tmp, + tri_options, + image_id, + mapper.get_modified_points3D(), + ) + logging.verbose( + 1, + "=> Merged observations: {0}".format( + report.num_merged_observations + ), + ) + logging.verbose( + 1, + "=> Completed observations: {0}".format( + report.num_completed_observations + ), + ) + logging.verbose( + 1, + "=> Filtered observations: {0}".format( + report.num_filtered_observations + ), + ) changed = 0 if report.num_adjusted_observations != 0: - changed = (report.num_merged_observations + report.num_completed_observations + report.num_filtered_observations) / report.num_adjusted_observations + changed = ( + report.num_merged_observations + + report.num_completed_observations + + report.num_filtered_observations + ) / report.num_adjusted_observations logging.verbose(1, "=> Changed observations: {0:.6f}".format(changed)) if changed < max_refinement_change: break @@ -295,4 +435,3 @@ def iterative_local_refinement(mapper, max_num_refinements, max_refinement_chang # Only use robust cost function for first iteration ba_options_tmp.loss_function_type = pycolmap.LossFunctionType.TRIVIAL mapper.clear_modified_points3D() - diff --git a/pycolmap/custom_incremental_mapping.py b/pycolmap/custom_incremental_mapping.py index 7e123838fc..1a979faf82 100644 --- a/pycolmap/custom_incremental_mapping.py +++ b/pycolmap/custom_incremental_mapping.py @@ -14,6 +14,7 @@ from pycolmap import logging import custom_bundle_adjustment + def extract_colors(image_path, image_id, reconstruction): if not reconstruction.extract_colors_for_image(image_id, image_path): logging.warning(f"Could not read image {image_id} at path {image_path}") From 8740cde574f65ec9b75ed85578b79d3731ab0ff4 Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Sat, 20 Apr 2024 16:42:01 +0200 Subject: [PATCH 21/42] update. --- pycolmap/custom_bundle_adjustment.py | 8 +++----- src/pycolmap/pipeline/sfm.cc | 1 + 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/pycolmap/custom_bundle_adjustment.py b/pycolmap/custom_bundle_adjustment.py index 0bf79c211f..142b138c94 100644 --- a/pycolmap/custom_bundle_adjustment.py +++ b/pycolmap/custom_bundle_adjustment.py @@ -24,7 +24,7 @@ def __init__( self.point3D_num_observations = dict() def solve(self, reconstruction: pycolmap.Reconstruction): - loss = pyceres.TrivialLoss() # TODO: use loss from config + loss = self.options.create_loss_function() self.set_up_problem(reconstruction, loss) if self.problem.num_residuals == 0: return False @@ -32,8 +32,6 @@ def solve(self, reconstruction: pycolmap.Reconstruction): self.problem, self.options.solver_options ) pyceres.solve(solver_options, self.problem, self.summary) - if self.options.print_summary: - logging.verbose(1, self.summary) # TODO return True def set_up_problem( @@ -192,8 +190,8 @@ def parameterize_points(self, reconstruction: pycolmap.Reconstruction): def solve_bundle_adjustment(reconstruction, ba_options, ba_config): - # bundle_adjuster = pycolmap.BundleAdjuster(ba_options, ba_config) - bundle_adjuster = PyBundleAdjuster(ba_options, ba_config) + bundle_adjuster = pycolmap.BundleAdjuster(ba_options, ba_config) + # bundle_adjuster = PyBundleAdjuster(ba_options, ba_config) bundle_adjuster.solve(reconstruction) return bundle_adjuster.summary diff --git a/src/pycolmap/pipeline/sfm.cc b/src/pycolmap/pipeline/sfm.cc index fac9522ed4..5affdb7f2e 100644 --- a/src/pycolmap/pipeline/sfm.cc +++ b/src/pycolmap/pipeline/sfm.cc @@ -297,6 +297,7 @@ void BindSfM(py::module& m) { auto PyBundleAdjustmentOptions = py::class_(m, "BundleAdjustmentOptions") .def(py::init<>()) + .def("create_loss_function", &BAOpts::CreateLossFunction) .def_readwrite("loss_function_type", &BAOpts::loss_function_type, "Loss function types: Trivial (non-robust) and Cauchy " From e4505d6567816ded6e8d889e87e501fcdd9646b8 Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Sat, 20 Apr 2024 16:43:48 +0200 Subject: [PATCH 22/42] add minor comment. --- pycolmap/custom_bundle_adjustment.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pycolmap/custom_bundle_adjustment.py b/pycolmap/custom_bundle_adjustment.py index 142b138c94..587097abe0 100644 --- a/pycolmap/custom_bundle_adjustment.py +++ b/pycolmap/custom_bundle_adjustment.py @@ -191,6 +191,7 @@ def parameterize_points(self, reconstruction: pycolmap.Reconstruction): def solve_bundle_adjustment(reconstruction, ba_options, ba_config): bundle_adjuster = pycolmap.BundleAdjuster(ba_options, ba_config) + # alternatively, use: # bundle_adjuster = PyBundleAdjuster(ba_options, ba_config) bundle_adjuster.solve(reconstruction) return bundle_adjuster.summary From 2276cb74bcd9aa81267b9e800aaa4dcc7679a61b Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Sat, 20 Apr 2024 16:44:51 +0200 Subject: [PATCH 23/42] minor --- pycolmap/custom_bundle_adjustment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycolmap/custom_bundle_adjustment.py b/pycolmap/custom_bundle_adjustment.py index 587097abe0..23cf178d16 100644 --- a/pycolmap/custom_bundle_adjustment.py +++ b/pycolmap/custom_bundle_adjustment.py @@ -191,7 +191,7 @@ def parameterize_points(self, reconstruction: pycolmap.Reconstruction): def solve_bundle_adjustment(reconstruction, ba_options, ba_config): bundle_adjuster = pycolmap.BundleAdjuster(ba_options, ba_config) - # alternatively, use: + # alternative equivalent python-based bundle adjustment (slower): # bundle_adjuster = PyBundleAdjuster(ba_options, ba_config) bundle_adjuster.solve(reconstruction) return bundle_adjuster.summary From 1f8ee801bc2433fd0b20647f86f832aa509c34bd Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Sat, 20 Apr 2024 17:14:43 +0200 Subject: [PATCH 24/42] minor. --- pycolmap/custom_incremental_mapping.py | 7 +++---- src/colmap/estimators/bundle_adjustment.h | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/pycolmap/custom_incremental_mapping.py b/pycolmap/custom_incremental_mapping.py index 1a979faf82..2dd87b4b0e 100644 --- a/pycolmap/custom_incremental_mapping.py +++ b/pycolmap/custom_incremental_mapping.py @@ -31,7 +31,7 @@ def write_snapshot(reconstruction, snapshot_path): def iterative_global_refinement(options, mapper_options, mapper): logging.info("Retriangulation and Global bundle adjustment") - # mapper.iterative_global_refinement( + # The following is equivalent to mapper.iterative_global_refinement(...) custom_bundle_adjustment.iterative_global_refinement( mapper, options.ba_global_max_refinements, @@ -73,9 +73,7 @@ def initialize_reconstruction( mapper_options, two_view_geometry, *init_pair ) logging.info("Global bundle adjustment") - # mapper.adjust_global_bundle( - # mapper_options, options.get_global_bundle_adjustment() - # ) + # The following is equivalent to: mapper.adjust_global_bundle(...) custom_bundle_adjustment.adjust_global_bundle( mapper, mapper_options, options.get_global_bundle_adjustment() ) @@ -150,6 +148,7 @@ def reconstruct_sub_model(controller, mapper, mapper_options, reconstruction): break if reg_next_success: mapper.triangulate_image(options.get_triangulation(), next_image_id) + # The following is equivalent to mapper.iterative_local_refinement(...) custom_bundle_adjustment.iterative_local_refinement( mapper, options.ba_local_max_refinements, diff --git a/src/colmap/estimators/bundle_adjustment.h b/src/colmap/estimators/bundle_adjustment.h index 1bc0c685d8..15f76193d3 100644 --- a/src/colmap/estimators/bundle_adjustment.h +++ b/src/colmap/estimators/bundle_adjustment.h @@ -184,7 +184,7 @@ class BundleAdjuster { const BundleAdjustmentConfig& Config() const; // Get the Ceres problem after the last call to "set_up" ceres::Problem* Problem(); - // Get the Ceres solver summary for the last call to `Solve`. + // Get the Ceres solver summary after the last call to `Solve`. const ceres::Solver::Summary& Summary() const; private: From 87dbc11bd829c4585874254b6e7e9ea66458d2e3 Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Mon, 22 Apr 2024 13:11:46 +0200 Subject: [PATCH 25/42] update. --- src/colmap/estimators/bundle_adjustment.cc | 2 -- src/colmap/sfm/incremental_mapper.cc | 20 ++++++++++++++------ src/colmap/sfm/incremental_mapper.h | 16 ++++++---------- src/pycolmap/sfm/incremental_mapper.cc | 10 +++++----- 4 files changed, 25 insertions(+), 23 deletions(-) diff --git a/src/colmap/estimators/bundle_adjustment.cc b/src/colmap/estimators/bundle_adjustment.cc index 474f707bf7..e1805daf15 100644 --- a/src/colmap/estimators/bundle_adjustment.cc +++ b/src/colmap/estimators/bundle_adjustment.cc @@ -301,8 +301,6 @@ const ceres::Solver::Summary& BundleAdjuster::Summary() const { ceres::Problem* BundleAdjuster::SetUpProblem( Reconstruction* reconstruction, ceres::LossFunction* loss_function) { THROW_CHECK_NOTNULL(reconstruction); - // THROW_CHECK(!problem_) - // << "Cannot set up problem from the same BundleAdjuster multiple times"; // Initialize an empty problem ceres::Problem::Options problem_options; diff --git a/src/colmap/sfm/incremental_mapper.cc b/src/colmap/sfm/incremental_mapper.cc index 871abf01a0..c8b257e924 100644 --- a/src/colmap/sfm/incremental_mapper.cc +++ b/src/colmap/sfm/incremental_mapper.cc @@ -105,7 +105,7 @@ IncrementalMapper::IncrementalMapper( num_shared_reg_images_(0) {} void IncrementalMapper::BeginReconstruction( - const std::shared_ptr& reconstruction) { + const std::shared_ptr& reconstruction) { THROW_CHECK(reconstruction_ == nullptr); reconstruction_ = reconstruction; reconstruction_->Load(*database_cache_); @@ -834,20 +834,28 @@ size_t IncrementalMapper::FilterPoints(const Options& options) { return num_filtered_observations; } -const std::unordered_set& IncrementalMapper::GetFilteredImages() +const std::shared_ptr& IncrementalMapper::Reconstruction() const { + return reconstruction_; +} + +const std::shared_ptr& +IncrementalMapper::Triangulator() const { + return triangulator_; +} + +const std::unordered_set& IncrementalMapper::FilteredImages() const { return filtered_images_; } -const std::unordered_set& IncrementalMapper::GetExistingImageIds() - const { +const std::unordered_set& IncrementalMapper::ExistingImageIds() const { return existing_image_ids_; } const std::unordered_map& -IncrementalMapper::GetNumRegImagesPerCamera() const { +IncrementalMapper::NumRegImagesPerCamera() const { return num_reg_images_per_camera_; -}; +} size_t IncrementalMapper::NumTotalRegImages() const { return num_total_reg_images_; diff --git a/src/colmap/sfm/incremental_mapper.h b/src/colmap/sfm/incremental_mapper.h index 24f1c11504..e1c85cc02f 100644 --- a/src/colmap/sfm/incremental_mapper.h +++ b/src/colmap/sfm/incremental_mapper.h @@ -243,15 +243,11 @@ class IncrementalMapper { size_t FilterPoints(const Options& options); // Getter functions - const std::shared_ptr& GetReconstruction() const { - return reconstruction_; - }; - const std::shared_ptr& GetTriangulator() const { - return triangulator_; - }; - const std::unordered_set& GetFilteredImages() const; - const std::unordered_set& GetExistingImageIds() const; - const std::unordered_map& GetNumRegImagesPerCamera() const; + const std::shared_ptr& Reconstruction() const; + const std::shared_ptr& Triangulator() const; + const std::unordered_set& FilteredImages() const; + const std::unordered_set& ExistingImageIds() const; + const std::unordered_map& NumRegImagesPerCamera() const; // Number of images that are registered in at least on reconstruction. size_t NumTotalRegImages() const; @@ -300,7 +296,7 @@ class IncrementalMapper { const std::shared_ptr database_cache_; // Class that holds data of the reconstruction. - std::shared_ptr reconstruction_; + std::shared_ptr reconstruction_; // Class that is responsible for incremental triangulation. std::shared_ptr triangulator_; diff --git a/src/pycolmap/sfm/incremental_mapper.cc b/src/pycolmap/sfm/incremental_mapper.cc index 4a4de6069b..01510ede11 100644 --- a/src/pycolmap/sfm/incremental_mapper.cc +++ b/src/pycolmap/sfm/incremental_mapper.cc @@ -301,17 +301,17 @@ void BindIncrementalMapperImpl(py::module& m) { .def("filter_images", &IncrementalMapper::FilterImages, "options"_a) .def("filter_points", &IncrementalMapper::FilterPoints, "options"_a) .def("get_reconstruction", - &IncrementalMapper::GetReconstruction, + &IncrementalMapper::Reconstruction, py::return_value_policy::reference_internal) .def("get_triangulator", - &IncrementalMapper::GetTriangulator, + &IncrementalMapper::Triangulator, py::return_value_policy::reference_internal) .def_property_readonly("filtered_images", - &IncrementalMapper::GetFilteredImages) + &IncrementalMapper::FilteredImages) .def_property_readonly("existing_image_ids", - &IncrementalMapper::GetExistingImageIds) + &IncrementalMapper::ExistingImageIds) .def_property_readonly("num_reg_images_per_camera", - &IncrementalMapper::GetNumRegImagesPerCamera) + &IncrementalMapper::NumRegImagesPerCamera) .def("num_total_reg_images", &IncrementalMapper::NumTotalRegImages) .def("num_shared_reg_images", &IncrementalMapper::NumSharedRegImages) .def("get_modified_points3D", &IncrementalMapper::GetModifiedPoints3D) From 1f7f58494d191b2628a4aa5fc7763e6515bbb333 Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Wed, 24 Apr 2024 17:13:16 +0200 Subject: [PATCH 26/42] update. --- src/colmap/estimators/bundle_adjustment.cc | 13 +++++-------- src/colmap/estimators/bundle_adjustment.h | 10 +++++----- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/colmap/estimators/bundle_adjustment.cc b/src/colmap/estimators/bundle_adjustment.cc index e1805daf15..ebfa395f0a 100644 --- a/src/colmap/estimators/bundle_adjustment.cc +++ b/src/colmap/estimators/bundle_adjustment.cc @@ -298,8 +298,8 @@ const ceres::Solver::Summary& BundleAdjuster::Summary() const { return summary_; } -ceres::Problem* BundleAdjuster::SetUpProblem( - Reconstruction* reconstruction, ceres::LossFunction* loss_function) { +void BundleAdjuster::SetUpProblem(Reconstruction* reconstruction, + ceres::LossFunction* loss_function) { THROW_CHECK_NOTNULL(reconstruction); // Initialize an empty problem @@ -322,7 +322,6 @@ ceres::Problem* BundleAdjuster::SetUpProblem( ParameterizeCameras(reconstruction); ParameterizePoints(reconstruction); - return problem_.get(); } ceres::Solver::Options BundleAdjuster::SetUpSolverOptions( @@ -559,10 +558,9 @@ bool RigBundleAdjuster::Solve(Reconstruction* reconstruction, return true; } -ceres::Problem* RigBundleAdjuster::SetUpProblem( - Reconstruction* reconstruction, - std::vector* camera_rigs, - ceres::LossFunction* loss_function) { +void RigBundleAdjuster::SetUpProblem(Reconstruction* reconstruction, + std::vector* camera_rigs, + ceres::LossFunction* loss_function) { THROW_CHECK_NOTNULL(reconstruction); THROW_CHECK_NOTNULL(camera_rigs); THROW_CHECK(!problem_) @@ -608,7 +606,6 @@ ceres::Problem* RigBundleAdjuster::SetUpProblem( ParameterizeCameras(reconstruction); ParameterizePoints(reconstruction); ParameterizeCameraRigs(reconstruction); - return problem_.get(); } void RigBundleAdjuster::TearDown(Reconstruction* reconstruction, diff --git a/src/colmap/estimators/bundle_adjustment.h b/src/colmap/estimators/bundle_adjustment.h index 15f76193d3..eadd6afc21 100644 --- a/src/colmap/estimators/bundle_adjustment.h +++ b/src/colmap/estimators/bundle_adjustment.h @@ -173,8 +173,8 @@ class BundleAdjuster { bool Solve(Reconstruction* reconstruction); // Set up the problem - ceres::Problem* SetUpProblem(Reconstruction* reconstruction, - ceres::LossFunction* loss_function); + void SetUpProblem(Reconstruction* reconstruction, + ceres::LossFunction* loss_function); ceres::Solver::Options SetUpSolverOptions( const ceres::Problem& problem, const ceres::Solver::Options& input_solver_options) const; @@ -229,9 +229,9 @@ class RigBundleAdjuster : public BundleAdjuster { bool Solve(Reconstruction* reconstruction, std::vector* camera_rigs); - ceres::Problem* SetUpProblem(Reconstruction* reconstruction, - std::vector* camera_rigs, - ceres::LossFunction* loss_function); + void SetUpProblem(Reconstruction* reconstruction, + std::vector* camera_rigs, + ceres::LossFunction* loss_function); void TearDown(Reconstruction* reconstruction, const std::vector& camera_rigs); From 214c22f000cac6e5a491d91c14449cf5e5fdb9ff Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Wed, 24 Apr 2024 17:18:35 +0200 Subject: [PATCH 27/42] update, --- src/pycolmap/estimators/bundle_adjustment.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pycolmap/estimators/bundle_adjustment.cc b/src/pycolmap/estimators/bundle_adjustment.cc index fae66cb1f9..5f401edec5 100644 --- a/src/pycolmap/estimators/bundle_adjustment.cc +++ b/src/pycolmap/estimators/bundle_adjustment.cc @@ -82,8 +82,7 @@ void BindBundleAdjuster(py::module& m) { .def("set_up_problem", &BundleAdjuster::SetUpProblem, "reconstruction"_a, - "loss_function"_a, - py::return_value_policy::take_ownership) + "loss_function"_a) .def("set_up_solver_options", &BundleAdjuster::SetUpSolverOptions, "problem"_a, From d6108962b81083ac929e1fce4f7422b95ca269c9 Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Wed, 24 Apr 2024 17:19:35 +0200 Subject: [PATCH 28/42] update. fix performance. --- pycolmap/custom_bundle_adjustment.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/pycolmap/custom_bundle_adjustment.py b/pycolmap/custom_bundle_adjustment.py index 23cf178d16..1661e2a085 100644 --- a/pycolmap/custom_bundle_adjustment.py +++ b/pycolmap/custom_bundle_adjustment.py @@ -25,8 +25,9 @@ def __init__( def solve(self, reconstruction: pycolmap.Reconstruction): loss = self.options.create_loss_function() - self.set_up_problem(reconstruction, loss) - if self.problem.num_residuals == 0: + self.set_up_problem_cpp(reconstruction, loss) + # self.set_up_problem(reconstruction, loss) + if self.problem.num_residuals() == 0: return False solver_options = self.set_up_solver_options( self.problem, self.options.solver_options @@ -34,6 +35,16 @@ def solve(self, reconstruction: pycolmap.Reconstruction): pyceres.solve(solver_options, self.problem, self.summary) return True + def set_up_problem_cpp( + self, + reconstruction: pycolmap.Reconstruction, + loss: pyceres.LossFunction, + ): + bundle_adjuster = pycolmap.BundleAdjuster(self.options, self.config) + bundle_adjuster.set_up_problem(reconstruction, loss) + self.problem = bundle_adjuster.problem + return self.problem + def set_up_problem( self, reconstruction: pycolmap.Reconstruction, @@ -190,10 +201,12 @@ def parameterize_points(self, reconstruction: pycolmap.Reconstruction): def solve_bundle_adjustment(reconstruction, ba_options, ba_config): - bundle_adjuster = pycolmap.BundleAdjuster(ba_options, ba_config) + # bundle_adjuster = pycolmap.BundleAdjuster(ba_options, ba_config) # alternative equivalent python-based bundle adjustment (slower): - # bundle_adjuster = PyBundleAdjuster(ba_options, ba_config) + bundle_adjuster = PyBundleAdjuster(ba_options, ba_config) bundle_adjuster.solve(reconstruction) + # debug: + # pyceres.solve(pyceres.SolverOptions(), bundle_adjuster.problem, pyceres.SolverSummary()) return bundle_adjuster.summary From 517d91ebf9a1edd8fc25f824b2789e6773e6e2ff Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Wed, 24 Apr 2024 18:03:14 +0200 Subject: [PATCH 29/42] fixed. --- pycolmap/custom_bundle_adjustment.py | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/pycolmap/custom_bundle_adjustment.py b/pycolmap/custom_bundle_adjustment.py index 1661e2a085..39a1750dad 100644 --- a/pycolmap/custom_bundle_adjustment.py +++ b/pycolmap/custom_bundle_adjustment.py @@ -25,8 +25,7 @@ def __init__( def solve(self, reconstruction: pycolmap.Reconstruction): loss = self.options.create_loss_function() - self.set_up_problem_cpp(reconstruction, loss) - # self.set_up_problem(reconstruction, loss) + self.set_up_problem(reconstruction, loss) if self.problem.num_residuals() == 0: return False solver_options = self.set_up_solver_options( @@ -35,16 +34,6 @@ def solve(self, reconstruction: pycolmap.Reconstruction): pyceres.solve(solver_options, self.problem, self.summary) return True - def set_up_problem_cpp( - self, - reconstruction: pycolmap.Reconstruction, - loss: pyceres.LossFunction, - ): - bundle_adjuster = pycolmap.BundleAdjuster(self.options, self.config) - bundle_adjuster.set_up_problem(reconstruction, loss) - self.problem = bundle_adjuster.problem - return self.problem - def set_up_problem( self, reconstruction: pycolmap.Reconstruction, @@ -201,14 +190,15 @@ def parameterize_points(self, reconstruction: pycolmap.Reconstruction): def solve_bundle_adjustment(reconstruction, ba_options, ba_config): - # bundle_adjuster = pycolmap.BundleAdjuster(ba_options, ba_config) + bundle_adjuster = pycolmap.BundleAdjuster(ba_options, ba_config) # alternative equivalent python-based bundle adjustment (slower): - bundle_adjuster = PyBundleAdjuster(ba_options, ba_config) - bundle_adjuster.solve(reconstruction) - # debug: - # pyceres.solve(pyceres.SolverOptions(), bundle_adjuster.problem, pyceres.SolverSummary()) - return bundle_adjuster.summary - + # bundle_adjuster = PyBundleAdjuster(ba_options, ba_config) + loss = ba_options.create_loss_function() + bundle_adjuster.set_up_problem(reconstruction, loss) + solver_options = bundle_adjuster.set_up_solver_options(bundle_adjuster.problem, ba_options.solver_options) + summary = pyceres.SolverSummary() + pyceres.solve(solver_options, bundle_adjuster.problem, summary) + return summary def adjust_global_bundle(mapper, mapper_options, ba_options): """Equivalent to mapper.adjust_global_bundle(...)""" From 58fc7c22413063d2ffad9f7a5e445aecccc79e3b Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Wed, 24 Apr 2024 18:06:49 +0200 Subject: [PATCH 30/42] minor. --- pycolmap/custom_bundle_adjustment.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pycolmap/custom_bundle_adjustment.py b/pycolmap/custom_bundle_adjustment.py index 39a1750dad..f74f39747a 100644 --- a/pycolmap/custom_bundle_adjustment.py +++ b/pycolmap/custom_bundle_adjustment.py @@ -244,7 +244,6 @@ def adjust_global_bundle(mapper, mapper_options, ba_options): summary = solve_bundle_adjustment(reconstruction, ba_options_tmp, ba_config) logging.info("Global Bundle Adjustment") logging.info(summary.BriefReport()) - return True def iterative_global_refinement( From 4f0b199b9b709d22bf7228523fa7eaa7bcebe869 Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Wed, 24 Apr 2024 18:07:03 +0200 Subject: [PATCH 31/42] fix formatting. --- pycolmap/custom_bundle_adjustment.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pycolmap/custom_bundle_adjustment.py b/pycolmap/custom_bundle_adjustment.py index f74f39747a..dc6e0c2f47 100644 --- a/pycolmap/custom_bundle_adjustment.py +++ b/pycolmap/custom_bundle_adjustment.py @@ -195,11 +195,14 @@ def solve_bundle_adjustment(reconstruction, ba_options, ba_config): # bundle_adjuster = PyBundleAdjuster(ba_options, ba_config) loss = ba_options.create_loss_function() bundle_adjuster.set_up_problem(reconstruction, loss) - solver_options = bundle_adjuster.set_up_solver_options(bundle_adjuster.problem, ba_options.solver_options) + solver_options = bundle_adjuster.set_up_solver_options( + bundle_adjuster.problem, ba_options.solver_options + ) summary = pyceres.SolverSummary() pyceres.solve(solver_options, bundle_adjuster.problem, summary) return summary + def adjust_global_bundle(mapper, mapper_options, ba_options): """Equivalent to mapper.adjust_global_bundle(...)""" reconstruction = mapper.get_reconstruction() From a4012a38b8e04b9883bd7e11af178fc45d5eeb17 Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Wed, 24 Apr 2024 18:56:36 +0200 Subject: [PATCH 32/42] add keep_alive to hold the life of loss. --- pycolmap/custom_bundle_adjustment.py | 9 ++++----- src/pycolmap/estimators/bundle_adjustment.cc | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/pycolmap/custom_bundle_adjustment.py b/pycolmap/custom_bundle_adjustment.py index dc6e0c2f47..93436be0c9 100644 --- a/pycolmap/custom_bundle_adjustment.py +++ b/pycolmap/custom_bundle_adjustment.py @@ -193,8 +193,9 @@ def solve_bundle_adjustment(reconstruction, ba_options, ba_config): bundle_adjuster = pycolmap.BundleAdjuster(ba_options, ba_config) # alternative equivalent python-based bundle adjustment (slower): # bundle_adjuster = PyBundleAdjuster(ba_options, ba_config) - loss = ba_options.create_loss_function() - bundle_adjuster.set_up_problem(reconstruction, loss) + bundle_adjuster.set_up_problem( + reconstruction, ba_options.create_loss_function() + ) solver_options = bundle_adjuster.set_up_solver_options( bundle_adjuster.problem, ba_options.solver_options ) @@ -370,9 +371,7 @@ def adjust_local_bundle( mapper.get_triangulator().complete_image(tri_options, image_id) ) - filter_image_ids = set() - filter_image_ids.add(image_id) - filter_image_ids.update(local_bundle) + filter_image_ids = {image_id, *local_bundle} report.num_filtered_observations = reconstruction.filter_points3D_in_images( mapper_options.filter_max_reproj_error, mapper_options.filter_min_tri_angle, diff --git a/src/pycolmap/estimators/bundle_adjustment.cc b/src/pycolmap/estimators/bundle_adjustment.cc index 5f401edec5..9e69bae02f 100644 --- a/src/pycolmap/estimators/bundle_adjustment.cc +++ b/src/pycolmap/estimators/bundle_adjustment.cc @@ -82,7 +82,7 @@ void BindBundleAdjuster(py::module& m) { .def("set_up_problem", &BundleAdjuster::SetUpProblem, "reconstruction"_a, - "loss_function"_a) + "loss_function"_a, py::keep_alive<1, 3>()) .def("set_up_solver_options", &BundleAdjuster::SetUpSolverOptions, "problem"_a, From 4f020ce033ca02befa3f1298e2c7013cb7e8d421 Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Wed, 24 Apr 2024 18:57:45 +0200 Subject: [PATCH 33/42] fix formatting. --- src/pycolmap/estimators/bundle_adjustment.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pycolmap/estimators/bundle_adjustment.cc b/src/pycolmap/estimators/bundle_adjustment.cc index 9e69bae02f..f176c41e1c 100644 --- a/src/pycolmap/estimators/bundle_adjustment.cc +++ b/src/pycolmap/estimators/bundle_adjustment.cc @@ -82,7 +82,8 @@ void BindBundleAdjuster(py::module& m) { .def("set_up_problem", &BundleAdjuster::SetUpProblem, "reconstruction"_a, - "loss_function"_a, py::keep_alive<1, 3>()) + "loss_function"_a, + py::keep_alive<1, 3>()) .def("set_up_solver_options", &BundleAdjuster::SetUpSolverOptions, "problem"_a, From 3e85da761aa68eae1ede3a014d24375bda54c4ca Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Sun, 28 Apr 2024 21:17:37 +0200 Subject: [PATCH 34/42] some updates on syntax. --- pycolmap/custom_bundle_adjustment.py | 41 +++---- src/pycolmap/estimators/bundle_adjustment.cc | 108 +++++++++---------- src/pycolmap/sfm/incremental_mapper.cc | 9 +- 3 files changed, 68 insertions(+), 90 deletions(-) diff --git a/pycolmap/custom_bundle_adjustment.py b/pycolmap/custom_bundle_adjustment.py index 93436be0c9..f929453758 100644 --- a/pycolmap/custom_bundle_adjustment.py +++ b/pycolmap/custom_bundle_adjustment.py @@ -206,7 +206,7 @@ def solve_bundle_adjustment(reconstruction, ba_options, ba_config): def adjust_global_bundle(mapper, mapper_options, ba_options): """Equivalent to mapper.adjust_global_bundle(...)""" - reconstruction = mapper.get_reconstruction() + reconstruction = mapper.reconstruction assert reconstruction is not None reg_image_ids = reconstruction.reg_image_ids() if len(reg_image_ids) < 2: @@ -260,14 +260,10 @@ def iterative_global_refinement( normalize_reconstruction=True, ): """Equivalent to mapper.iterative_global_refinement(...)""" - reconstruction = mapper.get_reconstruction() + reconstruction = mapper.reconstruction mapper.complete_and_merge_tracks(tri_options) - logging.verbose( - 1, - "=> Retriangulated observations: {0}".format( - mapper.retriangulate(tri_options) - ), - ) + num_retriangulated_observations = mapper.retriangulate(tri_options) + logging.verbose(1, f"=> Retriangulated observations: {num_retriangulated_observations}") for i in range(max_num_refinements): num_observations = reconstruction.compute_num_observations() # mapper.adjust_global_bundle(mapper_options, ba_options) @@ -281,7 +277,7 @@ def iterative_global_refinement( if num_observations == 0 else num_changed_observations / num_observations ) - logging.verbose(1, "=> Changed observations: {0:.6f}".format(changed)) + logging.verbose(1, f"=> Changed observations: {changed:.6f}") if changed < max_refinement_change: break @@ -290,7 +286,7 @@ def adjust_local_bundle( mapper, mapper_options, ba_options, tri_options, image_id, point3D_ids ): """Equivalent to mapper.adjust_local_bundle(...)""" - reconstruction = mapper.get_reconstruction() + reconstruction = mapper.reconstruction assert reconstruction is not None report = pycolmap.LocalBundleAdjustmentReport() @@ -350,25 +346,26 @@ def adjust_local_bundle( # Adjust the local bundle summary = solve_bundle_adjustment( - mapper.get_reconstruction(), ba_options, ba_config + mapper.reconstruction, ba_options, ba_config ) logging.info("Local Bundle Adjustment") logging.info(summary.BriefReport()) report.num_adjusted_observations = int(summary.num_residuals / 2) # Merge refined tracks with other existing points - report.num_merged_observations = mapper.get_triangulator().merge_tracks( + report.num_merged_observations = mapper.triangulator.merge_tracks( tri_options, variable_point3D_ids ) # Complete tracks that may have failed to triangulate before refinement - # of camera pose and calibration in bundle adjustment. This may avoid that some points are filtered and it helps for subsequent image registrations + # of camera pose and calibration in bundle adjustment. This may avoid that + # some points are filtered and it helps for subsequent image registrations report.num_completed_observations = ( - mapper.get_triangulator().complete_tracks( + mapper.triangulator.complete_tracks( tri_options, variable_point3D_ids ) ) report.num_completed_observations += ( - mapper.get_triangulator().complete_image(tri_options, image_id) + mapper.triangulator.complete_image(tri_options, image_id) ) filter_image_ids = {image_id, *local_bundle} @@ -408,21 +405,15 @@ def iterative_local_refinement( ) logging.verbose( 1, - "=> Merged observations: {0}".format( - report.num_merged_observations - ), + f"=> Merged observations: {report.num_merged_observations}" ) logging.verbose( 1, - "=> Completed observations: {0}".format( - report.num_completed_observations - ), + f"=> Completed observations: {report.num_completed_observations}" ) logging.verbose( 1, - "=> Filtered observations: {0}".format( - report.num_filtered_observations - ), + f"=> Filtered observations: {report.num_filtered_observations}" ) changed = 0 if report.num_adjusted_observations != 0: @@ -431,7 +422,7 @@ def iterative_local_refinement( + report.num_completed_observations + report.num_filtered_observations ) / report.num_adjusted_observations - logging.verbose(1, "=> Changed observations: {0:.6f}".format(changed)) + logging.verbose(1, f"=> Changed observations: {changed:.6f}") if changed < max_refinement_change: break diff --git a/src/pycolmap/estimators/bundle_adjustment.cc b/src/pycolmap/estimators/bundle_adjustment.cc index f176c41e1c..da9c443423 100644 --- a/src/pycolmap/estimators/bundle_adjustment.cc +++ b/src/pycolmap/estimators/bundle_adjustment.cc @@ -14,65 +14,55 @@ namespace py = pybind11; void BindBundleAdjuster(py::module& m) { using BACfg = BundleAdjustmentConfig; - auto PyBundleAdjustmentConfig = - py::class_(m, "BundleAdjustmentConfig") - .def(py::init<>()) - .def("num_images", &BACfg::NumImages) - .def("num_points", &BACfg::NumPoints) - .def("num_constant_cam_intrinsics", &BACfg::NumConstantCamIntrinsics) - .def("num_constant_cam_poses", &BACfg::NumConstantCamPoses) - .def("num_constant_cam_positions", &BACfg::NumConstantCamPositions) - .def("num_variable_points", &BACfg::NumVariablePoints) - .def("num_constant_points", &BACfg::NumConstantPoints) - .def("num_residuals", &BACfg::NumResiduals, "reconstruction"_a) - .def("add_image", &BACfg::AddImage, "image_id"_a) - .def("has_image", &BACfg::HasImage, "image_id"_a) - .def("remove_image", &BACfg::RemoveImage, "image_id"_a) - .def("set_constant_cam_intrinsics", - &BACfg::SetConstantCamIntrinsics, - "camera_id"_a) - .def("set_variable_cam_intrinsics", - &BACfg::SetVariableCamIntrinsics, - "camera_id"_a) - .def("has_constant_cam_intrinsics", - &BACfg::HasConstantCamIntrinsics, - "camera_id"_a) - .def( - "set_constant_cam_pose", &BACfg::SetConstantCamPose, "image_id"_a) - .def( - "set_variable_cam_pose", &BACfg::SetVariableCamPose, "image_id"_a) - .def( - "has_constant_cam_pose", &BACfg::HasConstantCamPose, "image_id"_a) - .def("set_constant_cam_positions", - &BACfg::SetConstantCamPositions, - "image_id"_a, - "idxs"_a) - .def("remove_variable_cam_positions", - &BACfg::RemoveConstantCamPositions, - "image_id"_a) - .def("has_constant_cam_positions", - &BACfg::HasConstantCamPositions, - "image_id"_a) - .def("add_variable_point", &BACfg::AddVariablePoint, "point3D_id"_a) - .def("add_constant_point", &BACfg::AddConstantPoint, "point3D_id"_a) - .def("has_point", &BACfg::HasPoint, "point3D_id"_a) - .def("has_variable_point", &BACfg::HasVariablePoint, "point3D_id"_a) - .def("has_constant_point", &BACfg::HasConstantPoint, "point3D_id"_a) - .def("remove_variable_point", - &BACfg::RemoveVariablePoint, - "point3D_id"_a) - .def("remove_constant_point", - &BACfg::RemoveConstantPoint, - "point3D_id"_a) - .def_property_readonly("constant_intrinsics", - &BACfg::ConstantIntrinsics) - .def_property_readonly("image_ids", &BACfg::Images) - .def_property_readonly("variable_point3D_ids", &BACfg::VariablePoints) - .def_property_readonly("constant_point3D_ids", &BACfg::ConstantPoints) - .def_property_readonly("constant_cam_poses", &BACfg::ConstantCamPoses) - .def("constant_cam_positions", - &BACfg::ConstantCamPositions, - "image_id"_a); + py::class_ PyBundleAdjustmentConfig(m, "BundleAdjustmentConfig"); + PyBundleAdjustmentConfig.def(py::init<>()) + .def("num_images", &BACfg::NumImages) + .def("num_points", &BACfg::NumPoints) + .def("num_constant_cam_intrinsics", &BACfg::NumConstantCamIntrinsics) + .def("num_constant_cam_poses", &BACfg::NumConstantCamPoses) + .def("num_constant_cam_positions", &BACfg::NumConstantCamPositions) + .def("num_variable_points", &BACfg::NumVariablePoints) + .def("num_constant_points", &BACfg::NumConstantPoints) + .def("num_residuals", &BACfg::NumResiduals, "reconstruction"_a) + .def("add_image", &BACfg::AddImage, "image_id"_a) + .def("has_image", &BACfg::HasImage, "image_id"_a) + .def("remove_image", &BACfg::RemoveImage, "image_id"_a) + .def("set_constant_cam_intrinsics", + &BACfg::SetConstantCamIntrinsics, + "camera_id"_a) + .def("set_variable_cam_intrinsics", + &BACfg::SetVariableCamIntrinsics, + "camera_id"_a) + .def("has_constant_cam_intrinsics", + &BACfg::HasConstantCamIntrinsics, + "camera_id"_a) + .def("set_constant_cam_pose", &BACfg::SetConstantCamPose, "image_id"_a) + .def("set_variable_cam_pose", &BACfg::SetVariableCamPose, "image_id"_a) + .def("has_constant_cam_pose", &BACfg::HasConstantCamPose, "image_id"_a) + .def("set_constant_cam_positions", + &BACfg::SetConstantCamPositions, + "image_id"_a, + "idxs"_a) + .def("remove_variable_cam_positions", + &BACfg::RemoveConstantCamPositions, + "image_id"_a) + .def("has_constant_cam_positions", + &BACfg::HasConstantCamPositions, + "image_id"_a) + .def("add_variable_point", &BACfg::AddVariablePoint, "point3D_id"_a) + .def("add_constant_point", &BACfg::AddConstantPoint, "point3D_id"_a) + .def("has_point", &BACfg::HasPoint, "point3D_id"_a) + .def("has_variable_point", &BACfg::HasVariablePoint, "point3D_id"_a) + .def("has_constant_point", &BACfg::HasConstantPoint, "point3D_id"_a) + .def("remove_variable_point", &BACfg::RemoveVariablePoint, "point3D_id"_a) + .def("remove_constant_point", &BACfg::RemoveConstantPoint, "point3D_id"_a) + .def_property_readonly("constant_intrinsics", &BACfg::ConstantIntrinsics) + .def_property_readonly("image_ids", &BACfg::Images) + .def_property_readonly("variable_point3D_ids", &BACfg::VariablePoints) + .def_property_readonly("constant_point3D_ids", &BACfg::ConstantPoints) + .def_property_readonly("constant_cam_poses", &BACfg::ConstantCamPoses) + .def( + "constant_cam_positions", &BACfg::ConstantCamPositions, "image_id"_a); MakeDataclass(PyBundleAdjustmentConfig); py::class_(m, "BundleAdjuster") diff --git a/src/pycolmap/sfm/incremental_mapper.cc b/src/pycolmap/sfm/incremental_mapper.cc index 01510ede11..ba6e431785 100644 --- a/src/pycolmap/sfm/incremental_mapper.cc +++ b/src/pycolmap/sfm/incremental_mapper.cc @@ -300,12 +300,9 @@ void BindIncrementalMapperImpl(py::module& m) { "normalize_reconstruction"_a = true) .def("filter_images", &IncrementalMapper::FilterImages, "options"_a) .def("filter_points", &IncrementalMapper::FilterPoints, "options"_a) - .def("get_reconstruction", - &IncrementalMapper::Reconstruction, - py::return_value_policy::reference_internal) - .def("get_triangulator", - &IncrementalMapper::Triangulator, - py::return_value_policy::reference_internal) + .def_property_readonly("reconstruction", + &IncrementalMapper::Reconstruction) + .def_property_readonly("triangulator", &IncrementalMapper::Triangulator) .def_property_readonly("filtered_images", &IncrementalMapper::FilteredImages) .def_property_readonly("existing_image_ids", From ee810cbf559950dc78a658b0030a3d7b400e72dd Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Sun, 28 Apr 2024 21:19:47 +0200 Subject: [PATCH 35/42] fix formatting. --- pycolmap/custom_bundle_adjustment.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/pycolmap/custom_bundle_adjustment.py b/pycolmap/custom_bundle_adjustment.py index f929453758..2bf9de25fb 100644 --- a/pycolmap/custom_bundle_adjustment.py +++ b/pycolmap/custom_bundle_adjustment.py @@ -263,7 +263,9 @@ def iterative_global_refinement( reconstruction = mapper.reconstruction mapper.complete_and_merge_tracks(tri_options) num_retriangulated_observations = mapper.retriangulate(tri_options) - logging.verbose(1, f"=> Retriangulated observations: {num_retriangulated_observations}") + logging.verbose( + 1, f"=> Retriangulated observations: {num_retriangulated_observations}" + ) for i in range(max_num_refinements): num_observations = reconstruction.compute_num_observations() # mapper.adjust_global_bundle(mapper_options, ba_options) @@ -359,13 +361,11 @@ def adjust_local_bundle( # Complete tracks that may have failed to triangulate before refinement # of camera pose and calibration in bundle adjustment. This may avoid that # some points are filtered and it helps for subsequent image registrations - report.num_completed_observations = ( - mapper.triangulator.complete_tracks( - tri_options, variable_point3D_ids - ) + report.num_completed_observations = mapper.triangulator.complete_tracks( + tri_options, variable_point3D_ids ) - report.num_completed_observations += ( - mapper.triangulator.complete_image(tri_options, image_id) + report.num_completed_observations += mapper.triangulator.complete_image( + tri_options, image_id ) filter_image_ids = {image_id, *local_bundle} @@ -404,16 +404,13 @@ def iterative_local_refinement( mapper.get_modified_points3D(), ) logging.verbose( - 1, - f"=> Merged observations: {report.num_merged_observations}" + 1, f"=> Merged observations: {report.num_merged_observations}" ) logging.verbose( - 1, - f"=> Completed observations: {report.num_completed_observations}" + 1, f"=> Completed observations: {report.num_completed_observations}" ) logging.verbose( - 1, - f"=> Filtered observations: {report.num_filtered_observations}" + 1, f"=> Filtered observations: {report.num_filtered_observations}" ) changed = 0 if report.num_adjusted_observations != 0: From 67f61f5849fa5d07b62f1d4d99b0baa000f24e26 Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Sun, 28 Apr 2024 21:39:52 +0200 Subject: [PATCH 36/42] make problem_ a shared pointer rather than unique. --- src/colmap/estimators/bundle_adjustment.cc | 8 +++++--- src/colmap/estimators/bundle_adjustment.h | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/colmap/estimators/bundle_adjustment.cc b/src/colmap/estimators/bundle_adjustment.cc index ebfa395f0a..5700bda24f 100644 --- a/src/colmap/estimators/bundle_adjustment.cc +++ b/src/colmap/estimators/bundle_adjustment.cc @@ -292,7 +292,9 @@ const BundleAdjustmentOptions& BundleAdjuster::Options() const { const BundleAdjustmentConfig& BundleAdjuster::Config() const { return config_; } -ceres::Problem* BundleAdjuster::Problem() { return problem_.get(); } +const std::shared_ptr& BundleAdjuster::Problem() { + return problem_; +} const ceres::Solver::Summary& BundleAdjuster::Summary() const { return summary_; @@ -305,7 +307,7 @@ void BundleAdjuster::SetUpProblem(Reconstruction* reconstruction, // Initialize an empty problem ceres::Problem::Options problem_options; problem_options.loss_function_ownership = ceres::DO_NOT_TAKE_OWNERSHIP; - problem_ = std::make_unique(problem_options); + problem_ = std::make_shared(problem_options); // Set up problem // Warning: AddPointsToProblem assumes that AddImageToProblem is called first. @@ -588,7 +590,7 @@ void RigBundleAdjuster::SetUpProblem(Reconstruction* reconstruction, // Initialize an empty problem ceres::Problem::Options problem_options; problem_options.loss_function_ownership = ceres::DO_NOT_TAKE_OWNERSHIP; - problem_ = std::make_unique(problem_options); + problem_ = std::make_shared(problem_options); // Set up problem ComputeCameraRigPoses(*reconstruction, *camera_rigs); diff --git a/src/colmap/estimators/bundle_adjustment.h b/src/colmap/estimators/bundle_adjustment.h index eadd6afc21..185f15eeff 100644 --- a/src/colmap/estimators/bundle_adjustment.h +++ b/src/colmap/estimators/bundle_adjustment.h @@ -183,7 +183,7 @@ class BundleAdjuster { const BundleAdjustmentOptions& Options() const; const BundleAdjustmentConfig& Config() const; // Get the Ceres problem after the last call to "set_up" - ceres::Problem* Problem(); + const std::shared_ptr& Problem(); // Get the Ceres solver summary after the last call to `Solve`. const ceres::Solver::Summary& Summary() const; @@ -202,7 +202,7 @@ class BundleAdjuster { const BundleAdjustmentOptions options_; BundleAdjustmentConfig config_; - std::unique_ptr problem_; + std::shared_ptr problem_; ceres::Solver::Summary summary_; std::unordered_set camera_ids_; std::unordered_map point3D_num_observations_; From 2762e5963a5bc7a9d5a28b055b57f7c4bec889e0 Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Sun, 28 Apr 2024 21:53:36 +0200 Subject: [PATCH 37/42] update. --- pycolmap/custom_bundle_adjustment.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pycolmap/custom_bundle_adjustment.py b/pycolmap/custom_bundle_adjustment.py index 2bf9de25fb..afb9d9889d 100644 --- a/pycolmap/custom_bundle_adjustment.py +++ b/pycolmap/custom_bundle_adjustment.py @@ -275,9 +275,9 @@ def iterative_global_refinement( num_changed_observations = mapper.complete_and_merge_tracks(tri_options) num_changed_observations += mapper.filter_points(mapper_options) changed = ( - 0 - if num_observations == 0 - else num_changed_observations / num_observations + num_changed_observations / num_observations + if num_observations > 0 + else 0 ) logging.verbose(1, f"=> Changed observations: {changed:.6f}") if changed < max_refinement_change: @@ -296,7 +296,7 @@ def adjust_local_bundle( local_bundle = mapper.find_local_bundle(mapper_options, image_id) # Do the bundle adjustment only if there is any connected images - if len(local_bundle) > 0: + if local_bundle: ba_config = pycolmap.BundleAdjustmentConfig() ba_config.add_image(image_id) for local_image_id in local_bundle: @@ -413,7 +413,7 @@ def iterative_local_refinement( 1, f"=> Filtered observations: {report.num_filtered_observations}" ) changed = 0 - if report.num_adjusted_observations != 0: + if report.num_adjusted_observations > 0: changed = ( report.num_merged_observations + report.num_completed_observations From 3133c415e9252277415c0eb052a2d45c48b31e01 Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Sun, 28 Apr 2024 22:00:04 +0200 Subject: [PATCH 38/42] change const ref to value for shared_ptr return type --- src/colmap/estimators/bundle_adjustment.cc | 4 +--- src/colmap/estimators/bundle_adjustment.h | 2 +- src/colmap/sfm/incremental_mapper.cc | 6 +++--- src/colmap/sfm/incremental_mapper.h | 4 ++-- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/colmap/estimators/bundle_adjustment.cc b/src/colmap/estimators/bundle_adjustment.cc index 5700bda24f..1435330142 100644 --- a/src/colmap/estimators/bundle_adjustment.cc +++ b/src/colmap/estimators/bundle_adjustment.cc @@ -292,9 +292,7 @@ const BundleAdjustmentOptions& BundleAdjuster::Options() const { const BundleAdjustmentConfig& BundleAdjuster::Config() const { return config_; } -const std::shared_ptr& BundleAdjuster::Problem() { - return problem_; -} +std::shared_ptr BundleAdjuster::Problem() { return problem_; } const ceres::Solver::Summary& BundleAdjuster::Summary() const { return summary_; diff --git a/src/colmap/estimators/bundle_adjustment.h b/src/colmap/estimators/bundle_adjustment.h index 185f15eeff..2a03c1097a 100644 --- a/src/colmap/estimators/bundle_adjustment.h +++ b/src/colmap/estimators/bundle_adjustment.h @@ -183,7 +183,7 @@ class BundleAdjuster { const BundleAdjustmentOptions& Options() const; const BundleAdjustmentConfig& Config() const; // Get the Ceres problem after the last call to "set_up" - const std::shared_ptr& Problem(); + std::shared_ptr Problem(); // Get the Ceres solver summary after the last call to `Solve`. const ceres::Solver::Summary& Summary() const; diff --git a/src/colmap/sfm/incremental_mapper.cc b/src/colmap/sfm/incremental_mapper.cc index c8b257e924..0b0dbc0313 100644 --- a/src/colmap/sfm/incremental_mapper.cc +++ b/src/colmap/sfm/incremental_mapper.cc @@ -834,13 +834,13 @@ size_t IncrementalMapper::FilterPoints(const Options& options) { return num_filtered_observations; } -const std::shared_ptr& IncrementalMapper::Reconstruction() +std::shared_ptr IncrementalMapper::Reconstruction() const { return reconstruction_; } -const std::shared_ptr& -IncrementalMapper::Triangulator() const { +std::shared_ptr IncrementalMapper::Triangulator() + const { return triangulator_; } diff --git a/src/colmap/sfm/incremental_mapper.h b/src/colmap/sfm/incremental_mapper.h index e1c85cc02f..99baff52ae 100644 --- a/src/colmap/sfm/incremental_mapper.h +++ b/src/colmap/sfm/incremental_mapper.h @@ -243,8 +243,8 @@ class IncrementalMapper { size_t FilterPoints(const Options& options); // Getter functions - const std::shared_ptr& Reconstruction() const; - const std::shared_ptr& Triangulator() const; + std::shared_ptr Reconstruction() const; + std::shared_ptr Triangulator() const; const std::unordered_set& FilteredImages() const; const std::unordered_set& ExistingImageIds() const; const std::unordered_map& NumRegImagesPerCamera() const; From edfb0407d2e6ae659d343126330873d23755a873 Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Sun, 28 Apr 2024 22:14:11 +0200 Subject: [PATCH 39/42] remove the binding of ceres options from pycolmap. --- src/pycolmap/pipeline/sfm.cc | 33 ++------------------------------- 1 file changed, 2 insertions(+), 31 deletions(-) diff --git a/src/pycolmap/pipeline/sfm.cc b/src/pycolmap/pipeline/sfm.cc index 9d9f0a3934..8e6756f541 100644 --- a/src/pycolmap/pipeline/sfm.cc +++ b/src/pycolmap/pipeline/sfm.cc @@ -263,36 +263,6 @@ void BindSfM(py::module& m) { .value("SOFT_L1", BAOpts::LossFunctionType::SOFT_L1) .value("CAUCHY", BAOpts::LossFunctionType::CAUCHY); AddStringToEnumConstructor(PyBALossFunctionType); - using CSOpts = ceres::Solver::Options; - auto PyCeresSolverOptions = - py::class_( - m, - "CeresSolverOptions", - // If ceres::Solver::Options is registered by pycolmap AND a - // downstream library, importing the downstream library results in - // error: - // ImportError: generic_type: type "CeresSolverOptions" is already - // registered! - // Adding a `py::module_local()` fixes this. - // https://github.com/pybind/pybind11/issues/439#issuecomment-1338251822 - py::module_local()) - .def(py::init<>()) - .def_readwrite("function_tolerance", &CSOpts::function_tolerance) - .def_readwrite("gradient_tolerance", &CSOpts::gradient_tolerance) - .def_readwrite("parameter_tolerance", &CSOpts::parameter_tolerance) - .def_readwrite("minimizer_progress_to_stdout", - &CSOpts::minimizer_progress_to_stdout) - .def_readwrite("minimizer_progress_to_stdout", - &CSOpts::minimizer_progress_to_stdout) - .def_readwrite("max_num_iterations", &CSOpts::max_num_iterations) - .def_readwrite("max_linear_solver_iterations", - &CSOpts::max_linear_solver_iterations) - .def_readwrite("max_num_consecutive_invalid_steps", - &CSOpts::max_num_consecutive_invalid_steps) - .def_readwrite("max_consecutive_nonmonotonic_steps", - &CSOpts::max_consecutive_nonmonotonic_steps) - .def_readwrite("num_threads", &CSOpts::num_threads); - MakeDataclass(PyCeresSolverOptions); auto PyBundleAdjustmentOptions = py::class_(m, "BundleAdjustmentOptions") .def(py::init<>()) @@ -330,7 +300,8 @@ void BindSfM(py::module& m) { "due to the overhead of threading. ") .def_readwrite("solver_options", &BAOpts::solver_options, - "Ceres-Solver options."); + "Ceres-Solver options. To be able to use this feature " + "one needs pyceres as a dependency"); MakeDataclass(PyBundleAdjustmentOptions); auto ba_options = PyBundleAdjustmentOptions().cast(); From 9378633bf72aa283aa35bdb8a2abaea12b1656d0 Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Mon, 29 Apr 2024 12:11:17 +0200 Subject: [PATCH 40/42] add minor comments for pyceres. --- src/pycolmap/pipeline/sfm.cc | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/pycolmap/pipeline/sfm.cc b/src/pycolmap/pipeline/sfm.cc index 8e6756f541..c55eac87f7 100644 --- a/src/pycolmap/pipeline/sfm.cc +++ b/src/pycolmap/pipeline/sfm.cc @@ -298,10 +298,11 @@ void BindSfM(py::module& m) { "single-threaded is typically better for small bundle " "adjustment problems " "due to the overhead of threading. ") - .def_readwrite("solver_options", - &BAOpts::solver_options, - "Ceres-Solver options. To be able to use this feature " - "one needs pyceres as a dependency"); + .def_readwrite( + "solver_options", + &BAOpts::solver_options, + "Ceres-Solver options. To be able to use this feature " + "one needs to install pyceres and import it beforehand"); MakeDataclass(PyBundleAdjustmentOptions); auto ba_options = PyBundleAdjustmentOptions().cast(); From e71d297dc098f95e6458ec36189031204040b990 Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Mon, 29 Apr 2024 12:13:04 +0200 Subject: [PATCH 41/42] minor. add pyceres comments to the head of BA python file --- pycolmap/custom_bundle_adjustment.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pycolmap/custom_bundle_adjustment.py b/pycolmap/custom_bundle_adjustment.py index afb9d9889d..1625c83230 100644 --- a/pycolmap/custom_bundle_adjustment.py +++ b/pycolmap/custom_bundle_adjustment.py @@ -1,6 +1,7 @@ """ Python reimplementation of the bundle adjustment for the incremental mapper of C++ with equivalent logic. As a result, one can add customized residuals on top of the exposed ceres problem from conventional bundle adjustment. +pyceres is needed as a dependency for this file. """ import pyceres From 5226714465fd13e54a83ef25dae25c71138540c9 Mon Sep 17 00:00:00 2001 From: B1ueber2y Date: Wed, 1 May 2024 16:48:10 +0200 Subject: [PATCH 42/42] minor + add ceres version string. --- src/pycolmap/main.cc | 2 ++ src/pycolmap/pipeline/sfm.cc | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pycolmap/main.cc b/src/pycolmap/main.cc index b7d5e83d27..b98c564b35 100644 --- a/src/pycolmap/main.cc +++ b/src/pycolmap/main.cc @@ -7,6 +7,7 @@ #include "pycolmap/timer.h" #include "pycolmap/utils.h" +#include #include #include #include @@ -29,6 +30,7 @@ PYBIND11_MODULE(pycolmap, m) { #else m.attr("__version__") = py::str("dev"); #endif + m.attr("__ceres_version__") = py::str(CERES_VERSION_STRING); m.attr("has_cuda") = IsGPU(Device::AUTO); m.attr("COLMAP_version") = py::str(GetVersionInfo()); m.attr("COLMAP_build") = py::str(GetBuildInfo()); diff --git a/src/pycolmap/pipeline/sfm.cc b/src/pycolmap/pipeline/sfm.cc index c55eac87f7..9cce726490 100644 --- a/src/pycolmap/pipeline/sfm.cc +++ b/src/pycolmap/pipeline/sfm.cc @@ -302,7 +302,7 @@ void BindSfM(py::module& m) { "solver_options", &BAOpts::solver_options, "Ceres-Solver options. To be able to use this feature " - "one needs to install pyceres and import it beforehand"); + "one needs to install pyceres and import it beforehand. "); MakeDataclass(PyBundleAdjustmentOptions); auto ba_options = PyBundleAdjustmentOptions().cast();