想象你要训练一位新员工送外卖,他叫阿蓝。上岗第一天,老师傅给了他三样东西:一张全城地图、一台实时路况播报器,还有一本《老司机出车手册》。地图帮他规划跨城大方向,路况播报帮他躲开临时拥堵,手册则告诉他堵车了该怎么办、迷路了找谁求助——这不就是导航吗?没错,阿蓝其实是一台自主移动机器人(AMR),而 ROS2 里的 Navigation2(简称 Nav2)导航栈,就是他的这套“导航大脑套装”。 Nav2 是 ROS2 生态下最主流的开源导航框架,覆盖了移动机器人从“我在哪”(定位)到“怎么走”(全局规划)、“怎么避开突发障碍”(局部规划)、“哪条路更安全”(代价地图)、再到“遇到麻烦怎么办”(行为树)的完整链条。这篇文章会用阿蓝送外卖的故事,把每个模块的角色、核心算法和部署调参的关键讲清楚。读完你会发现,所谓自主导航,本质上就是把一位老司机的本事,逐条写成了代码。
对 AI 从业者来说,Nav2 是通往移动机器人实战的“必修驾驶课”:掌握它的模块划分与调参思路,就能快速搭建可落地的仓储、配送、巡检机器人原型,也是各类 AMR 岗位面试的高频考点。
可视化演示
展示Nav2全局路径规划与代价地图、局部避障控制器实时速度调整及AMCL粒子滤波定位原理,切换模式理解自主移动机器人导航核心流程。
本节配有交互动效(进入视口后自动加载,约 3KB)。
Nav2全局路径规划与代价地图 全局规划器在静态代价地图上规划从当前位置到目标的最优路径,输出路径点序列供局部控制器跟踪 Costmap2D为每个栅格赋予0-255代价值:0为自由空间,255为致命障碍,中间值表示风险区域 Start → Global Planner → Waypoints → Goal Nav2采用模块化插件架构,全局规划器工作在静态代价地图上不考虑动态障碍物
Nav2架构总览
阿蓝的公司不大,但部门分得很清:调度室看地图定路线,司机手握方向盘躲障碍,安全员随时更新路况,还有一位“老法师”负责反复确认“咱们现在到哪了”。Nav2 就是把这家公司直接搬进了 ROS2:每个部门是一个独立节点,部门之间用“话题”和“服务”对讲机通信,整个公司的规章制度则写进 YAML 参数文件统一配置。
Nav2 的核心组件包括 PlannerServer(全局规划)、ControllerServer(局部控制)、Costmap2D(代价地图)、AMCL(定位)和 BehaviorTree(行为树)。它们都有完整的“生命周期”:先配置(configure)、再激活(activate)、出状况能停用(deactivate)——就像每位新员工都要先培训再上岗,出了岔子能随时叫停,而不用重启整个公司。
整个导航过程可以看成一条函数流水线:先定位输出位姿,再结合地图算全局路径,最后由局部控制器产出速度指令。用数学语言写出来:
每个格子 c_{ij} 标记可通行(0)或障碍(1)。
这是机器人在世界坐标系中的位置和朝向。
路径是地图上一串有序坐标点,相当于地图软件给的那条蓝色路线。
输出线速度 v 与角速度 ω,直接驱动底盘电机。
所以导航不是“自动驾驶一个函数搞定”,而是像公司一样各部门各管一段、靠通信总线协作。阿蓝能稳稳送单,靠的是整条流水线每个环节都不掉链子——Nav2 就是这套流水线的标准化实施方案。
- 地图 M预先建好的栅格地图,相当于阿蓝手里的城市地图。
- 定位 L把实时传感器观测 z_t 与地图“对表”,输出位姿。
- 全局规划 P根据位姿和目标点求整条路径。
- 局部控制 D沿路径前方一小段生成实时速度指令。
全局路径规划
出发前,阿蓝打开地图软件输入目的地,软件立刻给出“全程走主路、上高架”的方案。它不需要关心每个路口的红绿灯,只要大方向对、还能绕开已知的修路段。这就是全局路径规划:先在完整地图上找到“战略级”路线,细节留给之后的局部控制。
Nav2 的全局规划器在收到目标点后,基于静态栅格地图和全局代价地图执行搜索算法,找出从当前位置到目标的最优路径。默认实现是 NavFn(A* 的一种工程化变体),搜索出来的折线路径还会经过平滑处理,去掉锯齿,方便底盘平滑跟踪。A* 会在栅格上不断扩展“看起来最有希望”的格子,衡量标准是估价函数:
每走一格代价加 1;若考虑地形,也可为不同格子设不同权重。
这是欧氏距离,它永远不大于真实最短距离,能保证搜索找到最优解。
A* 每次都从优先队列中取出 f 最小的节点继续扩展,像扫地雷一样逐格推进。
地图软件推荐“最快路线”时也在做类似的事:已开的路段是 g,前方预估是 h。当然,地图软件不知道路上临时发生的事故——那是局部避障的活,也是下一章阿蓝要练的“真车技”。
- 已有代价 g(n)从起点一路走到格子 n 实际花的步数。
- 启发估计 h(n)n 到目标点的预估最短距离。
局部避障控制
真正上路后,阿蓝瞪大眼睛盯着车头前方两三米:突然窜出的电动车、路边刚停下的货车,都是地图上没有的“临时剧情”。全局规划帮不了这种忙,得靠临场反应——这正是局部规划器存在的意义。
局部规划器以 10~20Hz 的高频率运行,只参考全局路径前方一小段,结合最新激光雷达扫描,实时算出 (v, ω) 速度指令。Nav2 默认的 DWA(动态窗口法)思路很朴素:把未来 1 秒内所有可能的速度组合“撒网采样”,模拟出候选轨迹,再逐条打分,选最好的那条执行。评分函数是:
夹角越大扣分越多,鼓励机器人“别绕远路”。
距离越远越安全,会直接撞上障碍的轨迹优先淘汰。
相同安全性和朝向得分下,选更快的。
每轮都挑总评分最高的速度组合作为指令下发。
就像老司机在路口同时瞟三样东西——前方目标、侧方行人、脚下油门——靠经验和权重瞬间做出决定。DWA 把这套“瞬间决策”量化成了数学题,让阿蓝每秒钟能做几十次判断,遇到临时冒出来的障碍也能从容躲开。
- 朝向项 heading轨迹末端朝向与目标方向的夹角。
- 安全项 dist轨迹上离最近障碍物的距离。
- 速度项 vel速度绝对值,鼓励尽快到达。
代价地图建模
老司机心里有张“路况热力图”:哪条巷子窄、哪个商圈人挤人、哪个路口刚发生剐蹭,都会标成红色警戒区。阿蓝的这张热力图,在 Nav2 里叫代价地图(Costmap),它决定规划器“敢不敢走某条路”。
代价地图是分层结构:静态层(Static Layer)来自事先建好的栅格地图;障碍层(Obstacle Layer)由激光雷达实时扫描填充,行人、推车一出现就立刻“上色”;膨胀层(Inflation Layer)把障碍物向外“加胖”,让规划器对这段距离保持敬畏。整个地图持续更新,就像路况播报从未停过。膨胀层对障碍物周围格子的代价按指数衰减计算:
计算每个格子距离所有障碍物的最小欧氏距离。
内切半径以内的代价设为致命,规划器永远不敢进。
超过膨胀半径的格子完全不受影响。
距离每增加一点,代价就明显回落,形成“热力图渐变”。
正是这张持续刷新、层层标注的热力图,让阿蓝的规划器“自觉地”远离货架角、石墩和刚出现的行人,而不是真撞上去才后悔。代价地图就是机器人对世界的“敬畏心”。
- 距离 d格子中心到最近障碍物的距离。
- 内切半径 r_i小于它就贴脸碰撞。
- 膨胀半径 r_o超过它作用消失。
- 指数衰减中间区域按指数递减。
定位技术
开车到陌生街区,阿蓝会不断确认“我现在在哪个位置”:瞥见一家便利店、一个路牌,就对照地图修正判断。这个过程叫定位,Nav2 里的方案叫 AMCL(自适应蒙特卡洛定位)。
AMCL 的本质是粒子滤波。它撒出上千个“假说粒子”,每个粒子代表一个可能的机器人位姿;机器人一边动一边看雷达,同时依赖 TF 坐标树把激光数据换算到底盘坐标系,再与地图对齐——这相当于“传感器融合”:里程计给出“我大概走了多远”的预测,雷达扫描给出“我看到的世界长这样”的证据。观测与地图越吻合的粒子权重越高,最后重采样,让粒子簇渐渐收敛到真实位置。权重更新公式是:
上千个粒子就像上千个“可能站在哪”的猜测。
每一束激光都打分,整体越吻合似然越高。
与地图吻合的粒子权重大幅上升,不符的权重趋近于零。
低权重假说被淘汰,高权重假说被“复制”,粒子越聚越准。
阿蓝每到一个路口都像在“看路牌猜自己在哪”,猜错的方向被逐渐淘汰,剩下的越来越集中——粒子从一脸迷茫到锁定唯一答案,这就是定位收敛。没有这步,再好的路径规划都是空中楼阁。
- 粒子位姿 x^i_t第 i 个假说,包含位置和朝向。
- 观测似然 p(z_t|x^i_t)假设机器人在 x^i_t,雷达扫出的样子和真实观测 z_t 有多像。
- 权重更新用贝叶斯相乘再归一化。
- 重采样按权重抽签,留下高权重粒子。
行为树决策
老司机遇到路堵,不会原地发呆,而是按手册来:先试着绕一小段;绕不过就倒车换条道;再不行就靠边呼叫调度。这套“先试 A、不行试 B、再不行求助”的流程,在 Nav2 里就是行为树(Behavior Tree)。
Nav2 用行为树编排导航任务。树的叶子是具体动作(“转到目标点”“跟踪全局路径”“恢复位姿”),父节点是控制逻辑:Sequence(顺序)、Fallback(选择)、Parallel(并行)。每个节点运行后返回三种状态之一:Success(成功)、Failure(失败)、Running(执行中)。两种核心控制节点的语义可以用逻辑运算表达:
遇到第一个 Failure 立即短路返回失败,省时省力。
常用于“先试正常导航、不行走恢复、再不行求助”的容错链。
父节点遇到 Running 就挂起等待,避免重复下发指令。
有了行为树,阿蓝面对突发状况不再“原地死机”,而是像老司机一样按手册有序地轮换方案。这也是 Nav2 能长时间自主运行、遇到故障自动恢复而不需要人工介入的核心秘密。
- Sequence 与运算所有子节点都成功才算成功。
- Fallback 或运算任一子节点成功即成功。
- 节点状态 Running节点正在执行中。
部署与调参
新车到手都要磨合:方向盘虚位、刹车灵敏度都按司机的习惯调过才顺手。阿蓝的“车”——底盘、传感器加导航参数——也要这样调试之后才能正式接单。
部署 Nav2 要依次做好四件事:提供机器人 URDF 模型、搭好 TF 坐标树、接上激光雷达与里程计话题、用 YAML 参数文件配置规划器/代价地图/行为树。调参时常用 RViz 可视化工具观察路径、代价地图和粒子分布,再结合场景特点调整膨胀半径、速度上限和 DWA 权重——这就是“场景适配”与“性能优化”。其中最基础的模型是差速底盘的运动学:
轮子的角速度乘轮半径得到线速度。
两轮同速则直行,均值就是底盘前进速度。
L 是左右轮之间的距离(轮距),轮距越大,同样速差下转弯越平缓。
坐标变换矩阵把雷达坐标系中的点转换到机器人本体坐标。
磨合期结束后,阿蓝在测试场地跑通了直行、绕障、回充整套动作,老师傅才放心让它去仓库接单。部署调参就是让导航栈从“能跑 demo”变成“在真实场景里好用”的最后一公里。
- 左右轮速 v_L、v_R左右驱动轮的线速度。
- 线速度 v底盘前进的快慢取左右均值。
- 角速度 ω由左右轮速差产生转弯。
- TF 坐标变换把传感器数据映射到底盘坐标系。
AI 应用场景
仓储物流 AGV 搬运机器人
阶段:导航规划与定位(Nav2 系统集成)
解决的问题:传统磁条/二维码 AGV 改线必须停机铺设轨道,成本高、柔性差;采用 Nav2 后,全局规划、代价地图和 AMCL 定位让路线可以通过软件随时调整,货架布局变了也不用动地面。
具体案例:某电商仓 30 台搬运机器人在 1.2m 宽的货架通道间穿梭,拣货员频繁走动。全局规划器在通道间选择最优路线,DWA 以高频避让人员,膨胀半径按车身半宽 0.35m 调校后,货架对接精度稳定在 ±3cm。
医院配送机器人
阶段:局部避障与任务决策(DWA + 行为树)
解决的问题:医院走廊动态障碍极多——轮椅、输液架、病床随时出没,固定路线完全不可行;同时机器人需要在电梯超时、道路被堵时自主决策。
具体案例:某三甲医院送药机器人高峰时段穿行门诊楼,行为树编排“等待电梯→进梯→避让→送达”全流程;电梯超时触发 Fallback 重新排队;雷达实时发现轮椅时,DWA 主动降速绕行。
园区巡检机器人
阶段:长时间自主巡航与回充管理(全局规划 + 行为树)
解决的问题:安保巡检路线长、需全天候自主运行,同时必须解决低电量自动回充和任务恢复的问题。
具体案例:某工业园区 2.3km 巡检路线,机器人每小时巡逻一圈;电量低于 20% 时行为树切换到“回充导航”,充电完成后自动回到中断点继续巡检,全程无需人工干预。
Python 演示
import heapq GRID = [ [0, 0, 0, 1, 0, 0, 0], [0, 1, 0, 1, 0, 0, 0], [0, 1, 0, 0, 0, 1, 0], [0, 0, 0, 1, 0, 1, 0], [0, 0, 1, 0, 0, 0, 0],]START = (0, 0)GOAL = (4, 6) def heuristic(a, b): return abs(a[0] - b[0]) + abs(a[1] - b[1]) def astar(grid, start, goal): rows, cols = len(grid), len(grid[0]) frontier = [(heuristic(start, goal), 0, start)] g_cost = {start: 0} came_from = {start: None} while frontier: _, g, node = heapq.heappop(frontier) if node == goal: path = [] while node is not None: path.append(node) node = came_from[node] return path[::-1] for dx, dy in ((1, 0), (-1, 0), (0, 1), (0, -1)): nx, ny = node[0] + dx, node[1] + dy if nx < 0 or nx >= rows or ny < 0 or ny >= cols: continue if grid[nx][ny] == 1: continue new_cost = g + 1 if new_cost < g_cost.get((nx, ny), float("inf")): g_cost[(nx, ny)] = new_cost came_from[(nx, ny)] = node heapq.heappush(frontier, (new_cost + heuristic((nx, ny), goal), new_cost, (nx, ny))) return [] def follow_path(grid, path, dynamic_obs): rows, cols = len(grid), len(grid[0]) plan = [] robot = None for cell in path: if robot is None: robot = cell plan.append(robot) continue nxt = cell if any(abs(nxt[0] - o[0]) + abs(nxt[1] - o[1]) <= 1 for o in dynamic_obs): for dx, dy in ((0, 1), (1, 0), (0, -1), (-1, 0)): cand = (robot[0] + dx, robot[1] + dy) if 0 <= cand[0] < rows and 0 <= cand[1] < cols: if grid[cand[0]][cand[1]] != 1 and cand != robot and cand != nxt: nxt = cand break robot = nxt plan.append(robot) return plan def main(): path = astar(GRID, START, GOAL) print("全局路径:", path) obs = [(2, 3)] # 动态行人 plan = follow_path(GRID, path, obs) print("实际轨迹:", plan) print("到达终点:", plan[-1] == GOAL) if __name__ == "__main__": main()
frontier = [(heuristic(start, goal), 0, start)]优先队列初始只放起点,存“f 值、g 值、坐标”三件套,对应 DWA 之前的 A* 搜索入口。_, g, node = heapq.heappop(frontier)每次弹出 f 最小的节点,就是 A* “先扩展最有希望的格子”。if new_cost < g_cost.get((nx, ny), float("inf")):只有找到更短的到达路径才更新,保证最终路径全局最优。if any(abs(nxt[0] - o[0]) + abs(nxt[1] - o[1]) <= 1 for o in dynamic_obs):用曼哈顿距离判断路径点旁是否有动态障碍,模拟 DWA 的“雷达实时发现行人”。if grid[cand[0]][cand[1]] != 1 and cand != robot and cand != nxt:在相邻格中挑一个空闲格绕行,模拟局部规划器临时微调轨迹。
常见误区
误区“我装了 Nav2 的包,然后运行它,机器人自己就会导航了吧?不是开箱即用吗?”很多初学者照着官方 demo 跑成功后,以为把自己的机器人接上 Nav2 也能零配置直奔终点。
正解Nav2 不是成品 App,而是“司机框架”。你的机器人必须自带底盘驱动(里程计)、激光雷达话题、URDF 模型、TF 树,还要先建好一张地图,并针对自己的底盘尺寸调好膨胀半径和速度限制。没这些,阿蓝上车连“自己的手脚在哪”都不知道。
为何常见:官方 demo 的模拟器把传感器、地图、底盘全配好了,让人误以为这些都是 Nav2 自带的。
误区“你看激光雷达一直在扫描,数据实时更新,这不就是知道我在哪吗?”很多初学者把“感知”当成“定位”。
正解雷达输出的是“我看见的距离”,不是“我在哪里”。想知道“我在哪”,必须把雷达扫描和已有地图“对表”——这正是 AMCL 做的事。给一台没有地图的机器人一帧扫描数据,它依然不知道自己身处何方,就像闭着眼摸到一堵墙,摸得出墙却不知道自己在哪个城市。
为何常见:可视化界面里雷达点云非常直观,初学者容易把“看到了什么”和“知道在哪”混为一谈。
误区“全局规划已经算出最优路线了,局部规划照着走不就行了?还要它有什么用?”
正解全局路径在“地图上的世界”最优,但真实世界有地图上没有的行人、推车、临时护栏。全局重规划频率低(几秒甚至更慢),局部 DWA 却以 10~20Hz 实时避障。两者是“战略”与“战术”的关系,缺一不可。
为何常见:在静态仿真里跑,全局路径看起来完美无缺,让人忽视了真实环境的动态性。
误区“既然要远离障碍物,那把代价地图膨胀半径调到最大,机器人岂不更安全?”
正解膨胀半径过大会把窄通道、门缝、货架间的缝隙全部标红甚至标成不可通行,机器人会因此绕远路,极端情况下目标点周围全是“禁行区”,导致导航失败。膨胀半径要结合机器人半径和通道宽度综合权衡——安全与可达是一对矛盾。
为何常见:直觉上“离障碍越远越好”,但忽略了地图拓扑里“必经之路”的存在。
误区“车轮转几圈走了多远是数得出来的,里程计应该一直很可靠吧?”
正解轮式里程计在刚起步时误差很小,但打滑、路面不平、轮胎磨损会让误差不断累加——这叫累积漂移,跑几十米后可能偏出几十厘米,在货架间就是撞墙事故。因此 Nav2 用 AMCL 持续拿激光雷达和地图去校准里程计,而不是只信轮子。
为何常见:启动瞬间定位看起来非常准,给用户造成“一直准”的错觉。
面试题
Q1. 在 ROS2 Nav2 中,负责从起点到终点规划整体大路线的组件是?
- A.全局路径规划器← 正确答案
- B.局部避障控制器
- C.粒子滤波定位器
- D.激光雷达驱动器
答案:A。全局规划器在静态代价地图上做 A* 搜索,给出整条路径,相当于高德地图规划路线;局部控制器只负责实时躲障碍,定位器回答“我在哪”,雷达是传感器而非规划组件。
Q2. AMCL 定位技术的核心算法是?
- A.粒子滤波方法← 正确答案
- B.卡尔曼滤波方法
- C.帕累托最优方法
- D.遗传进化算法
答案:A。AMCL 全称 Adaptive Monte Carlo Localization(自适应蒙特卡洛定位),本质是粒子滤波——用上千粒子表示位姿假说,用观测更新权重再重采样;卡尔曼滤波适合线性高斯系统,不是 AMCL 的核心。
Q3. DWA 局部规划算法首先在什么空间里采样候选运动?
- A.速度空间 (v, ω)← 正确答案
- B.位置空间 (x, y)
- C.频率空间 (f, t)
- D.颜色空间 (R, G, B)
答案:A。DWA 全称 Dynamic Window Approach,在当前速度约束出的“动态窗口”内采样线速度 v 与角速度 ω 的组合,再模拟轨迹并评分,而不是直接在地图上枚举位置。
Q4. A* 算法的估价函数 f(n)=g(n)+h(n) 中,h(n) 表示什么?
- A.起点到 n 的实际代价
- B.从 n 到终点的启发式估计← 正确答案
- C.整张地图的格子数量
- D.栅格地图的分辨率数值
答案:B。g 是已经走过的实际代价,h 是当前节点到终点的估计距离(如欧氏距离),f 是总估计;A* 每次优先扩展 f 最小的节点,在 h 可采纳时保证最优。
Q5. 代价地图中“膨胀层”的主要作用是?
- A.给障碍物周围附加危险代价← 正确答案
- B.压缩地图占用的存储空间
- C.提高激光雷达的测量精度
- D.加快全局路径的搜索速度
答案:A。膨胀层把障碍物向外“加胖”,用指数衰减给周围格子标代价,让规划器自动远离危险区;它不改变地图存储、不影响雷达硬件,也不改变搜索算法本身。
Q6. 行为树中 Fallback(选择)节点遇到第一个子节点返回成功时?
- A.立即返回成功给父节点← 正确答案
- B.继续尝试后面的子节点
- C.返回失败并中断整棵树
- D.进入等待状态反复重试
答案:A。Fallback 的语义是“或”:只要一个子节点成功就整体成功,并停止尝试后续分支。这正是“先绕行、不行换道、再不行求助”恢复逻辑的实现基础。
Q7. 差速驱动机器人的角速度 ω 与左右轮速 v_L、v_R 的关系是?
- A.ω = (v_R - v_L) / L← 正确答案
- B.ω = (v_L + v_R) / L
- C.ω = (v_R - v_L) × L
- D.ω = v_R × v_L / L
答案:A。左右轮速相等则直行;速度差越大转弯越急。角速度等于轮速差除以轮距 L,这是差速底盘运动学的基本公式,也是部署时配置底盘参数的理论依据。
Q8. 以下哪些属于 Nav2 导航栈的核心组件?
- A.全局路径规划器← 正确答案
- B.局部避障控制器← 正确答案
- C.代价地图管理节点← 正确答案
- D.机器人模型可视化工具
答案:ABC。Nav2 核心由 Planner、Controller、Costmap、AMCL、BehaviorTree 等构成;RViz 只是调试时用的可视化工具,并不属于导航栈本身,初学者常把它们混为一谈。
Q9. 关于 AMCL 粒子滤波定位,下列说法正确的有?
- A.每个粒子代表一个位姿假说← 正确答案
- B.观测与地图越吻合的粒子权重越高← 正确答案
- C.定位过程不需要使用任何地图
- D.重采样会淘汰低权重粒子← 正确答案
答案:ABD。粒子=“我在哪”的假说;雷达观测与地图匹配后用似然更新权重;重采样淘汰低权重、复制高权重粒子。若没有地图,“对表”就无从谈起,所以 C 错误。
Q10. 关于代价地图各层与更新机制,下列说法正确的有?
- A.静态层来自预先构建的栅格地图← 正确答案
- B.障碍层由实时传感器数据驱动更新← 正确答案
- C.膨胀层代价与距离呈线性反比关系
- D.行人等动态障碍物会实时进入障碍层← 正确答案
答案:ABD。静态层来自建图结果;障碍层用实时激光数据填充,动态障碍物一旦出现就被标记;膨胀层代价按指数衰减而非线性反比,所以 C 错误。理解各层数据来源是调参的基础。