cartographer的ceres扫描匹配使用在cartographer/mapping/internal/2d/local_trajetory_builder.cc
中
ceres 调用处 1 2 3 4 5 ceres_scan_matcher_.Match (pose_prediction.translation (), initial_ceres_pose, filtered_gravity_aligned_point_cloud, *matching_submap->grid (), pose_observation.get (), &summary);
如果没有进行暴力匹配搜索的话,pose_prediction和initial_ceres_pose 是一样的,后面的是点云,地图,以及两个输出
ceres使用的头文件 位于文件cartographer/mapping/internal/2d/scan_matching/ceres_scan_matcher_2d.h
中,需要传入的是Options
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class CeresScanMatcher2D { public : explicit CeresScanMatcher2D (const proto::CeresScanMatcherOptions2D& options) ; virtual ~CeresScanMatcher2D (); CeresScanMatcher2D (const CeresScanMatcher2D&) = delete ; CeresScanMatcher2D& operator =(const CeresScanMatcher2D&) = delete ; void Match (const Eigen::Vector2d& target_translation, const transform::Rigid2d& initial_pose_estimate, const sensor::PointCloud& point_cloud, const Grid2D& grid, transform::Rigid2d* pose_estimate, ceres::Solver::Summary* summary) const ; private : const proto::CeresScanMatcherOptions2D options_; ceres::Solver::Options ceres_solver_options_; };
成员变量
options_: protobuf形式的option
ceressolver_options : ceres_solver形式的option
构造函数 1 2 3 4 5 6 7 CeresScanMatcher2D::CeresScanMatcher2D ( const proto::CeresScanMatcherOptions2D& options) : options_ (options), ceres_solver_options_ ( common::CreateCeresSolverOptions (options.ceres_solver_options ())) { ceres_solver_options_.linear_solver_type = ceres::DENSE_QR; }
对两个option进行赋值,线性分解的方法使用的是稠密的QR分解
Match 定义变量 1 2 3 double ceres_pose_estimate[3 ] = {initial_pose_estimate.translation ().x (), initial_pose_estimate.translation ().y (), initial_pose_estimate.rotation ().angle ()};
平移残差的逼近 1 2 3 4 problem.AddResidualBlock ( TranslationDeltaCostFunctor2D::CreateAutoDiffCostFunction ( options_.translation_weight (), target_translation), nullptr , ceres_pose_estimate);
第一个参数是函数,需要定义优化的目标函数,第二个nullptr表示没有loss function,第三个是更新的变量,
查看目标函数,类的静态函数,返回一个类的实例
1 2 3 4 5 6 7 8 9 10 class TranslationDeltaCostFunctor2D { public : static ceres::CostFunction* CreateAutoDiffCostFunction ( const double scaling_factor, const Eigen::Vector2d& target_translation) { return new ceres::AutoDiffCostFunction<TranslationDeltaCostFunctor2D, 2 , 3 >( new TranslationDeltaCostFunctor2D (scaling_factor, target_translation)); }
构造函数:
1 2 3 4 5 explicit TranslationDeltaCostFunctor2D ( const double scaling_factor, const Eigen::Vector2d& target_translation) : scaling_factor_(scaling_factor), x_(target_translation.x()), y_(target_translation.y()) { }
查看一下()的重载,即目标函数
1 2 3 4 5 6 7 template <typename T>bool operator () (const T* const pose, T* residual) const { residual[0 ] = scaling_factor_ * (pose[0 ] - x_); residual[1 ] = scaling_factor_ * (pose[1 ] - y_); return true ; }
优化变量pose,两个维度,减去的x 和y 是target_translation的值,是位姿估计器 的值。要把pose优化到位姿估计器上去
旋转残差 1 2 3 4 5 6 CHECK_GT (options_.rotation_weight (), 0. ); problem.AddResidualBlock ( RotationDeltaCostFunctor2D::CreateAutoDiffCostFunction ( options_.rotation_weight (), ceres_pose_estimate[2 ]), nullptr , ceres_pose_estimate);
查看旋转的目标函数
1 2 3 4 5 6 7 8 9 class RotationDeltaCostFunctor2D { public : static ceres::CostFunction* CreateAutoDiffCostFunction ( const double scaling_factor, const double target_angle) { return new ceres::AutoDiffCostFunction< RotationDeltaCostFunctor2D, 1 , 3 >( new RotationDeltaCostFunctor2D (scaling_factor, target_angle)); }
这里要优化的变量是ceres_pose_estimate的第三个变量,即角度。所以residuals这里是1
构造函数,也是对角度进行了赋值
1 2 3 explicit RotationDeltaCostFunctor2D (const double scaling_factor, const double target_angle) : scaling_factor_(scaling_factor), angle_(target_angle) { }
旋转的目标函数,转换到位姿估计器上的角度,如有暴力匹配,则是暴力匹配之后的角度
1 2 3 4 5 6 template <typename T>bool operator () (const T* const pose, T* residual) const { residual[0 ] = scaling_factor_ * (pose[2 ] - angle_); return true ; }
调参问题,如果位姿估计器不准确,可以把平移和旋转的逼近直接注释掉 ,有可能叠图
地图残差的逼近 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 CHECK_GT (options_.occupied_space_weight (), 0. );switch (grid.GetGridType ()) { case GridType::PROBABILITY_GRID: problem.AddResidualBlock ( CreateOccupiedSpaceCostFunction2D ( options_.occupied_space_weight () / std::sqrt (static_cast <double >(point_cloud.size ())), point_cloud, grid), nullptr , ceres_pose_estimate); break ; case GridType::TSDF: problem.AddResidualBlock ( CreateTSDFMatchCostFunction2D ( options_.occupied_space_weight () / std::sqrt (static_cast <double >(point_cloud.size ())), point_cloud, static_cast <const TSDF2D&>(grid)), nullptr , ceres_pose_estimate); break ; }
可以待补充,通过空闲的概率来获取插值后空闲的概率来表示残差项,free越小,概率才越大
总结
可能有问题的点 平移和旋转的残差项是逼近于先验位姿的, 当先验位姿不准确时会产生问题
可能的改进建议 先将地图的权重调大, 平移旋转的权重调小, 如 1000, 1, 1, 或者 100, 1, 1 调参没有作用的时候可以将平移和旋转的残差项注释掉
如果都不行,可以把位姿估计器改成gmapping的或者是karto的试一下