Cartographer代码阅读笔记:回环检测

有关文件:cartographer/mapping/2d/scan_matching/fast_correlative_scan_matcher_2d.cpp

Cartographer有关其他分析文章:回到目录

接下来探讨一下每个函数的具体实现过程:


Match函数分析
/** 
 * @brief 给定初始位姿时的匹配
 * @param initial_pose_estimate 初始位姿估计
 * @param point_cloud 将要考察激光点云数据
 * @param min_score 回环检测的最小得分,当做阈值使用
 * @param score 成功匹配后返回匹配得分
 * @param score 成功匹配后返回位姿估计
 * @return 如果闭环检测成功则返回ture,否则返回false
 */
bool FastCorrelativeScanMatcher2D::Match(
    const transform::Rigid2d& initial_pose_estimate,
    const sensor::PointCloud& point_cloud, 
    const float min_score, float* score,
    transform::Rigid2d* pose_estimate) const {

  // 计算搜索窗口
  const SearchParameters search_parameters(options_.linear_search_window(),
                                           options_.angular_search_window(),
                                           point_cloud, limits_.resolution());
  // 实现闭环检测
  return MatchWithSearchParameters(search_parameters, initial_pose_estimate,
                                   point_cloud, min_score, score,
                                   pose_estimate);
}
MatchWithSearchParameters函数分析
/** 
 * @brief 完成扫描匹配,进而实现闭环检测
 * @param search_parameters 搜索窗口的参数
 * @param initial_pose_estimate 机器人初始位姿
 * @param point_cloud 激光点云数据
 * @param min_score 回环检测匹配最小得分
 * @param score 记录回环检测匹配成功的得分
 * @param pose_estimate 记录回环检测匹配成功的位姿
 * @return 如果闭环检测成功则返回ture,否则返回false
 */
bool FastCorrelativeScanMatcher2D::MatchWithSearchParameters(
    SearchParameters search_parameters,
    const transform::Rigid2d& initial_pose_estimate,
    const sensor::PointCloud& point_cloud, float min_score, float* score,
    transform::Rigid2d* pose_estimate) const {
  CHECK_NOTNULL(score);
  CHECK_NOTNULL(pose_estimate);

  // 获取初始位姿估计的方向角,将激光点云中的点都绕Z轴转动相应的角度。
  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 std::vector<sensor::PointCloud> rotated_scans =
      GenerateRotatedScans(rotated_point_cloud, search_parameters);

  // 完成对旋转后的点云数据离散化操作,即将浮点类型的点云数据转换成整型的栅格单元索引。      
  const std::vector<DiscreteScan2D> discrete_scans = DiscretizeScans(
      limits_, rotated_scans,
      Eigen::Translation2f(initial_pose_estimate.translation().x(),
                           initial_pose_estimate.translation().y()));

  // 尽可能的缩小搜索窗口大小,以减少搜索空间,提高搜索效率。
  search_parameters.ShrinkToFit(discrete_scans, limits_.cell_limits());

  // 完成对搜索空间的第一次分割,得到初始子空间节点集合。
  const std::vector<Candidate2D> lowest_resolution_candidates =
      ComputeLowestResolutionCandidates(discrete_scans, search_parameters);

  // 完成分支定界搜索,搜索结果放在best_candidate中。
  const Candidate2D best_candidate = BranchAndBound(
      discrete_scans, search_parameters, lowest_resolution_candidates,
      precomputation_grid_stack_->max_depth(), min_score);

  // 检测最优解的值,如果大于指定阈值min_score就认为匹配成功,修改输入参数指针score和pose_estimate所指对象。      
  if (best_candidate.score > min_score) {
    *score = best_candidate.score;
    *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 true;
  }
  return false;
}
ComputeLowestResolutionCandidates函数分析
/** 
 * @brief 完成第一次分支
 * @param discrete_scans 离散化之后的各个搜索方向上的点云数据
 * @param search_parameters 搜索窗口的参数
 */
std::vector<Candidate2D>
FastCorrelativeScanMatcher2D::ComputeLowestResolutionCandidates(
    const std::vector<DiscreteScan2D>& discrete_scans,
    const SearchParameters& search_parameters) const {

  // 完成对搜索空间的初始化分割(生成最低分辨率的所有候选解)。
  std::vector<Candidate2D> lowest_resolution_candidates =
      GenerateLowestResolutionCandidates(search_parameters);

  // 计算各个候选点的评分并排序。
  ScoreCandidates(
      precomputation_grid_stack_->Get(precomputation_grid_stack_->max_depth()),
      discrete_scans, search_parameters, &lowest_resolution_candidates);
  return lowest_resolution_candidates;
}
GenerateLowestResolutionCandidates函数分析
/** 
 * @brief 生成最低分辨率的候选解
 * @param search_parameters 搜索窗口的参数
 */
std::vector<Candidate2D>
FastCorrelativeScanMatcher2D::GenerateLowestResolutionCandidates(
    const SearchParameters& search_parameters) const {

  // 根据预算图的层数计算初始分割的粒度z^(depth)    
  const int linear_step_size = 1 << precomputation_grid_stack_->max_depth();

  // 遍历所有搜索方向,累计各个方向下空间的分割数量,得到搜索空间初始分割的子空间数量。
  int num_candidates = 0;
  for (int scan_index = 0; scan_index != search_parameters.num_scans;
       ++scan_index) {
    const int num_lowest_resolution_linear_x_candidates =
        (search_parameters.linear_bounds[scan_index].max_x -
         search_parameters.linear_bounds[scan_index].min_x + linear_step_size) /
        linear_step_size;

    const int num_lowest_resolution_linear_y_candidates =
        (search_parameters.linear_bounds[scan_index].max_y -
         search_parameters.linear_bounds[scan_index].min_y + linear_step_size) /
        linear_step_size;

    num_candidates += num_lowest_resolution_linear_x_candidates *
                      num_lowest_resolution_linear_y_candidates;
  }

  // 构建各个候选点
  std::vector<Candidate2D> candidates;
  candidates.reserve(num_candidates);
  for (int scan_index = 0; scan_index != search_parameters.num_scans;
       ++scan_index) {
    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 += linear_step_size) {
      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 += linear_step_size) {
        candidates.emplace_back(scan_index, x_index_offset, y_index_offset, search_parameters);
      }
    }
  }
  CHECK_EQ(candidates.size(), num_candidates);
  return candidates;
}
ScoreCandidates函数分析
/** 
 * @brief 计算当前层的候选解与对应分辨率地图中的匹配得分,并按照得分从大到小的进行排序
 * @param precomputation_grid 当前层对应的预计算图
 * @param discrete_scans 待匹配的点云数据
 * @param search_parameters 搜索窗口的参数
 * @param candidates 当前层对应的候选解
 */
void FastCorrelativeScanMatcher2D::ScoreCandidates(
    const PrecomputationGrid2D& precomputation_grid,
    const std::vector<DiscreteScan2D>& discrete_scans,
    const SearchParameters& search_parameters,
    std::vector<Candidate2D>* const candidates) const {

  // 遍历候选解
  for (Candidate2D& candidate : *candidates) {
    int sum = 0;
    // 遍历所有激光点
    for (const Eigen::Array2i& xy_index : discrete_scans[candidate.scan_index]) {
      const Eigen::Array2i proposed_xy_index(xy_index.x() + candidate.x_index_offset,
          xy_index.y() + candidate.y_index_offset);
      //计算累计占用概率log_odd
      sum += precomputation_grid.GetValue(proposed_xy_index);
    }
    // 求平均并转换为概率。
    candidate.score = precomputation_grid.ToScore(
        sum / static_cast<float>(discrete_scans[candidate.scan_index].size()));
  }
  
  // 对评分进行降序排序
  std::sort(candidates->begin(), candidates->end(),
            std::greater<Candidate2D>());
}
BranchAndBound函数分析
/** 
 * @brief 分支定界方法实现
 * @param discrete_scans 离散化的不同搜索角度下的激光点云数据
 * @param search_parameters 搜索窗口的配置参数
 * @param candidates 候选点集合
 * @param candidate_depth 搜索树高度
 * @param min_score 候选点最小评分
 * @return 返回当前分支中的最优解(得分最高且大于min_score)
 */
Candidate2D FastCorrelativeScanMatcher2D::BranchAndBound(
    const std::vector<DiscreteScan2D>& discrete_scans,
    const SearchParameters& search_parameters,
    const std::vector<Candidate2D>& candidates, const int candidate_depth,
    float min_score) const {

  // 如果搜索树高度为0,意味着搜索到叶子节点。      
  if (candidate_depth == 0) {
    // 由于每次迭代过程中对新扩展的候选点进行了降序排序,所以认为队首的这个节点就是最优解,直接返回即可。
    return *candidates.begin();
  }

  // 创建一个临时的候选点对象,并为之赋予最小的评分。
  Candidate2D best_high_resolution_candidate(0, 0, 0, search_parameters);
  best_high_resolution_candidate.score = min_score;

  // 遍历所有候选点,
  for (const Candidate2D& candidate : candidates) { 
    // 如果遇到一个候选点的评分很低,意味着该分支之后的候选点中也没有合适的解。直接跳出循环,说明没有构成回环。
    if (candidate.score <= min_score) {  
      break;
    }
    
    // 进行分支, 分解为4个子候选解
    std::vector<Candidate2D> higher_resolution_candidates;
    const int half_width = 1 << (candidate_depth - 1);
    for (int x_offset : {0, half_width}) {
      if (candidate.x_index_offset + x_offset >
          search_parameters.linear_bounds[candidate.scan_index].max_x) {
        break;
      }
      for (int y_offset : {0, half_width}) {
        if (candidate.y_index_offset + y_offset >
            search_parameters.linear_bounds[candidate.scan_index].max_y) {
          break;
        }
        higher_resolution_candidates.emplace_back(
            candidate.scan_index, candidate.x_index_offset + x_offset,
            candidate.y_index_offset + y_offset, search_parameters);
      }
    }

    // 对新扩展的候选解评分(也称为定界)同时对评分进行排序。
    ScoreCandidates(precomputation_grid_stack_->Get(candidate_depth - 1),
                    discrete_scans, search_parameters,
                    &higher_resolution_candidates);

    // 递归调用BranchAndBound对新扩展候选解进一步执行分支、定界等搜索过程。                    
    best_high_resolution_candidate = std::max(
        best_high_resolution_candidate,
        BranchAndBound(discrete_scans, search_parameters,
                       higher_resolution_candidates, candidate_depth - 1,
                       best_high_resolution_candidate.score));
  }
  return best_high_resolution_candidate;
}

1.第一次分支的数量与所有子图总数相等,之后的每次分支数量都是4。

返回顶部 or 回到目录

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: cartographer 是一款常用的地图建图和定位系统,常用于机器人导航和路径规划中。它主要包含两个模块:建图模块和定位模块。 建图模块使用传感器数据(如激光数据或RGB-D数据)进行实时地图构建。它通过在机器人移动过程中,利用传感器测量的环境信息,将这些信息转化为二维或三维地图。这些地图可以被用于机器人的后续操作,如导航、避障等。 定位模块则提供了机器人在地图中确定自身姿态(位置和朝向)的功能。通过与先前建立的地图进行匹配,定位模块可以估计机器人当前的位置,并提供这些信息给其他模块使用。例如,在路径规划中,定位模块可以提供机器人当前所在位置,从而使得路径规划系统能够生成适合该位置的有效路径。 move_base 是一款用于机器人路径规划和导航的功能包。它与 cartographer 结合使用时,可以将 cartographer 提供的地图数据作为输入,同时利用定位模块提供的位置信息,为机器人生成路径规划。move_base 可以考虑机器人的动态环境,并生成安全有效的路径。在导航过程中,move_base 还可以进行障碍物的避障,以确保机器人安全到达目标位置。 所以,cartographer 和 move_base 的结合可以为机器人提供强大的地图建图和路径规划导航功能,使得机器人能够在复杂的环境中进行自主移动和导航。 ### 回答2: Cartographer是一种用于建图和定位的SLAM算法。它通过结合激光扫描数据和传感器信息来生成高质量的地图和机器人的定位。而move_base是一个ROS软件包,用于规划和控制机器人在已知地图上的导航。 使用Cartographer和move_base,机器人可以先通过激光传感器获取周围环境的扫描数据,并利用Cartographer算法将这些数据进行处理,生成一个准确的地图。然后,机器人可以使用move_base的规划器来根据地图和目标位置,计算出一个可行的导航路径。 在导航过程中,move_base还会实时更新机器人的定位信息,通过Cartographer提供的定位算法,精确计算机器人的位置和姿态。一旦导航路径完成规划,move_base会将路径上的目标点发送给底层的控制器,控制机器人沿着路径移动。 整个过程中,Cartographer为move_base提供了建图和定位的支持,使得机器人能够在已知的环境中实现精确的导航。这种结合可以广泛应用于机器人自主导航、自主探索和作业执行等领域。通过使用Cartographer和move_base,机器人可以在复杂的环境中高效地进行导航任务,实现自主决策和行动。 ### 回答3: cartographer move_base是一种使用于机器人导航的软件系统。它结合了cartographer和move_base两个功能模块,提供了高效的实时建图和路径规划功能。 首先,cartographer模块是一个先进的2D和3D实时建图算法库。它使用激光雷达或深度相机等传感器数据,将机器人周围环境进行高精度建模。它能够实时定位,地图存储和传输。因此,当机器人在未知环境中移动时,cartographer能够快速创建地图,并在机器人移动过程中对其进行更新。 而move_base模块是ROS(机器人操作系统)中的一个核心导航功能包。它基于全局规划和局部规划两个模块,为机器人提供路径规划和导航控制。全局规划使用地图信息和用户指定的目标位置,计算出机器人的最优导航路径。局部规划根据机器人当前位姿、感知信息和全局规划路径,实时生成机器人的局部规划路径,并控制机器人移动。 cartographer move_base结合了这两个功能模块。它能够通过cartographer实时建图获取机器人周围环境的精确信息,并结合move_base进行路径规划和导航控制。因此,机器人可以在未知环境中实时感知自己的位置,根据目标位置规划最优路径,并通过move_base进行实时控制,从而实现高效、精确的导航。 综上所述,cartographer move_base是一种能够结合实时建图和路径规划功能的软件系统。它为机器人导航提供了强大的功能支持,并广泛应用于自主机器人、无人车等领域。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值