做移动机器人这么多年,从 ROS1 的 move_base 一路迁到 ROS2 的 Nav2,最大的感受就是:在 Gazebo 仿真里跑通,大概只完成了整个项目的 20%。
真机一落地,轮子打滑、TF 抖动、激光穿透玻璃、狭窄走廊两边膨胀层打架导致规划不出路径、原地无休止旋转……这些坑一个都不会少。
今天把我们在差速底盘和阿克曼底盘上落地 Nav2(基于 ROS2 Humble)的实战调参经验理一遍。从建图、代价地图(Costmap)到控制器与恢复行为(Recovery Behaviors),尽量给可以直接抄作业的配置和排查方法。
1. 建图阶段(SLAM):别让劣质地图毁了全局路径
很多人导航跑不好,第一反应是改 Nav2 的参数,其实根源在建图质量极差。激光雷达打在白墙上退化、里程计漂移导致地图重影,Nav2 的 Costmap 自然会被带偏。
1.1 硬件与 TF 树自检
在启动任何建图算法前,先用 tf2_ros 检查基准链条:
ros2 run tf2_tools view_frames
确保 map -> odom -> base_link -> laser_link 之间没有断裂,且 odom -> base_link 的发布频率建议在 50Hz - 100Hz。如果里程计低于 20Hz,控制器在算速度输出时极容易抖动。
1.2 Slam Toolbox 关键参数
2D 室内场景目前首选 slam_toolbox 的异步模式(async_slam_toolbox_node)。真机实测中,这两个参数最影响闭环质量:
slam_toolbox:
ros__parameters:
# 激光雷达扫描匹配参数
max_laser_range: 12.0 # 别盲目写 25m,室内雷达远距离噪点多,建议截断
minimum_time_interval: 0.1 # 限制建图频率,降低 CPU 占用
transform_timeout: 0.2
tf_buffer_duration: 30.0
# 回环检测灵敏度控制
loop_search_maximum_distance: 3.0
do_loop_closing: true
loop_match_minimum_chain_size: 10
loop_match_maximum_variance_coarse: 3.0
loop_match_minimum_response_coarse: 0.35 # 提高阈值,防止把相似长走廊误识别为同一位置
避坑点:长走廊(特征缺失)场景下,单纯依靠激光匹配必崩。要么在长廊两边加反光板/障碍物打乱特征,要么融合轮速计与 IMU 的 EKF(robot_localization)输出的高精度 Odom。
2. Costmap(代价地图):让机器人知道自己有多“胖”
Costmap 是 Nav2 最容易翻车的地方。不是“卡在窄门过不去”,就是“离墙还有半米就报规划失败”。
2.1 局部与全局代价地图的分工
- Global Costmap:通常基于静态地图(Static Map)+ 膨胀层(Inflation Layer),更新频率建议 1.0Hz - 2.0Hz 即可,不需要频繁刷新,省算力。
- Local Costmap:基于滚动窗口(
rolling_window: true),依赖实时雷达/深度相机点云(Obstacle/Voxel Layer)+ 膨胀层,更新频率保持在 5.0Hz - 10.0Hz。
2.2 膨胀层(Inflation Layer)调参公式
很多人分不清 inflation_radius 和 cost_scaling_factor 的配合关系。
robot_radius或footprint:机器人的真实物理外廓(建议保守微调放大 2cm - 5cm 作为安全余量)。inflation_radius:从障碍物边缘往外扩散的范围。必须 大于 机器人外接圆半径。cost_scaling_factor:代价衰减指数。值越小,衰减越慢,障碍物周围高代价区越宽;值越大,衰减越陡峭。
配置示例(nav2_params.yaml 片段):
local_costmap:
local_costmap:
ros__parameters:
update_frequency: 5.0
publish_frequency: 2.0
global_frame: odom
robot_base_frame: base_link
use_roll: false
rolling_window: true
width: 4
height: 4
resolution: 0.05
footprint: "[ [0.25, 0.20], [0.25, -0.20], [-0.25, -0.20], [-0.25, 0.20] ]"
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"
raytrace_max_range: 3.0
obstacle_max_range: 2.5
inflation_layer:
plugin: "nav2_costmap_2d::InflationLayer"
cost_scaling_factor: 3.0 # 走廊狭窄时可以调大到 4.0~5.0,让通道中间代价迅速降为 0
inflation_radius: 0.55 # 略大于机器人半径
避坑点:
- 鬼影残留:如果机器人移开后,动态障碍物还在代价地图上,检查
raytrace_max_range和obstacle_max_range。Raytrace 范围必须大于或等于 Obstacle 范围,否则穿过障碍物的光束无法执行 Clearing。 - 窄门死锁:若门宽 80cm,机器人宽 50cm,
cost_scaling_factor设置过低会导致门中央的代价值依然高于通行阈值,全局路径规划器直接放弃。此时应适当增加cost_scaling_factor。
3. 控制器(Controller Server):走直线抖、转弯冲怎么办
Nav2 目前主流的 Local Controller 有 DWB、TEB 和 MPPI。
- DWB (Dynamic Window Approach):适合差速、计算资源有限的工控机(如树莓派、RK3588),但比较呆板,狭窄空间绕行能力一般。
- MPPI (Model Predictive Path Integral):基于采样的模型预测控制,处理动态避障极其丝滑,但需要算力,建议有 GPU 或强劲 x86 CPU 时采用。
以真机最常用的 DWB 为例,抖动和过冲往往出在这几个评分插件(Critics):
controller_server:
ros__parameters:
controller_plugins: ["FollowPath"]
FollowPath:
plugin: "dwb_core::DWBLocalPlanner"
min_vel_x: 0.0
max_vel_x: 0.4
min_vel_theta: -1.0
max_vel_theta: 1.0
acc_lim_x: 0.8
acc_lim_theta: 1.5
decel_lim_x: -0.8
decel_lim_theta: -1.5
critics: ["RotateToGoal", "Oscillation", "BaseObstacle", "GoalAlign", "PathAlign", "PathDist", "GoalDist"]
# 权重调优
PathAlign.scale: 32.0 # 贴合全局路径的权重
GoalAlign.scale: 24.0 # 朝向目标点的权重
PathDist.scale: 32.0 # 距离路径的惩罚权重
GoalDist.scale: 20.0 # 距离终点的引力
BaseObstacle.scale: 0.02 # 碰撞代价权重,切忌设太大,否则靠近障碍物不敢走
踩坑记录:
- 到位后反复打转:目标容差(XY Goal Tolerance 和 Yaw Goal Tolerance)设得过严。差速底盘实测,室内场景建议
xy_goal_tolerance: 0.08(8cm),yaw_goal_tolerance: 0.1(约 5.7 度)。太小的话底盘会在目标点反复微调超调。 - 大角度转弯时突然急停:确保启用了
RotateToGoal插件,并配置了合理的前置对齐阈值:yamlFollowPath: RotateToGoal.xy_goal_tolerance: 0.1 RotateToGoal.trans_stopped_velocity: 0.05 RotateToGoal.slowing_factor: 5.0 RotateToGoal.lookahead_time: -1.0
4. 恢复行为与行为树(BT):别让机器人卡成“木头人”
机器人遇到障碍物、被人挡住、或者进入死角时,Nav2 会触发恢复行为(在 ROS2 中被称为 Behaviors,由 Behavior Tree 调度)。
默认的 navigate_to_pose_w_replanning_and_recovery.xml 树往往过于机械:规划失败 -> 清理代价地图 -> 原地旋转 360 度 -> 倒退 -> 再次失败直接 Abort。
在工厂或办公区,原地旋转(Spin)非常容易扫落周围货物,甚至把雷达打盲。
4.1 调整恢复行为参数
在 behavior_server 中,把无脑自旋限制在安全范围内:
behavior_server:
ros__parameters:
costmap_topic: local_costmap/costmap_raw
footprint_topic: local_costmap/published_footprint
cycle_frequency: 10.0
behavior_plugins: ["spin", "backup", "wait"]
spin:
plugin: "nav2_behaviors/Spin"
simulate_ahead_time: 2.0
max_rotational_vel: 0.4 # 降低旋转速度,真机急转容易滑移失步
min_rotational_vel: 0.1
rotational_acc_lim: 0.8
backup:
plugin: "nav2_behaviors/BackUp"
simulate_ahead_time: 2.0
backup_dist: 0.15 # 倒车距离不要太大,防止屁股撞墙
backup_speed: 0.05 # 慢速蠕动倒车
4.2 自定义行为树逻辑
如果机器人经常被路过的人挡住,不要立刻触发 Spin。修改 Behavior Tree,插入 Wait 节点,让机器人先停在原地等待 2-3 秒,给人通过的时间:
<RecoveryNode number_of_retries="3" name="NavigateRecovery">
<PipelineSequence name="NavigateWithReplanning">
<RateController hz="1.0">
<ComputePathToPose goal="{goal}" path="{path}" planner_id="GridBased"/>
</RateController>
<FollowPath path="{path}" controller_id="FollowPath"/>
</PipelineSequence>
<ReactiveFallback name="RecoveryFallback">
<GoalUpdated/>
<RoundRobin name="RecoveryActions">
<!-- 优先原地等待,通常人走开后路径就空了 -->
<Wait wait_duration="2.0"/>
<ClearEntireCostmap name="ClearLocalCostmap" service_name="local_costmap/clear_entirely_local_costmap"/>
<BackUp backup_dist="0.10" backup_speed="0.05"/>
</RoundRobin>
</ReactiveFallback>
</RecoveryNode>
把默认的大动作旋转,替换为「停等 -> 清理残影 -> 小幅倒车」,真机运行的稳定性和安全性会大幅提升。
5. 真机落地 Checklist
遇到 Nav2 无法规划或行为异常,按以下顺序排查,能省下一大半抓瞎时间:
- 时钟同步(Chrono / NTP):如果使用的是多机架构(比如上位机跑 Nav2,下位机工控机跑驱动),两台机器的时钟偏差必须小于 5ms,否则 TF 会大量报
Lookup would require extrapolation into the past。 - 激光雷达畸变:底盘快速移动时激光数据是否拉伸?如果有,必须开启驱动层的时间戳去畸变,或者降速建图。
- 话题频率诊断:
bash检查实际发布频率是否稳定,有无丢包。
ros2 topic hz /scan ros2 topic hz /odom - 代价地图可视化:在 RViz2 中把
local_costmap/costmap调出来,Color Scheme 选择costmap。观察机器人足迹(Footprint)是否落在了非零甚至高代价的致命区域内。
导航系统的工程化落地没有灵丹妙药,本质就是把底盘物理特性、传感器噪点与上层规划器之间的数学假设一点点对齐。参数多不可怕,顺着数据流自底向上去理,坑总能填平。
License: CC BY-NC 4.0
Updated 13 hours ago
Was this article helpful? Give it a like.
0 comments


