AGV学习系列--(4)navigation2导航

发布时间:2026/8/11 5:29:16
AGV学习系列--(4)navigation2导航 小车是怎么导航成功的—— 初学者入门指南navigation2导航出发从你点击去那里的那一刻到小车稳稳到达目标中间到底发生了什么本文用最通俗的方式把 Navigation2 autopartol_robot_cpp 背后的导航全过程讲清楚。目录一句话回答导航的 5 个关键角色完整导航流程一步步看用 autopartol_robot_cpp 做实验你可能会问的问题怎么自己动手验证1. 一句话回答小车导航 知道自己在哪 知道要去哪 算出怎么走 一步一步走过去走的时候不断修正方向。就像你去一个陌生的地方先打开地图定位自己在哪定位搜一下目的地目标点地图给你规划一条路线路径规划你沿着路线走边走边看路牌修正方向运动控制小车做的事情和你一模一样只不过它用的是激光雷达、算法和代码。2. 导航的 5 个关键角色把导航想象成一个团队每个人分工不同队长行为树导航器 bt_navigator │ 负责指挥调度先规划再走路走不通就想办法 ┌────────┼─────────┐ ▼ ▼ ▼ 导航员 驾驶员 应急员 (planner) (controller) (behavior) 算路在哪 控制车轮转 卡住了后退/转圈 │ │ ▼ ▼ 地图costmap— 哪里能走哪里不能走 │ ▼ 定位员AMCL— 报告自己现在在哪 角色 1定位员 —— “我在哪”扮演者AMCL自适应蒙特卡洛定位手上有一张地图拿着激光雷达扫一圈看看周围的墙和障碍物和地图比对算出自己在地图上的精确位置结果map → odom坐标变换告诉所有人机器人在地图的这个位置类比你在商场里看了一眼周围的店铺对照商场导览图就知道自己站在哪了。️ 角色 2地图管理员 —— “哪里能走”扮演者costmap_2d代价地图静态地图是死的墙、房间不会动但实际环境会变有人走过、放了个箱子costmap 把传感器实时看到的障碍物叠加到静态地图上结果一张哪里危险、哪里安全的动态地图类比你走路时看到前面有个障碍物你脑子里会自动更新这里不能走了。 角色 3导航员 —— “走哪条路”扮演者planner_server全局规划器知道起点定位员给的和终点用户给的在地图上找一条不撞墙的最优路线常用算法A*、Dijkstra、Hybrid A*结果一条由很多小点组成的路径Path类比你用高德地图搜路线它给你推荐一条最快的路。 角色 4驾驶员 —— “怎么一步步开过去”扮演者controller_server局部控制器拿到导航员给的路径实时控制轮子的速度和方向边走边看偏了就修正快到了就减速常用算法DWB动态窗口法、纯追踪、MPPI结果输出cmd_vel线速度 角速度给底盘类比你开车时眼睛看着路手转方向盘脚踩油门——导航员给你一条路你自己决定每一秒方向盘打多少。 角色 5队长 —— “协调所有人”扮演者bt_navigator行为树导航器接收用户的去那里指令按顺序调用导航员、驾驶员如果走不通了调用应急员后退、转圈、重新规划结果把整个导航过程串起来类比队长发号施令——“先规划路线”“开始走”“卡住了后退一下再试”。3. 完整导航流程一步步看假设你对小车说“去 (3, 2) 那个位置面朝北。”下面是完整的 9 个步骤第 1 步你发出指令你通过autopartol_robot_cpp的partol_node发了一个导航目标。用户 / 代码 → NavigateToPose Goal → bt_navigatorpartol_node做的事很简单把 x、y、yaw 打包成一个PoseStamped消息发给 Nav2。第 2 步队长bt_navigator接到任务队长一看“要去那个位置啊行我安排一下。”它首先调用导航员planner_server“喂从当前位置到 (3,2)给我算一条路。”第 3 步导航员算路导航员拿到起点和终点打开costmap代价地图把地图看成一个个小格子墙是黑色不能走空地是白色能走用 A* 算法从起点开始一格一格试探找到终点算出一条不撞墙的路径路径大概长这样 起点 → (0.1,0.0) → (0.2,0.0) → ... → (3.0, 2.0) ← 终点第 4 步队长把路径交给驾驶员队长拿到路径递给驾驶员controller_server“路算好了你沿着这条路开过去。”第 5 步驾驶员开车驾驶员拿到路径开始干活。它每 0.05 秒20Hz做一次决策看看自己在哪从 TF 拿当前位置看看路径在哪离路径偏了多少算一下速度和方向往左打一点还是往右打一点开多快发指令给底盘cmd_vel线速度 角速度就像你开车时每隔一小会儿就要看一眼路、调一下方向盘。第 6 步边走边反馈驾驶员会不断告诉队长“还剩 2.5 米…还剩 1.2 米…还剩 0.3 米…”这就是Feedback反馈。partol_node里的feedback_callback就是用来接收这个的你会看到日志里不断打印剩余距离。第 7 步遇到问题怎么办如果走着走着前面突然出现一个障碍物——驾驶员发现走不通了告诉队长我卡住了队长启动应急方案恢复行为先试试后退一点再试试原地转个圈看看如果还是不行重新规划一条新路规划成功 → 继续走反复试了几次还是不行 → 宣告导航失败这就是为什么 Nav2 要用行为树——它能灵活处理各种异常情况而不是一条路走到黑。第 8 步到达目标驾驶员发现离目标点很近了方向也差不多了就停下来。检查条件goal_checker位置误差 某个阈值比如 0.25 米角度误差 某个阈值比如 0.25 弧度速度接近 0都满足 →导航成功第 9 步队长报告结果队长把结果告诉partol_node“任务完成啦。”result_callback被触发日志打印 “导航成功”然后partol_node继续去下一个航点。 一张图看懂整个流程发指令 │ ▼ ┌──────────┐ │bt_navigator│ ← 队长行为树 └─────┬────┘ │ ①算路 ▼ ┌──────────┐ │ planner │ ← 导航员全局规划 │ (A*) │ └─────┬────┘ │ 路径 ▼ ┌──────────┐ │controller│ ← 驾驶员局部控制 │ (DWB) │ └─────┬────┘ │ cmd_vel ▼ 底盘/车轮 │ │ 边走边看 ▼ ┌──────────┐ ┌──────────┐ │ costmap │◄────│ 传感器 │ ← 激光雷达 │ (障碍地图)│ └──────────┘ └─────┬────┘ │ ▼ ┌──────────┐ │ AMCL │ ← 定位员我在哪 └──────────┘4. 用 autopartol_robot_cpp 做实验autopartol_robot_cpp这个项目就是上面这套流程的最简单用法。4.1 它做了什么intmain(){autonodestd::make_sharedPartolNode();node-run();// 开始巡逻}run()函数里就干三件事voidPartolNode::run(){init_robot_pose();// ① 告诉机器人你在这初始位姿autopointsget_target_points();// ② 拿到要去的航点列表for(每个航点){// ③ 一个一个走过去nav_to_pose(目标点);}}4.2 nav_to_pose 是怎么工作的boolPartolNode::nav_to_pose(目标位姿){1.把目标打包成 Goal2.发给 Nav2 的 Action 服务器3.等...4.收到成功就返回true失败就返回false}注意partol_node自己不做路径规划也不做运动控制。它只是一个发令员——把目标告诉 Nav2然后等着 Nav2 干完活。真正干活的是 Nav2 里面的 planner、controller 那些模块。4.3 你看到的日志对应了什么日志对应步骤“初始位姿已设置: (0.00, 0.00, 0.00)”AMCL 收到初始位置“Nav2 已就绪”Action 服务器准备好接收任务“获取到目标点: 0 - (1.00, 2.00, 3.14)”解析了航点参数“发送导航目标…”第 1 步发出指令“Goal 已接受”第 2 步队长接到任务“剩余距离: 2.50 m”第 6 步实时反馈“导航成功”第 8-9 步到达目标5. 你可能会问的问题Q1为什么要先设置初始位姿AMCL 是蒙着眼睛开机的——它不知道自己在哪。它手里有地图但不知道自己在地图的哪个位置。你给它一个初始位姿就像说“你大概在这个位置你自己再精确看看。” 然后 AMCL 用激光雷达扫一下周围和地图比对就能精确定位了。类比你刚到一个新城市打开地图 APP 时它会先问是否使用当前位置这就是在设初始位姿。Q2为什么要有全局规划和局部控制两层一层不行吗不行因为全局规划看的是整张地图算的是哪条路最近——但它反应慢算一次要几十毫秒局部控制看的是周围几米反应快每秒 20 次能随时躲避突然出现的障碍物就像你开车导航 APP 告诉你走哪条路全局规划你自己握着方向盘躲行人、调方向局部控制两者缺一不可。Q3行为树是什么为什么不用 if-else行为树Behavior Tree是一种把做什么和怎么做组织起来的方式。想象一下导航的逻辑用 if-else 写if (还没到) { if (前面有路) { 往前走 } else { if (能后退) { 后退 if (退完后能重新规划) { 重新规划 } else { 失败 } } else { 失败 } } }逻辑一复杂if-else 就会嵌套得非常深很难改。行为树把这些逻辑画成一棵树每个节点是一个动作或判断改起来很方便。Nav2 把行为树放在 XML 文件里你甚至可以不用改代码直接改 XML 就能换导航策略。Q4costmap 和普通地图有什么区别普通地图map_server 加载的是静态的——墙在哪里、房间有多大都是固定的。costmap代价地图是动态的底层是静态地图上面叠加传感器实时看到的东西给每个格子打分0 完全安全254 在墙上255 未知规划器看的是 costmap所以它能绕开实时出现的障碍物。Q5小车怎么知道自己走了多少靠两个东西里程计odom轮子上有编码器轮子转了多少圈能算出来进而算出走了多远、转了多少度。但里程计会飘——轮子打滑、地面不平整误差会越积越大。AMCL 定位用激光雷达比对地图算出精确位置。它能纠正里程计的漂移。所以 TF 树是这样的map → odom → base_footprint ↑ ↑ AMCL 里程计AMCL 发布map → odom的变换用来纠正里程计的误差。6. 怎么自己动手验证想亲眼看一遍导航过程按下面来第 1 步启动仿真和导航ros2 launch robot_sim robot_bringup.launch.py你会看到 Gazebo仿真环境和 RViz可视化界面。第 2 步在 RViz 里看各个组件打开 RViz 后你可以看到Map—— 静态地图灰色 空地黑色 墙LaserScan—— 激光雷达扫到的红点Global Costmap—— 全局代价地图膨胀层会显示一圈红色就是不能太靠近墙Local Costmap—— 局部代价地图跟着机器人走的一个小窗口Path—— 规划出来的路径一条绿色的线TF—— 各个坐标系的位置关系第 3 步用 2D Pose Estimate 设置初始位姿在 RViz 顶部工具栏点2D Pose Estimate然后在地图上小车的位置点一下拖一下方向。你会看到一堆红色的箭头粒子云一开始可能很散开过一会儿就会聚成一团——这就是 AMCL 在定位。第 4 步用 2D Nav Goal 发一个目标点2D Nav Goal在地图上选个空旷的地方点一下。然后你会看到瞬间出现一条绿色的线 → 这是 planner 算出来的路径小车开始动 → controller 在控制路径可能会变 → 重规划了小车到目标位置停下 → 导航成功第 5 步运行 autopartol_robot_cpp手动玩够了试试自动巡逻ros2 run autopartol_robot_cpp partol_node\--ros-args --params-file\install/share/autopartol_robot_cpp/config/partol_config.yaml看着日志对比上面说的 9 个步骤你就能把理论和实际对应起来了。总结小车导航成功靠的是 5 个角色的配合角色职责关键词 AMCL我在哪粒子滤波、定位️ costmap哪里能走代价地图、障碍物 planner走哪条路A*、全局路径规划 controller怎么开过去DWB、局部控制、速度 bt_navigator指挥调度行为树、恢复行为autopartol_robot_cpp就是给 Nav2 当传声筒——把要去的地方告诉 Nav2然后坐着等结果。真正的硬核算法都在 Nav2 里面。