ARTICLE DETAIL

资讯详情

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

ROS2 AMR仿真与Nav2导航全链路:SLAM建图到实车迁移

ROS2 AMR仿真与Nav2导航全链路:SLAM建图到实车迁移 1. 先把三件套定下来ROS2 发行版、仿真器与导航栈的版本搭配做 AMR 机器人的仿真开发最先劝退人的往往不是算法而是环境。我见过太多人卡在第一步装完 ROS2起 Gazebo机器人要么掉进地板里要么激光点云死活不出现要么 Nav2 一启动满屏红色报错。这些问题里头有八成根因不在代码而在版本搭配——ROS2 发行版、仿真器、Nav2 三者的组合关系决定了你后面是顺利跑通还是把时间全花在查日志上。这篇内容我打算按一条完整的链路讲从选版本、建模型、跑 SLAM 建图一直到 Nav2 规划控制跑起来以及仿真到实车迁移时要提前做的准备。它适合刚接触 ROS2 的机器人方向学生、想从 ROS1 迁到 ROS2 的工程师也适合已经能跑通官方 demo、但一到自己搭车就各种崩的人。我会把每个参数为什么这么写、每个报错背后是哪条链路断了都尽量说清楚而不是只丢一堆配置文件。1.1 为什么这套项目一定要从仿真起步先说结论做 AMR仿真阶段不是能省就省的可选项而是成本最低的试错场。一台差速底盘的移动机器人实车调试要同时面对机械装配误差、电机响应非线性、编码器噪声、激光雷达安装角度偏差、地面材质打滑……任何一项出问题都会让导航表现变差而你在实车上几乎无法把这几件事拆开单独验证。仿真里就不一样。物理引擎给你一个理想的世界轮子转多少走多少激光雷达的噪声模型你自己定地面摩擦系数可以设。这意味着一旦导航效果不对你可以确定问题出在参数、算法或者配置上而不是今天地板有点滑。这种可控性对刚上手的人尤其关键先把算法链路搞明白再去实车处理物理世界的脏活。另外仿真是唯一能让你跑一百次同一段路的地方。调 Nav2 的控制器参数时你需要反复对比同一条路径下的速度曲线和避障表现。实车上每次跑都会有差异仿真里几乎完全一致这对参数整定来说是质变。注意仿真跑通不等于实车能用但仿真跑不通的实车基本不可能通。这句话我验证过很多次。1.2 Humble Gazebo Classic 与 Jazzy Gazebo Harmonic 两条路线怎么选这是第一个真正的岔路口。目前社区里主流的两条线是组合系统仿真器特点适合谁ROS2 Humble Gazebo Classic 11Ubuntu 22.04Classic资料多插件成熟libgazebo_ros_diff_drive.so类插件开箱即用初次搭建、要快速出结果ROS2 Jazzy Gazebo HarmonicUbuntu 24.04gz-sim官方长期维护方向物理精度更好走ros_gz_bridge想长期跟进、做新项目Humble 那条线的最大优势是抄得到。网上绝大多数 AMR 仿真教程、URDF 模板、diff_drive 插件示例都是按 Classic 写的你照着改能跑起来。缺点是 Gazebo Classic 已经进入维护末期新传感器模型支持偏弱。Jazzy 那条线是未来的方向但它最大的坑在于模型描述方式变了差速驱动不再是插一个插件就完事而是走gz-sim-diff-drive-system传感器数据通过ros_gz_bridge从 gz 话题桥接到 ROS2 话题。这里最容易翻车的地方是话题名对不上——比如 gz 侧是/scanROS2 侧要写成/scansensor_msgs/msg/LaserScan这样的桥接格式写错一个字符ros2 topic list里就是空的。我个人的建议是如果你这个项目是要在短期内出成果、或者团队里没人踩过 gz-sim 的坑先上 Humble Classic如果你明确知道要跟平台一起演进直接上 Jazzy但要留出半天到一天专门解决桥接问题。提示无论选哪条线把ros2 doctor和ros2 topic list当成你的第一道体温计。环境问题一定先在这两个命令上暴露。1.3 工作空间与包结构AMR 工程不要写成一个包新手最常见的做法是把 URDF、launch、参数、地图全塞进一个包。跑到 Nav2 阶段就会痛苦不堪——因为 Nav2 会加载nav2_params.yamlSLAM 要加载slam_params.yaml仿真世界又有自己的 launch混在一起改一个参数要翻半天。我更推荐这样的分层amr_description只放 URDF/Xacro、mesh、RViz 配置。职责是把机器人是什么样描述清楚。amr_gazebo只放仿真世界文件、gz 插件配置、仿真相关 launch。amr_bringup所有 launch 文件的入口负责把上面两个包和 Nav2、SLAM 串起来。amr_navigationnav2_params.yaml、slam_params.yaml、行为树 XML。amr_maps只放.yaml.pgm地图文件。这样分的好处是将来从仿真切到实车你只需要换掉amr_gazebo那一层的 launchamr_navigation里的参数文件可以整体复用最多改几处和里程计相关的阈值。这是我在做第二个AMR项目时才悟到的第一个项目因为全塞一个包迁实车时几乎重写了一遍。创建的时候记得带上依赖声明ament_cmake还是ament_python其实不重要但package.xml里的exec_depend一定要写全否则跨机器部署时会莫名其妙缺依赖。2. 把一台 AMR 造进仿真里URDF/Xacro 建模与传感器插件模型这一层看着简单实际上决定了后面 80% 的疑难杂症的来源。TF 树错一个父节点Nav2 就永远在等变换车轮直径填错 1 厘米里程计就会持续漂移激光雷达的frame_name和 URDF 里的 link 名不一致扫描数据就永远挂不到机器人身上。2.1 坐标系约定map、odom、base_footprint 到底谁连着谁先把链条摆清楚一套标准的移动机器人 TF 树是这样的map - odom - base_footprint - base_link - laser_link / imu_link每一段的发布者是不一样的这一点新手最容易搞混map - odom由定位模块发布。slam_toolbox 在建图时发AMCL 在导航时发。仿真里没有定位模块时这一段就是断的。odom - base_footprint由里程计发布。仿真里是差速驱动插件发实车上一般是底盘驱动板发。base_footprint - base_link由robot_state_publisher根据 URDF 发通常是一个固定的 z 偏移。base_link - laser_link同样是robot_state_publisher从 URDF 里读出来的固定变换。为什么要分odom和map两层因为里程计是连续的、短期准的但它会累积漂移map是不漂移的、但不连续的定位跳变时会突然跳一下。分开之后控制器可以只关心odom下的平滑运动而规划器关心map下的全局一致坐标。这就是为什么 Nav2 里global_costmap用map做全局坐标系local_costmap用odom。提示排查 TF 问题最快的两个命令是ros2 run tf2_tools view_frames生成整棵树和ros2 run tf2_ros tf2_echo map base_footprint看两帧之间的实时变换。后者如果一直输出 Invalid frame ID说明那段链路断了。2.2 差速底盘的关键尺寸与惯量为什么不能随便填差速底盘需要说清楚的物理参数其实只有几个但每个都直接影响仿真真实性轮子直径wheel_diameter这个值决定了轮子转一圈走多远。填错会导致仿真里程计和实际位移产生比例误差。我用卷尺量出来的轮径是 0.098 m但插件里填 0.1 m 也能跑只是跑半小时后位置差出几厘米。想认真做就量准。轮距wheel_separation两个驱动轮中心之间的距离。这个值影响差速转向的角速度计算。填大了机器人转弯半径会算小实际转不够填小了则相反。这个错误在仿真里表现为按给定角速度转 90 度实际转了 100 度。最大轮扭矩与加速度这两个是限制项。扭矩太小机器人带不动自己表现为速度给上去但不动加速度限制太小启动会有明显延迟Nav2 的进度检查器可能直接判定卡住。惯性矩阵则容易被完全忽略。Gazebo 里如果inertial不写或者随便填个 1会出现两种反直觉现象一是机器人被轻微碰撞后疯狂旋转惯量太小二是机器人加速迟钝、像在泥里开惯量太大。我的经验是按真实质量估算用box或者近似形状算转动惯量比瞎填省事得多。inertial mass value12.0/ inertia ixx0.12 ixy0.0 ixz0.0 iyy0.18 iyz0.0 izz0.22/ /inertial2.3 激光雷达与 IMU 插件的参数怎么写才不飘激光雷达在 Gazebo Classic 里用的是libgazebo_ros_ray_sensor.so关键是三件事话题重映射、输出类型、frame 名。gazebo referencelaser_link sensor namelaser typeray update_rate10/update_rate ray scan horizontal samples360/samples min_angle-3.14159/min_angle max_angle3.14159/max_angle /horizontal /scan range min0.12/min max12.0/max /range noise typegaussian/type mean0.0/mean stddev0.01/stddev /noise /ray plugin namelaser_controller filenamelibgazebo_ros_ray_sensor.so ros remapping~/out:scan/remapping /ros output_typesensor_msgs/LaserScan/output_type frame_namelaser_link/frame_name /plugin /sensor /gazebo这里的min0.12/min不是随便写的。如果你的车体半径是 0.28 m而激光装在前方中心理论上最小量程应该大于车体自身的遮挡范围否则你会看到一圈自己挡自己的假障碍物代价地图上就会在机器人周围生成一圈莫名其妙的障碍层导致局部规划器永远认为自己在障碍里。噪声标准差也别设成 0。完全没有噪声的激光会让 SLAM 的扫描匹配陷入一种病态——点云完美重叠匹配器找不到梯度方向反而容易在长走廊里产生跳变。设 0.01 m 左右比较接近真实的中低端雷达。IMU 插件要注意的是initial_orientation_as_reference这个选项。默认情况下它会把启动时的姿态当成参考零点对差速底盘来说通常是好事但如果你后面要做多传感器融合最好明确写清楚避免换机器人时行为不一致。2.4 仿真世界的搭建与物理参数世界文件也值得花点心思。我用的是最朴素的做法一个带墙面纹理的室内场景加几个纸箱和圆柱当障碍物。为什么强调纹理因为如果你后面要用视觉或者仿真里带相机的雷达纯色墙面会导致特征点提取失败这一点在做视觉 SLAM 时尤其致命。物理参数里我一般会明确设定max_step_size为 0.001real_time_update_rate为 1000。默认值有时候会让机器人高速运动时穿墙——因为一个物理步里它已经走过了 5 厘米的墙。这个现象在仿真里表现为机器人莫名其妙出现在墙外面然后 slam_toolbox 就会把一条诡异的直线写进地图。提示仿真里如果机器人跑得明显比设定速度慢先看 Gazebo 界面的 RTF实时因子。RTF 低于 1 说明物理计算跟不上这时候再怎么调控制器参数都没用得降传感器更新频率或者简化场景。3. SLAM 建图从能跑起来到地图能用模型建好、能在 RViz 里看到激光点之后下一步就是把地图建出来。很多人以为地图建出来就行实际上地图质量直接决定了后面导航能不能用。一张有重影、有断开、有虚假障碍的地图Nav2 会围绕这些错误持续给你制造麻烦。3.1 建图前的数据链路自检在跑 slam_toolbox 之前我强烈建议按顺序确认这几件事顺序很重要ros2 topic hz /scan是否稳定在 10 Hz 左右。抖动很大说明仿真性能不够。ros2 topic echo /odom --once里header.frame_id是不是odom子 frame 是不是base_footprint。ros2 run tf2_ros tf2_echo odom base_footprint能不能输出变换。在 RViz 里把 Fixed Frame 设成odom看激光点云是否随机器人移动而正确移动。第 4 步是最直观的。如果点云在 RViz 里是飘的或者跟着机器人一起平移看起来像贴在机器人身上不动那说明激光的 frame 和 TF 不匹配。这个现象很常见点云看起来画在正确位置但你稍微转一下车点云整体旋转而不是更新那就是frame_name写错了。还有一点把所有节点的use_sim_time都设成true。少一个节点没设就会出现时间戳来自两个不同的时钟slam_toolbox 会报 TF 超时或者干脆不出图。这是我在仿真里踩得最多、也最没技术含量的一个坑。ros2 run slam_toolbox sync_slam_toolbox_node \ --ros-args --params-file ./slam_params.yaml -p use_sim_time:true3.2 slam_toolbox 的在线异步模式与核心参数slam_toolbox 有几个模式建图我用online_async它在后台线程处理扫描匹配不会阻塞话题回调对仿真性能更友好。核心参数我一般这么设slam_toolbox: ros__parameters: mode: mapping odom_frame: odom map_frame: map base_frame: base_footprint scan_topic: /scan resolution: 0.05 max_laser_range: 12.0 minimum_travel_distance: 0.3 minimum_travel_heading: 0.3 map_update_interval: 2.0 transform_publish_period: 0.02 transform_timeout: 0.5 minimum_time_interval: 0.2 use_scan_matching: true do_loop_closing: true loop_search_maximum_distance: 3.0 correlation_search_space_dimension: 0.5逐个说下我的理解resolution: 0.05是 5 厘米一格。这是行业里的默认值。降到 0.025 地图更细但内存和匹配计算量翻倍仿真是没关系实车上跑到后面会很卡。minimum_travel_distance: 0.3意味着机器人至少移动 30 厘米才处理一帧。这个值太小会让计算量暴涨且精度没提升太大则转弯处地图会失真。minimum_travel_heading: 0.3是旋转约 17 度才处理一次同理。transform_timeout: 0.5是等 TF 的时间。仿真里给了 0.5 秒还报超时说明你的 TF 发布频率有问题而不是给得不够。有人喜欢调到 2.0 来解决报错那是把病症压住了。do_loop_closing一定要开。AMR 在室内跑走一圈回到起点是常态没有闭环检测地图首尾就对不上。3.3 建图实操速度、转弯与回环的经验建图时的操作手法比参数更重要。我的习惯是线速度控制在 0.20.3 m/s角速度不超过 0.5 rad/s。为什么这么慢因为激光雷达是 10 Hz如果机器人以 0.5 m/s 走每个扫描周期它已经移动了 5 厘米——正好是一格地图。扫描匹配在两个相邻帧之间找不到足够重叠区域就会开始猜猜错一次就是一条歪掉的墙线。转弯的时候更要注意绝对不要原地高速自转。差速底盘原地转的时候轮子打滑里程计的角度估计误差最大。我一般会用一个半径 0.5 m 左右的圆弧去转弯让轮子保持滚动状态里程计角度更准。这一点在仿真里影响不大但养成习惯后到实车上会救命。还有一个容易忽略的点建图前把机器人的初始位置放在一个特征明显的地方而不是空荡荡的大厅中央。slam_toolbox 建图初期需要靠环境特征来建立初始位姿四面都是空白墙的地方会让它一上来就漂。回环的时候尽量走回原路线上不要抄近道。因为回环检测是靠识别我来过这里路径越接近特征匹配越稳。3.4 地图保存与验收标准建完图保存用的是map_saver_cli。这里有个坑在仿真里保存地图必须加use_sim_time否则会花很长时间才写入甚至一直等不到地图话题。ros2 run nav2_map_server map_saver_cli -f ./maps/amr_room \ --ros-args -p use_sim_time:true -p free_thresh:0.25 -p occupied_thresh:0.65保存出来的是一张.pgm加一个.yaml。free_thresh和occupied_thresh这两个阈值我一般调成 0.25 / 0.65比默认值更严格一点这样模糊不清的灰色区域会被划成未知而不是可通行。宁可让机器人绕开也不要让它一头撞进不确定的地方。地图验收我只看三件事墙面是不是直的。歪斜说明扫描匹配在某个时刻漂了。走过的路径首尾能不能对上。对不上说明回环检测没生效。有没有孤立的黑色小点或细碎白洞。这些会让代价地图产生假障碍或假通道。如果这三条有任意一条不满足我会直接重新建而不是想着后面导航的时候调参数绕过。一张脏地图带来的麻烦比重新走十分钟多得多。提示地图文件建议带上版本号和日期比如amr_room_v3_0821.pgm。我吃过亏——改了环境之后还在用旧地图跑导航机器人一直在试图穿过一堵新加的柜子。4. Nav2 导航栈落地代价地图、定位、规划与控制的参数逻辑到了 Nav2 这一层报错会突然变多因为节点多、依赖多、生命周期管理还带状态机。我的经验是不要一次性把整个nav2_bringup用起来而是分三次跑通——先只起定位再只起代价地图和规划器最后才接控制器和目标点。4.1 Nav2 节点构成与一次导航任务的完整数据流先理解数据流比记参数名重要得多。一次导航任务大致是这样流转的你给一个NavigateToPose目标。bt_navigator加载行为树行为树去调用planner_server。planner_server从global_costmap拿全局地图用全局规划器算一条路径。路径交给controller_server它从local_costmap拿局部障碍实时算出速度指令。速度指令发到cmd_vel仿真里的差速插件收到后驱动轮子。amcl持续用激光和地图修正map - odom变换。里程计继续提供odom - base_footprint。理解了这条链排查就变得有方向感机器人不动先看cmd_vel有没有数据有数据但不动看插件订阅的话题名机器人乱走看map - odom是不是在跳能规划但路径穿墙看global_costmap的静态层有没有加载对地图。ros2 topic echo /cmd_vel --once ros2 lifecycle get /controller_server ros2 lifecycle get /planner_server生命周期节点的状态如果是inactive那它就不会工作。Nav2 的启动脚本通常会自动激活但如果某个节点因为参数错误加载失败它会停在unconfigured而整个导航栈看起来起来了实际是瘫的。4.2 AMCL 定位初始位姿不是随便给的AMCL 是粒子滤波定位它需要你先告诉它我在哪。这一步很多人用默认值直接开跑结果机器人一启动就满地图乱窜。两种做法一是在 RViz 里用2D Pose Estimate手动给一个初始位姿二是在参数里写死初始位姿适合仿真反复调试。amcl: ros__parameters: use_sim_time: true alpha1: 0.2 alpha2: 0.2 alpha3: 0.2 alpha4: 0.2 alpha5: 0.2 base_frame_id: base_footprint global_frame_id: map odom_frame_id: odom scan_topic: scan laser_model_type: likelihood_field laser_max_range: 12.0 max_beams: 60 min_particles: 500 max_particles: 2000 update_min_d: 0.15 update_min_a: 0.2 set_initial_pose: true initial_pose: x: 0.0 y: 0.0 z: 0.0 yaw: 0.0几个关键点alpha1到alpha5是里程计噪声模型。仿真里里程计很干净可以调小实车里程计噪声大这几个值要适当放大否则粒子会过度自信定位跟不上实际运动。max_beams是每次更新用多少条激光。60 条足够了用全部 360 条只会让 CPU 满载定位精度提升微乎其微。update_min_d: 0.15表示移动 15 厘米才做一次更新update_min_a: 0.2表示转动约 11 度更新一次。这两个值调太小会导致粒子滤波器计算量暴涨调太大则定位滞后。我的经验是让它们和 SLAM 建图时的minimum_travel_distance保持同量级。还有一点AMCL 的laser_model_type。likelihood_field对地图噪声更宽容室内结构化环境用它比较稳likelihood_field_prob在动态障碍多的时候更好。如果你的仿真场景里有走动的障碍物可以试试后者。4.3 代价地图分层配置与膨胀半径的算法代价地图是 Nav2 里最需要理解原理的部分。它不是一个图而是多层叠加的结果。全局代价地图一般三层static_layer加载你存的.pgm静态地图。obstacle_layer把实时激光点写入标记动态障碍。inflation_layer沿障碍物向外扩散代价让规划器远离墙。局部代价地图通常是voxel_layerinflation_layer用体素来容纳三维或带高度的障碍信息。膨胀层有个参数必须自己算inflation_radius。它决定机器人离墙多远开始觉得贵。给个参考公式inflation_radius 机器人外接圆半径 安全余量假设车体是 0.5 m × 0.4 m 的矩形外接圆半径约 0.32 m留 0.15 m 安全余量那inflation_radius就是 0.47 m。设太小规划出来的路径会贴着墙角走实车稍微有点横向误差就蹭上设太大窄门口会直接被膨胀成不可通行机器人绕路甚至无解。另一个参数cost_scaling_factor控制代价衰减速度。值越小代价衰减越慢机器人越倾向于走在路中间值越大衰减越快机器人就敢贴着障碍走。仿真里我一般用 3.0 到 5.0实车会调到 2.0 到 3.0 让它更保守。local_costmap: local_costmap: ros__parameters: use_sim_time: true update_frequency: 5.0 publish_frequency: 2.0 global_frame: odom robot_base_frame: base_footprint rolling_window: true width: 6 height: 6 resolution: 0.05 plugins: [voxel_layer, inflation_layer]局部代价地图用rolling_window: true意思是它跟着机器人滚动只维护周围 6 米 × 6 米的范围。这个尺寸取决于你的最大速度和感知距离速度 0.5 m/s、最快 0.3 秒反应时间那 6 米足够。4.4 全局规划器与局部控制器插件选型全局规划器我一般用SmacPlanner2D或者NavFn。NavFn是经典的 Dijkstra 变体稳定但路径不够平滑SmacPlanner2D支持运动学约束路径更自然代价是参数多一点。做 AMR 我优先SmacPlanner2D因为它能设minimum_turning_radius对差速底盘虽然意义不大但换成阿克曼底盘时可以直接复用。局部控制器是重头。现在主流三个选择插件原理优点代价DWB采样速度空间并打分参数直观调参经验多窄通道里容易犹豫MPPI采样轨迹并做加权优化平滑、动力学生成好参数不直观吃 CPURPP受控纯追踪纯几何追踪极轻量、稳定不做主动避障优化仿真里我建议先用 DWB 把整条链跑通因为它的失败现象最容易解释——机器人抖、贴墙、过不去基本都能归到几个打分权重上。等你对链路熟了再换 MPPI 提升平滑度。DWB 调参有个顺序经验先调max_vel_x和max_vel_theta到实际能力范围再调acc_lim_x、acc_lim_theta最后才调各critics的权重。反过来调会一直在原地打转。4.5 行为树与恢复行为卡住时机器人应该做什么Nav2 的行为树是它比自己写导航更实用的地方。默认的navigate_to_pose_w_replanning_and_recovery.xml逻辑是规划、跟路径走如果进度不够就重新规划反复失败就依次触发恢复行为。常见的恢复行为有Spin原地转一圈用新的激光数据重建局部地图。BackUp后退一段距离脱离卡死位置。Wait等一会儿适用于动态障碍场景。ClearEntireCostmap清空代价地图强制重新感知。这几个行为的顺序和参数值得改。默认的BackUp后退距离是 0.3 m如果你的机器人后面装了拖挂或者结构长0.3 m 不够如果车很小0.3 m 又太远。我一般会把它调到车长的三分之一左右。Spin的角度默认约 1.57 rad我习惯调到 3.14因为仿真里转半圈经常刚好错过视野死角里的障碍。提示调行为树的时候用ros2 topic echo /behavior_tree_log能看到具体走到了哪个节点。这个比在 RViz 里看机器人瞎转悠高效太多。5. 排错实录仿真里最消耗时间的几类问题这一节我把踩过的坑按类别整理每条都给出排查顺序而不是只给结论。因为同样一条报错原因可能完全不同直接抄答案往往无效。5.1 时间不一致与 TF 超时最典型的报错是Lookup would require extrapolation into the future Timed out waiting for transform from base_footprint to map这个报错出现时我的排查顺序是ros2 param get /节点名 use_sim_time把每个相关节点都查一遍。Nav2 里节点多经常漏掉map_server或者amcl。ros2 topic hz /clock确认仿真时钟在发。ros2 run tf2_tools view_frames看整棵树是不是完整。ros2 topic hz /tf和/tf_static确认频率正常。我遇到过一次特别隐蔽的情况robot_state_publisher没设use_sim_time于是它发布的静态变换时间戳来自系统时间而其他所有节点都用仿真时间。结果就是激光能显示但一旦开始移动就报 TF 超时。这种问题只能靠逐个节点查参数来定位。5.2 定位漂移、原地打转与机器人抖机器人原地打转是最常见的现象之一一般有这几个原因一是速度指令里角速度在正负之间高频震荡。这通常是 DWB 的多个轨迹得分接近规划器在两个方向之间来回选。解决方法是稍微降低max_vel_theta或者提高路径对齐的权重。二是 TF 抖动。如果map - odom的更新频率不稳定控制器每一帧算出来的位姿都跳自然就会抖。检查 AMCL 的update_min_d是否设得太小。三是局部代价地图里出现了幽灵障碍。仿真里常见的原因是激光的min量程小于车体遮挡范围或者机器人自身的某个 link 被误加进了扫描。表现是机器人周围有一圈动态障碍只要它一移动障碍就换位置控制器就慌。四是轮径和轮距参数错误导致的里程计失配。这个特征是机器人在 RViz 里走过的路径和实际形状不符比如明明走了直线RViz 里是弧线。5.3 代价地图不显示 / 障碍物不进入局部地图RViz 里看不到代价地图先确认三件事/global_costmap/costmap和/local_costmap/costmap有没有数据Lifecycle 状态是不是activeRViz 里的 Topic 配置是不是过期了RViz 配置经常缓存旧的 QoS。障碍物不进局部地图最常见的原因是obstacle_layer的observation_sources里topic写错或者data_type写成了PointCloud但实际是LaserScan。这类错误不会报致命异常只会静默地什么都不做特别难发现。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: 12.0 raytrace_min_range: 0.0 obstacle_max_range: 10.0 obstacle_min_range: 0.0clearing: true我从来不会关。它的作用是让激光射线经过的空间被清空成自由区域。如果关掉它机器人走过之后障碍会永久留在代价地图上走两圈地图就花掉了。5.4 用 3D 雷达跑 Nav2 的额外坑从 2D 雷达升级到 3D 雷达比如机械式多线雷达时很多人直接把LaserScan换成PointCloud2就完事然后发现导航完全不行。原因有几个第一3D 点云的数据量比 2D 扫描大一到两个数量级obstacle_layer处理不过来。正确做法是用voxel_layer或者过滤层ObstacleFilter先降采样、裁掉地面点。第二地面点会把整个地图涂黑。3D 雷达会扫到地面如果不过滤代价地图上全是障碍。要么在 URDF 里把雷达安装高度调高要么在配置里用min_obstacle_height卡掉地面。第三如果只是想让 3D 雷达继续跑 2D 导航可以用pointcloud_to_laserscan把点云压成一圈扫描。这个方案最省事代价是丢掉了立体信息对低矮障碍比如门槛、地脚线的检测能力下降。5.5 一张排查对照表现象优先怀疑快速验证机器人不动无报错/cmd_vel无数据或插件话题名不匹配ros2 topic hz /cmd_velRViz 中机器人位置乱跳map - odom抖动tf2_echo map base_footprint激光点云不显示frame_name与 URDF link 不一致RViz 切换 Fixed Frame 试规划路径穿墙静态地图未加载或坐标偏移检查map_server的 yaml局部规划贴着墙走inflation_radius太小重算外接圆半径报 TF 超时某个节点缺use_sim_time逐节点param get建图有重影速度太快或扫描匹配失败降速重建地图首尾对不上未开闭环或回环路径偏离打开do_loop_closing6. 仿真跑通之后往实车迁移要提前做的几件事仿真里跑得漂亮到实车上翻车是 AMR 项目的常态。但只要在仿真阶段提前做几件事迁移成本能降很多。6.1 参数分层把仿真与实车差异隔离到最小最有效的做法是把参数拆成三层通用层规划器、控制器、行为树、代价地图的算法参数。仿真实车共用。平台层机器人尺寸、轮距、最大速度、传感器安装位置。这些是硬件属性实车和仿真应该几乎一致。环境层仿真时钟、仿真器插件、是否用模拟时间。这一层是唯一真正属于仿真的部分。具体到文件组织我一般让nav2_params.yaml只包含前两层然后在 launch 文件里用参数覆盖的方式注入use_sim_time。这样切换实车的时候只需要改 launch 里一个use_sim_time:false其余参数原封不动。如果一开始就把仿真参数和实车参数混写在一起迁移时会陷入到底哪个值是给谁的这种泥潭。我在第二个项目里就吃过这个亏两个环境参数交叉污染调了整整两天。6.2 里程计与标定带来的真实差距仿真里最理想的是里程计。实车上轮式里程计的误差来自三处轮径实际值和设定值的偏差、左右轮实际直径不一致、地面打滑。这三者的综合表现是机器人走十米里程计报九米八而且角度慢慢偏。在仿真阶段就可以提前准备好标定流程让机器人沿直线走一段已知距离比对里程计读数算出轮径修正系数。原地旋转固定角度比如 10 圈比对终端角度算出轮距修正系数。把这两个系数写进底盘驱动的参数里而不是放在 Nav2 层修。为什么强调放在底盘层因为如果靠 Nav2 的alpha参数去容忍里程计误差那只是让滤波器更宽容机器人实际走过的路径还是错的只是定位看起来对了。这是治标不治本。6.3 安全与降级策略仿真里机器人撞墙就是穿模或者卡住实车上撞墙是财产损失。所以有些实车专用的关卡要在仿真阶段就设计好并且在仿真里测试它们的行为速度上限的二级限制。Nav2 给的速度是一级底盘驱动还要有独立的一级硬限制防止上层参数写错导致飞车。急停逻辑。一旦激光检测到前方 0.2 m 内有障碍底盘驱动直接截断速度指令不依赖上层。定位丢失的降级。当 AMCL 的协方差超过阈值或者map - odom长时间不更新机器人应该停下来并报警而不是继续按老位姿走。我在仿真里专门用过一个测试方法运行中突然把/scan话题停掉看机器人会怎样。如果它继续按记忆走几十米那说明安全链路设计得不够实车上这就是事故。最后分享一个我用了很久的小技巧。在仿真里跑长距离自动巡航测试时我会把机器人的起始位姿在 YAML 里随机化每次启动给一个不同的初始位置和朝向然后跑同一套路径。跑十次之后哪一段路反复出问题那一段就是参数或地图真正需要修的地方。这比手动跑一百次快得多也比我凭感觉调参数靠谱得多。
返回列表