赞
踩
关于Colmap中BA的Ceres源码
如果遗忘了Ceres的用法,请查看这个博客,理解Ceres的用法
https://blog.csdn.net/cqrtxwd/article/details/78956227
Colmap源码中,关于BA的部分总体由如下部分执行:
BundleAdjuster bundle_adjuster(ba_options, ba_config);
bundle_adjuster.Solve(reconstruction_);
看着是,先实例化一个对象,然后调用Solve方法,那么进入Solve方法:整体代码如下:
bool BundleAdjuster::Solve(Reconstruction* reconstruction) { CHECK_NOTNULL(reconstruction); CHECK(!problem_) << "Cannot use the same BundleAdjuster multiple times"; problem_.reset(new ceres::Problem()); ceres::LossFunction* loss_function = options_.CreateLossFunction(); SetUp(reconstruction, loss_function); if (problem_->NumResiduals() == 0) { return false; } ceres::Solver::Options solver_options = options_.solver_options; // 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) { 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; CHECK(solver_options.IsValid(&solver_error)) << solver_error; ceres::Solve(solver_options, problem_.get(), &summary_); if (solver_options.minimizer_progress_to_stdout) { std::cout << std::endl; } if (options_.print_summary) { PrintHeading2("Bundle adjustment report"); PrintSolverSummary(summary_); } TearDown(reconstruction); return true; }
SetUp(reconstruction, loss_function);
其中首当其冲的是一个 SetUp 方法,这个应该是Ceres中损失的定义,也就是ceres中problem的定义,problems也就是向其中添加残差实现的,然后我们进入其定义看看:
void BundleAdjuster::SetUp(Reconstruction* reconstruction, ceres::LossFunction* loss_function) { // Warning: AddPointsToProblem assumes that AddImageToProblem is called first. // Do not change order of instructions! for (const image_t image_id : config_.Images()) { AddImageToProblem(image_id, reconstruction, loss_function); } for (const auto point3D_id : config_.VariablePoints()) { AddPointToProblem(point3D_id, reconstruction, loss_function); } for (const auto point3D_id : config_.ConstantPoints()) { AddPointToProblem(point3D_id, reconstruction, loss_function); } ParameterizeCameras(reconstruction); ParameterizePoints(reconstruction); }
进入 AddPointToProblem中看看:
void BundleAdjuster::AddPointToProblem(const point3D_t point3D_id, Reconstruction* reconstruction, ceres::LossFunction* loss_function) { Point3D& point3D = reconstruction->Point3D(point3D_id); // Is 3D point already fully contained in the problem? I.e. its entire track // is contained in `variable_image_ids`, `constant_image_ids`, // `constant_x_image_ids`. if (point3D_num_observations_[point3D_id] == point3D.Track().Length()) { return; } for (const auto& track_el : point3D.Track().Elements()) { // Skip observations that were already added in `FillImages`. if (config_.HasImage(track_el.image_id)) { continue; } point3D_num_observations_[point3D_id] += 1; Image& image = reconstruction->Image(track_el.image_id); Camera& camera = reconstruction->Camera(image.CameraId()); const Point2D& point2D = image.Point2D(track_el.point2D_idx); // We do not want to refine the camera of images that are not // part of `constant_image_ids_`, `constant_image_ids_`, // `constant_x_image_ids_`. if (camera_ids_.count(image.CameraId()) == 0) { camera_ids_.insert(image.CameraId()); config_.SetConstantCamera(image.CameraId()); } ceres::CostFunction* cost_function = nullptr; switch (camera.ModelId()) { #define CAMERA_MODEL_CASE(CameraModel) \ case CameraModel::kModelId: \ cost_function = \ BundleAdjustmentConstantPoseCostFunction<CameraModel>::Create( \ image.Qvec(), image.Tvec(), point2D.XY()); \ break; CAMERA_MODEL_SWITCH_CASES #undef CAMERA_MODEL_CASE } problem_->AddResidualBlock(cost_function, loss_function, point3D.XYZ().data(), camera.ParamsData()); } }
我们终于接触到了问题的本质,
class BundleAdjustmentConstantPoseCostFunction { public: //构造函数, 9个内部变量,4个q、3个t、 然后是 x y BundleAdjustmentConstantPoseCostFunction(const Eigen::Vector4d& qvec, const Eigen::Vector3d& tvec, const Eigen::Vector2d& point2D) : qw_(qvec(0)), qx_(qvec(1)), qy_(qvec(2)), qz_(qvec(3)), tx_(tvec(0)), ty_(tvec(1)), tz_(tvec(2)), observed_x_(point2D(0)), observed_y_(point2D(1)) {} // Create函数,构造一个损失函数 static ceres::CostFunction* Create(const Eigen::Vector4d& qvec, const Eigen::Vector3d& tvec, const Eigen::Vector2d& point2D) { // Auto... 这个函数,三个参数分别是:代价函数结构体、输出维度、输入维度 return (new ceres::AutoDiffCostFunction< BundleAdjustmentConstantPoseCostFunction<CameraModel>, 2, 3, CameraModel::kNumParams>( new BundleAdjustmentConstantPoseCostFunction(qvec, tvec, point2D))); } template <typename T> // 这里是通过重载 (), 实现伪函数 bool operator()(const T* const point3D, const T* const camera_params, T* residuals) const { const T qvec[4] = {T(qw_), T(qx_), T(qy_), T(qz_)}; // Rotate and translate. // 讲作为参数的3D点进行旋转和平移 T projection[3]; ceres::UnitQuaternionRotatePoint(qvec, point3D, projection); projection[0] += T(tx_); projection[1] += T(ty_); projection[2] += T(tz_); // Project to image plane. //将3D点投影到图像平面 projection[0] /= projection[2]; projection[1] /= projection[2]; // Distort and transform to pixel space. //图像到像素,把像素坐标的结果保存在 residuals[0] residuals[1]中。 CameraModel::WorldToImage(camera_params, projection[0], projection[1], &residuals[0], &residuals[1]); // Re-projection error. // 这里就是重投影误差!!!! // 计算得到2D像素座标 和 传入的2D点座标的差 作为ceres优化为0 的目标 residuals[0] -= T(observed_x_); residuals[1] -= T(observed_y_); return true; } private: const double qw_; const double qx_; const double qy_; const double qz_; const double tx_; const double ty_; const double tz_; const double observed_x_; const double observed_y_; };
上面的代码算是构建了一个损失函数,也就是优化目标。
添加进problem里面
第一个参数是:优化函数,第二个是ceres提供的核函数优化器,第三个第四个是待优化选项
problem_->AddResidualBlock(cost_function, loss_function,
point3D.XYZ().data(), camera.ParamsData());
继续看第二段代码,可以看到 loss_function = options_.CreateLossFunction(); 这个loss_function就是后面用到的损失函数。 然后后面很长一段都是在配置 solver_options; 我们来看一下
// 这个 linear_solver_type 代表 配置增量方程的解法 // 根据图片数量和solver的数量比较,判断选用是稀疏的还是稠密的 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) { 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 }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。