cartographer_real_time_correlative_scan_matcher_2d

  • Post author:
  • Post category:其他




0.引言

实时相关性扫描匹配:

暴力匹配

  1. 暴力搜索三层 for 循环



    x

    ,

    y

    ,

    θ

    x, y, θ






    x


    ,




    y


    ,




    θ





    枚举每一个位姿,分别计算每一个位姿的得分,计算量巨大。因为激光雷达数据在每一个位姿都要重新投影,投影需要计算 sin 和 cos 函数。

  2. 预先投影搜索把暴力搜索中的三层 for 循环



    x

    ,

    y

    ,

    θ

    x, y, θ






    x


    ,




    y


    ,




    θ





    交换一下顺序,最外层对



    θ

    θ






    θ





    进行搜索,这样内层



    x

    ,

    y

    x, y






    x


    ,




    y





    的投影变成的加法,需要计算 sin 和 cos 函数的位姿数从



    n

    x

    n

    y

    n

    θ

    n_x n_y n_θ







    n










    x



















    n










    y



















    n










    θ





















    降为



    n

    θ

    n_θ







    n










    θ





















    。能极大的加速算法的运行速度.

主要实现在

real_time_correlative_scan_matcher_2d.h



correlative_scan_matcher_2d.h

中,接口函数核心函数为

match

.

/**
 * @brief 相关性扫描匹配 - 计算量很大
 * 
 * @param[in] initial_pose_estimate 预测出来的先验位姿
 * @param[in] point_cloud 用于匹配的点云 点云的原点位于local坐标系原点
 * @param[in] grid 用于匹配的栅格地图
 * @param[out] pose_estimate 校正后的位姿
 * @return double 匹配得分
 */
double RealTimeCorrelativeScanMatcher2D::Match(
    const transform::Rigid2d& initial_pose_estimate,
    const sensor::PointCloud& point_cloud, const Grid2D& grid,
    transform::Rigid2d* pose_estimate) const {

相关配置参数:

  -- 是否使用 real_time_correlative_scan_matcher 为ceres提供先验信息
  -- 计算复杂度高 , 但是很鲁棒 , 在odom或者imu不准时依然能达到很好的效果
  use_online_correlative_scan_matching = false,
  real_time_correlative_scan_matcher = {
    linear_search_window = 0.1,             -- 线性搜索窗口的大小
    angular_search_window = math.rad(20.),  -- 角度搜索窗口的大小
    translation_delta_cost_weight = 1e-1,   -- 用于计算各部分score的权重
    rotation_delta_cost_weight = 1e-1,
  },

整体思路:

1、在设定的初始位置,在窗口范围内搜索,提取出每个候选位姿。

2、计算出每个候选位姿下,点云打在栅格图上面的概率,计算其和。

3、概率和最大的候选位姿,就为所要寻找的最佳位姿。

  • step 1 将点云旋转到预测的方向上

      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())));
    



1.SearchParameters

  • step 2 根据配置参数初始化 SearchParameters

      const SearchParameters search_parameters(
        options_.linear_search_window(), options_.angular_search_window(),
        rotated_point_cloud, grid.limits().resolution());
    

位于

correlative_scan_matcher_2d.h

中。

参考

  • 角度步长angular_step_size的计算:

请添加图片描述

余弦定理:

请添加图片描述

  float max_scan_range = 3.f * resolution;

  // 求得 point_cloud 中雷达数据的 最大的值(最远点的距离)
  for (const sensor::RangefinderPoint& point : point_cloud) {
    const float range = point.position.head<2>().norm();
    max_scan_range = std::max(range, max_scan_range);
  }

  // 根据论文里的公式 求得角度分辨率 angular_perturbation_step_size
  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);
  // num_scans是要生成旋转点云的个数, 将 num_angular_perturbations 扩大了2倍
  num_scans = 2 * num_angular_perturbations + 1;

所以,参数设置的其实是搜索角度的一半。

  • x,y搜索栅格数的计算
  // XY方向的搜索范围, 单位是多少个栅格
  const int num_linear_perturbations =
      std::ceil(linear_search_window / resolution);

  // linear_bounds 的作用是确定每一个点云的最大最小边界
  // 类似于 八连通遍历的 -1 0 +1 数组作用
  linear_bounds.reserve(num_scans);
  for (int i = 0; i != num_scans; ++i) {
    linear_bounds.push_back(
        LinearBounds{-num_linear_perturbations, num_linear_perturbations,
                     -num_linear_perturbations, num_linear_perturbations});
  }

所有的参数进行了初始化:

  int num_angular_perturbations;            // 角度步数
  double angular_perturbation_step_size;    // 角度每步的度数
  double resolution;
  int num_scans;                            // 实际角度步数,旋转后的点云集合的个数?
  std::vector<LinearBounds> linear_bounds;  // 确定每一个点云的最大最小边界// 类似于 四连通遍历的 -1 0 +1 数组



2.生成所有的候选解

  • step 3 生成按照不同角度旋转后的点云集合

      const std::vector<sensor::PointCloud> rotated_scans =
        GenerateRotatedScans(rotated_point_cloud, search_parameters);
    

输入参数为待匹配的点云,以及搜索参数

search_parameters

, 生成 num_scans 个旋转后的点云,代码很直观:

  std::vector<sensor::PointCloud> rotated_scans;
  // 生成 num_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) {
    // 将 point_cloud 绕Z轴旋转了delta_theta
    rotated_scans.push_back(sensor::TransformPointCloud(
        point_cloud, transform::Rigid3f::Rotation(Eigen::AngleAxisf(
                         delta_theta, Eigen::Vector3f::UnitZ()))));
  }
  • Step: 4 将旋转后的点云集合按照预测出的平移量进行平移, 获取平移后的点在地图中的索引

    const std::vector<DiscreteScan2D> discrete_scans = DiscretizeScans(
        grid.limits(), rotated_scans,
        Eigen::Translation2f(initial_pose_estimate.translation().x(),
                             initial_pose_estimate.translation().y()));
    

前面 step 1 中只进行了旋转调整了姿态,没有进行平移。这步就对 step 3 按照步长旋转得到的各个候选点云进行平移。


待匹配点云一帧 –> 按步长旋转生成候选帧 num_scans 帧 –> 对 num_scans 帧进行平移

这步中,返回的就直接是 num_scans 帧各个点云在栅格地图中的索引。

 // 将旋转平移后的点 对应栅格的索引放入discrete_scans
 discrete_scans.back().push_back(
     map_limits.GetCellIndex(translated_point));
  • Step: 5 生成所有的候选解

    std::vector<Candidate2D> candidates =
       GenerateExhaustiveSearchCandidates(search_parameters);
    

    把所有 num_scans 候选帧的旋转平移计算出来。

    注意这里使用

    scan_index

    标记来自于哪一

    个候选帧。



3.获取最优解

  • step 6 计算所有候选解的加权得分

    ScoreCandidates(grid, discrete_scans, search_parameters, &candidates);
    

请添加图片描述

  float candidate_score = 0.f;
  for (const Eigen::Array2i& xy_index : discrete_scan) {
    // 对每个点都加上像素坐标的offset, 相当于对点云进行平移
    const Eigen::Array2i proposed_xy_index(xy_index.x() + x_index_offset,
                                           xy_index.y() + y_index_offset);
    // 获取占用的概率
    const float probability =
        probability_grid.GetProbability(proposed_xy_index);
    // 以概率为得分
    candidate_score += probability;
  }
  // 计算平均得分
  candidate_score /= static_cast<float>(discrete_scan.size());
  • Step: 7 获取最优解

    const Candidate2D& best_candidate =
        *std::max_element(candidates.begin(), candidates.end());
    

std::max_element需要有比较函数, 在 Candidate2D结构体中有实现:

  bool operator<(const Candidate2D& other) const { return score < other.score; }
  bool operator>(const Candidate2D& other) const { return score > other.score; }
  • Step: 8 将计算出的偏差量加上原始位姿获得校正后的位姿

    *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));
    



版权声明:本文为fb_941219原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。