机器人导航这件事,最容易被误解的一点是:很多人以为上了 Nav2 就等于机器人会自己走路了。实际干过几个项目之后你会发现,Nav2 更像是一套“执行机构”——它负责路径规划、代价地图、恢复行为、控制器这些底层动作,但“我该去哪、现在这个场景意味着什么、要不要绕路、要不要等一等”这类判断,Nav2 本身并不擅长。这也是为什么越来越多团队开始把“导航框架”和“决策大脑”拆成两层来看:Nav2 负责导航框架,AiBrain OS 这类系统负责机器人理解、场景适配和导航决策。我最近正好在梳理这套分层思路,把踩过的坑和想明白的逻辑整理一下,给同样在做机器人导航的朋友一个参考。
1. 为什么要把导航拆成“框架”和“大脑”两层
1.1 Nav2 到底解决了什么,又没解决什么
先把 Nav2 的定位说清楚。Nav2 是 ROS 2 体系下的一套导航框架,核心模块包括行为树导航器(BT Navigator)、规划器(Planner)、控制器(Controller)、恢复器(Recovery)、代价地图(Costmap)以及生命周期管理。它做的事情非常明确:给定一个目标位姿,结合当前地图和传感器数据,算出一条可行路径,然后控制底盘沿着路径走,遇到障碍就重规划或触发恢复行为。
这套东西在结构化环境里非常好用。比如仓库里划好通道、地图提前建好、障碍物基本静态,Nav2 开箱即用的表现相当稳。但问题在于,真实场景往往不是这样的。我遇到过几次典型情况:机器人在走廊里遇到一群人围着聊天,Nav2 的代价地图会把这团人标成障碍,然后规划器开始疯狂找缝隙,找不到就触发恢复行为原地转圈。从“不撞人”的角度看它没错,但从“我其实可以等一下或者从旁边绕”的角度看,它完全没有这个判断能力。
这就是 Nav2 的边界:它擅长“在已知约束下求最优路径”,不擅长“理解当前场景并决定约束本身要不要变”。换句话说,Nav2 是一个优秀的执行者,但它不是决策者。
1.2 “机器人理解 + 场景适配 + 导航决策”这三件事为什么不能塞进 Nav2
有人可能会想,那我直接在 Nav2 的行为树里加节点不就行了?理论上可以,但实际做起来会很别扭。原因有三点。
第一,Nav2 的行为树是围绕“导航任务”设计的,它的节点语义是“计算路径”“跟随路径”“清除代价地图”这类动作。你硬要把“识别前方是临时施工还是长期障碍”塞进去,行为树会变得极其臃肿,而且和导航逻辑耦合太深,后期维护成本很高。
第二,场景适配需要跨模态信息。机器人理解不只是看激光雷达,还要结合视觉、语义地图、甚至业务系统的状态(比如“这个区域现在是否允许进入”)。这些信息不属于 Nav2 的职责范围,硬塞进去会让导航框架承担它不该承担的复杂度。
第三,导航决策往往需要“全局视角”。比如多机器人调度时,谁先走、谁让路,这不是单机 Nav2 能决定的。它需要一个更高层的系统来统筹。
所以更合理的做法是分层:Nav2 专注做好“给定目标怎么走”,AiBrain OS 这类系统专注“目标从哪来、当前场景怎么理解、要不要调整目标”。两层之间通过清晰的接口通信,各司其职。
1.3 分层之后,接口设计比算法更重要
分层架构最容易翻车的地方不是算法,而是接口。我见过不少项目,上层决策系统和 Nav2 之间用一堆自定义 topic 硬连,结果状态同步一塌糊涂。比如上层以为机器人还在去 A 点的路上,实际上 Nav2 已经因为连续失败触发了恢复行为停在原地,两边状态不一致,决策就全乱了。
比较稳妥的做法是:上层通过 Nav2 的 action 接口(NavigateToPose)下发目标,同时订阅导航状态和反馈;Nav2 的恢复行为、规划失败等事件通过状态机暴露给上层。上层不直接干预 Nav2 内部,而是通过“取消目标、下发新目标、调整参数”这些方式间接影响。这样职责清晰,出问题也容易定位。
2. AiBrain OS 在导航链路里具体承担哪些判断
2.1 场景理解:把传感器数据翻译成“可决策的语义”
AiBrain OS 的第一层能力是机器人理解,说白了就是把原始传感器数据变成有意义的场景描述。激光雷达给你的是点云,视觉给你的是图像,但这些数据本身不携带“这是什么”的信息。场景理解要做的是回答几个问题:前方那团东西是人、是货架、还是临时堆的箱子?这个区域现在是畅通的、拥挤的、还是封闭的?地面状况适不适合继续走?
我实际做的时候,通常会把场景理解拆成静态和动态两部分。静态部分依赖语义地图,比如提前标注好的房间、通道、禁行区;动态部分依赖实时检测,比如行人检测、临时障碍识别。两者融合之后,输出一个“场景状态向量”,供决策层使用。
这里有个经验:不要追求一次性把所有东西都识别准。场景理解的目标不是做完美的感知,而是给决策提供“足够好的输入”。比如你只需要知道“前方 3 米内有人且移动缓慢”,不需要精确到每个人的身份。过度追求感知精度会让系统变得又慢又脆。
2.2 场景适配:根据当前情况调整导航策略
场景适配是承上启下的一层。它拿到场景理解的结果后,决定当前应该用什么样的导航策略。举个例子:在空旷走廊里,可以用较高的速度和较小的膨胀半径,让机器人走得快一点;在人群密集区域,应该降低速度、增大膨胀半径、甚至切换到“跟随模式”慢慢挪。
这些策略调整最终会落到 Nav2 的参数上,比如max_vel_x、inflation_radius、cost_scaling_factor这些。但注意,调整参数这件事应该由 AiBrain OS 决策,通过参数服务或者动态参数接口下发给 Nav2,而不是让 Nav2 自己去猜。
我踩过的一个坑是:早期直接把场景判断写进了 Nav2 的控制器插件里,结果每次改策略都要重新编译,调试极其痛苦。后来改成上层通过 ROS 2 参数接口动态调整,改策略只需要改配置,效率高了很多。
2.3 导航决策:决定“去哪”和“要不要去”
导航决策是最高层,它回答的是“目标是什么”以及“现在要不要执行这个目标”。这听起来简单,实际很复杂。比如一个巡检机器人,它的任务是按顺序巡检若干点位,但中途发现某个区域有异常,决策层可能要临时插入一个“去异常点确认”的任务。又比如多机器人场景,决策层要判断当前机器人是否应该让行。
这一层的输出通常是一个目标序列或者一个目标位姿,通过 action 下发给 Nav2。同时它要监听 Nav2 的执行反馈,如果连续失败,决策层要决定是换目标、等待、还是请求人工介入。
2.4 三层之间的数据流和状态同步
把这三层串起来,数据流大致是这样的:传感器数据进入场景理解模块,输出场景状态;场景适配模块根据场景状态和当前任务,生成导航策略参数;导航决策模块结合任务队列和场景状态,决定下一个目标;目标通过 action 发给 Nav2,Nav2 执行并反馈状态;状态回流到决策层,形成闭环。
这里的关键是状态同步。我建议用一个统一的状态管理器来维护“当前任务、当前目标、当前导航状态、当前场景状态”这几个核心变量,所有模块都从这里读写,避免各自维护一份状态导致不一致。
3. 从零搭一套分层导航系统的实操路径
3.1 环境准备:ROS 2 版本和 Nav2 选型
先说环境。目前比较稳的组合是 ROS 2 Humble 加 Nav2 对应版本,Ubuntu 22.04。如果你用的是较新的 ROS 2 Jazzy,Nav2 的接口有一些变化,行为树节点命名和参数结构都有调整,迁移时要注意。
安装 Nav2 最省事的方式是直接装二进制包:
sudo apt install ros-humble-navigation2 ros-humble-nav2-bringup如果你需要改 Nav2 源码,那就得从源码编译,这时候要注意依赖版本对齐,尤其是 behavior_tree_cpp_v3 和 bond 这些。我一般建议先用二进制包跑通,确认整体链路没问题,再考虑要不要改源码。
AiBrain OS 这类上层系统,如果团队自己开发,通常是一个独立的 ROS 2 节点或者节点组,通过 action 和 topic 与 Nav2 通信。如果用的是现成平台,要确认它是否提供 ROS 2 接口,否则集成会很麻烦。
3.2 让 Nav2 先单独跑起来:地图、定位、参数三件事
在接入上层之前,一定要先让 Nav2 单独跑通。这一步的核心是三件事:地图、定位、参数。
地图方面,用 slam_toolbox 或者 cartographer 建好图,保存成 pgm 加 yaml。注意地图分辨率不要设得太细,5cm 左右通常够用,太细会让代价地图计算量暴涨。
定位方面,AMCL 是默认选择,但在一些动态环境里表现一般。如果场景允许,可以考虑用激光雷达里程计加地图匹配的方案,稳定性更好。
参数方面,重点调这几个:robot_radius或footprint要准确,inflation_radius根据场景调整,controller_frequency一般 20Hz 左右,planner_frequency可以低一些。我习惯先用默认参数跑一圈,看机器人在哪些地方表现不好,再针对性调。
3.3 上层决策系统与 Nav2 的对接方式
对接的核心是 action 接口。上层用NavigateToPose下发目标,同时订阅/navigate_to_pose/_action/feedback获取进度,订阅/navigate_to_pose/_action/status获取状态。
一个容易忽略的点是:Nav2 的 action 是长期运行的,一个目标没完成之前不能直接发下一个。上层如果要切换目标,必须先取消当前目标,等取消确认后再发新的。我见过有人直接覆盖发目标,结果 Nav2 内部状态错乱,机器人行为诡异。
另外,Nav2 的恢复行为会触发一些事件,比如clear_costmap、spin、backup。上层应该监听这些事件,判断是否需要介入。比如连续触发 spin 说明机器人可能被困住了,决策层可以考虑换目标或者请求帮助。
3.4 联调阶段最容易出现的三类问题
联调阶段问题很多,我归纳成三类。
第一类是坐标系问题。Nav2 依赖 map、odom、base_link 这条 TF 链,任何一环断了或者延迟太大,导航都会出问题。联调时先用ros2 run tf2_tools view_frames确认 TF 树完整,再用ros2 topic hz /tf看频率是否正常。
第二类是时间同步问题。如果传感器和决策系统在不同机器上,时间不同步会导致数据错位。建议统一用 chrony 做时间同步,并且所有消息都带时间戳。
第三类是参数冲突。上层下发的参数和 Nav2 配置文件里的参数可能冲突,导致行为不符合预期。建议明确哪些参数由上层动态控制,哪些固定写在配置文件里,避免两边打架。
4. 实际项目里踩过的坑和对应解法
4.1 场景理解误判导致机器人“不敢走”
有一次在展厅环境,机器人前方有一块反光地面,激光雷达把它识别成了障碍,代价地图上出现一片虚假障碍,机器人死活不肯往前走。Nav2 这边看没问题,它只是忠实执行了代价地图的约束。问题出在场景理解层没有区分“真实障碍”和“传感器噪声”。
解法是在场景理解层加一层滤波,结合视觉信息判断地面是否真的不可通行。如果视觉显示地面平整,就降低激光雷达在该区域的权重。这个逻辑放在上层做比放在 Nav2 里做合适得多,因为 Nav2 不应该关心“这个障碍是真的还是假的”,它只关心“代价地图上有没有障碍”。
4.2 决策层频繁切换目标导致 Nav2 状态机卡死
早期版本里,决策层发现当前路径拥堵就立刻切换目标,结果 Nav2 的 action 被频繁取消和重发,状态机进入异常状态,机器人停在原地不动。排查后发现是取消和重发之间没有等待确认。
修复方式是加一个状态机:切换目标时先发取消请求,等收到取消确认后再发新目标,中间加一个超时保护。这个改动不大,但稳定性提升非常明显。
4.3 多机器人场景下的目标冲突
多机器人共用一张地图时,如果两个机器人目标点很近,Nav2 各自规划路径会互相把对方当成动态障碍,导致双方都走不动。这个问题在 Nav2 层面很难解,因为它不知道另一个机器人的意图。
我们的做法是在决策层做协调:给每个机器人分配优先级,低优先级机器人在冲突区域主动让行,让行时下发一个临时避让点给 Nav2。这样 Nav2 只需要执行,不需要理解多机协调逻辑。
4.4 恢复行为触发过于频繁的调参经验
Nav2 默认的恢复行为比较激进,稍微卡一下就开始 spin 或 backup。在窄通道里这会导致机器人反复横跳。我的经验是:先调大recovery_behavior的触发阈值,比如把costmap_clear的触发条件放宽;然后在上层加一个判断,如果恢复行为连续触发超过 N 次,就通知决策层换策略,而不是让 Nav2 一直重试。
5. 这套分层思路适合什么样的项目
5.1 单机巡检和配送:收益最直接
单机场景下,分层带来的最大好处是策略可配置。同一个机器人,在仓库里用一套参数,在办公区用另一套,切换只需要改上层配置,不用动 Nav2。而且场景理解可以针对不同环境做定制,比如仓库里重点识别托盘,办公区重点识别行人。
5.2 多机协同:决策层价值最大
多机器人场景是分层架构价值最明显的地方。Nav2 本身不处理多机协调,如果硬要在每台机器人的 Nav2 里做,会非常复杂且难以保证一致性。把协调逻辑放到上层统一决策,Nav2 只管执行,整个系统会清晰很多。
5.3 动态环境:场景适配是刚需
动态环境里,固定参数基本没法用。人流量变化、临时障碍、地面状况变化,都需要实时调整策略。这时候场景适配层的作用就体现出来了,它可以根据实时场景动态调整 Nav2 参数,让机器人在不同情况下都有合理表现。
5.4 什么情况下不需要分层
也不是所有项目都需要这套架构。如果你的场景非常固定,比如固定轨道上的 AGV,或者环境完全结构化且静态,那直接用 Nav2 就够了,加一层决策反而增加复杂度。分层的价值在于应对不确定性和复杂性,如果这两者都不高,就没必要。
6. 几个容易被忽略的工程细节
6.1 日志和可观测性要提前设计
分层之后,问题可能出在任意一层,如果没有好的日志,排查会很痛苦。我的做法是:每层都输出结构化日志,包含时间戳、模块名、关键状态变量;用一个统一的日志聚合工具收集,方便按时间线回放。特别是决策层的目标切换、场景理解的状态输出、Nav2 的恢复行为触发,这三个一定要记清楚。
6.2 仿真环境要尽量贴近真实
分层逻辑在仿真里跑通不代表真机没问题。仿真里的传感器噪声、通信延迟、动态障碍行为和真实差距很大。建议仿真里也加入噪声模型和延迟模拟,尽量逼近真实条件。Gazebo 和 Isaac Sim 都可以做这些配置,虽然麻烦,但能省下大量真机调试时间。
6.3 参数管理要版本化
分层之后参数变多了,上层策略参数、Nav2 参数、场景理解阈值,加起来几十上百个。如果不做版本管理,改着改着就忘了哪个版本对应哪种行为。建议用 git 管理配置文件,每次调参都提交,并且记录调参原因和效果。
6.4 异常处理要覆盖“上层失联”
如果上层决策系统挂了或者通信中断,Nav2 应该怎么办?我的做法是让 Nav2 进入一个安全模式:停止当前目标,原地等待或者返回预设安全点。这个逻辑可以通过 Nav2 的生命周期管理实现,上层定期发心跳,心跳断了就触发安全模式。
这套分层架构我用了几个项目之后,最大的体会是:Nav2 是个非常好的工具,但不要指望它解决所有问题。把“怎么走”和“去哪、要不要走”分开,每层做自己擅长的事,整个系统会稳很多。当然,分层也意味着更多的接口和状态要维护,如果项目规模不大,直接上 Nav2 反而更省事。选不选分层,取决于你的场景有多复杂、变化有多快。