1. 坐标系基础:世界坐标系、车体坐标系、激光雷达坐标系定义与转换关系

做激光雷达标定,说白了就是搞清楚「我在哪,车在哪,雷达又在哪」这三个问题。

我刚开始接触这个领域时,觉得坐标系不就是几个箭头嘛,有什么好讲的?结果第一次做实车标定,雷达装歪了5度,点云投影到地图上直接偏了半条车道。嗯,从那以后我再也不敢小看坐标系了。

这一节,咱们就把三个核心坐标系掰开揉碎讲清楚。你想想看,搞懂了它们之间的转换关系,标定这件事就成功了一半。

1.1 世界坐标系:一切测量的基准

世界坐标系,也叫全局坐标系。它是整个场景的「绝对参考系」。

在实际工程中,我们通常用 WCS(World Coordinate System) 来表示。它的原点一般选在某个固定位置,比如停车场的一个墙角、路边的某个标志杆,或者更常见的——GNSS 基站的位置。

世界坐标系的特点:

  • 原点固定不动,不随车辆移动
  • 通常采用右手坐标系:X轴指向东,Y轴指向北,Z轴指向天
  • 单位一般是米(m)
  • 用于描述地图、路标、障碍物的绝对位置

我记得有一次做园区自动驾驶项目,客户要求把激光雷达检测到的障碍物投影到高精地图上。结果发现点云位置总是偏了30厘米。查了半天,原来是世界坐标系的原点定义不一致——地图用的是基站坐标,而我们默认用了UTM投影的东北天坐标系。说白了,就是「基准」没对齐。

我的个人习惯: 在项目初期,先和地图、定位团队确认好世界坐标系的定义。最好写一个文档,把原点位置、轴向、单位都写清楚。这一步省不了,否则后面全是坑。

1.2 车体坐标系:车辆自身的参考系

车体坐标系,也叫车辆坐标系,英文是 VCS(Vehicle Coordinate System) 或 Body Frame。

它的原点通常选在车辆后轴中心在地面的投影点。为什么选这里?因为后轴中心是车辆运动学模型的核心点,阿克曼转向模型就是绕着后轴中心转的。

车体坐标系的定义方式各家略有不同,但主流标准是这样的:

轴 方向 说明
X轴 车辆前进方向 指向车头
Y轴 车辆左侧方向 垂直于X轴,指向驾驶员左侧
Z轴 车辆上方方向 垂直于地面,指向天空

这里要注意一个容易搞混的点:有些车厂用的是「前-右-上」坐标系,也就是Y轴指向右侧。我曾在项目中遇到过这个问题——供应商给的标定参数是按「前-左-上」算的,但我们自己的算法用的是「前-右-上」,结果转换出来点云全反了。排查了整整两天才找到原因。

避坑指南: 我曾经因为车体坐标系定义不一致,导致标定结果在实车上完全不能用。建议在项目一开始就统一约定:使用ISO 8855标准(前-左-上),或者至少写清楚用的是哪种约定。别指望「大家都默认一样」。

1.3 激光雷达坐标系:传感器的本地视角

激光雷达坐标系,简称 LCS(Lidar Coordinate System)。每个激光雷达都有自己的坐标系,原点在雷达的物理中心(通常是激光发射器的位置)。

不同型号的雷达,坐标系定义可能完全不同:

  • 机械旋转式雷达(如Velodyne、Hesai):通常Z轴向上,X轴指向雷达前方(与线束0度方向对齐)
  • 固态雷达(如Livox):坐标系定义更灵活,取决于安装方式
  • MEMS雷达:一般遵循厂家提供的坐标系定义

举个例子,Velodyne HDL-64E的坐标系是这样的:

# Velodyne HDL-64E 坐标系定义
# 原点:雷达底部中心
# X轴:指向雷达前方(0度方位角方向)
# Y轴:指向雷达左侧(90度方位角方向)
# Z轴:垂直向上

# 一个点云数据示例(x, y, z, intensity)
point = [1.23, 0.45, -0.12, 128]  # 单位:米

我个人习惯在拿到一个新雷达时,第一件事就是画一个坐标系示意图贴在墙上。你想想看,如果连雷达自己的坐标系都没搞明白,后面的标定工作就是空中楼阁。

1.4 坐标系转换:从雷达到车体再到世界

好了,三个坐标系都定义清楚了。接下来就是核心问题:怎么把一个点从雷达坐标系转换到世界坐标系?

转换过程分两步:

  1. LCS → VCS:通过外参标定,把雷达点云转换到车体坐标系
  2. VCS → WCS:通过车辆位姿(来自IMU/GNSS/里程计),把车体坐标转换到世界坐标系

数学上,这就是一个刚体变换:旋转 + 平移。

import numpy as np

def transform_point(point_lidar, R_l2v, t_l2v, R_v2w, t_v2w):
    """
    将激光雷达坐标系下的点转换到世界坐标系
    
    参数:
        point_lidar: 雷达坐标系下的点 [x, y, z]
        R_l2v: 雷达到车体的旋转矩阵 (3x3)
        t_l2v: 雷达到车体的平移向量 (3x1)
        R_v2w: 车体到世界的旋转矩阵 (3x3)
        t_v2w: 车体到世界的平移向量 (3x1)
    
    返回:
        point_world: 世界坐标系下的点 [x, y, z]
    """
    # 第一步:雷达 → 车体
    point_vehicle = R_l2v @ point_lidar + t_l2v.flatten()
    
    # 第二步:车体 → 世界
    point_world = R_v2w @ point_vehicle + t_v2w.flatten()
    
    return point_world

# 举个实际例子
# 假设雷达检测到一个点,在雷达坐标系下是 (1.0, 0.0, -0.5)
p_lidar = np.array([1.0, 0.0, -0.5])

# 外参:雷达装在车顶,向右偏0.3米,向前0.1米,向上1.5米
R_l2v = np.eye(3)  # 假设安装角度无偏转
t_l2v = np.array([0.1, -0.3, 1.5])  # 注意Y轴方向

# 车辆位姿:车头朝北,位置在 (100, 200, 0)
R_v2w = np.eye(3)  # 假设车辆朝向与坐标系对齐
t_v2w = np.array([100.0, 200.0, 0.0])

p_world = transform_point(p_lidar, R_l2v, t_l2v, R_v2w, t_v2w)
print(f"世界坐标:{p_world}")
# 输出:世界坐标:[101.1 199.7 1.0]

我的经验: 写转换代码时,一定要先验证旋转矩阵的正交性。我见过太多因为旋转矩阵不满足 R^T R = I 导致的bug。加一行 assert np.allclose(R.T @ R, np.eye(3)),能省很多调试时间。

1.5 知识体系总览

为了让你更直观地理解这三个坐标系的关系,我画了一张图:

坐标系转换关系总览 世界坐标系 WCS 原点:固定位置 用于:地图、定位 车体坐标系 VCS 原点:后轴中心 用于:车辆运动 激光雷达坐标系 LCS 原点:雷达中心 用于:点云数据 外参标定 R_l2v, t_l2v 车辆位姿 R_v2w, t_v2w 转换流程:LCS → VCS → WCS 每个转换包含:旋转矩阵 R (3x3) + 平移向量 t (3x1) 核心公式 P_world = R_v2w · (R_l2v · P_lidar + t_l2v) + t_v2w

这张图把整个转换链路画得很清楚。你想想看,从雷达采集到原始点云,到最终在地图上显示障碍物位置,中间就经过这两步变换。每一步的精度都会直接影响最终结果。

总结一下核心要点:

  • 三个坐标系各司其职:WCS管全局,VCS管车体,LCS管传感器
  • 转换就是旋转+平移,数学上很简单,但工程上容易出错
  • 坐标系定义的一致性比精度更重要——定义错了,精度再高也没用
  • 写代码时一定要加验证逻辑,比如旋转矩阵的正交性检查

好了,坐标系基础就讲到这里。搞懂了这些,后面的标定工作就有了坚实的根基。记住我的一句话:标定之前,先画坐标系。这能帮你避免至少一半的坑。

可可精品资料库