2、Nav2中的坐标框架:map、odom、base_link、base_footprint、laser_frame等标准框架的定义与用途
做导航,说白了就是回答三个问题:我在哪?我要去哪?怎么过去? 而坐标框架就是回答「我在哪」的基准。ROS 2 里有一套约定俗成的框架体系,Nav2 完全遵循这套规则。今天咱们就把这几个核心框架掰开揉碎聊清楚。
2.1 框架总览与层级关系
先看一张我手绘的框架关系图。你想想看,机器人导航时,这些坐标系就像俄罗斯套娃一样,一层套一层。
从图上你能看到,map 在最顶层,下面是 odom,再往下是 base_link。而 base_footprint 和 laser_frame 通常挂在 base_link 下面。我个人习惯把这种结构叫做「三明治架构」——稳定、清晰、好排查。
2.2 逐个拆解:每个框架的定位
| 框架名称 | 定义 | 典型用途 | 父框架 |
|---|---|---|---|
map | 全局固定坐标系,与真实世界锚定 | 全局路径规划、重定位、AMCL | 无(世界根) |
odom | 里程计局部世界坐标系,连续且无漂移 | 局部规划、速度控制、航迹推算 | map (通过定位修正) |
base_link | 机器人本体坐标系,原点在底盘中心 | 控制指令、速度、所有传感器挂载 | odom |
base_footprint | base_link 在水平面的投影,忽略高度 | 碰撞检测、代价地图投影 | base_link |
laser_frame | 激光雷达光学中心 | 激光扫描数据、障碍物检测 | base_link (或 base_footprint) |
嗯,这里要注意:map 和 odom 之间的变换通常由定位模块(比如 AMCL)发布,而 odom 到 base_link 则由里程计节点发布。这两个变换是导航的命脉。
2.3 深入理解:为什么需要这么多框架?
你可能会问:一个坐标系不够用吗?非得搞五六个?
我在项目里遇到过这样的坑:直接把激光雷达的数据放到 base_link 下,结果机器人一转弯,点云全飘了。为什么?因为激光雷达的安装位置有偏移,必须用单独的 laser_frame 来描述这个偏移。
说白了,每个框架各司其职:
- map 是「不动产登记本」—— 墙、柱子、家具的位置永远不变。
- odom 是「计步器」—— 记录你走了多少步,但不知道你在小区哪栋楼。
- base_link 是「你的身体」—— 你往左转,它就往左转。
- base_footprint 是「你的脚印」—— 忽略身高,只看落脚点。
- laser_frame 是「你的眼睛」—— 眼睛长在头上,头动眼睛就动。
base_footprint 到 base_link 的静态变换,结果代价地图一直报错「找不到有效 footprint」。后来加了一行 static_transform_publisher 就解决了。记住:base_footprint 的 z 轴偏移通常为 0,但必须显式声明。
2.4 框架之间的变换关系
在 Nav2 中,TF 树必须完整且连续。下面是一个典型的 TF 树片段,你可以在终端用 ros2 run tf2_tools view_frames 看到类似结构。
map (broadcaster: amcl)
└─ odom (broadcaster: robot_localization 或 里程计)
└─ base_link (broadcaster: 里程计/ekf)
├─ base_footprint (static: 0 0 0 0 0 0)
└─ laser_frame (static: x y z yaw pitch roll)
我个人习惯把 base_footprint 作为 base_link 的子节点,并且 z=0。这样在代价地图中做二维投影时,直接取 base_footprint 的位姿即可,省去高度干扰。
2.5 实际配置示例(launch 片段)
下面是一个典型的静态 TF 发布配置,通常在机器人的启动文件中出现。注意看 base_footprint 和 laser_frame 的发布方式。
# 静态变换:base_link → base_footprint (无平移旋转)
<node pkg="tf2_ros" exec="static_transform_publisher" name="base_footprint_broadcaster">
<param name="translation" value="0 0 0"/>
<param name="rotation" value="0 0 0 1"/>
<param name="frame_parent" value="base_link"/>
<param name="frame_child" value="base_footprint"/>
</node>
# 静态变换:base_link → laser_frame (根据实际安装位置)
<node pkg="tf2_ros" exec="static_transform_publisher" name="laser_broadcaster">
<param name="translation" value="0.15 0.0 0.25"/>
<param name="rotation" value="0 0 0.707 0.707"/> <!-- 四元数,绕z转90度 -->
<param name="frame_parent" value="base_link"/>
<param name="frame_child" value="laser_frame"/>
</node>
tf2_ros::Quaternion 或者 quaternion_from_euler 工具转换一下,不费事。
2.6 框架命名的最佳实践
最后聊点经验。框架命名虽然自由,但团队协作时最好统一。我建议:
- 所有传感器框架以
_frame结尾,比如camera_frame、lidar_frame。 - 不要用大写字母,ROS 2 框架名大小写敏感,全小写最安全。
- 如果有多台激光雷达,用
front_laser_frame、rear_laser_frame区分。 base_footprint是约定俗成的名字,不要改成robot_foot之类的,否则 Nav2 的 footprint 插件会找不到。
好了,这一章的内容就是这些。记住:框架是导航的骨架,骨架歪了,后面所有算法都白搭。