坐标系统与变换:世界坐标系、车辆坐标系、传感器坐标系、坐标变换矩阵、欧拉角与四元数
各位同学,今天我们来聊聊自动驾驶里最基础、也最容易出问题的一个模块——坐标系统与变换。
说实话,我见过太多新手工程师,算法写得飞起,结果车一上路就偏了。查来查去,最后发现是坐标系搞反了。嗯,这种坑我自己也踩过,所以今天咱们把这部分彻底讲透。
1. 为什么需要这么多坐标系?
你想想看,一辆自动驾驶车上,有GPS、有激光雷达、有摄像头、有IMU。每个传感器都有自己的“视角”。
- GPS告诉你:你在北纬多少度、东经多少度——这是世界坐标系。
- IMU告诉你:车头朝哪个方向、车身倾斜了多少——这是车辆坐标系。
- 激光雷达告诉你:前方3米处有个障碍物——这是传感器坐标系。
说白了,我们做规划控制,就是要把这些不同“语言”的数据,统一翻译成一种“语言”。不然的话,GPS说“前面有坑”,激光雷达说“右边有墙”,车就不知道该往哪走了。
2. 三大核心坐标系
2.1 世界坐标系(World Coordinate System)
世界坐标系是绝对的、固定的。通常我们使用UTM坐标系(通用横轴墨卡托投影),或者直接使用经纬度+海拔。
我个人习惯用UTM,因为它是平面直角坐标系,计算距离和角度非常方便。经纬度虽然直观,但涉及到球面几何,计算起来比较麻烦。
关键点:世界坐标系的原点通常是固定的,比如某个基准点。所有的高精地图、全局路径规划,都在这个坐标系下完成。
2.2 车辆坐标系(Vehicle Coordinate System)
车辆坐标系是跟着车走的。通常定义:
- X轴:车头方向(前进方向)
- Y轴:车身左侧方向
- Z轴:垂直向上(符合右手定则)
我记得有一次,团队里新来的同事把Y轴方向搞反了,结果车辆在变道时总是往反方向打方向盘。还好是在仿真环境里测试,不然真上路就出大事了。
注意:不同车厂对车辆坐标系的定义可能略有不同。有的把Y轴定义为右侧方向。拿到数据后,第一件事就是确认坐标系定义。
2.3 传感器坐标系(Sensor Coordinate System)
每个传感器都有自己的坐标系。比如:
- 摄像头:通常以光心为原点,Z轴指向拍摄方向,X轴向右,Y轴向下。
- 激光雷达:通常以旋转中心为原点,X轴指向正前方,Y轴指向左侧。
- IMU:通常以安装位置为原点,坐标轴与车辆坐标系对齐。
这里有个坑:传感器的安装位置和朝向,必须精确标定。我曾经遇到过一个项目,激光雷达装歪了0.5度,结果在100米外的障碍物位置偏差了将近1米。你说这能忍吗?
3. 坐标变换矩阵
坐标变换,说白了就是用一个矩阵,把一个坐标系下的点,映射到另一个坐标系下。
假设我们有一个点P,在坐标系A下的坐标为(x_a, y_a, z_a),想把它转换到坐标系B下。那么:
P_b = R * P_a + T
其中:
- R:旋转矩阵(3x3),描述两个坐标系之间的旋转关系。
- T:平移向量(3x1),描述两个坐标系原点之间的位移。
在实际代码中,我们通常使用齐次坐标,把旋转和平移合并成一个4x4的变换矩阵:
| R T |
| 0 1 |
这样,一次矩阵乘法就能完成变换,非常高效。
我的习惯:在代码里,我通常用Eigen库或者Sophus库来处理坐标变换。千万别自己手写矩阵求逆,容易出错。用现成的库,既安全又高效。
4. 欧拉角与四元数
旋转的表达方式有很多种,最常用的是欧拉角和四元数。
4.1 欧拉角
欧拉角用三个角度来描述旋转:
- 偏航角(Yaw):绕Z轴旋转,也就是车头的朝向。
- 俯仰角(Pitch):绕Y轴旋转,也就是车头上仰或下俯。
- 横滚角(Roll):绕X轴旋转,也就是车身侧倾。
欧拉角很直观,但有一个致命问题——万向锁(Gimbal Lock)。当俯仰角接近±90度时,偏航和横滚会失去一个自由度,导致旋转无法唯一表示。
避坑指南:我曾经在IMU数据融合时,直接用欧拉角做插值,结果车辆在坡道上出现了诡异的抖动。后来换成四元数,问题立刻解决了。所以,千万不要在插值和滤波中使用欧拉角。
4.2 四元数
四元数用四个数来表示旋转:q = (w, x, y, z)。它没有万向锁问题,而且插值平滑,是自动驾驶领域的首选。
四元数的好处:
- 没有奇点,可以表示任意旋转。
- 插值(球面线性插值,SLERP)非常平滑。
- 组合旋转只需要一次乘法,计算效率高。
举个例子,把欧拉角转成四元数:
// 假设欧拉角为 (yaw, pitch, roll)
// 转换为四元数
double cy = cos(yaw * 0.5);
double sy = sin(yaw * 0.5);
double cp = cos(pitch * 0.5);
double sp = sin(pitch * 0.5);
double cr = cos(roll * 0.5);
double sr = sin(roll * 0.5);
double w = cr * cp * cy + sr * sp * sy;
double x = sr * cp * cy - cr * sp * sy;
double y = cr * sp * cy + sr * cp * sy;
double z = cr * cp * sy - sr * sp * cy;
嗯,这段代码我闭着眼睛都能写出来。因为我在项目里用过不下几百次了。
5. 实战中的坐标系管理
在实际项目中,我建议你建立一个统一的坐标系管理类。比如:
class CoordinateManager {
public:
// 世界坐标系 -> 车辆坐标系
Eigen::Matrix4d worldToVehicle(const Eigen::Matrix4d& pose);
// 车辆坐标系 -> 传感器坐标系
Eigen::Matrix4d vehicleToSensor(const std::string& sensor_name);
// 直接转换:世界坐标系 -> 传感器坐标系
Eigen::Matrix4d worldToSensor(const std::string& sensor_name);
private:
std::map<std::string, Eigen::Matrix4d> sensor_extrinsics_; // 传感器外参
Eigen::Matrix4d vehicle_pose_; // 当前车辆位姿(世界坐标系下)
};
这样做的好处是,所有坐标变换都集中管理,不会出现“这个点是在哪个坐标系下”的混乱。
我的建议:在代码里,给每个坐标点加上一个“坐标系标签”。比如用枚举类型:
enum class CoordinateFrame {
WORLD,
VEHICLE,
LIDAR,
CAMERA
};
struct Point3D {
double x, y, z;
CoordinateFrame frame;
};
这样,即使代码写得再快,也不会搞混坐标系。
6. 总结
坐标系统与变换,是自动驾驶规划控制的基石。说白了,你所有的算法——路径规划、行为决策、运动控制——都建立在正确的坐标变换之上。
我个人觉得,这部分内容虽然基础,但再怎么强调也不为过。因为一旦出错,后面的所有工作都是白费。
最后送大家一句话:坐标系搞不对,代码写得再漂亮也是零。
好了,今天就讲到这里。下一章我们聊聊路径规划中的曲线生成,到时候见。