KITTI数据集中的坐标变换
一、概述
KITTI数据集是自动驾驶领域最知名的数据集之一,可用于立体图像、光流估计、三维检测、三维跟踪等方面的研究。KITTI数据采集平台装配有 2个灰度摄像机、2个彩色摄像机、1个Velodyne 64线3D激光雷达、4个光学镜头以及1个GPS/IMU导航系统。
由于存在多个传感器,获得的图像和点云分别处于不同的坐标系,因此当需要同时利用图像和点云时,就涉及到了 坐标转换。
二、传感器坐标系约定
KITTI数据集中各传感器的坐标系定义如下:
| 传感器 | 坐标系方向 |
|---|---|
| 摄像机(Camera) | x — 向右,y — 向下,z — 向前 |
| 激光雷达(Velodyne/LiDAR) | x — 向前,y — 向左,z — 向上 |
| GPS/IMU | x — 向前,y — 向左,z — 向上 |
摄像机索引(xx ∈ {0, 1, 2, 3}):
- 0 — 左侧灰度相机(参考相机)
- 1 — 右侧灰度相机
- 2 — 左侧彩色相机
- 3 — 右侧彩色相机
相机 0 号(参考相机) 是所有坐标变换的中间桥梁。所有相机中心均对齐,位于同一 x/y 平面上。
三、标定文件解析
KITTI 数据集为每个数据序列提供了标定文件,主要包括以下三个文件:
1. calib_cam_to_cam.txt — 相机到相机的标定
各参数含义如下:
| 参数 | 维度 | 含义 |
|---|---|---|
P_rect_xx |
3×4 | 矫正后的投影矩阵(内参矩阵) |
R_rect_xx |
3×3 | 矫正旋转矩阵(使图像平面共面) |
S_rect_xx |
1×2 | 矫正后的图像尺寸 |
T_xx |
3×1 | 从相机0到相机xx的平移矢量(外参) |
R_xx |
3×3 | 从相机0到相机xx的旋转矩阵(外参) |
K_xx |
3×3 | 矫正前摄像机xx的校准矩阵 |
D_xx |
1×5 | 矫正前摄像头xx的畸变向量 |
S_xx |
1×2 | 矫正前的图像尺寸 |
当使用同步和矫正后的数据集时,只有带 rect 下标的变量(P_rect_xx、R_rect_xx 等)是实际使用的。
2. calib_velo_to_cam.txt — 激光雷达到相机的标定
包含 Tr_velo_to_cam 矩阵(3×4),由旋转矩阵 R(3×3)和平移向量 T(3×1)组成:
Tr_velo_to_cam = [R | T]
该矩阵用于将激光雷达坐标系中的点变换到参考相机(相机0)坐标系中。
3. calib_imu_to_velo.txt — IMU到激光雷达的标定
描述 GPS/IMU 导航坐标系到激光雷达坐标系的旋转和平移矩阵。
四、核心坐标变换流程
将激光雷达点云投影到图像上的完整流程可分为 三步:
点云 (Velodyne)
↓ Tr_velo_to_cam (V2C)
参考相机坐标系 (Camera 0)
↓ R0_rect (矫正矩阵)
矫正相机坐标系
↓ P_rect (投影矩阵)
图像像素坐标
关键变换矩阵
| 矩阵 | 含义 | 维度 | 来源 |
|---|---|---|---|
Tr_velo_to_cam |
激光雷达到参考相机的刚体变换(V2C) | 3×4 | calib_velo_to_cam.txt |
R0_rect |
参考相机的矫正旋转矩阵 | 3×3 | calib_cam_to_cam.txt |
P_rect_i |
第 i 个相机的矫正后投影矩阵 | 3×4 | calib_cam_to_cam.txt |
注意:R0_rect 是 3×3 矩阵,使用时需扩展为 4×4 齐次矩阵(右下角补 1,其余补 0)。Tr_velo_to_cam 是 3×4 矩阵(R|t),同样需扩展为 4×4。所有矩阵按 行主序(row-major) 存储。
完整公式
将 Velodyne 坐标系中的点 x 投影到 左侧彩色图像(相机2) 中的点 y:
y = P_rect_2 × R0_rect × Tr_velo_to_cam × x
投影到 右侧彩色图像(相机3):
y = P_rect_3 × R0_rect × Tr_velo_to_cam × x
逐步详解
步骤1:Velodyne → 参考相机坐标系(Camera 0)
def project_velo_to_ref(self, pts_3d_velo):
pts_3d_velo = self.cart2hom(pts_3d_velo) # 齐次化,n×4
return np.dot(pts_3d_velo, np.transpose(self.V2C)) # 左乘变换矩阵
其中 V2C = Tr_velo_to_cam。
步骤2:参考相机坐标系 → 矫正相机坐标系
def project_ref_to_rect(self, pts_3d_ref):
''' Input and Output are nx3 points '''
return np.transpose(np.dot(self.R0, np.transpose(pts_3d_ref)))
步骤3:矫正相机坐标系 → 图像像素坐标
def project_rect_to_image(self, pts_3d_rect):
''' Input: nx3 points in rect camera coord.
Output: nx2 points in image2 coord. '''
pts_3d_rect = self.cart2hom(pts_3d_rect) # 齐次化
pts_2d = np.dot(pts_3d_rect, np.transpose(self.P)) # n×3
pts_2d[:, 0] /= pts_2d[:, 2] # u = x / z 归一化
pts_2d[:, 1] /= pts_2d[:, 2] # v = y / z 归一化
return pts_2d[:, 0:2]
五、3D目标框坐标变换
5.1 相机 → LiDAR 变换
将相机坐标系中的 3D 边界框转换到 LiDAR 坐标系:
- 计算 3D 边界框角点(在相机坐标系下,坐标系在盒子底部中心)
- 绕 y 轴旋转
rotation_y角度 - 平移 到质心坐标
- 投影到 LiDAR 坐标系:
- a. 逆矫正:
R0_rect的逆矩阵 - b. 逆刚体变换:
Tr_velo_to_cam的逆矩阵
- a. 逆矫正:
5.2 LiDAR → 相机变换
将 LiDAR 坐标系中的 3D 边界框转换到相机坐标系:
- 质心投影:通过
Tr_velo_to_cam和R0_rect将质心从 LiDAR 变换到相机坐标系 - 航向角变换:LiDAR 中绕 z 轴的偏航角对应相机中绕 -y 轴的旋转
- 计算角点、旋转、平移
5.3 相机 → 图像变换
将 3D 边界框投影到 2D 图像平面:
- 使用投影矩阵 P 将 3D 角点从相机坐标系投影到图像平面
- 用 z 坐标归一化得到像素坐标 (u, v)
六、数据读取示例
读取点云(.bin 文件)
import numpy as np
def read_lidar_bin(file_path):
"""读取 KITTI .bin 点云文件"""
points = np.fromfile(file_path, dtype=np.float32).reshape(-1, 4)
# 每行: [x, y, z, reflectance]
return points
读取标定文件
import numpy as np
def read_calib_file(filepath):
"""读取 KITTI 标定文件(calib_xxx.txt)"""
data = {}
with open(filepath, 'r') as f:
for line in f.readlines():
line = line.strip()
if len(line) == 0 or line.startswith('#'):
continue
key, val = line.split(':', 1)
try:
data[key] = np.array([float(x) for x in val.split()])
except ValueError:
pass
return data
七、总结
KITTI 数据集的坐标变换核心是 三步流水线:
Velodyne → 参考相机(Cam0) → 矫正坐标系 → 图像像素
×V2C ×R0_rect ×P_rect_i
其中:
Tr_velo_to_cam(V2C):激光雷达 → 参考相机的外参(旋转 + 平移)R0_rect:参考相机的立体矫正旋转矩阵P_rect_i:第 i 个相机的投影矩阵(内参)
弄清楚这套变换流程对于理解基于 KITTI 的 3D 目标检测算法(如 PointPillars、VoxelNet、PV-RCNN 等)至关重要,同时也是将算法迁移到自有数据集时不可避免的步骤。
参考文献:
- Geiger et al., Vision meets Robotics: The KITTI Dataset, IJRR 2013
- KITTI 官方网站:http://www.cvlibs.net/datasets/kitti/
- KITTI 3D 目标检测基准:http://www.cvlibs.net/datasets/kitti/eval_object.php?obj_benchmark=3d