cartographer代码学习笔记-ScanMatch

博客主要是ScanMatch代码解析的学习笔记。介绍了在AddAccumulatedRangeData函数中通过调用ScanMatch获取位置,详细解析了RealTimeCorrelativeScanMatcher2D的SearchParameters、GenerateRotatedScans等部分,还提及CeresScanMatcher2D::Match,给出了相关参考资料。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在AddAccumulatedRangeData函数中通过调用ScanMatch获取位置

  // local map frame <- gravity-aligned frame
  std::unique_ptr<transform::Rigid2d> pose_estimate_2d =
      ScanMatch(time, pose_prediction, filtered_gravity_aligned_point_cloud);
  if (pose_estimate_2d == nullptr) {
    LOG(WARNING) << "Scan matching failed.";
    return nullptr;
  }

1、 ScanMatch 代码解析

std::unique_ptr<transform::Rigid2d> LocalTrajectoryBuilder2D::ScanMatch(
    const common::Time time, //最后一个点云对应的时刻
    const transform::Rigid2d& pose_prediction,//重力矫正后的最新预测位姿
    const sensor::PointCloud& filtered_gravity_aligned_point_cloud)//过滤后的点云数据
{
  if (active_submaps_.submaps().empty()) { //暂无active_submaps_时
    return absl::make_unique<transform::Rigid2d>(pose_prediction);
  }
  //存在active_submaps_,取子图
  std::shared_ptr<const Submap2D> matching_submap =
      active_submaps_.submaps().front();
  // The online correlative scan matcher will refine the initial estimate for
  // the Ceres scan matcher.
  transform::Rigid2d initial_ceres_pose = pose_prediction;
//如果实时匹配参数为true
  if (options_.use_online_correlative_scan_matching()) {
    const double score = real_time_correlative_scan_matcher_.Match(
        pose_prediction, filtered_gravity_aligned_point_cloud,
        *matching_submap->grid(), &initial_ceres_pose);
    kRealTimeCorrelativeScanMatcherScoreMetric->Observe(score);
  }

  auto pose_observation = absl::make_unique<transform::Rigid2d>();
  ceres::Solver::Summary summary;
  ceres_scan_matcher_.Match(pose_prediction.translation(), initial_ceres_pose,
                            filtered_gravity_aligned_point_cloud,
                            *matching_submap->grid(), pose_observation.get(),
                            &summary);
  if (pose_observation) {
    kCeresScanMatcherCostMetric->Observe(summary.final_cost);
    const double residual_distance =
        (pose_observation->translation() - pose_prediction.translation())
            .norm();
    kScanMatcherResidualDistanceMetric->Observe(residual_distance);
    const double residual_angle =
        std::abs(pose_observation->rotation().angle() -
                 pose_prediction.rotation().angle());
    kScanMatcherResidualAngleMetric->Observe(residual_angle);
  }
  return pose_observation;
}

1.1 RealTimeCorrelativeScanMatcher2D

double RealTimeCorrelativeScanMatcher2D::Match(
    const transform::Rigid2d& initial_pose_estimate,
    const sensor::PointCloud& point_cloud,
    const ProbabilityGrid& probability_grid,
    transform::Rigid2d* pose_estimate) const {
  CHECK_NOTNULL(pose_estimate);
//将点云旋转到预测的方向上
  const Eigen::Rotation2Dd initial_rotation = initial_pose_estimate.rotation();
  const sensor::PointCloud rotated_point_cloud = sensor::TransformPointCloud(
      point_cloud,
      transform::Rigid3f::Rotation(Eigen::AngleAxisf(
          initial_rotation.cast<float>().angle(), Eigen::Vector3f::UnitZ())));
  const SearchParameters search_parameters(
      options_.linear_search_window(), options_.angular_search_window(),
      rotated_point_cloud, probability_grid.limits().resolution());
//旋转点云数据
  const std::vector<sensor::PointCloud> rotated_scans =
      GenerateRotatedScans(rotated_point_cloud, search_parameters);
  //旋转后的点云加平移
  const std::vector<DiscreteScan2D> discrete_scans = DiscretizeScans(
      probability_grid.limits(), rotated_scans,
      Eigen::Translation2f(initial_pose_estimate.translation().x(),
                           initial_pose_estimate.translation().y()));
   // 生成所有的候选解                        
  std::vector<Candidate2D> candidates =
      GenerateExhaustiveSearchCandidates(search_parameters);
     //计算候选解得分
  ScoreCandidates(probability_grid, discrete_scans, search_parameters,
                  &candidates);
//筛选出最优解
  const Candidate2D& best_candidate =
      *std::max_element(candidates.begin(), candidates.end());
  *pose_estimate = transform::Rigid2d(
      {initial_pose_estimate.translation().x() + best_candidate.x,
       initial_pose_estimate.translation().y() + best_candidate.y},
      initial_rotation * Eigen::Rotation2Dd(best_candidate.orientation));
  return best_candidate.score; //返回得分
}

1.1.1 SearchParameters

构建搜索框参数

搜索角度步长论文中计算公式
在这里插入图片描述

SearchParameters::SearchParameters(const double linear_search_window,
                                   const double angular_search_window,
                                   const sensor::PointCloud& point_cloud,
                                   const double resolution)
    : resolution(resolution) {
  // We set this value to something on the order of resolution to make sure that
  // the std::acos() below is defined.
  float max_scan_range = 3.f * resolution;
  for (const Eigen::Vector3f& point : point_cloud) {
    const float range = point.head<2>().norm();
    max_scan_range = std::max(range, max_scan_range);
  }
  const double kSafetyMargin = 1. - 1e-3;
  //角度分辨率计算 根据论文公式计算
  angular_perturbation_step_size =
      kSafetyMargin * std::acos(1. - common::Pow2(resolution) /
                                         (2. * common::Pow2(max_scan_range)));
  //计算搜索角度的个数                                     
  num_angular_perturbations =
      std::ceil(angular_search_window / angular_perturbation_step_size);
      //搜索扩大2倍
  num_scans = 2 * num_angular_perturbations + 1;
//x,y搜索栅格数的计算
  const int num_linear_perturbations =
      std::ceil(linear_search_window / resolution);
  linear_bounds.reserve(num_scans);
  /*
    struct LinearBounds {
    int min_x;
    int max_x;
    int min_y;
    int max_y;
  };
  */
  for (int i = 0; i != num_scans; ++i) {
  //根据num_linear_perturbations 设定区间范围
    linear_bounds.push_back(
        LinearBounds{-num_linear_perturbations, num_linear_perturbations,
                     -num_linear_perturbations, num_linear_perturbations});
  }
}

1.1.2 GenerateRotatedScans

根据search_parameters生成旋转后的点云

std::vector<sensor::PointCloud> GenerateRotatedScans(
    const sensor::PointCloud& point_cloud,
    const SearchParameters& search_parameters) {
  std::vector<sensor::PointCloud> rotated_scans;
  rotated_scans.reserve(search_parameters.num_scans);//设置旋转点云大小

//设定搜索起始角度
  double delta_theta = -search_parameters.num_angular_perturbations *
                       search_parameters.angular_perturbation_step_size;
  for (int scan_index = 0; scan_index < search_parameters.num_scans;
       ++scan_index,
           delta_theta += search_parameters.angular_perturbation_step_size) {//角度递增
    rotated_scans.push_back(sensor::TransformPointCloud(
        point_cloud, transform::Rigid3f::Rotation(Eigen::AngleAxisf(
                         delta_theta, Eigen::Vector3f::UnitZ()))));
  }
  return rotated_scans;
}

按照角度旋转后的旋转向量

transform::Rigid3f::Rotation(Eigen::AngleAxisf(
                         delta_theta, Eigen::Vector3f::UnitZ()))

将点云按照旋转向量进行旋转得到旋转后的点云数据

sensor::TransformPointCloud(
        point_cloud, transform::Rigid3f::Rotation(Eigen::AngleAxisf(
                         delta_theta, Eigen::Vector3f::UnitZ())))

1.1.3 DiscretizeScans

将旋转后的点云集合按照预测出的平移量进行平移, 获取平移后的点在地图中的索引typedef std::vector<Eigen::Array2i> DiscreteScan2D;

std::vector<DiscreteScan2D> DiscretizeScans(
    const MapLimits& map_limits, const std::vector<sensor::PointCloud>& scans,
    const Eigen::Translation2f& initial_translation) {
  std::vector<DiscreteScan2D> discrete_scans;
  discrete_scans.reserve(scans.size());
  for (const sensor::PointCloud& scan : scans) {
    discrete_scans.emplace_back();
    discrete_scans.back().reserve(scan.size());
    for (const Eigen::Vector3f& point : scan) {
      const Eigen::Vector2f translated_point =
          Eigen::Affine2f(initial_translation) * point.head<2>();
          
      discrete_scans.back().push_back(
          map_limits.GetCellIndex(translated_point));
    }
  }
  return discrete_scans; //返回的就直接是 num_scans 帧各个点云在栅格地图中的索引。
}

每个旋转后的点云加上平移得到world的坐标

      const Eigen::Vector2f translated_point =
          Eigen::Affine2f(initial_translation) * point.head<2>();

获取激光点在栅格下的索引值

map_limits.GetCellIndex(translated_point)

  Eigen::Array2i GetCellIndex(const Eigen::Vector2f& point) const {
    // Index values are row major and the top left has Eigen::Array2i::Zero()
    // and contains (centered_max_x, centered_max_y). We need to flip and
    // rotate.
    return Eigen::Array2i(
        common::RoundToInt((max_.y() - point.y()) / resolution_ - 0.5),
        common::RoundToInt((max_.x() - point.x()) / resolution_ - 0.5));
  }

1.1.4 GenerateExhaustiveSearchCandidates

std::vector<Candidate2D>
RealTimeCorrelativeScanMatcher2D::GenerateExhaustiveSearchCandidates(
    const SearchParameters& search_parameters) const {
  int num_candidates = 0;
  // 计算候选点总量
  for (int scan_index = 0; scan_index != search_parameters.num_scans;
       ++scan_index) {
    const int num_linear_x_candidates =
        (search_parameters.linear_bounds[scan_index].max_x -
         search_parameters.linear_bounds[scan_index].min_x + 1);
    const int num_linear_y_candidates =
        (search_parameters.linear_bounds[scan_index].max_y -
         search_parameters.linear_bounds[scan_index].min_y + 1);
    num_candidates += num_linear_x_candidates * num_linear_y_candidates;
  }
  std::vector<Candidate2D> candidates;
  candidates.reserve(num_candidates);
  //遍历所有点云数据
  for (int scan_index = 0; scan_index != search_parameters.num_scans;
       ++scan_index) {
       //一个xy位置上对应一个点云数据
    for (int x_index_offset = search_parameters.linear_bounds[scan_index].min_x;
         x_index_offset <= search_parameters.linear_bounds[scan_index].max_x;
         ++x_index_offset) {
      for (int y_index_offset =
               search_parameters.linear_bounds[scan_index].min_y;
           y_index_offset <= search_parameters.linear_bounds[scan_index].max_y;
           ++y_index_offset) {
        candidates.emplace_back(scan_index, x_index_offset, y_index_offset,
                                search_parameters);
      }
    }
  }
  CHECK_EQ(candidates.size(), num_candidates);
  return candidates;
}

1.1.5ScoreCandidates

计算候选者得分,暴力匹配

void RealTimeCorrelativeScanMatcher2D::ScoreCandidates(
    const ProbabilityGrid& probability_grid,
    const std::vector<DiscreteScan2D>& discrete_scans,
    const SearchParameters& search_parameters,
    std::vector<Candidate2D>* const candidates) const {
   //遍历所有候选解
  for (Candidate2D& candidate : *candidates) {
    candidate.score = 0.f;
    for (const Eigen::Array2i& xy_index :
         discrete_scans[candidate.scan_index]) { //discrete_scans存储的是对应栅格索引
      const Eigen::Array2i proposed_xy_index( //激光索引值+候选者在xy上的平移
          xy_index.x() + candidate.x_index_offset,
          xy_index.y() + candidate.y_index_offset);
      const float probability =
          probability_grid.GetProbability(proposed_xy_index); //根据索引获取对应栅格概率
      candidate.score += probability; 
    }
     //计算候选解得分
    candidate.score /= 
        static_cast<float>(discrete_scans[candidate.scan_index].size()); 
    candidate.score *=
        std::exp(-common::Pow2(std::hypot(candidate.x, candidate.y) *
                                   options_.translation_delta_cost_weight() +
                               std::abs(candidate.orientation) *
                                   options_.rotation_delta_cost_weight()));
    CHECK_GT(candidate.score, 0.f);
  }

1.2 CeresScanMatcher2D::Match

内容在 cartographer代码学习笔记-CeresScanMatcher2D

参考

Cartographer源码阅读2D-前端暴力匹配-RealTimeCorrelativeScanMatcher2D
cartographer_real_time_correlative_scan_matcher_2d

### Ceres Solver 和 Cartographer 的拓扑依赖关系 Ceres Solver 是一个用于优化问题的开源 C++ 库,广泛应用于机器人学中的 SLAM (Simultaneous Localization and Mapping) 问题。Cartographer 则是一个基于 Google 开发的实时 SLAM 系统,它利用 Ceres Solver 来解决地图构建过程中涉及的各种非线性最小二乘问题。 #### 构建过程中的拓扑顺序分析 在 ROS 中集成 Cartographer 及其依赖项时,通常遵循以下逻辑上的拓扑顺序: 1. **安装 Ceres Solver**: Ceres Solver 需要在 Cartographer 安装之前完成配置和编译。这是因为 Cartographer 使用 Ceres Solver 进行轨迹优化以及回环检测后的全局调整[^2]。如果未正确安装 Ceres Solver,则可能导致 Cartographer 编译失败或运行异常。 2. **设置 Protobuf 支持**: Cartographer 对 Protocol Buffers (Protobuf) 有强依赖,因此需要先验证并安装合适的版本。此步骤应在 Ceres Solver 后执行,因为某些情况下 Protobuf 版本冲突可能会干扰后续组件的正常工作[^2]。 3. **克隆与初始化 Cartographer 源码仓库**: 用户需通过 Git 将 Cartographer 转移到本地环境,并创建必要的目录结构以便于编译工具链操作。例如,在引用中提到的操作 `cd cartographer && mkdir build` 即为此目的服务[^2]。 4. **应用 Ninja 或其他构建系统生成目标文件**: 使用 Ninja 工具可以显著加速大型项目的增量构建流程。命令序列如 `cmake .. -G Ninja`, followed by `ninja` 实现了这一阶段的任务自动化处理[^2]。 5. **部署至系统路径下供 ROS 访问**: 执行 `sudo ninja install` 步骤后,所有必需库会被复制到标准位置,从而允许 ROS 寻找这些资源而无需额外指定查找范围[^2]。 6. **启动 roslaunch 文件测试功能模块交互情况**: 最终可以通过调用类似于 `roslaunch cartographer_ros demo_backpack_2d.launch ...` 命令来检验整个链条是否搭建成功[^1]。 ```bash # 示例脚本展示如何按序准备开发环境 git clone https://github.com/googlecartographer/cartographer.git ~/catkin_ws/src/ cd ~/catkin_ws/src/cartographer mkdir build && cd build cmake .. -GNinja ninja sudo ninja install source /opt/ros/<distro>/setup.bash rosdep update; rosdep install --from-paths . --ignore-src -r -y ``` 以上代码片段展示了从源码获取直至激活所需依赖的一系列动作概览。 --- ### 关键技术点解析 - **CMakeLists.txt 解析**:每一步都由项目根目录下的 CMakeLists.txt 控制,定义好各个子包之间的相互作用规则。 - **Ninja 替代 Makefile 提升效率**:相比传统 GNU make 方法,采用 Ninja 显著减少了重复劳动时间开销。 - **跨平台兼容考量**:无论是 Linux 平台还是 Windows Subsystem for Linux(WSL),均能依照相同指导方针顺利完成部署。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值