第1章 坐标系与空间变换:多传感器融合的数学基石

各位同学,我是你们这门课的主讲。今天咱们聊点实在的——坐标系与空间变换。

说实话,我刚入行那会儿,觉得坐标系这东西太简单了,不就是x、y、z三个轴嘛。直到有一次,我负责的L4级自动驾驶项目在路测时,激光雷达检测到的障碍物位置和摄像头看到的完全对不上。排查了整整两天,最后发现是IMU安装时偏了0.5度,导致整个坐标变换链全错了。嗯,从那以后,我再也不敢小看坐标系了。

1.1 为什么坐标系这么重要?

你想想看,一辆自动驾驶车上,至少装着:

  • 1个GPS/IMU组合导航(告诉你车在哪)
  • 1个前向激光雷达(看前方障碍物)
  • 4个角雷达(看侧后方)
  • 6-8个摄像头(看车道线、红绿灯、行人)

每个传感器都有自己的"小世界"——也就是它的坐标系。激光雷达说"前方5米有个障碍物",摄像头说"图像右下角有个物体"。这两个信息怎么对应起来?

核心思想:所有传感器数据,最终都要统一到同一个坐标系下,才能做融合。这就是坐标系变换存在的意义。

1.2 四大坐标系,一个都不能少

我个人习惯把自动驾驶中的坐标系分成四层:

坐标系 原点 用途
世界坐标系 地球上的某个固定点(如GPS原点) 全局定位、路径规划
车辆坐标系 车辆质心或后轴中心 车辆运动控制、车身状态
传感器坐标系 传感器安装位置 原始数据采集
图像/像素坐标系 图像左上角或中心 视觉感知结果

举个例子:我在做多传感器标定时,经常遇到一个问题——激光雷达装在车顶,摄像头装在前保险杠。它们看到的同一个物体,坐标值完全不同。这时候就需要把激光雷达的点云从"激光雷达坐标系"变换到"车辆坐标系",再把摄像头检测结果从"像素坐标系"变换到"车辆坐标系",最后才能做数据关联。

1.3 刚体变换:旋转与平移

说白了,坐标系变换就是两件事:旋转和平移。

旋转:坐标系绕某个轴转了角度。比如车辆转弯时,车辆坐标系相对于世界坐标系就在旋转。

平移:坐标系的原点移动了位置。比如传感器装在车顶,它的原点就比车辆坐标系原点高了一截。

旋转可以用旋转矩阵R表示,平移用平移向量t表示。一个3D空间中的点P,从坐标系A变换到坐标系B,公式就是:

P_B = R * P_A + t

这里R是3x3的旋转矩阵,t是3x1的平移向量。

我的经验:旋转矩阵有9个元素,但实际自由度只有3个(绕x、y、z轴的旋转角)。所以实际工程中,我们常用欧拉角或四元数来表示旋转,避免冗余。我个人更推荐四元数,因为它没有万向锁问题。

1.4 齐次坐标与变换矩阵

刚才的公式P_B = R * P_A + t,每次都要写两个部分,有点麻烦。有没有更优雅的方式?

有——齐次坐标。

我们把3D点P = (x, y, z)扩展成4D齐次坐标P_h = (x, y, z, 1)。然后旋转和平移就可以合并成一个4x4的变换矩阵T:

P_B_h = T * P_A_h

其中 T = [R  t]
          [0  1]

这个T矩阵,就是多传感器融合中最核心的数学工具。你想想看,激光雷达到车辆坐标系的变换是一个T1,车辆坐标系到世界坐标系的变换是T2,那么激光雷达的点云直接到世界坐标系,就是T2 * T1。

避坑指南:我曾经在项目中犯过一个低级错误——把变换矩阵的乘法顺序搞反了。记住:从传感器到世界,变换矩阵是从右往左乘的。先乘传感器到车辆的T1,再乘车辆到世界的T2。

1.5 知识体系总览

下面这张图,是我自己总结的坐标系变换知识体系。你可以把它当作本章的"地图":

坐标系与空间变换知识体系 世界坐标系 车辆坐标系 传感器坐标系 像素坐标系 T_world2vehicle T_vehicle2sensor 投影矩阵 核心变换方法 旋转 旋转矩阵 R (3x3) 欧拉角 / 四元数 平移 平移向量 t (3x1) Δx, Δy, Δz 齐次坐标 4D坐标 (x,y,z,1) 变换矩阵 T (4x4) 工程应用场景 传感器标定 多源数据融合 目标跟踪 仿真场景构建

1.6 实战:用Python实现坐标变换

光说不练假把式。下面是我写的一个简单的坐标变换工具函数,你可以在仿真脚本中直接复用:

import numpy as np

def build_transform_matrix(rotation_matrix, translation_vector):
    """
    构建4x4齐次变换矩阵
    
    参数:
        rotation_matrix: 3x3旋转矩阵
        translation_vector: 3x1平移向量
    
    返回:
        4x4变换矩阵 T
    """
    T = np.eye(4)
    T[:3, :3] = rotation_matrix
    T[:3, 3] = translation_vector.flatten()
    return T

def transform_point(point_3d, T):
    """
    将3D点从源坐标系变换到目标坐标系
    
    参数:
        point_3d: 3x1或(3,)的3D点坐标
        T: 4x4变换矩阵
    
    返回:
        变换后的3D点坐标 (3,)
    """
    # 转为齐次坐标
    point_h = np.append(point_3d, 1.0)
    # 变换
    transformed_h = T @ point_h
    # 转回3D坐标
    return transformed_h[:3] / transformed_h[3]

# 示例:将激光雷达坐标系下的点变换到车辆坐标系
# 假设激光雷达安装在车顶,向前平移2米,向上平移1.5米
R_lidar2vehicle = np.eye(3)  # 假设安装角度无旋转
t_lidar2vehicle = np.array([2.0, 0.0, 1.5])

T_lidar2vehicle = build_transform_matrix(R_lidar2vehicle, t_lidar2vehicle)

# 激光雷达检测到前方5米处有一个障碍物
point_lidar = np.array([5.0, 0.0, 0.0])
point_vehicle = transform_point(point_lidar, T_lidar2vehicle)

print(f"激光雷达坐标系下的点: {point_lidar}")
print(f"车辆坐标系下的点: {point_vehicle}")
# 输出: [7.0, 0.0, 1.5]  — 向前7米,高1.5米

我的建议:在实际项目中,不要每次都手动构建变换矩阵。我习惯把所有的变换矩阵预先计算好,存成一个字典。比如:

transforms = {
    'lidar2vehicle': T_lidar2vehicle,
    'vehicle2world': T_vehicle2world,
    'camera2vehicle': T_camera2vehicle,
    'lidar2world': T_vehicle2world @ T_lidar2vehicle
}

这样在仿真循环中直接查表,效率高很多。

1.7 常见坑点与避坑指南

警告:以下是我踩过的坑,你千万别再踩了。

  1. 旋转矩阵的正交性检查:我曾经从标定文件里读到一个旋转矩阵,直接拿来用,结果变换出来的点全歪了。后来发现那个矩阵不是正交矩阵(R * R^T ≠ I)。记住:真正的旋转矩阵必须是正交的,行列式为+1。
  2. 左手系 vs 右手系:不同传感器可能使用不同的坐标系约定。比如ROS用的是右手系(x向前,y向左,z向上),但有些相机坐标系是z向前。我在做仿真时,每次都要先确认坐标系的手性。
  3. 平移向量的单位:激光雷达用毫米,GPS用米,IMU用厘米...这种单位不一致的问题,我至少遇到过3次。建议在仿真脚本一开始就统一单位。

好了,这一章的内容就到这里。坐标系变换是后面所有章节的基础,你花时间把它吃透,后面的传感器标定、数据融合、仿真场景构建都会轻松很多。

可可精品资料库