Cartographer学习记录:cartographer源码3D SLAM部分(三)

上一篇是由于node_main.cc中调用了node类的构造函数,所以展开阅读了下node.h和node.cc文件,从而大致了解node类内各种函数的主要功能。本篇将回到node_main.cc文件,继续读完剩余的代码。从node类的构造函数往下阅读,并了解如何具体应用node类的成员函数。

1.LoadState函数

if (!FLAGS_load_state_filename.empty()) {
    //加载数据包数据;
    node.LoadState(FLAGS_load_state_filename, FLAGS_load_frozen_state);
  }

在node_main.cc函数的前面中,若是load_state_filename设置为非空,则调用node类的LoadState函数加载数据包。如下是具体实现:

void Node::LoadState(const std::string& state_filename,
                     const bool load_frozen_state) {
  absl::MutexLock lock(&mutex_);// 互斥锁
  map_builder_bridge_.LoadState(state_filename, load_frozen_state);
}

可以看到调用了MapBuilderBridge类的LoadState函数,跟着深入到MapBuilderBridge类,在map_builder_bridge.h中可以查看到其声明为:

void LoadState(const std::string& state_filename, bool load_frozen_state);

参数一为加载数据包名称,参数二为是否加载已保存的非优化路径状态。

接下来转到map_builder_bridge.cc文件中查看具体实现:

void MapBuilderBridge::LoadState(const std::string& state_filename,
                                 bool load_frozen_state) {
  // Check if suffix of the state file is ".pbstream".
  const std::string suffix = ".pbstream";
  CHECK_EQ(state_filename.substr(
               std::max<int>(state_filename.size() - suffix.size(), 0)),
           suffix)
      << "The file containing the state to be loaded must be a "
         ".pbstream file.";
  LOG(INFO) << "Loading saved state '" << state_filename << "'...";
  cartographer::io::ProtoStreamReader stream(state_filename);
  map_builder_->LoadState(&stream, load_frozen_state);
}

从这里我们可以发现,MapBuilderBridge类的LoadState函数调用了 MapBuilder的成员函数 LoadState 来加载一个pbstream 文件,所以具体细节问题留到后续更新的cartographer底层代码阅读时讲解。map_builder_是接口MapBuilderInterface 的实例化对象,而根据是 2d 还是 3d 情况,其具体实现会略有不同,至于如何具体化为2D或者3D对象,在后面我也会慢慢细说。

2.StartTrajectoryWithDefaultTopics函数

if (FLAGS_start_trajectory_with_default_topics) {
    //以默认话题开始规划路径;
    node.StartTrajectoryWithDefaultTopics(trajectory_options);
  }

同理,在node_main.cc函数的前面也是定义了start_trajectory_with_default_topics,默认情况下为true,cartographer的ROS上层也是从这里开始以默认话题进行轨迹的更新,这里稍带提一句,默认的话题名称设置可以到node_constants.h里面进行设置,如下所示:

// Default topic names; expected to be remapped as needed.
constexpr char kLaserScanTopic[] = "scan";
constexpr char kMultiEchoLaserScanTopic[] = "echoes";
constexpr char kPointCloud2Topic[] = "points2";
constexpr char kImuTopic[] = "imu";
constexpr char kOdometryTopic[] = "odom";
constexpr char kNavSatFixTopic[] = "fix";
constexpr char kLandmarkTopic[] = "landmark";
constexpr char kFinishTrajectoryServiceName[] = "finish_trajectory";
constexpr char kOccupancyGridTopic[] = "map";
constexpr char kScanMatchedPointCloudTopic[] = "scan_matched_points2";
constexpr char kSubmapListTopic[] = "submap_list";
constexpr char kTrackedPoseTopic[] = "tracked_pose";
constexpr char kSubmapQueryServiceName[] = "submap_query";
constexpr char kTrajectoryQueryServiceName[] = "trajectory_query";
constexpr char kStartTrajectoryServiceName[] = "start_trajectory";
constexpr char kWriteStateServiceName[] = "write_state";
constexpr char kGetTrajectoryStatesServiceName[] = "get_trajectory_states";
constexpr char kReadMetricsServiceName[] = "read_metrics";
constexpr char kTrajectoryNodeListTopic[] = "trajectory_node_list";
constexpr char kLandmarkPosesListTopic[] = "landmark_poses_list";
constexpr char kConstraintListTopic[] = "constraint_list";
constexpr double kConstraintPublishPeriodSec = 0.5;
constexpr double kTopicMismatchCheckDelaySec = 3.0;

constexpr int kInfiniteSubscriberQueueSize = 0;
constexpr int kLatestOnlyPublisherQueueSize = 1;

下面回到node.h里面有关StartTrajectoryWithDefaultTopics函数的声明:

void StartTrajectoryWithDefaultTopics(const TrajectoryOptions& options);

参数只有一个,是有关轨迹的配置信息,也就是在trajectory_builder.lua文件里面的配置。下面看node.cc文件内的具体实现:

void Node::StartTrajectoryWithDefaultTopics(const TrajectoryOptions& options) {
  absl::MutexLock lock(&mutex_);
  CHECK(ValidateTrajectoryOptions(options));
  AddTrajectory(options);
}

可以看到简单三行代码,先是设置互斥锁,防止多个对象同时访问该资源产生冲突,然后是调用node类的成员函数ValidateTrajectoryOptions对配置信息进行验证,根据2D和3D的不同进行是否拥有相关设置信息的验证,否则返回false,最后是根据配置信息添加轨迹信息,下面为两个函数的具体实现。

bool Node::ValidateTrajectoryOptions(const TrajectoryOptions& options) {
  if (node_options_.map_builder_options.use_trajectory_builder_2d()) {
    return options.trajectory_builder_options
        .has_trajectory_builder_2d_options();
  }
  if (node_options_.map_builder_options.use_trajectory_builder_3d()) {
    return options.trajectory_builder_options
        .has_trajectory_builder_3d_options();
  }
  return false;
}

 从node类的AddTrajectory函数我们可以看到主要调用了ComputeExpectedSensorIds、MapBuilderBridge类的AddTrajectory函数、AddExtrapolator、AddSensorSamplers、LaunchSubscribers函数,这里将继续挖掘MapBuilderBridge类的AddTrajectory函数,其余函数的功能可以参照上一篇,主要是返回具体topic名称、添加位姿估计器、添加传感器采样器、ROS topic订阅与回调函数。

int Node::AddTrajectory(const TrajectoryOptions& options) {
  const std::set<cartographer::mapping::TrajectoryBuilderInterface::SensorId>
      expected_sensor_ids = ComputeExpectedSensorIds(options);
  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),
      &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.h文件内的声明:

int AddTrajectory(
      const std::set<
          ::cartographer::mapping::TrajectoryBuilderInterface::SensorId>&
          expected_sensor_ids,
      const TrajectoryOptions& trajectory_options);

输入参数为指定的topic名称集合、轨迹配置信息,下面看map_builder_bridge.cc文件内的具体实现:

int MapBuilderBridge::AddTrajectory(
    const std::set<cartographer::mapping::TrajectoryBuilderInterface::SensorId>&
        expected_sensor_ids,
    const TrajectoryOptions& trajectory_options) {
  //调用了 map_builder_->AddTrajectoryBuilder,传入的数据有传感器的 id,trajectory 的一些配置参数,以及一个lambda表达式    
  const int trajectory_id = map_builder_->AddTrajectoryBuilder(
      expected_sensor_ids, trajectory_options.trajectory_builder_options,
      [this](const int trajectory_id, const ::cartographer::common::Time time,
             const Rigid3d local_pose,
             ::cartographer::sensor::RangeData range_data_in_local,
             const std::unique_ptr<
                 const ::cartographer::mapping::TrajectoryBuilderInterface::
                     InsertionResult>) {
        OnLocalSlamResult(trajectory_id, time, local_pose, range_data_in_local);
      });
  LOG(INFO) << "Added trajectory with ID '" << trajectory_id << "'.";

  // Make sure there is no trajectory with 'trajectory_id' yet.
  CHECK_EQ(sensor_bridges_.count(trajectory_id), 0);
  sensor_bridges_[trajectory_id] = absl::make_unique<SensorBridge>(
      trajectory_options.num_subdivisions_per_laser_scan,
      trajectory_options.tracking_frame,
      node_options_.lookup_transform_timeout_sec, tf_buffer_,
      map_builder_->GetTrajectoryBuilder(trajectory_id));
  auto emplace_result =
      trajectory_options_.emplace(trajectory_id, trajectory_options);
  CHECK(emplace_result.second == true);
  return trajectory_id;
}

可以清楚的看到,依旧是稳健地调用了MapBuilder类的AddTrajectoryBuilder函数,其余主要是打印错误信息以及检查相应参数变量是否正确,该函数返回的是添加的轨迹编号。

3.FinishAllTrajectories函数

node.h文件内的声明:

void FinishAllTrajectories();

很简单,接下来看下node.cc文件内的具体实现:

void Node::FinishAllTrajectories() {
  absl::MutexLock lock(&mutex_);
  for (const auto& entry : map_builder_bridge_.GetTrajectoryStates()) {
    if (entry.second == TrajectoryState::ACTIVE) {
      const int trajectory_id = entry.first;
      CHECK_EQ(FinishTrajectoryUnderLock(trajectory_id).code,
               cartographer_ros_msgs::StatusCode::OK);
    }
  }
}

主要调用了MapBuilderBridge类的GetTrajectoryStates函数和node类内的FinishTrajectoryUnderLock函数。

(1)MapBuilderBridge类的GetTrajectoryStates函数依旧与MapBuilder类中的posegraph(用于全局优化)的GetTrajectoryStates函数相关,返回的是存储轨迹状态的map容器。

std::map<int, ::cartographer::mapping::PoseGraphInterface::TrajectoryState>
MapBuilderBridge::GetTrajectoryStates() {
  auto trajectory_states = map_builder_->pose_graph()->GetTrajectoryStates();
  // Add active trajectories that are not yet in the pose graph, but are e.g.
  // waiting for input sensor data and thus already have a sensor bridge.
  for (const auto& sensor_bridge : sensor_bridges_) {
    trajectory_states.insert(std::make_pair(
        sensor_bridge.first,
        ::cartographer::mapping::PoseGraphInterface::TrajectoryState::ACTIVE));
  }
  return trajectory_states;
}

(2)FinishTrajectoryUnderLock函数前面检查了一下是否可以关掉,指定 id 是否存在,是否已经被 Finished 了等情况后,如果一切正常,则停止订阅 Topic、清除 id 及其他与该 trajectory 相关的量。最后调用 map_builder_bridge_中的FinishTrajectory 函数。

cartographer_ros_msgs::StatusResponse Node::FinishTrajectoryUnderLock(
    const int trajectory_id) {
  cartographer_ros_msgs::StatusResponse status_response;
  //对是否可以结束相应路径进行判断,并进行异常处理
  if (trajectories_scheduled_for_finish_.count(trajectory_id)) {
    status_response.message = absl::StrCat("Trajectory ", trajectory_id,
                                           " already pending to finish.");
    status_response.code = cartographer_ros_msgs::StatusCode::OK;
    LOG(INFO) << status_response.message;
    return status_response;
  }

  // First, check if we can actually finish the trajectory.
  status_response = TrajectoryStateToStatus(
      trajectory_id, {TrajectoryState::ACTIVE} /* valid states */);
  if (status_response.code != cartographer_ros_msgs::StatusCode::OK) {
    LOG(ERROR) << "Can't finish trajectory: " << status_response.message;
    return status_response;
  }

  // Shutdown the subscribers of this trajectory.
  // A valid case with no subscribers is e.g. if we just visualize states.
  if (subscribers_.count(trajectory_id)) {
    for (auto& entry : subscribers_[trajectory_id]) {
      entry.subscriber.shutdown();
      subscribed_topics_.erase(entry.topic);
      LOG(INFO) << "Shutdown the subscriber of [" << entry.topic << "]";
    }
    CHECK_EQ(subscribers_.erase(trajectory_id), 1);
  }
  map_builder_bridge_.FinishTrajectory(trajectory_id);
  trajectories_scheduled_for_finish_.emplace(trajectory_id);
  status_response.message =
      absl::StrCat("Finished trajectory ", trajectory_id, ".");
  status_response.code = cartographer_ros_msgs::StatusCode::OK;
  return status_response;
}

map_builder_bridge_中的FinishTrajectory 函数主要细节藏在MapBuilder类的FinishTrajectory函数。

void MapBuilderBridge::FinishTrajectory(const int trajectory_id) {
  LOG(INFO) << "Finishing trajectory with ID '" << trajectory_id << "'...";

  // Make sure there is a trajectory with 'trajectory_id'.
  CHECK(GetTrajectoryStates().count(trajectory_id));
  map_builder_->FinishTrajectory(trajectory_id);
  sensor_bridges_.erase(trajectory_id);
}

到此,cartographer的上层基本已经介绍完毕,俗话说的好,“魔鬼总是藏在细节中”,通过阅读我们发现MapBuilderBridge这个类很形象,完美地承担了“桥梁”的作用,搭建了我们从上层通向底层算法的路,接下来,我将仔细解读MapBuilder这个类是如何一步一步完成我们最终的SLAM的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值