3、坐标系与变换:世界坐标系、相机坐标系、图像坐标系、像素坐标系、激光雷达坐标系,刚体变换与欧拉角

做传感器融合,说白了就是跟坐标系打交道。你想想看,激光雷达说「前方5米有个障碍物」,相机说「图像右下角有个红色物体」,这两句话用的坐标系完全不一样。怎么让它们对上话?这就是本章要解决的问题。

我个人习惯把坐标系变换比作「翻译官」。每个传感器都有自己的语言,我们需要一个通用的翻译规则。嗯,这个规则就是刚体变换和欧拉角。

3.1 五大坐标系,一个都不能少

在自动驾驶中,我们主要跟五个坐标系打交道。我刚开始做项目时,经常搞混它们的关系,后来画了一张图才彻底理清。

核心要点:所有坐标系变换的最终目标,就是把激光雷达点云投影到像素坐标上,实现「点云着色」或「障碍物融合」。

3.1.1 世界坐标系(World Coordinate System)

这是全局参考系。你可以把它想象成「上帝视角」。通常我们以车辆启动位置为原点,或者用GPS/IMU定义。它用 (Xw, Yw, Zw) 表示。

  • 作用:统一所有传感器的位置信息
  • 特点:固定不动,不随车辆移动而改变
  • 我在项目中遇到过:有一次标定结果总是不对,排查了半天才发现是世界坐标系的原点定义跟IMU不一致。从那以后,我每次都会先确认「原点在哪」。

3.1.2 相机坐标系(Camera Coordinate System)

以相机光心为原点,Z轴指向镜头前方,X轴向右,Y轴向下。这是相机「看到」的世界。

  • 表示:(Xc, Yc, Zc)
  • 注意:相机坐标系是右手系,但Y轴方向跟常规右手系相反

3.1.3 图像坐标系(Image Coordinate System)

这是相机内部的物理成像平面。原点在图像中心,单位是毫米。用 (x, y) 表示。

  • 关系:通过小孔成像模型,从相机坐标系投影得到
  • 公式:x = f * Xc / Zc,y = f * Yc / Zc

3.1.4 像素坐标系(Pixel Coordinate System)

这就是我们最终看到的图像。原点在左上角,u轴向右,v轴向下。单位是像素。

  • 转换:从图像坐标系到像素坐标系,需要知道像素尺寸和主点偏移
  • 公式:u = x / dx + u0,v = y / dy + v0

3.1.5 激光雷达坐标系(LiDAR Coordinate System)

以激光雷达中心为原点,通常X轴指向车辆前方,Y轴向左,Z轴向上。这是点云数据的「母语」。

  • 表示:(Xl, Yl, Zl)
  • 特点:点云数据直接就是三维坐标,不需要投影

我的小技巧:在代码里,我习惯用变量名后缀来区分坐标系。比如 pts_world、pts_cam、pts_lidar。这样一眼就能看出数据在哪个坐标系下,避免搞混。

3.2 坐标系变换的数学本质

坐标系变换,说白了就是「旋转+平移」。你想想看,把一个点从激光雷达坐标系挪到世界坐标系,无非就是先转个角度,再挪个位置。

3.2.1 刚体变换(Rigid Transformation)

刚体变换不改变物体的形状和大小。它由两部分组成:

  • 旋转矩阵 R:3x3的正交矩阵,描述旋转
  • 平移向量 t:3x1的向量,描述平移

数学表达式:P' = R * P + t

其中 P 是原始坐标系下的点,P' 是目标坐标系下的点。

避坑指南:我曾经在项目中直接用旋转矩阵的逆矩阵做反向变换,结果点云全飞了。后来才意识到,旋转矩阵的逆等于它的转置(因为它是正交矩阵),但平移向量不能直接取负。正确的反向变换是:P = RT * (P' - t)。

3.2.2 欧拉角(Euler Angles)

欧拉角是描述旋转的另一种方式。它用三个角度表示:绕X轴旋转(roll)、绕Y轴旋转(pitch)、绕Z轴旋转(yaw)。

角度 符号 描述 自动驾驶中的常见含义
横滚角 roll (φ) 绕X轴旋转 车辆侧倾
俯仰角 pitch (θ) 绕Y轴旋转 车辆抬头/低头
偏航角 yaw (ψ) 绕Z轴旋转 车辆转向

欧拉角转旋转矩阵的公式(以ZYX顺序为例):

R = Rz(yaw) * Ry(pitch) * Rx(roll)

其中:
Rz(yaw) = [[cosψ, -sinψ, 0],
           [sinψ,  cosψ, 0],
           [0,      0,    1]]

Ry(pitch) = [[cosθ,  0, sinθ],
             [0,     1, 0],
             [-sinθ, 0, cosθ]]

Rx(roll) = [[1, 0,    0],
            [0, cosφ, -sinφ],
            [0, sinφ,  cosφ]]

重要提醒:欧拉角有「万向锁」问题。当pitch接近±90°时,roll和yaw会失去一个自由度。我个人建议在代码内部统一用四元数或旋转矩阵,只在可视化或调试时用欧拉角。

3.3 从激光雷达到像素:完整的变换链路

现在我们把所有知识串起来。假设你有一个激光雷达点 Pl,想把它投影到像素坐标 (u, v)。

  1. 激光雷达 → 世界坐标系:Pw = Rl2w * Pl + tl2w
  2. 世界坐标系 → 相机坐标系:Pc = Rw2c * Pw + tw2c
  3. 相机坐标系 → 图像坐标系:x = f * Xc / Zc,y = f * Yc / Zc
  4. 图像坐标系 → 像素坐标系:u = x / dx + u0,v = y / dy + v0

实际项目中,我们通常把前两步合并成一个外参矩阵,后两步合并成内参矩阵。这样只需要一次矩阵乘法:

# 伪代码示例
def project_lidar_to_pixel(pt_lidar, extrinsic, intrinsic):
    # pt_lidar: [x, y, z, 1] 齐次坐标
    # extrinsic: 4x4 外参矩阵 (激光雷达到相机)
    # intrinsic: 3x3 内参矩阵
    
    # 变换到相机坐标系
    pt_cam = extrinsic @ pt_lidar
    
    # 投影到像素
    u = intrinsic[0,0] * pt_cam[0] / pt_cam[2] + intrinsic[0,2]
    v = intrinsic[1,1] * pt_cam[1] / pt_cam[2] + intrinsic[1,2]
    
    return u, v

经验之谈:写代码时一定要检查Zc是否为正。如果Zc为负,说明点在相机后方,投影结果没有意义。我曾经因为这个bug,把车后面的点云投影到了图像前方,调试了整整一个下午。

3.4 本章知识体系

下面这张图展示了五个坐标系之间的变换关系,以及刚体变换和欧拉角在其中的作用。你可以把它当作一个「导航图」,随时回来查阅。

坐标系变换知识体系 世界坐标系 (Xw, Yw, Zw) 相机坐标系 (Xc, Yc, Zc) 图像坐标系 (x, y) 单位mm 像素坐标系 (u, v) 单位pixel 激光雷达坐标系 (Xl, Yl, Zl) 外参R,t 外参R,t 小孔成像 像素尺寸+主点 刚体变换 P' = R * P + t 旋转矩阵R + 平移向量t 欧拉角 roll, pitch, yaw 绕X/Y/Z轴旋转角度

这张图里,你从激光雷达坐标系出发,经过外参变换到世界坐标系,再经过另一组外参到相机坐标系,然后通过内参投影到图像和像素坐标系。刚体变换和欧拉角就是连接这些坐标系的「桥梁」。

再次提醒:不同传感器厂商对坐标轴的定义可能不同。比如有的激光雷达Z轴向上,有的Z轴向前。拿到传感器后,第一件事就是确认它的坐标系定义。我吃过这个亏,不想你再踩坑。

好了,坐标系和变换就讲到这里。记住一句话:所有传感器融合的问题,最终都是坐标系变换的问题。把这个基础打牢,后面的联合标定就是水到渠成的事。

推荐练习:找一组真实的标定数据(网上有很多开源数据集),自己写代码实现从激光雷达到像素的投影。把点云投影到图像上,看看颜色对不对得上。这是检验你理解是否到位的最好方法。

可可精品资料库