3. 坐标系与投影:从3D世界到2D图像
做BEV感知,说白了就是要把多个摄像头看到的2D图像,统一映射到车顶俯瞰的3D空间里。这中间最核心的一步,就是搞清楚坐标系怎么转、投影怎么做。我刚开始接触这块时,也被各种矩阵绕得头晕。今天咱们就把这事彻底捋清楚。
3.1 四大坐标系,一个都不能少
在自动驾驶感知里,我们主要跟四个坐标系打交道。你想想看,一辆车在路上跑,摄像头装在车上,图像存在像素里——每个环节都有自己的“语言”。
- 车辆坐标系:以车为中心。通常原点在后轴中心,X轴朝前,Y轴朝左,Z轴朝上。这是BEV感知的“世界原点”。
- 相机坐标系:以摄像头为中心。原点在相机光心,Z轴沿光轴朝前,X轴朝右,Y轴朝下。嗯,这里要注意,相机坐标系的Y轴是向下的,跟车辆坐标系不一样。
- 图像坐标系:在成像平面上。原点在图像中心,x轴朝右,y轴朝下。单位是毫米或米。
- 像素坐标系:就是我们看到的图片。原点在左上角,u轴朝右,v轴朝下。单位是像素。
核心关系:车辆→相机→图像→像素。每一步都是一个变换矩阵。
3.2 内外参矩阵:相机的“身份证”
每个摄像头都有两套参数:内参和外参。我习惯把它们理解为“相机自己的脾气”和“相机在车上的位置”。
3.2.1 内参矩阵(K)
内参描述的是相机本身的特性。说白了,就是光线怎么通过镜头落到传感器上。
K = [fx, 0, cx,
0, fy, cy,
0, 0, 1]
- fx, fy:焦距,单位是像素。我遇到过一些同学把焦距单位搞混,其实fx = 物理焦距 / 像素尺寸。
- cx, cy:主点偏移,通常是图像中心。但实际生产中,镜头安装会有偏差,所以需要标定。
我的经验:内参一般不会变,除非你换了镜头或者动了CMOS。所以标定一次,存成文件,每次启动时加载就行。
3.2.2 外参矩阵([R|t])
外参描述的是相机在车辆坐标系下的位姿。旋转矩阵R和平移向量t,共同决定了“相机坐标系”到“车辆坐标系”的变换。
P_vehicle = R * P_camera + t
这里有个坑:外参是随时间变化的。车在颠簸,摄像头支架可能松动,甚至碰撞后位置会偏移。我曾经在一个项目中,因为车辆过了一个大坑,右侧摄像头外参变了2度,结果BEV拼接出现明显错位。从那以后,我坚持每次上线前做一次外参校验。
3.3 从3D到2D:投影的完整链路
现在我们把所有步骤串起来。假设车辆前方有一个3D点,它最终是怎么变成图像上一个像素的?
- 世界点 → 车辆坐标系:如果点是用GPS/IMU给的,需要先转到车辆坐标系。
- 车辆坐标系 → 相机坐标系:用外参矩阵[R|t]变换。
- 相机坐标系 → 图像坐标系:用内参矩阵K做透视投影。
- 图像坐标系 → 像素坐标系:加上主点偏移和缩放。
完整公式长这样:
z * [u, v, 1]^T = K * [R|t] * [X, Y, Z, 1]^T
其中z是相机坐标系下的深度值。为什么要有z?因为透视投影会丢失深度信息,z就是那个被“除”掉的分量。
避坑指南:我曾经在写代码时,忘了把齐次坐标的最后一维归一化,结果投影出来的点全跑到图像外面去了。检查了半天才发现是忘了除以z。记住:投影后一定要做归一化!
3.4 知识体系总览
下面这张图,是我自己总结的坐标系与投影的完整链路。你可以把它当作一个“地图”,每次做投影时对照着看。
3.5 实战:写一个投影函数
理论说完了,咱们直接上代码。这是我项目中常用的一个投影函数,把车辆坐标系下的3D点投影到像素坐标。
import numpy as np
def project_vehicle_to_pixel(points_3d, K, R, t):
"""
将车辆坐标系下的3D点投影到像素坐标系
参数:
points_3d: (N, 3) 车辆坐标系下的点
K: (3, 3) 内参矩阵
R: (3, 3) 旋转矩阵
t: (3, 1) 平移向量
返回:
pixels: (N, 2) 像素坐标
"""
# 1. 车辆 → 相机
points_cam = (R @ points_3d.T + t).T # (N, 3)
# 2. 透视投影
x = points_cam[:, 0] / points_cam[:, 2]
y = points_cam[:, 1] / points_cam[:, 2]
# 3. 内参映射到像素
u = K[0, 0] * x + K[0, 2]
v = K[1, 1] * y + K[1, 2]
pixels = np.stack([u, v], axis=1)
return pixels
个人习惯:我一般会在投影前先过滤掉Z <= 0的点,因为这些点在相机后面,投影出来也没意义。另外,如果做BEV,我习惯把投影后的像素坐标再转成BEV网格坐标,这一步其实就是逆透视映射(IPM)。
3.6 避坑总结
| 常见问题 | 原因 | 解决办法 |
|---|---|---|
| 投影点跑到图像外面 | 深度Z为负或太小 | 检查外参方向,过滤Z<=0的点 |
| BEV拼接错位 | 外参不准或松动 | 定期标定,加装防松支架 |
| 图像扭曲变形 | 内参标定不准确 | 用棋盘格重新标定,至少20张图 |
| 坐标轴方向搞反 | 不同坐标系定义不一致 | 统一用右手系,写注释标注方向 |
嗯,坐标系与投影这部分,说白了就是“矩阵乘法+归一化”。但真正做工程时,细节决定成败。我见过太多同学在仿真里跑得飞起,一上车就崩——多半是坐标系没对齐。记住:先验证,再集成。拿一个已知3D点,手动算一遍像素坐标,跟代码输出对比,这一步能省你半天调试时间。
可可精品资料库