ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

ROS2+SLAM+Nav2全链路实战:从Gazebo仿真到实机部署的避坑指南

ROS2+SLAM+Nav2全链路实战:从Gazebo仿真到实机部署的避坑指南 1. 从一台扫地机说起为什么我要跑通这条全链路去年年底我接手了一个小项目需求说起来很简单让一台差速轮式机器人底盘结构跟主流扫地机几乎一样在未知的室内环境里自己跑起来先建图再基于建好的图做自主导航能自己规划路径、绕开障碍、到达指定点位。听起来像是把SLAM和Nav2两个包跑起来就完事了但真正动手之后我才发现从点云到地图、从地图到导航这条链路上坑远比想象中多。这篇文章就是我把整条链路完整跑通之后的复盘。核心关键词是SLAM、Nav2、ROS2、LiDAR、Gazebo我会从仿真环境搭建一直讲到实机部署把每个环节的关键参数、踩过的坑、以及为什么这么选的理由都摊开来说。适合谁看如果你正在学ROS2想找一个完整的项目把SLAM和导航串起来或者你已经跑过TurtleBot3的官方demo但换成自己的机器人就各种报错再或者你对激光雷达建图和Nav2导航的底层逻辑感兴趣想搞清楚每个参数到底在干什么——那这篇内容应该能帮到你。整条链路我拆成五块仿真环境与机器人模型、LiDAR与IMU的标定与数据接入、SLAM建图、地图后处理与八叉树转换、Nav2导航配置与调优。每一块我都会给出可直接复现的步骤和参数同时解释背后的原理。文章里涉及的所有代码和配置都基于ROS2 Humble Gazebo Classic 11这是目前最稳定、资料最全的组合新手直接抄作业就行。2. 仿真环境搭建Gazebo里的扫地机模型怎么建2.1 为什么先用Gazebo而不是直接上实机很多人一上来就想接实机跑SLAM我的建议是先别急。实机调试的成本太高了——传感器标定不准、时间同步有问题、底盘控制有延迟任何一个环节出问题都会让SLAM建图直接崩掉而你根本分不清是算法的问题还是硬件的问题。Gazebo的价值在于它提供了一个可控的、可重复的测试环境激光雷达的噪声模型、IMU的漂移、轮子的打滑这些都可以在仿真里复现而且你可以随时暂停、回放、改参数。更重要的是Gazebo里的传感器数据是干净的没有实机上那些莫名其妙的干扰。先用仿真把SLAM和Nav2的配置调通确认算法逻辑没问题再迁移到实机这样排查问题的范围会小很多。我自己的流程就是Gazebo跑通 → 录bag → 用bag离线调参 → 实机验证。2.2 机器人URDF模型的关键部件扫地机这类差速底盘URDF模型里必须包含这几个核心部件base_footprint地面投影坐标系、base_link底盘中心、左右驱动轮left_wheel_link和right_wheel_link、万向轮caster_link以及激光雷达laser_link和IMUimu_link。这里有几个容易出错的点第一base_footprint到base_link的z轴偏移要设对。如果你的底盘中心离地5cm那这个偏移就是0.05设错了会导致激光雷达扫描平面高度不对建出来的图会有重影。第二轮子的joint类型要用continuous而不是revolute因为驱动轮是无限旋转的。同时要在gazebo标签里给轮子加mu1和mu2摩擦系数默认值0.5在仿真里经常打滑我一般设成1.0。第三激光雷达的samples和min_angle/max_angle要匹配真实雷达。我用的是360度单线雷达所以samples设360角度范围-π到πrange设0.12到8.0米。如果你用的是思岚A1这类雷达最大距离12米那就改成12.0。gazebo referencelaser_link sensor typeray namelaser_sensor pose0 0 0 0 0 0/pose visualizetrue/visualize update_rate10/update_rate ray scan horizontal samples360/samples resolution1/resolution min_angle-3.14159/min_angle max_angle3.14159/max_angle /horizontal /scan range min0.12/min max8.0/max /range noise typegaussian/type mean0.0/mean stddev0.01/stddev /noise /ray /sensor /gazebo注意update_rate设10Hz就够了设太高会让Gazebo的CPU占用飙升而且实际雷达也就10-15Hz仿真里没必要追求更高。2.3 差速控制插件配置Gazebo里控制差速底盘要用libgazebo_ros_diff_drive.so插件。关键参数包括left_joint、right_joint、wheel_separation轮距、wheel_diameter轮径。轮距和轮径这两个值必须和URDF里的几何尺寸完全一致否则里程计会算错SLAM建图会直接歪掉。我见过有人轮距填了0.3但实际模型是0.35结果机器人转90度里程计显示转了105度建出来的图整个是扭曲的。这种问题在仿真里还好看出来实机上你根本不知道是哪里错了。plugin namediff_drive filenamelibgazebo_ros_diff_drive.so ros namespace//namespace /ros update_rate50/update_rate left_jointleft_wheel_joint/left_joint right_jointright_wheel_joint/right_joint wheel_separation0.35/wheel_separation wheel_diameter0.065/wheel_diameter max_wheel_torque20/max_wheel_torque max_wheel_acceleration1.0/max_wheel_acceleration command_topiccmd_vel/command_topic odometry_topicodom/odometry_topic odometry_frameodom/odometry_frame robot_base_framebase_footprint/robot_base_frame publish_odomtrue/publish_odom publish_odom_tftrue/publish_odom_tf publish_wheel_tftrue/publish_wheel_tf /plugin2.4 仿真世界文件的选择与修改Gazebo自带的empty_world太干净了建图没什么挑战。我建议用turtlebot3_world或者自己搭一个带家具的室内场景。如果要自己搭在.world文件里加几个立方体当墙壁、圆柱体当桌腿就行。关键是要有足够多的特征纯白墙的走廊对激光SLAM来说是噩梦因为扫描到的都是一样的距离匹配算法会失效。我一般会在场景里放一些不规则形状的障碍物比如斜放的箱子、不同直径的柱子这样激光扫描到的轮廓才有区分度。另外地面要设摩擦系数太滑的话轮子会打滑里程计就不准了。3. LiDAR与IMU标定与数据接入的实操细节3.1 为什么单线雷达也需要IMU很多人觉得2D激光SLAM不需要IMU只用轮式里程计就够了。在理想情况下确实可以但实际中轮式里程计有两个致命问题一是打滑二是角度累积误差。机器人转几个弯之后里程计的角度偏差可能就有十几度这时候激光匹配就会失败。IMU的作用是提供角速度和线加速度在SLAM里主要用来做两个事一是给激光匹配提供一个初始的角度预测减少匹配搜索范围二是和轮式里程计做融合得到更准的odom。我用的是MPU6050这类六轴IMU在Gazebo里可以直接用libgazebo_ros_imu_sensor.so插件模拟。3.2 LiDAR和IMU的外参标定外参标定就是确定LiDAR和IMU之间的相对位置和姿态。在仿真里如果你在URDF里把两个link的joint位置写对了外参就是准的。但实机上雷达和IMU的安装位置总有偏差这个偏差如果不标定SLAM建图会有系统性的偏移。标定方法我说一个最实用的手动测量激光匹配验证。先拿尺子量出LiDAR到IMU的x、y、z偏移填到URDF或TF里。然后跑SLAM看建出来的图有没有明显的重影或弯曲。如果有微调外参里的yaw角直到地图变直。这个过程可能需要反复几次但比用什么标定算法都快。实操心得外参里的roll和pitch一般不用管因为雷达和IMU通常都是水平安装的。重点调yaw也就是两个传感器之间的旋转角度偏差。3.3 时间同步一个容易被忽略的坑ROS2里多个传感器数据融合时间同步是必须处理的。如果LiDAR和IMU的时间戳差了几十毫秒融合出来的位姿就会抖。在仿真里Gazebo的时间是统一的一般没问题。但实机上每个传感器的驱动都有自己的时间戳需要用message_filters做时间同步。我在实机上遇到过一个问题雷达的时间戳用的是系统时间IMU用的是自己的硬件时间两者差了200ms结果SLAM建图时机器人一转弯地图就糊。后来统一用/clock话题做时间源问题才解决。如果你用ROS2的ros2 bag record录数据记得加--use-sim-time参数保证回放时时间一致。3.4 点云数据格式与预处理2D雷达输出的是sensor_msgs/LaserScan3D雷达输出的是sensor_msgs/PointCloud2。如果你用的是3D雷达但只想做2D SLAM需要把点云压扁成LaserScan。方法很简单取点云中z坐标在某个范围内比如-0.1到0.1米的点投影到xy平面然后按角度分bin每个bin取最近距离。这个预处理步骤在pointcloud_to_laserscan这个包里已经实现了配置好min_height、max_height、angle_min、angle_max就行。但要注意3D雷达的点云密度比2D雷达高很多直接转成LaserScan可能会有很多噪点建议先做一次体素滤波降采样。4. SLAM建图从第一帧点云到一张完整地图4.1 SLAM算法选型Cartographer还是SLAM ToolboxROS2里主流的2D SLAM方案有两个Cartographer和SLAM Toolbox。我两个都跑过说下我的选择逻辑。Cartographer的优势是建图质量高回环检测强适合大场景。但它的配置复杂参数多而且对IMU的依赖比较重。SLAM Toolbox的优势是配置简单和Nav2的集成度高支持在线建图和离线建图两种模式而且它的pose graph优化在中小场景下效果很好。我的选择是SLAM Toolbox原因有三个一是它原生支持ROS2不需要像Cartographer那样折腾编译二是它的async模式可以在建图的同时做定位方便后续导航三是它的参数少调起来快。对于扫地机这种室内中小场景SLAM Toolbox完全够用。4.2 SLAM Toolbox的核心参数配置SLAM Toolbox的配置文件里这几个参数最关键mode建图时用mapping导航时用localization。resolution地图分辨率默认0.05米。如果你的机器人很小可以设0.03如果场景很大设0.1可以减少内存占用。max_laser_range雷达最大距离设成和实际雷达一致设大了会引入远处噪点。minimum_travel_distance机器人移动多少距离后更新一次地图默认0.5米。设太小会导致地图更新太频繁CPU占用高设太大则地图更新不及时。minimum_travel_heading机器人转动多少弧度后更新地图默认0.5。这个参数对转弯多的场景很重要设小了地图会更精细。slam_toolbox: ros__parameters: solver_plugin: solver_plugins::CeresSolver ceres_linear_solver: SPARSE_NORMAL_CHOLESKY ceres_preconditioner: SCHUR_JACOBI ceres_trust_strategy: LEVENBERG_MARQUARDT ceres_dogleg_type: TRADITIONAL_DOGLEG ceres_loss_function: None odom_frame: odom map_frame: map base_frame: base_footprint scan_topic: /scan mode: mapping resolution: 0.05 max_laser_range: 8.0 minimum_travel_distance: 0.3 minimum_travel_heading: 0.3 map_update_interval: 2.0 transform_publish_period: 0.02 transform_timeout: 0.2 tf_buffer_duration: 30.0 stack_size_to_use: 40000000 enable_interactive_mode: true4.3 建图实操从启动到保存地图建图的完整流程是这样的启动Gazebo仿真环境ros2 launch my_robot gazebo.launch.py启动SLAM Toolboxros2 launch slam_toolbox online_async_launch.py params_file:mapper_params_online_async.yaml启动RViz2添加Map、LaserScan、TF显示用键盘或手柄控制机器人移动ros2 run teleop_twist_keyboard teleop_twist_keyboard让机器人走遍整个场景注意要走闭环也就是回到起点这样回环检测才能生效建图完成后保存地图ros2 run nav2_map_server map_saver_cli -f my_map注意建图时速度不要太快建议线速度0.2m/s角速度0.5rad/s。速度太快会导致激光匹配跟不上地图会歪。转弯时要慢因为转弯时里程计误差最大。4.4 建图质量评估与常见问题建图完成后怎么判断地图质量好不好我的标准是墙壁是不是直的直角是不是90度有没有重影。如果墙壁有弯曲说明里程计或外参有问题如果有重影说明激光匹配没做好可能是回环检测没生效。常见问题排查表问题现象可能原因解决方法地图墙壁弯曲里程计标定不准重新标定轮距和轮径地图有重影回环检测未生效确保走闭环检查回环参数地图缺失区域雷达被遮挡检查雷达安装位置地图整体旋转IMU方向错误检查IMU的yaw角符号建图过程中地图跳变激光匹配失败降低移动速度增加迭代次数5. 地图后处理与八叉树转换让导航更高效5.1 为什么需要八叉树地图SLAM建出来的是一张2D栅格地图每个格子只有占用和空闲两种状态。这种地图对2D导航够用但如果你想做3D导航或者想区分障碍物和可穿越区域就需要八叉树地图。八叉树把3D空间递归分成八个子空间每个节点存储占用概率这样既能表示3D结构又能压缩存储。在ROS2里八叉树地图用octomap_server这个包来生成。输入是3D点云输出是octomap_msgs/Octomap。对于扫地机来说八叉树地图的好处是可以区分地面和障碍物导航时不会把地面当成障碍。5.2 从2D地图到八叉树的转换流程如果你只有2D雷达没有3D点云也可以生成八叉树地图方法是把2D地图的每个占用格子拉伸成3D柱体。但更常见的做法是直接用3D雷达的点云生成八叉树。流程是这样的启动3D雷达驱动发布/points话题启动octomap_server订阅/points发布/octomap_full和/octomap_binary在RViz2里添加Octomap显示查看3D地图保存八叉树地图ros2 run octomap_server octomap_saver_node -f my_octomap.btoctomap_server: ros__parameters: frame_id: map resolution: 0.05 sensor_model: max_range: 8.0 hit: 0.7 miss: 0.4 min_range: 0.12 occupancy_min_z: -0.1 occupancy_max_z: 2.0 filter_ground: true ground_filter_distance: 0.04 ground_filter_angle: 0.15 ground_filter_plane_distance: 0.07实操心得filter_ground这个参数很关键设成true可以过滤掉地面点避免地面被当成障碍物。ground_filter_distance设0.04米意思是高度差小于4cm的点被认为是地面。5.3 八叉树地图在Nav2中的使用Nav2默认用的是2D代价地图不直接支持八叉树。但你可以把八叉树地图投影成2D代价地图方法是取八叉树中z坐标在机器人高度范围内的占用格子投影到xy平面。这个工作在nav2_costmap_2d里可以通过voxel_layer插件实现它订阅3D点云实时生成3D代价地图。如果你用的是预建的八叉树地图可以用octomap_server的projected_map话题它会把八叉树投影成2D栅格地图然后直接给Nav2用。这样既保留了3D信息又兼容了2D导航。6. Nav2导航配置从代价地图到路径规划6.1 Nav2的整体架构与插件选型Nav2的架构比ROS1的move_base复杂得多它把导航拆成了多个独立的服务器planner_server负责全局路径规划controller_server负责局部路径跟踪behavior_server负责恢复行为bt_navigator负责行为树调度。每个服务器都可以换不同的插件。我的插件选型是这样的全局规划器NavfnPlanner基于Dijkstra算法稳定可靠适合室内场景。局部控制器RegulatedPurePursuitController纯跟踪算法参数少调起来快。代价地图层static_layer静态地图、obstacle_layer实时障碍物、inflation_layer膨胀层。恢复行为Spin、BackUp、Wait。6.2 代价地图的关键参数代价地图是Nav2里最需要调的部分。inflation_radius膨胀半径决定了机器人离障碍物多远就开始避让。这个值应该设成机器人半径加上安全余量。我的机器人半径0.17米安全余量0.1米所以膨胀半径设0.27米。cost_scaling_factor代价缩放因子决定了代价随距离衰减的速度。设大了机器人会贴着障碍物走设小了机器人会离障碍物很远。我一般设3.0实测下来比较平衡。local_costmap: local_costmap: ros__parameters: update_frequency: 5.0 publish_frequency: 2.0 global_frame: odom robot_base_frame: base_footprint rolling_window: true width: 3 height: 3 resolution: 0.05 plugins: [obstacle_layer, inflation_layer] obstacle_layer: plugin: nav2_costmap_2d::ObstacleLayer enabled: true observation_sources: scan scan: topic: /scan max_obstacle_height: 2.0 clearing: true marking: true data_type: LaserScan inflation_layer: plugin: nav2_costmap_2d::InflationLayer cost_scaling_factor: 3.0 inflation_radius: 0.276.3 路径规划与跟踪的调参经验全局路径规划器NavfnPlanner的参数不多主要是tolerance目标点容差和use_astar是否用A*算法。我一般设tolerance为0.5米use_astar为false因为Dijkstra在室内场景下更稳定。局部控制器RegulatedPurePursuitController的参数需要仔细调desired_linear_vel期望线速度我设0.26m/s和扫地机实际速度匹配。lookahead_dist前视距离设0.6米。设小了机器人会震荡设大了会切内弯。min_approach_linear_velocity接近目标时的最小速度设0.05m/s避免急停。max_allowed_time_to_collision_up_to_carrot最大碰撞时间设1.0秒。实操心得调参时先用仿真跑录下bag然后离线反复回放调。实机上调参风险太大撞一次可能就坏了。6.4 行为树与恢复行为配置Nav2的行为树决定了导航的整体流程。默认的行为树是规划路径 → 跟踪路径 → 如果失败执行恢复行为 → 重新规划。恢复行为包括Spin原地旋转、BackUp后退、Wait等待。我一般会把Spin的角度设成90度BackUp的距离设成0.3米Wait的时间设成5秒。这些值可以根据你的场景调整。如果机器人经常在狭窄空间卡住可以把BackUp距离设大一点。7. 常见问题与排查技巧实录7.1 TF树问题最常见的报错来源TF树问题是ROS2导航里最常见的报错。典型症状是RViz里机器人模型不显示或者Nav2报Timed out waiting for transform。排查方法是ros2 run tf2_tools view_frames生成TF树图看有没有断链。常见原因有三个一是base_footprint到base_link的TF没发布二是odom到base_footprint的TF没发布三是时间戳不一致。前两个检查URDF和里程计插件第三个检查use_sim_time参数。7.2 导航时机器人原地转圈或抖动这个问题通常是局部控制器参数没调好。先检查lookahead_dist是不是太小太小会导致机器人频繁修正方向。然后检查min_approach_linear_velocity是不是太大太大会导致机器人到目标点附近时来回震荡。最后检查代价地图的inflation_radius是不是太大太大会导致机器人觉得到处都是障碍。7.3 建图时地图漂移严重地图漂移的根本原因是里程计不准。先检查轮距和轮径参数这两个值必须和实际完全一致。然后检查IMU的yaw角方向如果IMU的yaw和里程计的yaw方向相反融合后会互相抵消导致角度估计完全错误。最后检查激光雷达的安装位置如果雷达不在机器人中心转弯时会有额外的位移误差。7.4 仿真里跑得好实机上就崩这是最典型的问题。仿真和实机的差异主要来自三个方面传感器噪声、时间延迟、底盘响应。仿真里的雷达没有噪声实机上有仿真里的控制指令立即生效实机上有延迟仿真里的轮子不打滑实机上会打滑。解决方法是在仿真里加噪声模型给控制指令加延迟然后重新调参。实机上先低速跑确认没问题再提速。我一般实机调试时线速度不超过0.15m/s等稳定了再慢慢加。7.5 常见问题速查表问题排查方向快速解决RViz不显示地图检查/map话题ros2 topic echo /map --once导航无路径检查全局代价地图看目标点是否在障碍物内机器人不动检查/cmd_velros2 topic echo /cmd_vel定位丢失检查AMCL粒子重新初始化位姿地图与雷达不重合检查TF时间戳统一use_sim_time建图卡顿检查CPU占用降低update_rate8. 从仿真到实机迁移时要注意的几个细节仿真跑通之后迁移到实机是最后一步也是最容易出问题的一步。我的经验是先录bag再离线调参最后实机验证。具体做法是在实机上手动控制机器人走一圈用ros2 bag record录下/scan、/odom、/imu、/tf这几个话题然后在电脑上回放bag用SLAM Toolbox离线建图。这样你可以反复调参不用担心实机撞坏。实机部署时有几个参数必须改max_laser_range改成实际雷达的最大距离robot_radius改成实际机器人半径inflation_radius相应调整。另外实机的odom话题频率可能和仿真不一样需要在Nav2配置里调整expected_odom_rate。最后说一个我踩过的坑实机上电机的死区。仿真里给0.01m/s的速度轮子就会转实机上给0.05m/s可能都不动。这个死区如果不处理Nav2发很小的速度指令时机器人不动但里程计认为它在动结果就是定位漂移。解决方法是在底盘驱动里加一个死区补偿或者把Nav2的最小速度设大一点。这条链路我从头到尾跑了大概两个月中间推翻重来了好几次。现在回头看最难的不是某个算法而是各个模块之间的衔接——TF、时间同步、坐标系转换这些胶水部分才是真正花时间的地方。希望这篇复盘能帮你少走一些弯路。
返回列表