想象一下你在一个完全漆黑的房间里,蒙着眼睛,只能通过数自己走了多少步、转了多少度来猜测自己走到了哪里。这个“猜测”的过程,就是里程计的核心。在机器人学中,里程计坐标系(Odometry Frame)就是机器人基于自身运动传感器(如编码器、IMU、相机)对自身位置和姿态进行连续估计所构建的坐标系。它不是绝对真实的“世界”,而是机器人“自以为”的世界。
你的手机计步器能告诉你今天走了5000步,但它无法告诉你这5000步具体是从家走到了公园,还是在跑步机上原地跑的。计步器提供的是相对运动量,类似于里程计。而地图APP结合GPS,能告诉你精确的经纬度,这是绝对位置,类似于世界坐标系或地图坐标系。里程计坐标系就是那个不断累加“步数”和“转向角度”的本地估算结果。
里程计坐标系,通常记为 odom 或 odometry_frame,是一个在机器人启动时刻,与其本体坐标系(base_link)重合的坐标系。随着机器人运动,我们通过传感器数据(如轮子转动的圈数)来推算 odom 相对于其初始位置(即世界坐标系原点)的变换关系。
这个变换链是理解导航的基础。机器人本体的实时位姿,是通过“世界->里程计”和“里程计->本体”两次变换得到的。关键在于:odom->base_link 的变换由里程计数据实时、高频更新,反映了机器人的瞬时运动估计。而 map->odom 的变换通常是低频、缓慢修正的,它由更高层的定位算法(如SLAM)来修正里程计的累积漂移。
里程计数据不是凭空产生的,它依赖于具体的传感器。主流来源可分为两大类,其原理和误差特性截然不同。
| 里程计类型 | 数据来源 | 工作原理简述 | 典型误差来源 | 适用场景 |
|---|---|---|---|---|
| 轮式里程计 | 电机编码器、轮速计 | 测量轮子转动圈数,根据机器人运动学模型(差分、阿克曼等)计算机体位移和转角。 | 车轮打滑、地面不平、轮胎气压变化、轮距标定误差。 | 室内平整地面机器人、AGV、自动驾驶车辆。 |
| 视觉里程计 (VO) | 单目/双目/RGB-D相机 | 通过连续图像帧间的特征点匹配,估计相机自身的运动。 | 图像模糊、特征缺失(如白墙)、快速运动导致的图像模糊、尺度不确定性(单目)。 | 无人机、手持设备、复杂地形机器人。 |
| 惯性里程计 | IMU(陀螺仪、加速度计) | 积分角速度得到角度,积分加速度(需去除重力)得到速度与位移。 | 加速度计零偏和噪声导致位移积分迅速发散,通常不单独使用。 | 与轮式或视觉融合,提供高频姿态估计。 |
在实际项目中,大部分地面机器人会优先使用轮式里程计,因为它成本低、频率高、在理想条件下短期精度可靠。我在一个仓储AGV项目中就主要依赖高精度光电编码器提供里程计,视觉传感器仅用于辅助纠正。而对于无人机或手持扫描设备,视觉里程计或视觉惯性里程计(VIO)则是更常见的选择。
map->odom 的变换也在被修正,使得 odom 的原点相对于地图在“漂移”。里程计漂移是里程计坐标系最核心的问题。即使你的编码器精度高达每转10000线,一次微小的测量误差(例如,因为地面一颗小石子导致轮子空转了1%圈)也会被积分到后续的所有位姿估计中。
漂移在平移和旋转上都会发生。旋转漂移(角度误差)的影响往往比平移漂移更致命,因为一个微小的角度误差会导致机器人前进方向偏差,随着移动距离增加,产生的横向位置误差会呈指数级增长。
编者提示:一个简单的漂移实验
你可以用一个带编码器的差分轮机器人做实验:让它在光滑的环氧地坪上和粗糙的短毛地毯上分别走一个10米×10米的正方形。在地毯上,由于轮子打滑更严重,回到起点时的位置误差会远大于在光滑地面上的误差。这个误差就是里程计漂移最直观的体现。实际开发中,我们会在不同地面上对机器人的里程计参数进行标定,以最小化这种系统误差。
在机器人操作系统(ROS2)中,里程计坐标系的概念被集成到了TF2库中。一个标准的定位/导航节点会发布两类关键的变换:
// 1. 高频发布的里程计变换:odom -> base_link
// 这由你的里程计解算节点(如robot_localization)发布
geometry_msgs::msg::TransformStamped odom_tf;
odom_tf.header.frame_id = "odom";
odom_tf.child_frame_id = "base_link";
// transform 的内容由里程计数据填充 (x, y, z, 四元数)
odom_broadcaster_.sendTransform(odom_tf);
// 2. 低频发布的地图修正变换:map -> odom
// 这通常由你的定位算法(如AMCL或SLAM)发布
geometry_msgs::msg::TransformStamped map_tf;
map_tf.header.frame_id = "map";
map_tf.child_frame_id = "odom";
// transform 的内容用于修正漂移。当定位算法认为机器人实际在map下的位姿与odom推算的位姿有偏差时,
// 就通过调整这个变换,使得 base_link 通过 map->odom->base_link 链最终得到的位姿是正确的。
map_broadcaster_.sendTransform(map_tf);
这种设计的巧妙之处在于解耦。控制模块可以放心地订阅 odom->base_link 来获取平滑连续的速度和位置反馈进行闭环控制,而不必关心底层全局定位是否发生了跳变。导航规划模块则基于更准确的 map->base_link 变换(由上述两个变换组合而成)在全局地图上进行路径规划。
odom 与 base_link 在原点重合。map->odom 变换初始为单位矩阵(无修正)。odom->base_link 开始变化。odom->base_link。map->odom 变换。map->base_link 位姿。odom->base_link,反向计算出一个新的 map->odom 变换。如果你在调试时发现机器人在Rviz中运动不连续、出现跳变,或者控制不稳定,第一个要检查的就是TF树中 odom、base_link、map 这几个帧的变换关系是否完整、是否由正确的节点发布、时间戳是否同步。
任务:观察里程计漂移
如果你有一个ROS2机器人(实体或仿真均可),尝试以下步骤:
1. 启动机器人,在Rviz中显示激光扫描和机器人模型,确保TF树正确。
2. 用键盘控制机器人缓慢地走一个尽可能大的正方形或圆形,并回到你认定的起点附近。
3. 观察现象:激光扫描点云与地图(如果是静态地图)是否还能对齐?机器人模型在Rviz中的位置与它实际的位置偏差有多大?
4. 记录下机器人仅依赖里程计走完路径后,最终的位姿误差(x, y, θ)。这个误差就是本次运行的累积漂移量。尝试不同的速度、不同的地面(仿真中可调整摩擦系数)重复实验,感受漂移如何变化。
map->odom->base_link 的变换链来优雅地处理漂移:高频里程计更新 odom->base_link,低频定位算法修正 map->odom。