slam建图与定位_cartographer代码阅读(7)后端约束构建

这篇具有很好参考价值的文章主要介绍了slam建图与定位_cartographer代码阅读(7)后端约束构建。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1.cartographer里的节点:当扫描匹配结束后,有新的一帧scan加入到submap中,这个扫描匹配的结果就叫做节点
global_trajectory_builder.cc

     // 将匹配后的结果 当做节点 加入到位姿图中
  auto node_id = pose_graph_->AddNode(
      matching_result->insertion_result->constant_data, trajectory_id_,
      matching_result->insertion_result->insertion_submaps);

2.子图内约束,子图的原点坐标与节点间的约束pose_graph_2d.cc

	// 遍历2个子图, 将节点加入子图的节点列表中, 计算子图原点与及节点间的约束(子图内约束)
    for (size_t i = 0; i < insertion_submaps.size(); ++i) {
      const SubmapId submap_id = submap_ids[i];
      // Even if this was the last node added to 'submap_id', the submap will
      // only be marked as finished in 'data_.submap_data' further below.
      CHECK(data_.submap_data.at(submap_id).state ==
            SubmapState::kNoConstraintSearch);
      // 将node_id放到子图保存的node_ids的set中
      data_.submap_data.at(submap_id).node_ids.emplace(node_id);
      // 计算 子图原点 指向 node坐标 间的坐标变换(子图内约束)
      const transform::Rigid2d constraint_transform =
          constraints::ComputeSubmapPose(*insertion_submaps[i]).inverse() *
          local_pose_2d;
      // 新生成的 子图内约束 放入容器中
      data_.constraints.push_back(
          Constraint{submap_id,
                     node_id,
                     {transform::Embed3D(constraint_transform),
                      options_.matcher_translation_weight(),
                      options_.matcher_rotation_weight()},
                     Constraint::INTRA_SUBMAP}); // 子图内约束
    } // end for

3.回环检测:当前的节点与所有已经完成的子图进行约束的计算pose_graph_2d.cc

  // Step: 当前节点与所有已经完成的子图进行约束的计算---实际上就是回环检测
  for (const auto& submap_id : finished_submap_ids) {
    // 计算旧的submap和新的节点间的约束
    ComputeConstraint(node_id, submap_id);
  }

4.回环检测(子图间约束):计算所有节点与刚完成子图的约束pose_graph_2d.cc

	  // Step: 计算所有节点与刚完成子图间的约束---实际上就是回环检测
  if (newly_finished_submap) {
    const SubmapId newly_finished_submap_id = submap_ids.front();
    // We have a new completed submap, so we look into adding constraints for
    // old nodes.
    for (const auto& node_id_data : optimization_problem_->node_data()) {
      const NodeId& node_id = node_id_data.id;
      // 刚结束的子图内部的节点, 不再与这个子图进行约束的计算
      if (newly_finished_submap_node_ids.count(node_id) == 0) {
        // 计算新的submap和旧的节点间的约束
        ComputeConstraint(node_id, newly_finished_submap_id);
      }
    }
  }

5.全局搜索和局部搜索pose_graph_2d.cc
节点和子图时间差小于阈值或者是同一条轨迹 进行局部搜索;
节点和子图时间间隔间隔了一段时间间隔并且不属于同一条轨迹 全局搜索 纯定位模式;

		/**
 * @brief 进行子图间约束计算, 也可以说成是回环检测
 * 
 * @param[in] node_id 节点的id
 * @param[in] submap_id submap的id
 */
void PoseGraph2D::ComputeConstraint(const NodeId& node_id,
                                    const SubmapId& submap_id) {
  bool maybe_add_local_constraint = false;
  bool maybe_add_global_constraint = false;
  const TrajectoryNode::Data* constant_data;
  const Submap2D* submap;

  {
    absl::MutexLock locker(&mutex_);
    CHECK(data_.submap_data.at(submap_id).state == SubmapState::kFinished);
    // 如果是未完成状态的地图不进行约束计算
    if (!data_.submap_data.at(submap_id).submap->insertion_finished()) {
      // Uplink server only receives grids when they are finished, so skip
      // constraint search before that.
      return;
    }
    // 获取该 node 和该 submap 中的 node 中较新的时间
    const common::Time node_time = GetLatestNodeTime(node_id, submap_id);
    // 两个轨迹的最后连接时间
    const common::Time last_connection_time =
        data_.trajectory_connectivity_state.LastConnectionTime(
            node_id.trajectory_id, submap_id.trajectory_id);

// 如果节点和子图属于同一轨迹, 或者时间小于阈值
// 则只需进行 局部搜索窗口 的约束计算(对局部子图进行回环检测)
if (node_id.trajectory_id == submap_id.trajectory_id ||
    node_time <
        last_connection_time +
            common::FromSeconds(
                options_.global_constraint_search_after_n_seconds())) {
  // If the node and the submap belong to the same trajectory or if there
  // has been a recent global constraint that ties that node's trajectory to
  // the submap's trajectory, it suffices to do a match constrained to a
  // local search window.

  maybe_add_local_constraint = true;
}
// 如果节点与子图不属于同一条轨迹 并且 间隔了一段时间, 同时采样器为true
// 才进行 全局搜索窗口 的约束计算(对整体子图进行回环检测)
else if (global_localization_samplers_[node_id.trajectory_id]->Pulse()) {
  maybe_add_global_constraint = true;
}

// 获取节点信息数据与地图数据
constant_data = data_.trajectory_nodes.at(node_id).constant_data.get();
submap = static_cast<const Submap2D*>(
    data_.submap_data.at(submap_id).submap.get());
 } // end {}

6.创建多分辨率地图constraint_builder_2d.cc

	// 为每个子图新建一个匹配器
const ConstraintBuilder2D::SubmapScanMatcher*
ConstraintBuilder2D::DispatchScanMatcherConstruction(const SubmapId& submap_id,
                                                     const Grid2D* const grid) {
  CHECK(grid);
  // 如果匹配器里已经存在, 则直接返回对应id的匹配器
  if (submap_scan_matchers_.count(submap_id) != 0) {
    return &submap_scan_matchers_.at(submap_id);
  }
  // submap_scan_matchers_新增加一个 key
  auto& submap_scan_matcher = submap_scan_matchers_[submap_id];
  kNumSubmapScanMatchersMetric->Set(submap_scan_matchers_.size());
  // 保存栅格地图的指针
  submap_scan_matcher.grid = grid;

  auto& scan_matcher_options = options_.fast_correlative_scan_matcher_options();
  auto scan_matcher_task = absl::make_unique<common::Task>();
  // 生成一个将初始化匹配器的任务, 初始化时会计算多分辨率地图, 比较耗时
  scan_matcher_task->SetWorkItem(
      [&submap_scan_matcher, &scan_matcher_options]() {
        // 进行匹配器的初始化, 与多分辨率地图的创建
        submap_scan_matcher.fast_correlative_scan_matcher =
            absl::make_unique<scan_matching::FastCorrelativeScanMatcher2D>(
                *submap_scan_matcher.grid, scan_matcher_options);
      });
  // 将初始化匹配器的任务放入线程池中, 并且将任务的智能指针保存起来
  submap_scan_matcher.creation_task_handle =
      thread_pool_->Schedule(std::move(scan_matcher_task));

  return &submap_scan_matchers_.at(submap_id);
}

7.基于分支定界进行粗匹配,使用ceres进行精匹配constraint_builder_2d.cc

/**
 * @brief 计算节点和子图之间的一个约束(回环检测)
 *        用基于分支定界算法的匹配器进行粗匹配,然后用ceres进行精匹配
 * 
 * @param[in] submap_id submap的id
 * @param[in] submap 地图数据
 * @param[in] node_id 节点id
 * @param[in] match_full_submap 是局部匹配还是全子图匹配
 * @param[in] constant_data 节点数据
 * @param[in] initial_relative_pose 约束的初值
 * @param[in] submap_scan_matcher 匹配器
 * @param[out] constraint 计算出的约束
 */
void ConstraintBuilder2D::ComputeConstraint(
    const SubmapId& submap_id, const Submap2D* const submap,
    const NodeId& node_id, bool match_full_submap,
    const TrajectoryNode::Data* const constant_data,
    const transform::Rigid2d& initial_relative_pose,
    const SubmapScanMatcher& submap_scan_matcher,
    std::unique_ptr<ConstraintBuilder2D::Constraint>* constraint) {
  CHECK(submap_scan_matcher.fast_correlative_scan_matcher);

  // Step:1 得到节点在local frame下的坐标
  const transform::Rigid2d initial_pose =
      ComputeSubmapPose(*submap) * initial_relative_pose;

  // The 'constraint_transform' (submap i <- node j) is computed from:
  // - a 'filtered_gravity_aligned_point_cloud' in node j,
  // - the initial guess 'initial_pose' for (map <- node j),
  // - the result 'pose_estimate' of Match() (map <- node j).
  // - the ComputeSubmapPose() (map <- submap i)

  float score = 0.;
  transform::Rigid2d pose_estimate = transform::Rigid2d::Identity();

  // Compute 'pose_estimate' in three stages:
  // 1. Fast estimate using the fast correlative scan matcher.
  // 2. Prune if the score is too low.
  // 3. Refine.
  // param: global_localization_min_score 对整体子图进行回环检测时的最低分数阈值
  // param: min_score 对局部子图进行回环检测时的最低分数阈值

  // Step:2 使用基于分支定界算法的匹配器进行粗匹配
  if (match_full_submap) {
    // 节点与全地图进行匹配
    kGlobalConstraintsSearchedMetric->Increment();
    if (submap_scan_matcher.fast_correlative_scan_matcher->MatchFullSubmap(
            constant_data->filtered_gravity_aligned_point_cloud,
            options_.global_localization_min_score(), &score, &pose_estimate)) {
      CHECK_GT(score, options_.global_localization_min_score());
      CHECK_GE(node_id.trajectory_id, 0);
      CHECK_GE(submap_id.trajectory_id, 0);
      kGlobalConstraintsFoundMetric->Increment();
      kGlobalConstraintScoresMetric->Observe(score);
    } else {
      // 计算失败了就退出
      return;
    }
  } 
  else {
    // 节点与局部地图进行匹配
    kConstraintsSearchedMetric->Increment();
    if (submap_scan_matcher.fast_correlative_scan_matcher->Match(
            initial_pose, constant_data->filtered_gravity_aligned_point_cloud,
            options_.min_score(), &score, &pose_estimate)) {
      // We've reported a successful local match.
      CHECK_GT(score, options_.min_score());
      kConstraintsFoundMetric->Increment();
      kConstraintScoresMetric->Observe(score);
    } else {
      return;
    }
  }
  
  {
    absl::MutexLock locker(&mutex_);
    score_histogram_.Add(score);
  }

  // Use the CSM estimate as both the initial and previous pose. This has the
  // effect that, in the absence of better information, we prefer the original
  // CSM estimate.

  // Step:3 使用ceres进行精匹配, 就是前端扫描匹配使用的函数
  ceres::Solver::Summary unused_summary;
  ceres_scan_matcher_.Match(pose_estimate.translation(), pose_estimate,
                            constant_data->filtered_gravity_aligned_point_cloud,
                            *submap_scan_matcher.grid, &pose_estimate,
                            &unused_summary);

  // Step:4 获取节点到submap坐标系原点间的坐标变换
  // pose_estimate 是 节点在 loacl frame 下的坐标
  const transform::Rigid2d constraint_transform =
      ComputeSubmapPose(*submap).inverse() * pose_estimate;

  // Step:5 返回计算后的约束
  constraint->reset(new Constraint{submap_id,
                                   node_id,
                                   {transform::Embed3D(constraint_transform),
                                    options_.loop_closure_translation_weight(),
                                    options_.loop_closure_rotation_weight()},
                                   Constraint::INTER_SUBMAP});

  // log相关
  if (options_.log_matches()) {
    std::ostringstream info;
    info << "Node " << node_id << " with "
         << constant_data->filtered_gravity_aligned_point_cloud.size()
         << " points on submap " << submap_id << std::fixed;
    if (match_full_submap) {
      info << " matches";
    } else {
      const transform::Rigid2d difference =
          initial_pose.inverse() * pose_estimate;
      info << " differs by translation " << std::setprecision(2) // c++11: std::setprecision(2) 保留2个小数点
           << difference.translation().norm() << " rotation "
           << std::setprecision(3) << std::abs(difference.normalized_angle());
    }
    info << " with score " << std::setprecision(1) << 100. * score << "%.";
    LOG(INFO) << info.str();
  }
}

总结:
1.子图内约束包含:当前节点与当前子图原点的约束和当前节点和所有已经完成子图的约束,目的是为了构建局部地图
2.子图间的约束:当前完成子图与所有节点的约束 ,目的是为了构建全局地图
3.全局搜索窗与局部搜索窗的区别 局部搜索窗有距离范围限制文章来源地址https://www.toymoban.com/news/detail-611631.html

到了这里,关于slam建图与定位_cartographer代码阅读(7)后端约束构建的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 基于视觉语义信息的建图与定位综述

    点云PCL免费知识星球,点云论文速读。 文章:Semantic Visual Simultaneous Localization and Mapping: A Survey 作者:Kaiqi Chen, Jianhua Zhang, Jialing Liu, Qiyi Tong, Ruyu Liu, Shengyong Chen 编辑:点云PCL 来源:arXiv 2022 欢迎各位加入免费知识星球,获取PDF论文,欢迎转发朋友圈。文章仅做学术分享,如有

    2023年04月18日
    浏览(32)
  • ROS-基于PX4的无人机SLAM建图(Cartographer)仿真

    首先在电脑上安装好Ubuntu系统和ROS系统,我安装的是Ubuntu18.04和ROS Melodic,不同的Ubuntu版本对应不同的ROS版本 ROS发布日期 ROS版本 停止支持日期 对应Ubuntu版本 2018年5月23日 ROS Melodic Morenia 2023年5月 Ubuntu 18.04 2016年5月23日 ROS Kinetic Kame 2021年4月 Ubuntu 16.04 (Xenial) Ubuntu 15.10 (Wily) 201

    2024年02月15日
    浏览(30)
  • 经典文献阅读之--Orbeez-SLAM(单目稠密点云建图)

    对于现在的VSLAM而言,现在越来越多的工作开始聚焦于如何将深度学习结合到VSLAM当中,而最近的这个工作就给出了一个比较合适的方法。《Orbeez-SLAM: A Real-time Monocular Visual SLAM with ORB Features and NeRF-realized Mapping》这篇文章,可以轻松适应新的场景,而不需要预先训练,并实时为

    2024年02月13日
    浏览(29)
  • 基于Gazebo搭建移动机器人,并结合SLAM系统完成定位和建图仿真

    博客地址:https://www.cnblogs.com/zylyehuo/ gazebo小车模型创建及仿真详见之前博客 gazebo小车模型(附带仿真环境) - zylyehuo - 博客园 gazebo+rviz 仿真 - zylyehuo - 博客园 参考链接 Autolabor-ROS机器人入门课程《ROS理论与实践》 安装 gmapping 包(用于构建地图): sudo apt install ros-melodic-gmapping 安

    2024年02月04日
    浏览(33)
  • Cartographer算法2D激光雷达与IMU融合建图

     上一篇文章讲了cartographer算法手持雷达建图的参数调试,这篇进一步讲如何融合2D雷达与IMU采用cartographer算法进行slam建图。 cartographer算法手持二维激光雷达建图(不使用里程计及IMU) https://blog.csdn.net/wangchuchua/article/details/127268037?spm=1001.2014.3001.5502 思岚s1激光雷达、Tobotics

    2024年02月07日
    浏览(32)
  • Cartographer算法2D激光雷达与IMU融合建图 首先先说一下我的硬件设备:

     上一篇文章讲了cartographer算法手持雷达建图的参数调试,这篇进一步讲如何融合2D雷达与IMU采用cartographer算法进行slam建图。 cartographer算法手持二维激光雷达建图(不使用里程计及IMU) https://blog.csdn.net/wangchuchua/article/details/127268037?spm=1001.2014.3001.5502 思岚s1激光雷达、Tobotics

    2023年04月10日
    浏览(31)
  • 移动机器人激光SLAM导航(五):Cartographer SLAM 篇

    参考 Cartographer 官方文档 Cartographer 从入门到精通 1.1 前置条件 推荐在刚装好的 Ubuntu 16.04 或 Ubuntu 18.04 上进行编译 ROS 安装:ROS学习1:ROS概述与环境搭建 1.2 依赖库安装 资源下载完解压并执行以下指令 https://pan.baidu.com/s/1LWqZ4SOKn2sZecQUDDXXEw?pwd=j6cf 1.3 编译 本文只编译 cartographer

    2024年02月21日
    浏览(33)
  • SLAM【十一】建图

    建图的功能: 定位:第一次跑,就把地图保存下来,让机器人在下次开机后依然能够在地图中定位。 导航:需要创建稠密地图,因为要进行路径规划,需要知道 地图中那些地方可以通过,那些地方不可以通过 避障:与导航类似,但更注重局部的、动态的障碍物的处理。也需

    2023年04月13日
    浏览(25)
  • Docker搭建SLAM进行建图

    目录 [前言] Docker介绍 Docker使用步骤 1.将.tar文件导入docker中 2.查看镜像是否成功导入 3. 将镜像生成容器 4.docker使用方法 [前言] 本文主要介绍了使用docker-slam进行建图实验,博主的Ubuntu系统的内存不够用了,跑不了Docker,是在别人的电脑上跑的,故这里就无法提供相关的材料及

    2024年01月18日
    浏览(22)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包