坐标系统与变换:世界坐标系、传感器坐标系、刚体变换

做多传感器融合,说白了就是让不同传感器「说同一种语言」。你想想看,激光雷达看到的是自己坐标系下的点,IMU测量的是自己身上的加速度,摄像头看到的像素坐标——这些数据要融合到一起,必须先把它们统一到同一个坐标系下。

我记得刚入行那会儿,第一次做激光雷达和相机的融合项目。数据采集回来,怎么都对不上。折腾了两天,最后发现是坐标变换矩阵写反了。嗯,从那以后,我对坐标系变换这件事就格外上心。

世界坐标系与传感器坐标系

先搞清楚两个基本概念:

  • 世界坐标系:一个固定的参考系,所有传感器都相对于它来描述位置。通常我们选第一帧时的车体位置作为原点,或者用GPS/UTM坐标。
  • 传感器坐标系:每个传感器自己的小天地。比如激光雷达的坐标系,原点在激光雷达中心,x轴朝前,y轴朝左,z轴朝上。

传感器坐标系和世界坐标系之间,靠的就是刚体变换来转换。

核心思想:世界坐标系是「绝对」的,传感器坐标系是「相对」的。融合的本质,就是把所有「相对」数据映射到「绝对」空间里。

刚体变换:旋转矩阵 + 平移向量

刚体变换,说白了就是「转一下,再挪一下」。它由两部分组成:

  • 旋转矩阵 R:3x3的正交矩阵,描述朝向变化
  • 平移向量 t:3x1的向量,描述位置偏移

公式很简单:p_world = R * p_sensor + t

这里有个坑——旋转矩阵必须是正交矩阵,也就是说 RTR = I。我在项目中遇到过有人手写旋转矩阵,结果数值误差导致矩阵不正交,最后融合出来的轨迹全是扭曲的。所以,能用库函数就别自己手算。

我曾经踩过的坑:旋转矩阵的乘法顺序。左乘和右乘结果完全不同。ROS里用的是左乘,Eigen默认也是左乘。如果你从论文里抄公式,一定要看清楚是左乘还是右乘。

四元数与欧拉角

欧拉角直观,但有个致命问题——万向锁。我建议你在实际工程中尽量用四元数。

表示方式优点缺点
欧拉角 (roll, pitch, yaw)直观,容易理解万向锁,插值困难
四元数 (w, x, y, z)无奇点,可平滑插值不够直观
旋转矩阵计算方便参数冗余,需正交化

四元数其实没那么可怕。你只要记住:q = [w, x, y, z],其中 w 是实部,x,y,z 是虚部。单位四元数满足 w² + x² + y² + z² = 1。

我个人习惯在代码里统一用四元数存储姿态,只在可视化或调试时转成欧拉角。这样既避免了万向锁,又方便做插值。

实用技巧:Eigen库中,四元数转旋转矩阵用 .toRotationMatrix(),旋转矩阵转四元数用 Quaterniond(R)。ROS里 tf2 的 transformPose 函数可以直接帮你做坐标系变换,省去手写矩阵的麻烦。

知识体系结构图

下面这张图梳理了本章的核心逻辑:

坐标系统与变换 世界坐标系 传感器坐标系 刚体变换 旋转矩阵 四元数 欧拉角 多传感器数据统一 → 融合

代码示例:Eigen实现坐标变换

下面是一个实际可用的C++代码片段,演示了如何用Eigen库做坐标变换:

#include <Eigen/Dense>
#include <iostream>

int main() {
    // 定义旋转:绕z轴转45度
    Eigen::AngleAxisd roll(0.0, Eigen::Vector3d::UnitX());
    Eigen::AngleAxisd pitch(0.0, Eigen::Vector3d::UnitY());
    Eigen::AngleAxisd yaw(M_PI/4, Eigen::Vector3d::UnitZ());
    
    Eigen::Quaterniond q = yaw * pitch * roll;
    Eigen::Matrix3d R = q.toRotationMatrix();
    
    // 平移向量:传感器在世界坐标系中的位置
    Eigen::Vector3d t(1.0, 2.0, 0.5);
    
    // 传感器坐标系下的一个点
    Eigen::Vector3d p_sensor(0.0, 1.0, 0.0);
    
    // 变换到世界坐标系
    Eigen::Vector3d p_world = R * p_sensor + t;
    
    std::cout << "世界坐标: " << p_world.transpose() << std::endl;
    
    return 0;
}

我个人的工程习惯:在代码里统一用 Eigen::Isometry3d 来存储变换矩阵。它内部就是4x4齐次矩阵,用 .rotate() 和 .pretranslate() 来构建,比手写R和t要安全得多。

好了,坐标变换这块内容就讲到这里。记住一句话:所有传感器数据,最终都要统一到同一个坐标系下。这是多传感器融合的基石,也是你写代码时最容易出错的地方。多检查,多验证,别像我当年那样栽跟头。

可可精品资料库