Cartographer_learn_2

本文详细解析了Cartographer中的Node类如何通过Node::map_builder_bridge_实现服务功能,重点剖析了AddTrajectory函数,涉及轨迹生成、位姿估计器、传感器采样器和数据订阅。Node更像是一个封装,大部分功能实际由map_builder_bridge驱动。
摘要由CSDN通过智能技术生成

Cartographer_learn_2


#Node的所提供的服务
从上一篇中我们看到Node类提供了一些服务,有"submap_query"即通过submap获取对应的轨迹(回调函数为Node::HandleSubmapQuery),打开这个函数我们看到这个函数其实是调用了 Node::map_builder_bridge_来实现具体功能。同样的我们看到调用了回调函数Node::HandleTrajectoryQuery和Node::HandleWriteState的服务,它们具体的实现也是由map_builder_bridge_这个对象去具体实现功能的。
乍一看好像调用了
Node::HandleStartTrajectory
Node::HandleFinishTrajectory的两个服务并没有调用了map_builder_bridge_来实现,这里上代码:

bool Node::HandleStartTrajectory(
    ::cartographer_ros_msgs::StartTrajectory::Request& request,
    ::cartographer_ros_msgs::StartTrajectory::Response& response) {
    ......//前面是一系列的检查,检查即设置,比如检查有没有初始位姿,如果由便设置,检查是否存在生成轨迹的必要配置信息,没有就报错等
    else {
    response.status.message = "Success.";
    response.trajectory_id = AddTrajectory(trajectory_options);   //真正生成轨迹
    response.status.code = cartographer_ros_msgs::StatusCode::OK;
  }
  return true;
 }

##生成轨迹的函数
int Node::AddTrajectory(const TrajectoryOptions& options) {

  const std::set<cartographer::mapping::TrajectoryBuilderInterface::SensorId>
      expected_sensor_ids = ComputeExpectedSensorIds(options);
  // 调用map_builder_bridge的AddTrajectory, 添加一个轨迹
  const int trajectory_id =
      map_builder_bridge_.AddTrajectory(expected_sensor_ids, options);
  // 新增一个位姿估计器
  AddExtrapolator(trajectory_id, options);
  // 新生成一个传感器数据采样器
  AddSensorSamplers(trajectory_id, options);
  // 订阅话题与注册回调函数
  LaunchSubscribers(options, trajectory_id);
  wall_timers_.push_back(node_handle_.createWallTimer(
      ::ros::WallDuration(kTopicMismatchCheckDelaySec), // kTopicMismatchCheckDelaySec = 3s
      &Node::MaybeWarnAboutTopicMismatch, this, /*oneshot=*/true));
  for (const auto& sensor_id : expected_sensor_ids) {
    subscribed_topics_.insert(sensor_id.id);
  }
  return trajectory_id;
}

我们看到其实轨迹的生成也是调用了map_builder_bridge_来具体生成的,下一节我们再来具体的看看Node::AddTrajectory这个函数,很重要。
同样的这里直接给出结论Node::HandleFinishTrajectory这个回调函数是调用了Node::FinishTrajectoryUnderLock,再由FinishTrajectoryUnderLock调用map_builder_bridge_去具体实现功能。
所以我们得到了结论,再提供ros服务这方面,Node这个类只是一个封装,其具体实现是依靠它的成员变量
*MapBuilderBridge map_builder_bridge_***具体实现。

#函数Node::AddTrajectory
这个函数虽然调用了map_builder_bridge_去具体的生成一条轨迹,但是我们看到生成一条轨迹后它还做了大量的工作
(1)生成位姿估计器(还没学完,但是我认为(瞎猜)是利用imu和里程计给出位姿的初值,便于在前端做迭代求解),这个位姿估计器生成后存储在map<int, ::cartographer::mapping::PoseExtrapolator> Node::extrapolators_中,其中索引是轨迹的id
(2)生成传感器采样器,这里也有目前想不明白的点,是要丢弃掉一些传感器数据吗?生成后存储在unordered_map<int, TrajectorySensorSamplers> Node::sensor_samplers_中,索引是轨迹的id
(3)重点又来了(怎么全是重点,吐槽),订阅一些话题。这里说一下目前我对轨迹的理解,还不全面,至少做一次slam过程就要生成一条轨迹,那么一条轨迹要怎么接受传感器数据呢?就在这,通过订阅一些话题去接收传感器数据。又上代码了

void Node::LaunchSubscribers(const TrajectoryOptions& options,
                             const int trajectory_id) {
  // laser_scan 的订阅与注册回调函数, 多个laser_scan 的topic 共用同一个回调函数
  for (const std::string& topic :
       ComputeRepeatedTopicNames(kLaserScanTopic, options.num_laser_scans)) {
    subscribers_[trajectory_id].push_back(
        {SubscribeWithHandler<sensor_msgs::LaserScan>(
             &Node::HandleLaserScanMessage, trajectory_id, topic, &node_handle_,
             this),
         topic});
  }
  // multi_echo_laser_scans的订阅与注册回调函数
  for (const std::string& topic : ComputeRepeatedTopicNames(
           kMultiEchoLaserScanTopic, options.num_multi_echo_laser_scans)) {
    subscribers_[trajectory_id].push_back(
        {SubscribeWithHandler<sensor_msgs::MultiEchoLaserScan>(
             &Node::HandleMultiEchoLaserScanMessage, trajectory_id, topic,
             &node_handle_, this),
         topic});
  }
  // point_clouds 的订阅与注册回调函数
  for (const std::string& topic :
       ComputeRepeatedTopicNames(kPointCloud2Topic, options.num_point_clouds)) {
    subscribers_[trajectory_id].push_back(
        {SubscribeWithHandler<sensor_msgs::PointCloud2>(
             &Node::HandlePointCloud2Message, trajectory_id, topic,
             &node_handle_, this),
         topic});
  }
  // For 2D SLAM, subscribe to the IMU if we expect it. For 3D SLAM, the IMU is
  // required.
  if (node_options_.map_builder_options.use_trajectory_builder_3d() ||
      (node_options_.map_builder_options.use_trajectory_builder_2d() &&
       options.trajectory_builder_options.trajectory_builder_2d_options()
           .use_imu_data())) {
    subscribers_[trajectory_id].push_back(
        {SubscribeWithHandler<sensor_msgs::Imu>(&Node::HandleImuMessage,
                                                trajectory_id, kImuTopic,
                                                &node_handle_, this),
         kImuTopic});
  }
  // odometry 的订阅与注册回调函数
  if (options.use_odometry) {
    subscribers_[trajectory_id].push_back(
        {SubscribeWithHandler<nav_msgs::Odometry>(&Node::HandleOdometryMessage,
                                                  trajectory_id, kOdometryTopic,
                                                  &node_handle_, this),
         kOdometryTopic});
  }
  // gps 的订阅与注册回调函数
  if (options.use_nav_sat) {
    subscribers_[trajectory_id].push_back(
        {SubscribeWithHandler<sensor_msgs::NavSatFix>(
             &Node::HandleNavSatFixMessage, trajectory_id, kNavSatFixTopic,
             &node_handle_, this),
         kNavSatFixTopic});
  }
  // landmarks 的订阅与注册回调函数
  if (options.use_landmarks) {
    subscribers_[trajectory_id].push_back(
        {SubscribeWithHandler<cartographer_ros_msgs::LandmarkList>(
             &Node::HandleLandmarkMessage, 
             trajectory_id, 
             kLandmarkTopic,
             &node_handle_, 
             this),
         kLandmarkTopic});
  }
}

我们看到订阅了laser_scan,multi_echo_laser_scans,点云,imu(根据需要),里程计(根据需要),gps(根据需要)和landmark(根据需要)的数据。同时我们也看到了一系列的数据处理的函数,就是这些订阅者的回调函数。如果你有兴趣,可以打开这些回调函数看看,大多是先通过前面的传感器采样器判断要不要接收本次数据,要的话又又又调用*map_builder_bridge_*去具体处理,当然有点小小不同,这次调用的map_builder_bridge_中的一个sensor_bridge去具体处理,相信后面我们还会具体讨论它。

#Node发布的话题
这里就简单的提一下,可以在Cartographer_learn_1这篇文章中看到,这些话题的发布都是通过与定时器绑定去发布的,所以我们可以在Node构造函数的step4中看到它们的回调函数,打开这些回调函数具体看会发现这些话题发布的数据来源依然是map_builder_bridge_。看到这,有一种感觉,就是这个Node好像就是个封装,其具体的实现大多都在map_builder_bridge_中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值