cartographer开始轨迹时的处理

首先是MapBuilder的声明与构造

node_main.cc

auto map_builder = 
cartographer::mapping::CreateMapBuilder(node_options.map_builder_opt
ions);
Node node(node_options, std::move(map_builder), &tf_buffer, 
FLAGS_collect_metrics);

 CreateMapBuilder函数

std::unique_ptr<MapBuilderInterface> CreateMapBuilder(
    const proto::MapBuilderOptions& options) {
  return absl::make_unique<MapBuilder>(options);
}

函数使用 absl::make_unique 来创建一个 MapBuilder 对象,并将 options 作为参数传递给 MapBuilder 的构造函数,然后将其封装在一个 std::unique_ptr<MapBuilderInterface> 中,并返回该指针。

在Node的构造函数中

Node::Node(
 const NodeOptions& node_options,
 std::unique_ptr<cartographer::mapping::MapBuilderInterface> 
map_builder,
 tf2_ros::Buffer* const tf_buffer, const bool collect_metrics)
 : node_options_(node_options),
 map_builder_bridge_(node_options_, std::move(map_builder),

 将map_builder的所有权给转移

map_builder_bridge_是MapBuilderBridge类

再看MapBuilderBridge类的构造

MapBuilderBridge::MapBuilderBridge(
 const NodeOptions& node_options,
 std::unique_ptr<cartographer::mapping::MapBuilderInterface> 
map_builder,
 tf2_ros::Buffer* const tf_buffer)
 : node_options_(node_options),
 map_builder_(std::move(map_builder)),
 tf_buffer_(tf_buffer) {}

 将map_builder的所有权在给转移

MapBuilder的构造在上文的工厂函数就创建了

轨迹开始的处理

node_main.cc的StartTrajectoryWithDefaultTopics函数

void Node::StartTrajectoryWithDefaultTopics(const TrajectoryOptions& options) {
  absl::MutexLock lock(&mutex_);
  // 检查TrajectoryOptions是否存在2d或者3d轨迹的配置信息
  CHECK(ValidateTrajectoryOptions(options));
  // 添加一条轨迹
  AddTrajectory(options);
}

 Node的AddTrajectory函数

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

  // 创建了一个3s执行一次的定时器,由于oneshot=true, 所以只执行一次
  // 检查设置的topic名字是否在ros中存在, 不存在则报错
  wall_timers_.push_back(node_handle_.createWallTimer(
      ::ros::WallDuration(kTopicMismatchCheckDelaySec), // kTopicMismatchCheckDelaySec = 3s
      &Node::MaybeWarnAboutTopicMismatch, this, /*oneshot=*/true));

  // 将topic名字保存下来,用于之后的新建轨迹时检查topic名字是否重复
  for (const auto& sensor_id : expected_sensor_ids) {
    subscribed_topics_.insert(sensor_id.id);
  }

  return trajectory_id;
}

map_builder_bridge_.AddTrajectory(expected_sensor_ids, options);

 MapBuilderBridge类的AddTrajectory函数、

int MapBuilderBridge::AddTrajectory(
    const std::set<cartographer::mapping::TrajectoryBuilderInterface::SensorId>&
        expected_sensor_ids,
    const TrajectoryOptions& trajectory_options) {
  // Step: 1 开始一条新的轨迹, 返回新轨迹的id,需要传入一个函数
  const int trajectory_id = map_builder_->AddTrajectoryBuilder(
      expected_sensor_ids, trajectory_options.trajectory_builder_options,
      // lambda表达式 local_slam_result_callback_
      [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>) {
        // 保存local slam 的结果数据 5个参数实际只用了4个
        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);
  // Step: 2 为这个新轨迹 添加一个SensorBridge
  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)); // CollatedTrajectoryBuilder
  
  // Step: 3 保存轨迹的参数配置
  auto emplace_result =
      trajectory_options_.emplace(trajectory_id, trajectory_options);
  CHECK(emplace_result.second == true);
  return trajectory_id;
}

 调用了MapBuilder::AddTrajectoryBuilder函数

int MapBuilder::AddTrajectoryBuilder(
 const std::set<SensorId>& expected_sensor_ids,
 const proto::TrajectoryBuilderOptions& trajectory_options,
 LocalSlamResultCallback local_slam_result_callback) {
 // CollatedTrajectoryBuilder 初始化
 
trajectory_builders_.push_back(absl::make_unique<CollatedTrajectoryB
uilder>(
 trajectory_options, 
 sensor_collator_.get(), // sensor::Collator
 trajectory_id,
 expected_sensor_ids,
 CreateGlobalTrajectoryBuilder2D(
 std::move(local_trajectory_builder), trajectory_id,
 static_cast<PoseGraph2D*>(pose_graph_.get()),
 local_slam_result_callback, 
pose_graph_odometry_motion_filter)));
 return trajectory_id;
}
// 返回指向 CollatedTrajectoryBuilder 的指针
mapping::TrajectoryBuilderInterface *GetTrajectoryBuilder(
 int trajectory_id) const override {
 return trajectory_builders_.at(trajectory_id).get(); 
}

<think>好的,用户的问题是关于在使用Cartographer完成建图后如何进行轨迹规划。首先,我需要明确Cartographer的主要功能是SLAM,也就是同定位与建图,它生成的是环境的地图以及机器人的定位信息。而轨迹规划通常是在已有的地图基础上,结合定位数据,规划出一条从起点到目标点的安全、高效路径。 接下来,用户可能需要了解轨迹规划的具体步骤和相关技术。轨迹规划一般分为全局路径规划和局部路径规划。全局规划基于整个地图,比如使用A*、Dijkstra或RRT等算法生成大致路径;局部规划则处理障碍物避让,常用动态窗口法(DWA)或模型预测控制(MPC)。 ROS中常用的工具是move_base,它整合了全局和局部规划器。用户需要配置move_base的参数文件,指定全局和局部规划器,比如使用navfn做全局规划,dwa_local_planner做局部规划。同,需要确保Cartographer发布的地图话题(如/map)和定位信息(如/odom或/tf)正确连接到规划系统。 另外,用户可能需要将Cartographer生成的地图保存,并在规划加载。这可以通过map_server包来实现。建图完成后,使用map_saver保存地图,然后在规划节点中加载该地图作为静态层。 可能的问题点包括坐标系的转换,比如odom、map和base_link之间的tf树是否正确,以及传感器数据的配置是否正确。例如,Cartographer在纯定位模式下可能需要调整参数,确保里程计和地图数据的融合正确,避免定位漂移影响规划。 用户提供的引用中提到了Cartographer的配置,比如是否使用外部里程计。这会影响定位信息的来源,进而影响轨迹规划的准确性。如果使用外部里程计,需要确保odom_frame和提供里程计的话题正确配置,避免出现坐标系错误。 最后,用户可能需要具体的操作步骤,比如如何启动move_base,如何配置参数文件,如何测试规划算法等。可能需要指导用户编写launch文件,调整参数,以及使用RViz进行可视化调试。同,建议用户参考ROS的官方文档和相关教程,比如Navigation Tuning Guide,以解决实际应用中可能遇到的问题。</think>在使用Cartographer完成建图后,轨迹规划通常需要结合ROS导航栈(Navigation Stack)实现。以下是具体步骤和方法: --- ### 一、准备工作 1. **保存并加载地图** 使用`map_server`保存Cartographer生成的地图: ```bash rosrun map_server map_saver -f my_map ``` 在轨迹规划,通过`map_server`加载地图: ```xml <node name="map_server" pkg="map_server" type="map_server" args="$(find my_pkg)/maps/my_map.yaml"/> ``` 2. **确认坐标系关系** Cartographer输出的坐标系通常包括`map`(全局地图坐标系)、`odom`(里程计坐标系)和`base_link`(机器人基座坐标系)。需确保`tf`树正确发布,例如: ``` map -> odom -> base_link ``` 若使用外部里程计,需调整`lua`配置中的`odom_frame`和`use_odometry`参数[^3]。 --- ### 二、配置导航栈(Navigation Stack) 1. **安装必要功能包** ```bash sudo apt-get install ros-<distro>-navigation ``` 2. **设置全局与局部规划器** - **全局规划器**:常用`navfn`或`global_planner`(支持A*、Dijkstra算法) - **局部规划器**:常用`dwa_local_planner`或`teb_local_planner`(支持动态避障) 在`move_base`参数文件(如`move_base_params.yaml`)中指定: ```yaml base_global_planner: navfn/NavfnROS base_local_planner: dwa_local_planner/DWAPlannerROS ``` 3. **配置代价地图** 定义`costmap_common_params.yaml`、`global_costmap_params.yaml`和`local_costmap_params.yaml`,设置障碍物层、膨胀层及传感器输入(如激光雷达话题`/scan`)。 --- ### 三、轨迹规划实现 1. **启动move_base节点** ```xml <node pkg="move_base" type="move_base" name="move_base" output="screen"> <rosparam file="$(find my_robot)/config/move_base_params.yaml" command="load"/> <rosparam file="$(find my_robot)/config/costmap_common_params.yaml" command="load" ns="global_costmap"/> <rosparam file="$(find my_robot)/config/costmap_common_params.yaml" command="load" ns="local_costmap"/> <rosparam file="$(find my_robot)/config/global_costmap_params.yaml" command="load"/> <rosparam file="$(find my_robot)/config/local_costmap_params.yaml" command="load"/> </node> ``` 2. **发送目标点** 通过RViz的`2D Nav Goal`工具或程序发布目标位姿到`/move_base_simple/goal`话题: ```python import rospy from geometry_msgs.msg import PoseStamped goal = PoseStamped() goal.header.frame_id = "map" goal.pose.position.x = 3.0 goal.pose.orientation.w = 1.0 pub = rospy.Publisher('/move_base_simple/goal', PoseStamped, queue_size=10) pub.publish(goal) ``` --- ### 四、调试与优化 1. **调整规划器参数** - 全局规划:修改`planner_window`和`default_tolerance` - 局部规划:调整`max_vel_x`、`acc_lim_theta`和`path_distance_bias`等参数[^1] 2. **处理定位漂移** 若使用Cartographer纯定位模式,需确保实点云与地图匹配,并优化`pose_graph`参数[^2]。 --- ### 五、应用案例 - **仓库AGV调度**:基于全局地图规划固定路线,结合局部避障应对动态障碍 - **服务机器人导航**:通过`AMCL`(自适应蒙特卡罗定位)与`move_base`实现动态环境路径规划 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值