
最近有个朋友过来问我手里一台差速机器人SLAM建图已经跑通了但一进到 dansy 导航配置就彻底卡住。我仔细一问卡住的点不是“不知道导航是什么”而是这个配置框架把参数藏得太深文档又写得像天书根本不知道从哪下手。dansy 这套导航配置框架核心思路其实是把 Nav2 里散落在十几个 yaml 里的参数全部收拢通过模板和分层覆盖来简化配置流程解决的就是机器人导航项目里“参数多、耦合强、改一个崩一片”的经典问题。这篇文章我就用实际跑通的一台差速机器人做例子把 dansy 从原理到实操完整拆一遍包括怎么接 2D 雷达、怎么接 3D 点云、怎么调代价地图参数、导航不动和画龙怎么排查适合刚接触 ROS2 导航、或者被 Nav2 参数折磨过的朋友直接抄作业。1. 先聊聊导航配置到底难在哪1.1 一个经典场景你拿到一台差速机器人先说一个最常见的场景。你手里有一台自己做的小车两个驱动轮加一个万向轮装了一个单线激光雷达树莓派或者 Jetson 上跑了 Ubuntu ROS2用 Cartographer 或者 SLAM Toolbox 已经能建出挺漂亮的地图了。到了这一步你感觉机器人已经“认识”环境了接下来很自然就想让它自己跑起来给它一个目标点它能自己规划路径、避开障碍物、走过去。结果你会发现从“能建图”到“能自主导航”中间隔着一大堆配置工作。不是单纯写一个 launch 文件就能跑而是要同时搞定机器人模型、传感器数据、TF 树、定位模块、全局规划器、局部规划器、代价地图、行为树、速度限制……这一整套东西。很多人的导航第一次启动rviz2 里全是红色报错不是 map 没有就是 odom 的 TF 断链或者代价地图一片空白。这个阶段劝退了大量刚入门的人。dansy 这个配置框架的定位就是想把这一大堆东西“结构化”地管起来。它不替代 Nav2而是帮你把 Nav2 那套复杂的配置流程压缩成一套清晰的模板你只需要填机器人本体参数、传感器参数、算法偏好它帮你生成整套可用的 Nav2 配置和启动文件。听起来挺美好但实际用起来有一些门槛和需要注意的地方下面慢慢讲。1.2 Nav2 里那几十个参数是怎么把人逼疯的ROS2 的导航栈 Nav2本身是个非常强大的框架但它的问题也很明显模块太多参数太多而且模块之间强耦合。你打开 nav2_bringup 自带的参数文件看一眼就明白了——planner_server 有全局规划器的参数controller_server 有局部规划器和速度限制的参数bt_navigator 有行为树的参数amcl 有定位参数global_costmap 和 local_costmap 分别又有一整套代价地图参数。这些参数之间不是孤立的。举个例子你给局部规划器设置了很大的最大速度但代价地图的膨胀半径设置得很小机器人在靠近障碍物的时候就会因为来不及减速而撞上去反过来你把膨胀半径调得很大路径规划又会变得特别保守窄通道根本过不去。你调一个参数往往要连带看另外好几个参数整个调参过程就像在拆一个互相咬合的齿轮组。更麻烦的是很多参数的“正确值”完全取决于你的机器人本体。同样是差速底盘轴距不一样、轮子大小不一样、电机响应速度不一样最优的加速度限制就完全不同。Nav2 的文档只会告诉你“这个参数控制什么”但不会告诉你“你这个车应该填多少”。这就是为什么那么多人在网上搜教程照着别人填好的参数抄结果机器人跑起来像抽风。dansy 想解决的就是这种“散落 耦合 依赖本体”的三重痛苦。1.3 dansy 的解决思路模板化加分层覆盖dansy 我做下来感觉它的核心思路就八个字模板驱动分层覆盖。它把导航配置拆成三个层次机器人层、传感器层、算法偏好层。机器人层管底盘类型、几何尺寸、速度加速度限制传感器层管雷达类型、话题名、安装位置、坐标系算法偏好层管规划器选型、代价地图策略、行为树选择、定位策略。这三个层次分开维护最后 dansy 再通过一套模板机制把它们合并生成完整的 Nav2 参数文件。这样带来的好处非常明显你换一台机器人只需要改机器人层你换了一个传感器只需要改传感器层你想从 DWA 换成 Regulated Pure Pursuit只需要改算法偏好层。其它参数保持默认不需要每次重头来。这个思路有点像做菜时提前配好的调料包底料已经准备好了你只需要根据自己的口味往里加料。当然它也有一个学习成本就是你必须先理解这套分层结构否则配置文件的路径、字段、变量引用会让你一头雾水。我接下来会把这套结构彻底拆开讲清楚。2. dansy 的核心设计与配置思路2.1 配置分层的设计逻辑dansy 的分层设计从实际使用的角度来说是非常合理的。我自己在配置的时候最直观的感受是它逼着你想清楚两件事——你的机器人到底是什么你希望它怎么走。机器人层是最底层的描述。比如这台差速小车底盘类型是 differentialbase_frame 是 base_linkodom_frame 是 odom机器人几何用圆形近似半径 0.25 米最大线速度 0.5 m/s最大角速度 1.5 rad/s。这些参数看着简单但它们是导航参数的基础。因为 Nav2 的代价地图要知道机器人占多大地方局部规划器要知道速度极限在哪里膨胀层要根据这些几何信息做膨胀半径的参考。如果这层填错后面所有层都白搭。传感器层则是告诉系统“你靠什么感知世界”。单线激光雷达就是 2D LaserScan话题是 /scan坐标系是 laser_link如果是 3D 雷达或者深度相机话题可能是一个 PointCloud2 点云这就需要额外的投影处理后面我会专门讲。传感器层的参数直接影响代价地图的 obstacle layer 和 voxel layer 的行为传感器装歪了、坐标系写错了、点云太密导致性能下降这些问题的根子都在这一层。算法偏好层是最灵活的一层。同一个机器人在走廊环境里和在仓库货架环境里采用的规划策略可能完全不同。走廊里直接用 NavFn 或 A* 就很好用货架林立的场景可能需要更保守的膨胀参数差速小车用 Regulated Pure Pursuit 比较稳全向机器人可能要配 MPC。这些选择放在算法层不会污染底层参数切换起来也很快。我在实际操作中的体会是这套分层模型真正解决的不是“能不能配置出来”的问题而是“配置能不能长期维护”的问题。项目一旦跑起来你总会改东西——换雷达、加传感器、调速度限制。没有分层结构的话每次改动都是一次全局排查有了分层改动范围被限制在一个文件里风险小很多。2.2 参数模板是怎么工作的dansy 的参数模板我自己理解的机制是这样的它内置了一套 Nav2 参数的“默认值全集”覆盖 planner_server、controller_server、behavior_server、bt_navigator、amcl、costmap 这些模块。这套默认值不是乱填的基本对应 Nav2 官方示例里比较通用的配置加上一些针对差速和全向底盘的合理预设。用户在机器人层、传感器层里填写的自定义值会通过变量引用和模板覆盖机制注入到最终的参数文件里。打个比方你在机器人层写了一个 max_vel_x: 0.5模板里对应的 controller_server 参数就会把默认的 0.5 绑定过去最终生成的文件里Rooms 相关参数会沿用默认但跟底盘速度相关的参数会变成你自己的。这个机制很像从前端开发里“默认参数加用户覆盖”的思路学习曲线不算陡。但有个关键点需要注意dansy 生成的参数文件是最终形态直接给 Nav2 用的所以你如果想要临时改点东西最好的方式是改 dansy 的源头配置然后重新生成而不是直接去改生成后的 yaml。否则下一次重新生成你的手动修改会被覆盖掉。这一点我一开始没注意直接在生成文件里改了局部代价地图的分辨率结果一次重新生成全没了白调了半天。2.3 地图、代价地图与传感器模型的接入配置里最容易出问题的其实是地图与传感器模型的接入方式。这里的“地图”不是指 SLAM 建出来的那张栅格图而是指导航系统对环境的理解方式。Nav2 的全局代价地图和局部代价地图是导航系统做规划和控制的基础而那 SLAM 建出来的静态地图只是全局代价地图里的 static layer 的数据来源。在 dansy 里接 2D 激光雷达是比较简单的把 sensor 层配置好生成的代价地图 obstacle layer 会订阅你的 /scan 话题LaserScan 数据会被直接投影到代价地图栅格上标记障碍物。这个过程里激光雷达的安装高度、扫描范围、最大探测距离都会影响代价地图的质量。雷达装太高矮小的障碍物检测不到雷达扫描范围标错地图上会出现虚假障碍物。如果是 3D 雷达或者深度相机情况就稍微复杂了。Nav2 本身不直接消费三维点云做 2D 导航通常的做法是先用 PointCloud2 投影生成 LaserScan也就是用一个 pointcloud_to_laserscan 节点把点云转成 2D 扫描数据再喂给代价地图。我自己用的方案就是让 dansy 的传感器层支持配置投影参数输入点云话题、输出 scan 话题、目标坐标系、最小高度、最大高度、最小距离、最大距离。这个投影节点配置对了3D 雷达就能像 2D 雷达一样参与导航。热词里还提到八叉树地图导航这里也顺便说一下。八叉树地图也就是 OctoMap是一种三维占据栅格地图在无人机导航、机械臂避障这些真正需要三维信息感知的场景里很常用。在 ROS2 生态里一般用 octomap_server 订阅点云生成 OctoMap然后在 Nav2 的 costmap 里挂一个 voxel layer 或者自己实现一个图层去查询三维障碍物。dansy 当前版本对纯 2D 导航支持得比较顺手三维修避障更多是留给用户自己扩展。我的看法是如果你做的是地面轮式机器人2D 代价地图完全够用没必要上 OctoMap如果你做的是无人机那重点就变成三维占据栅格的更新效率和避障规划策略这是另一个量级的话题。3. dansy 导航配置实操全流程3.1 环境准备与安装开始实操之前先把环境准备说清楚。导航这事对 ROS2 版本有硬性要求我自己用的是 Ubuntu 22.04 ROS2 Humble这也是目前兼容性最好的组合。如果你的系统是 Ubuntu 20.04 配 ROS2 Foxy大部分功能也能用但有些新的 Nav2 插件可能不支持建议直接上 Humble。dansy 本体的安装目前还没有特别方便的二进制包主流方式是源码编译。clone 下来之后用 colcon build 编译注意它依赖 navigation2、tf2、robot_localization 这些 ROS2 包编译之前最好先把这些依赖装齐。如果你之前已经跑通过 Nav2 自带的 navigation_launch.py那环境基本是现成的直接装 dansy 就行。装好之后我建议先跑一遍它自带的示例配置确认整个链路是通的再往里填自己的机器人参数。这一步非常重要就像新买的仪器先用标准样品校验一下别一上来就用自己的参数出了问题你根本分不清是 dansy 的问题还是你参数的问题。我当时就是先跑通示例再改差速底盘配置排查范围一下子小了很多。3.2 配置一个差速机器人导航实例下面用我那台差速小车作为实例完整走一遍 dansy 的配置流程。这台车底盘是常规的差速结构两个驱动轮在前万向轮在后装了一个 2D 单线激光雷达激光坐标系是 laser_link里程计由电机编码器计算发布到 odom。整车的控制话题是 /cmd_vel反馈速度话题是 /odom。第一步是配置机器人层。dansy 里我填的大概是这样robot: name: my_diff_bot chassis_type: differential base_frame: base_link odom_frame: odom robot_radius: 0.25 length: 0.5 width: 0.4 max_vel_x: 0.5 min_vel_x: 0.0 max_vel_theta: 1.5 min_vel_theta: 0.1 max_acc_x: 0.5 max_rot_acc: 1.0这几个参数直接影响后面生成的 controller_server 配置。max_vel_x 是线速度上限max_vel_theta 是角速度上限max_acc_x 和 max_rot_acc 是加速度上限。加速度这个参数很多人忽略但它特别关键——电机响应慢的话加速度设太大会导致实际速度跟不上规划器的期望速度机器人路径就会很“飘”电机响应快的话加速度设太小又会让机器人看起来特别肉。我一开始 max_acc_x 填了 1.0电机根本追不上后来改成 0.5 就顺了。第二步是配置传感器层。2D 激光雷达的配置相对简单sensor: type: lidar2d topic: /scan frame: laser_link range_min: 0.12 range_max: 10.0这里的 range_min 和 range_max 必须跟雷达实际规格一致。雷达的近距盲区如果填大了地图上会把很近的障碍物当成不存在填小了雷达本身的噪声会扫出一些虚假回波。我一般是把雷达说明书上标注的最小测量距离再加一点点余量填进去。第三步是算法偏好层。我觉得差速小车上路比较稳的组合是全局规划用 NavFn 或者 A*局部规划用 Regulated Pure Pursuit。全局规划器选 NavFn 速度够快A* 在复杂环境里路径质量更高局部规划器用 Regulated Pure Pursuit因为它自带速度缩放和障碍物避让机制对差速底盘非常友好。DWA 也不是不行但参数比较敏感新手容易调出抖动的效果。配置完之后dansy 会根据模板生成整套 Nav2 参数文件和一个 navigation launch 文件。我把生成的参数文件打开检查了一遍里面 planner_server、controller_server、bt_navigator、amcl、costmap 的配置都全了代价地图里 static layer 指向建图阶段保存的 mapobstacle layer 订阅 /scaninflation layer 使用我填的 robot_radius 做膨胀基础。看到这里我知道配置的主体已经通了。3.3 把 3D 雷达接进 dansy热词里专门有“nav2导航使用3d雷达”这一条说明这个问题问的人很多。现实里确实有人在室内小车、配送机器人、巡检机器人上装了 3D 激光雷达或深度相机希望直接利用这些三维数据做导航。但 Nav2 的 2D 代价地图本身不消费 PointCloud2所以必须先做一步转换。我的接法是这样在机器人侧跑一个 pointcloud_to_laserscan 节点把 3D 雷达或者深度相机输出的点云投影成一张 2D 的 LaserScan 话题再让这个投影后的 scan 进入 costmap 的 obstacle layer。dansy 的传感器层里支持这种模式配置大概长这样sensor: type: lidar3d pointcloud_topic: /livox/lidar output_scan_topic: /scan target_frame: base_link min_height: 0.1 max_height: 0.35 min_range: 0.2 max_range: 15.0这里最关键的是 min_height 和 max_height。这两个参数决定了点云投影时保留哪个高度范围内的点。地面点必须过滤掉否则代价地图上会全是障碍物但也不能过滤得太狠否则桌腿、矮桩这些需要避开的障碍物就丢了。我自己设的是 0.1 到 0.35 米相当于只保留机器人身体高度附近的障碍物信息。如果你的机器人更高或者需要越障这个范围要相应调整。还有一点要提醒3D 雷达点云往往非常密尤其是 Livox 这种非重复扫描的雷达一帧几万甚至几十万个点。pointcloud_to_laserscan 如果直接把所有点都投影CPU 消耗会比较高。我实测的优化办法是先降采样再用体素滤波最后再投影。dansy 的配置里如果你要接 3D 雷达最好在点云到 scan 之间加一个 VoxelGrid 节点保留一个体素里最有代表性的点压力会小很多。3.4 启动与 rviz2 调试配置生成之后启动就很简单了。dansy 生成的 launch 文件大概做了这么几件事启动 Nav2 导航栈的所有 lifecycle 节点、加载生成好的参数文件、启动 rviz2 并加载预设的导航视图。我自己是直接用命令行启动ros2 launch dansy_bringup navigation.launch.py params_file:/path/to/dansy_generated/nav2_params.yaml map:/path/to/map.yaml启动之后打开 rviz2如果你在 RViz 里添加了 Map、RobotModel、ParticleCloud、Path、Global Costmap、Local Costmap 这些显示项应该依次能看到静态地图正常显示、机器人在 map 坐标下定位成功、amcl 粒子云收敛、全局代价地图和局部代价地图正常更新、发布一个目标点后出现全局路径和局部路径。我第一次启动时地图有了机器人模型也有了但粒子云一直在乱飘全局代价地图灰蒙蒙一片。后来用 tf2_echo 查 TF 树发现 odom 到 base_link 的变换一直在跳是因为编码器里程计不够准导致定位发散。这个问题通过增加 robot_localization 的扩展卡尔曼滤波融合 IMU 解决了。调通之后我给机器人发了一个 3 米外的目标点看它平稳加速、直行、到点附近减速、停下那种从“建图能跑”到“自主导航能跑”的跨越感是真的值得折腾一场的。4. 常见问题与排查技巧实录4.1 导航不动或规划失败的高频原因第一个高频问题给目标点之后机器人完全没反应rviz2 里没有出现路径。这种情况十有八九是 TF 树断了或者代价地图没有数据。排查顺序我建议这么来先用 ros2 run tf2_tools tf2_echo map odom 和 ros2 run tf2_tools tf2_echo odom base_link 看地图到车体的变换能不能查到。TF 树一旦断链nav2 直接罢工。如果 TF 正常再看传感器数据。用 ros2 topic hz /scan 确认话题在发频率正常。如果话题频率是 0去查传感器驱动有没有启动话题名是不是跟 dansy 传感器层里填的一致。我踩过一个坑机器人驱动把激光话题发布成了 /laser/scan而 dansy 配置里默认订阅 /scan差一个斜杠代价地图直接空白。这就是为什么我配置完传感器层后一定会 ros2 topic list 拉一遍确认名字一个字符不差。还有一个常踩的坑是代价地图全变成未知或者全黑。这多半是 static layer 的 map 参数没指对或者 map 的话题类型对不上。检查方法是在 rviz2 里添加一个 Map 显示话题选 /global_costmap/costmap如果层的颜色不对几乎可以断定是 static layer 或 obstacle layer 的问题。4.2 路径规划出来但机器人抖动或画龙第二个高频问题路径规划出来了机器人也走了但走起来像喝醉了酒一样左右摆头或者在一个目标点附近来回画圈。这个问题的根子通常在局部规划器参数和速度限制不匹配上。我调参的心得是按这个顺序来先看最大速度和加速度是不是超过了电机的实际能力。速度太高、加速太猛局部规划器算出的轨迹执行跟不上控制器就会反复修正看起来就是抖动。再把 Regulated Pure Pursuit 的 lookahead_dist前视距离调大一点。前视距离太小机器人看到的目标点太近路径追踪就很神经质调大之后会更顺滑但也不能太大否则过弯的时候会抄近路。我现在的差速小车0.6 米的前视距离配 0.25 m/s 的期望线速度跑起来很稳。还有一种情况是机器人在原地转圈出不去这通常是 min_vel_theta 和 max_vel_theta 设置不合理或者膨胀层把出口堵死了。先检查代价地图里通道是不是真的存在再用 rviz2 的 Publish Point 在通道里发几个中间点确认全局规划器能找到路。如果全局路径能出但局部走不了把局部代价地图的膨胀半径稍微减小一点给规划器多留一点活动空间。在排查这些问题时有个特别好用的工具组合rqt_graph 看节点连接是否正常ros2 doctor --report 看系统状态是否有异常ros2 bag record 把出现问题的工况录下来回放分析。录数据这个习惯我非常推荐调参的时候不能靠感觉回放同一段数据对比不同参数的行为效率比盲调高太多了。4.3 3D 雷达和八叉树地图相关的问题3D 雷达接进 nav2 之后最容易出现的问题是投影出来的 scan 数据有空洞。因为点云投影成 2D 时如果点云太稀疏远处的点上会有大块缺口代价地图看到的就是断断续续的障碍物。解决办法就是在投影之前加大点云的密度或者把 min_height 和 max_height 的范围放宽一点让更多点参与投影。还有一个性能问题3D 雷达的数据量大如果配置时没有加体素滤波代价地图的 obstacle layer 处理起来会非常吃力直接导致规划器响应变慢。我实测过一台 Jetson Orin Nano 上纯 2D 雷达时局部代价地图更新毫无压力接 3D 雷达不降采样时CPU 占用直接飙到 80% 以上导航明显卡顿。加上 VoxelGrid 滤波CPU 降到 20% 左右就完全正常了。至于 OctoMap很多网友把“3D 雷达导航”和“OctoMap 导航”混在一起讨论其实它们解决的问题不一样。2D 导航只需要知道二维平面上的障碍物位置用 2D scan 或者投影后的 scan 就足够OctoMap 解决的是真正的三维空间认知问题比如无人机要绕开头顶的电线机械臂要避开悬空的障碍物。如果你只是地面导航用 2D 代价地图加简单的 3D 投影就够用不用自己给自己加戏。真到了需要 OctoMap 的场景关注点主要在 octomap_server 的建图语义和导航规划器的三维代价查询开源社区有一些示例但整体没有 2D 导航那么成熟要抱着折腾的心态去做。4.4 lifecycle 节点与话题超时问题Nav2 的模块都是 lifecycle 节点也就是每个节点都有未配置、未激活、激活三个状态。dansy 生成的标准 launch 文件会自动激活所有节点但如果你手动启动 Nav2 组件或者修改了 launch 文件就容易遇到节点一直不激活的情况。检查命令是 ros2 lifecycle get /planner_server如果输出是 unconfigured 或 inactive需要手动执行配置和激活。启动时报这个话题不匹配、那个车里没响应先查一遍所有核心节点的 lifecycle 状态比什么都有用。还有一个很隐蔽的问题话题超时。Nav2 的代价地图对传感器数据有超时机制如果一个传感器话题长时间没有新数据costmap 会直接清空这层障碍物表现出来就是机器人“看不见”障碍了。dansy 生成的配置里一般有 obstacle_layer 的 enabled、observation_sources、topic、timeout 这几个参数。我建议在项目早期就把所有传感器话题的 hz 测一遍然后根据实际频率把 timeout 设成略大于数据周期比如 1Hz 的话题就设 1.5 秒。这样既不会误报超时又能在传感器真正挂掉的时候及时反映出来。另外时间同步问题在 ROS2 里虽然比 ROS1 少但如果你开了 use_sim_time而传感器驱动没有正确跟随模拟时间也会出现数据流异常。用真机的时候确保 use_sim_time 是 false 或者直接删掉用仿真的时候所有节点统一 use_sim_time true这个混乱了我好几次。最后分享一个我自己的习惯dansy 配置里每改一个关键参数我都会先记录之前的数值然后只改一个变量重新生成、重新启动、跑同一段测试路径前后对比。调参最忌讳一次改三四个参数出了问题根本不知道是哪个参数引起的。这跟做实验控制单一变量是一个道理。导航配置这活看似是参数游戏实际拼的是系统思维和耐心。dansy 能帮你把散落的参数整理清楚但真正让机器人稳定跑起来的还是你对每一个参数背后物理含义的理解。希望这篇记录能让你少走点弯路第一次配置导航就稳稳跑通。