4、BEV视角下的坐标系与变换:车辆坐标系、传感器坐标系、自车坐标系到BEV网格的映射、逆透视映射(IPM)原理

各位同学,欢迎来到第四章。这一章我们聊点硬核的——坐标系与变换。

说实话,做BEV感知,坐标系就是你的“世界观”。你连车在哪、传感器在哪、目标在哪都搞不清楚,后面的融合、预测、规划全是空中楼阁。我当年刚入行时,就因为坐标系搞混,把一个障碍物的位置算偏了2米,差点让测试车撞上路肩。从那以后,我对坐标系变换就格外较真。

4.1 车辆坐标系:一切的原点

车辆坐标系,说白了就是定义“车本身”在哪。

通常我们采用ISO 8855标准:

  • X轴:指向车辆正前方
  • Y轴:指向车辆左侧(注意,有些自动驾驶系统用右手系,Y轴指向右侧,这个要小心)
  • Z轴:指向车辆正上方
  • 原点:一般放在后轴中心,或者车辆质心在地面的投影点

我个人习惯把原点放在后轴中心的地面投影上。为什么?因为车辆运动学模型(比如自行车模型)就是以后轴中心为参考的,这样后续做轨迹预测、控制时,坐标系天然对齐,省去很多麻烦。

重要提醒:不同车厂、不同数据集可能定义不同。比如nuScenes数据集,它的ego坐标系原点在车辆后轴中心,但Z轴向上,Y轴向左。而Waymo数据集,原点在车辆几何中心。你每次换数据集,第一件事就是去查它的坐标系定义文档,千万别想当然。

4.2 传感器坐标系:各说各话

车上装了那么多传感器,每个都有自己的“小算盘”。

相机坐标系:

  • 原点在相机光心
  • Z轴指向相机前方(沿着光轴)
  • X轴向右,Y轴向下(这是图像坐标系的标准,注意Y轴是向下的)

激光雷达坐标系:

  • 原点在激光雷达的旋转中心
  • X轴指向车辆前方(通常与车辆坐标系对齐)
  • Y轴向左,Z轴向上

毫米波雷达坐标系:

  • 原点在雷达天线中心
  • X轴指向雷达波束发射方向
  • Z轴向上

你想想看,这些传感器各自为政,数据格式都不一样。相机输出的是像素坐标(u, v),激光雷达输出的是三维点(x, y, z),毫米波雷达输出的是距离、角度、速度。要把它们统一到同一个“语言”下,就必须做坐标系变换。

我的经验:在做传感器标定时,我建议你先把每个传感器的外参(相对于车辆坐标系的旋转和平移)存成一个4x4的齐次变换矩阵。这样后续所有变换都可以用矩阵乘法搞定,代码写起来非常清爽。

4.3 自车坐标系到BEV网格的映射

好了,现在所有传感器数据都变换到了车辆坐标系下。接下来,我们要把它们投影到BEV网格上。

BEV网格,本质上就是一个二维的栅格地图。每个网格代表真实世界中的一个矩形区域。比如,一个200x200的网格,每个网格代表0.1米,那么它覆盖的范围就是前后各10米,左右各10米。

映射过程很简单:

  1. 确定BEV网格的范围(比如X方向:-10m到10m,Y方向:-10m到10m)
  2. 确定网格分辨率(比如0.1m/格)
  3. 对于车辆坐标系下的任意一点(x, y),计算它对应的网格索引:
# 伪代码示例
grid_x = int((x - x_min) / resolution)
grid_y = int((y - y_min) / resolution)

# 检查是否在网格范围内
if 0 <= grid_x < grid_width and 0 <= grid_y < grid_height:
    # 将该点的特征填入对应网格
    bev_grid[grid_y, grid_x] += point_feature

嗯,这里要注意:网格的索引顺序。有些实现用(row, col)表示,row对应Y方向,col对应X方向。我见过有人搞反了,结果画出来的BEV图是旋转了90度的,排查了半天才发现是索引顺序的问题。

避坑指南:我曾经在一个项目中,把网格的X轴和Y轴搞反了,导致车辆检测框全部横着走。后来花了整整一天才定位到问题——就是grid_x和grid_y赋值反了。所以,我建议你在代码里明确注释:grid_x对应车辆坐标系的X轴(前方),grid_y对应Y轴(左侧)。

4.4 逆透视映射(IPM)原理

IPM,全称Inverse Perspective Mapping,逆透视映射。这玩意儿是干嘛的?

说白了,就是把相机拍到的“近大远小”的图像,变成一张“俯视图”。你想想看,相机拍到的车道线,在图像里是两条汇聚的线,但实际地面上它们是平行的。IPM就是把这个透视效果给“掰直”了。

IPM的核心思想:

假设地面是平坦的(这个假设很重要),那么图像上的每个像素点,都可以通过相机内参和外参,反投影到地面上的一点。

数学上,这个过程是这样的:

  1. 已知相机内参矩阵K(焦距、主点)
  2. 已知相机外参(旋转R、平移t),即相机相对于车辆坐标系的位姿
  3. 对于图像上的像素点(u, v),我们想找到它对应的地面点(X, Y, 0)
# IPM映射伪代码
def ipm_mapping(u, v, K, R, t):
    # 1. 将像素坐标转为归一化相机坐标
    x_cam = (u - K[0,2]) / K[0,0]
    y_cam = (v - K[1,2]) / K[1,1]
    
    # 2. 构建相机坐标系下的射线方向
    ray_cam = np.array([x_cam, y_cam, 1.0])
    
    # 3. 将射线变换到车辆坐标系
    ray_ego = R @ ray_cam
    
    # 4. 假设地面Z=0,计算射线与地面的交点
    # 射线方程:P = t * ray_ego,其中P的Z分量为0
    # 所以 t = -t_z / ray_ego[2]
    t = -t[2] / ray_ego[2]
    point_ego = t * ray_ego + t[:2]  # 只取X和Y
    
    return point_ego[0], point_ego[1]

你看,代码其实不长。但实际工程中,坑很多。

关键问题:IPM假设地面是平坦的。但现实世界哪有那么平?一个小坡、一个减速带,都会导致IPM结果出现偏差。我做过一个项目,在停车场里用IPM做车位检测,结果因为地面有轻微坡度,映射出来的车位线全是歪的。后来我们加了一个地面高程估计模块,才把这个问题解决掉。

IPM的局限性:

  • 只适用于平坦地面
  • 距离越远,映射精度越差(因为一个像素对应的地面区域越大)
  • 相机俯仰角变化(比如车辆加速刹车时)会导致映射错误

所以,在实际的BEV感知系统中,IPM通常只用于近场(比如0-30米)的语义信息提取,比如车道线、路沿。远场的目标检测,还是得靠激光雷达或者多相机立体视觉。

4.5 总结与思考

这一章我们聊了坐标系和变换,从车辆坐标系到传感器坐标系,再到BEV网格映射,最后讲了IPM原理。

我个人觉得,坐标系变换是BEV感知中最基础也最容易出错的部分。很多同学一上来就调模型、跑网络,结果效果不好,回头一查,发现是坐标系没对齐。嗯,磨刀不误砍柴工,先把坐标系搞明白,后面的路会顺很多。

下一章,我们会聊BEV特征提取的具体方法,包括如何把多模态数据(图像、点云)编码到BEV空间。到时候你会发现,坐标系变换的知识会反复用到。

好,今天就到这里。有问题随时交流。