引言
ROS1 导航架构概述
ROS1 Navigation依赖move_base 管理全局/局部规划/costmap以及恢复行为,通过硬编码状态机的方式实现,节点在(PLANNING,CONTROLLING, CLEARING )几个状态进行切换。
- 下图为ROS1 Navigation Stack的核心架构:
核心接口定义,元包] nav_core --> base_global_planner[nav_core::BaseGlobalPlanner
全局规划器接口] base_global_planner --> base_local_planner[nav_core::BaseLocalPlanner
局部规划器接口] base_local_planner --> recovery_behavior[nav_core::RecoveryBehavior
恢复行为接口] %% --- Main Node --- %% 将状态机子图逻辑上置于 move_base 之上 %% SM --- move_base %% 使用不可见连线强制 SM 在 move_base 上方 SM ~~~ move_base subgraph SLAM [定位建图模块] direction TB Localization[Localization
定位] --> Mapping[Mapping
建图] --> map_server[map_server
地图保存与发布] end Mapping-->StaticMap move_base[move_base
导航栈的“大脑”,主状态机循环以及各个模块调用] %% --- Costmap Section --- move_base --> Costmap2D[Costmap 2D
全局/局部代价地图
将各种数据源(地图、传感器)融合成一张供规划器使用的代价地图] SensorData[传感器数据] --> Costmap2D StaticMap[静态地图] --> Costmap2D Costmap2D --> StaticLayer[静态层
加载静态地图] Costmap2D --> ObstacleLayer[障碍物层
接收激光、点云等传感器数据,标记实时障碍物] Costmap2D --> InflationLayer[膨胀层
根据障碍物生成膨胀区域,使机器人远离障碍物] %% --- Global Planner Section --- move_base --> GlobalPlannerMain[Global Planner
全局规划器] GlobalPlannerMain --> Navfn[Navfn
另一实现,同样支持 A* 和 Dijkstra] GlobalPlannerMain --> GlobalPlannerSub[Global Planner
支持 A* 和 Dijkstra ] GlobalPlannerMain --> GlobalPath[全局路径] %% --- Local Planner Section --- GlobalPath --> LocalPlannerMain[Local Planner
局部规划器] move_base --> LocalPlannerMain LocalPlannerMain --> TebLocalPlanner[Teb Local Planner
基于时间弹性带算法] LocalPlannerMain --> DWA[DWA
动态窗口法] LocalPlannerMain --> BaseLocalPlanner[Base Local Planner] LocalPlannerMain --> VelocityCommand[速度指令] %% --- Recovery Behaviors Section --- move_base --> RecoveryBehaviors[Recovery Behaviors
恢复行为] RecoveryBehaviors --> ClearCostmap[清除代价地图] RecoveryBehaviors --> RotateRecovery[旋转360度恢复/移动恢复]
- 若链接CostMap与规划器,则更加复杂:
地图保存与发布"] Mapping["Mapping
建图"] Localization["Localization
定位"] end PLANNING --> CONTROLLING CONTROLLING --> CLEARING nav_core["nav_core
核心接口定义,元包"] --> base_global_planner["nav_core::BaseGlobalPlanner
全局规划器接口"] base_global_planner --> base_local_planner["nav_core::BaseLocalPlanner
局部规划器接口"] base_local_planner --> recovery_behavior["nav_core::RecoveryBehavior
恢复行为接口"] SM ~~~ move_base["move_base
导航栈的“大脑”,主状态机循环以及各个模块调用"] Localization --> Mapping Mapping --> map_server & StaticMap["静态地图"] move_base --> Costmap2D["Costmap 2D
全局/局部代价地图
将各种数据源(地图、传感器)融合成一张供规划器使用的代价地图"] & GlobalPlannerMain["Global Planner
全局规划器"] & LocalPlannerMain["Local Planner
局部规划器"] & RecoveryBehaviors["Recovery Behaviors
恢复行为"] SensorData["传感器数据"] --> Costmap2D StaticMap --> Costmap2D Costmap2D --> StaticLayer["静态层
加载静态地图"] & ObstacleLayer["障碍物层
接收激光、点云等传感器数据,标记实时障碍物"] & InflationLayer["膨胀层
根据障碍物生成膨胀区域,使机器人远离障碍物"] & GlobalPlannerMain & LocalPlannerMain GlobalPlannerMain --> Navfn["Navfn
另一实现,同样支持 A* 和 Dijkstra"] & GlobalPlannerSub["Global Planner
支持 A* 和 Dijkstra"] & GlobalPath["全局路径"] GlobalPath --> LocalPlannerMain LocalPlannerMain --> TebLocalPlanner["Teb Local Planner
基于时间弹性带算法"] & DWA["DWA
动态窗口法"] & BaseLocalPlanner["Base Local Planner"] & VelocityCommand["速度指令"] RecoveryBehaviors --> ClearCostmap["清除代价地图"] & RotateRecovery["旋转360度恢复/移动恢复"] classDef default fill:#fff,stroke:#333,stroke-width:1px,color:#000,rx:2,ry:2
ROS2 导航架构概述
ROS2 Nav2 将 ROS1 单体的 move_base 拆分为多个独立 Server(模块化解耦)每个 Server 专注单一功能,通过标准化接口协作,配合行为树(BT) 完成导航。
Nav2 的核心思想是 “服务器(Server)+ 行为树(Behavior Tree)+ 生命周期管理(Lifecycle Manager)”。它不再是一个像 move_base 那样的单体大节点,而是由多个独立的、可管理的节点组成。
- 下图为ROS2核心架构与工作流程:
NavFn"] P_Smac["插件2
Smac Planner
含2D/3D版本"] C_DWB["插件1
DWB</br>稳定+轻量"] C_TEB["插件2
TEB"] end LifecycleManager["Lifecycle Manager
周期管理器"] --> ManageNode(("管理")) ManageNode -- 加载 --> MapServer["Map Server
地图服务器"] ManageNode --> AMCL["AMCL
定位节点"] & PlannerServer["Planner Server
规划服务器"] & ControllerServer["Controller Server
控制服务器"] & BTNavigator["Behavior Tree Navigator
行为树导航器"] StaticMapData["静态地图"] -- 加载 --> MapServer MapServer -- 静态地图数据 --> AMCL & PlannerServer & ControllerServer SensorData["传感器数据"] -- 激光/点云 --> AMCL & PlannerServer & ControllerServer AMCL -- 机器人位姿 --> PlannerServer & BTNavigator & ControllerServer UserApp["用户/应用"] -- 导航目标
机器人位姿 --> BTNavigator BTNavigator -- 计算路径任务 --> PlannerServer PlannerServer -- 全局路径 --> BTNavigator BTNavigator -- 路径跟踪任务 --> ControllerServer BTNavigator -- 全局路径 --> ControllerServer PlannerServer -.- P_NavFn & P_Smac ControllerServer -.- C_DWB & C_TEB ControllerServer --> CmdVel["速度指令
cmd_vel"] CmdVel --> RobotBase["机器人底座"] classDef default fill:#fff,stroke:#333,stroke-width:1px,color:#000,rx:2,ry:2 classDef highlight fill:#fdfbe8,stroke:#d4c359,stroke-width:2px
解读costmap
ROS1/ROS2架构中,代价地图层都是核心部件(ROS2的为nav2_costmap_2d也是集成自costmap_2d但重构为生命周期节点,可见更为重要),接下来对其进行深入解读。首先看看什么是代价地图~~~
代价地图(CostMap)是一种用于描述环境中各个位置(栅格)的导航代价的地图。这个导航代价反映了机器人在不同区域移动的相对难易程度。 代价地图可以有如下的意义:
- 路径规划: 代价地图用于路径规划算法,帮助机器人找到从起点到目标的最优路径。代价地图中的代价值通常表示机器人在特定位置的可行性和安全性,路径规划算法会优先选择代价较低的路径。
- 避障: 通过代价地图,机器人能够避开障碍物、危险区域或其他不利于移动的区域。代价地图中高代价的区域通常对应于障碍物或其他需要规避的物体,从而确保机器人选择安全的路径。
- 动态环境适应: 代价地图可以反映环境的动态变化,比如移动的障碍物或临时障碍。通过实时更新代价地图,机器人能够及时调整路径规划,适应环境的变化。
- 速度调整: 代价地图中的代价值可以与机器人的速度相关联。在规划路径时,代价地图可以影响机器人在不同区域的运动速度,使其在复杂环境中更加灵活和高效。
- 全局与局部规划结合: 代价地图可以支持全局路径规划和局部路径规划的结合。全局规划负责长距离导航,而局部规划负责处理机器人在实时环境中的微调,以应对临时障碍或其他突发情况。
costmap对应的是costmap_2d功能包,该功能包是机器人导航系统的核心组件,负责维护环境成本的 2D 表示。它采用分层架构(Layered Architecture),将不同类型的信息(静态地图、实时传感器数据和安全缓冲区)聚合为一个单一的“主”代价地图(Master Costmap),供规划器使用。
代价地图层次架构分类
代价地图是由多层数据融合,获取的,主要包括以下:
- 静态层(Static Layer):来自预先构建的静态地图
- 障碍层(Obstacle Layer):实时传感器数据(如激光雷达/深度相机)检测到的动态障碍物
- 膨胀层(Inflation Layer):对障碍物进行膨胀,以确保机器人与障碍物之间保持安全距离。
- 体素层(Voxel Layer)
代价地图通用参数costmap_common_params.yaml:
- obstacle_range 障碍物探测范围 (米)。传感器读数超出此范围则不会作为障碍物加入代价地图。典型值: 2.5。
- raytrace_range 光线追踪范围 (米)。用于清除已知障碍物之间空间的传感器读数范围,通常略大于 obstacle_range。典型值: 3.0。
- robot_radius (圆形机器人) 机器人半径 (米)。用于足迹计算(圆形模型简化)。
- footprint (多边形机器人) 机器人足迹。以坐标点列表描述机器人轮廓(多边形模型更精确),有这个应该就可以不用上面一个参数了。
- inflation_radius 膨胀半径 (米)。设置障碍物产生代价的距离,值越大,机器人离障碍物越远。典型值: 0.55。
- cost_scaling_factor 代价缩放因子。控制代价随距离衰减的速度,值越大,代价衰减越快。
核心文件及主要功能解读:
1. costmap_2d_ros.cpp
【主要逻辑,图层管理】ROS 节点的顶层接口,负责将代价地图与ROS系统连接起来。
Costmap2DROS类:主类,它封装了Costmap2D(通过LayeredCostmap/layered_costmap.cpp)并协调与 ROS 的交互。它管理图层的加载、更新、发布可视化信息,并通过tf将地图关联到机器人坐标系。- 关于TF树:需要完整(如
map-->base_link),若定位没有启动,代价地图是无法运行的 - plugins:以插件的形式加载不同的图层。不硬编码“静态地图”或“障碍物”,而是通过
plugin_loader_动态加载。
- 关于TF树:需要完整(如
Costmap2D类: 核心数据结构,存储底层二维网格代价地图。- 地图更新主线程
mapUpdateLoop,根据设定的频率循环调用地图更新逻辑。注意,这部分应该是当参数发生变化是才更新reconfigureCB(参数不变,是不需要更新的)reconfigureCB的设计体现了“彻底同步”的思想。它不是简单地改两个数字,而是确保了从底层的内存大小(Resize)、到算法层的碰撞半径(Footprint)、再到执行层的刷新工作频率(Thread),全部刷新一遍。这种做法虽然在执行的一瞬间会有短暂的 CPU 峰值,但保证了系统参数在动态调整过程中的绝对一致性。
void updateMap(): 核心方法,循环调用所有图层的updateBounds和updateCosts来更新代价地图。- 更新边界 (Update Bounds):
- 调用每个层,根据新的传感器数据或机器人移动,报告其需要更新的区域。
- 这些边界被合并为一个单一的矩形边界框。
- 更新代价值 (Update Costs):
- 在边界框内清除主代价地图。
- 每个层按顺序(在 plugins.xml 或 YAML 配置中定义)将其数据写入主网格。写入方式包括覆盖(Overwrite)、取最大值(Max)等。
- 更新边界 (Update Bounds):
- 图层管理: 负责根据参数加载插件图层,并初始化它们。
- TF 关联: 通过
tf缓冲区查找机器人的实时位姿,将代价地图关联到机器人坐标系(如base_link到map)。 - 可视化与发布: 通过
Costmap2DPublisher发布代价地图数据及其可视化信息(网格、足迹等)。 - 动态调参: 集成了
dynamic_reconfigure,允许在运行时修改更新频率、代价地图大小等参数。 - footprint管理: 订阅并处理机器人模型(footprint/机器人在投影到地面的 2D 多边形轮廓),用于后续的膨胀和碰撞检查。注意,最终应该只有膨胀层会根据footfrpint进行设置。
2. layered_costmap.cpp
所有代价地图插件的中央管理器。代价地图的核心在于“分层聚合”。通过多个继承自 Layer 的插件共同协作。
- 持有主 Costmap2D 对象。
- 编排更新流程,按特定顺序调用各个插件。
- 管理滚动窗口(Rolling Window)逻辑(地图中心随机器人移动)。
- 在一帧的更新周期内,它指挥所有插件完成“两阶段”融合:
- 第一阶段:UpdateBounds (边界收集)
- 每个图层(Layer)报出自己本次变动的地理范围(MinX, MinY, MaxX, MaxY)。
- 第二阶段:UpdateCosts (代价绘制)
- 在上述计算的区域,先清空旧数据。
- 然后让各个图层依次(按照加载顺序)将新的代价值填入主地图。
- 第一阶段:UpdateBounds (边界收集)
3. layer.cpp
图层基类,定义了标准化的接口
onInitialize(): 插件被加载时的初始化。updateBounds(): 告诉系统哪里变了。updateCosts(): 告诉系统变成了什么值。-
matchSize(): 当主地图尺寸变化时同步调整自己的缓冲区。 src/static_layer.cpp- 静态地图图层- 加载预构建的占据栅格地图(
nav_msgs/OccupancyGrid,通常来自 map_server) - 初始化
onInitialize函数,订阅了地图话题,并进入while循环阻塞等待,在静态地图到位之前,整个 costmap_2d 节点不会完成初始化,这是为了确保路径规划有基准底图。接收到topic后就会回调函数incomingMap。 - interpretValue: 将 OccupancyGrid [0, 100] 的概率映射到 [0, 254] 的代价:
- 未知空间处理:如果 track_unknown_space 为真,-1 (unknown) 映射为 255 (NO_INFORMATION)。
- 三值化 (Trinary):默认情况下,只要概率值小于 lethal_threshold,就统一设为 0 (FREE_SPACE);大于等于则设为 254 (LETHAL)。这极大地简化了全局规划。
- 连续映射:如果禁用了三值化,它会线性缩放概率值到 [0, 254]。
- 同步锁定: 在接收到第一张地图后,通常会锁定代价地图的大小和分辨率。
- 加载预构建的占据栅格地图(
src/obstacle_layer.cpp- 障碍物图层- 处理实时传感器数据(如激光雷达、点云)。通过射线追踪(Raytracing)清除空间,并通过标记(Marking)添加新障碍物。
ObservationBuffer:为每个源创建一个ObservationBuffer。这是一个线程安全的类,负责将接收到的数据暂存在一个队列中,并根据observation_persistence设置过期时间。- Marking (标注): 解析传感器点云,在对应栅格位置写入代价(LETHAL_OBSTACLE)。
- Clearing (清除): 应用射线追踪 (Raytracing) 算法(通常是 Bresenham 算法),清除传感器原点与探测到的障碍点连线路径上的无效障碍,维持地图的实时准确性。
src/inflation_layer.cpp- 膨胀图层- 在障碍物周围添加安全缓冲区。成本随离障碍物的距离呈指数级衰减。
4. costmap_2d.cpp
- 在障碍物周围添加安全缓冲区。成本随离障碍物的距离呈指数级衰减。
代价地图实现