1. KITTI数据集简介与下载指南

KITTI数据集是自动驾驶领域最著名的公开数据集之一,由德国卡尔斯鲁厄理工学院和丰田美国技术研究院联合创建。这个数据集采集自德国卡尔斯鲁厄市区的真实交通场景,包含丰富的传感器数据,特别适合用于3D目标检测算法的开发和验证。

我第一次接触KITTI数据集是在2015年做自动驾驶项目时,当时就被它丰富的数据类型和精确的标注所吸引。相比其他数据集,KITTI最大的特点是同时提供了高质量的图像数据和激光雷达点云数据,这对于研究多模态融合的3D目标检测非常有用。

数据集主要包含以下几个部分:

  • 彩色图像数据(左/右摄像头)
  • 激光雷达点云数据(Velodyne HDL-64E)
  • 校准文件(相机与激光雷达之间的坐标转换参数)
  • 标注文件(2D/3D边界框标注)

要下载数据集,首先访问KITTI官网的评估页面。这里有个小技巧:建议使用学校或公司的邮箱注册,因为有些免费邮箱可能会被拦截。下载时你会看到多个选项,对于3D目标检测任务,我们主要需要以下四个部分:

  1. Left color images (12GB)
  2. Velodyne point clouds (29GB)
  3. Camera calibration matrices (16MB)
  4. Training labels (5MB)

下载完成后,我建议按照如下目录结构组织数据:

KITTI/
├── object/
│   ├── training/
│   │   ├── calib/
│   │   ├── image_2/ 
│   │   ├── label_2/
│   │   └── velodyne/
│   └── testing/
│       ├── calib/
│       ├── image_2/
│       └── velodyne/

2. 数据格式解析与预处理

2.1 点云数据解析

KITTI的点云数据以.bin格式存储,每个文件对应一帧激光雷达扫描结果。我第一次处理这些数据时,发现直接用文本编辑器打开会显示乱码,后来才明白需要用特定的方式读取。

用Python解析点云数据的代码如下:

import numpy as np

def load_point_cloud(bin_path):
    point_cloud = np.fromfile(bin_path, dtype=np.float32).reshape(-1, 4)
    return point_cloud

每个点包含4个值:x、y、z坐标和反射强度。这里有个需要注意的地方:KITTI的坐标系定义。激光雷达坐标系的原点在设备中心,x轴向前,y轴向左,z轴向上。这与常见的相机坐标系不同,后续做坐标转换时要特别注意。

2.2 标注文件解析

标注文件是纯文本格式,每行对应一个物体,包含15个字段。以Car类别的一行为例:

Car 0.00 0 -1.58 587.01 173.33 614.12 200.12 1.65 1.67 3.64 1.53 1.56 8.34 0.57

这些字段依次表示:

  1. 类别名称
  2. 截断程度(0-1)
  3. 遮挡程度(0-3)
  4. 观察角度(弧度) 5-8: 2D边界框(xmin, ymin, xmax, ymax) 9-11: 3D尺寸(高、宽、长) 12-14: 3D位置(x,y,z) 15: 旋转角度ry

2.3 校准文件解析

校准文件包含多个相机和激光雷达之间的转换矩阵。其中最重要的是:

  • P2: 左彩色相机的投影矩阵
  • R0_rect: 旋转矫正矩阵
  • Tr_velo_to_cam: 激光雷达到相机的变换矩阵

理解这些矩阵的关系对后续的可视化和检测至关重要。我刚开始时经常混淆它们的顺序,后来总结出一个记忆技巧:先矫正(R0_rect),再转换(Tr_velo_to_cam),最后投影(P2)。

3. 点云可视化实战

3.1 基础可视化工具安装

我推荐使用Mayavi进行点云可视化,它比Matplotlib更适合处理大规模点云。安装很简单:

pip install mayavi

不过要注意,Mayavi依赖VTK,在Windows上可能需要先安装VTK的whl文件。如果遇到问题,可以尝试conda安装:

conda install -c conda-forge mayavi

3.2 单帧点云可视化

下面是一个简单的可视化代码示例:

from mayavi import mlab
import numpy as np

def visualize_point_cloud(points):
    fig = mlab.figure(bgcolor=(0,0,0), size=(800,600))
    
    # 绘制点云
    mlab.points3d(
        points[:,0], points[:,1], points[:,2],
        color=(0,1,0), mode='point',
        colormap='spectral', scale_factor=0.1
    )
    
    # 设置视角
    mlab.view(azimuth=180, elevation=70, distance=50)
    mlab.show()

这个可视化虽然简单,但已经能展现点云的基本特征。在实际项目中,我通常会添加一些增强功能:

  • 根据反射强度设置颜色
  • 添加坐标轴指示
  • 设置不同的点大小

3.3 3D标注框可视化

将标注的3D框显示在点云上能直观评估标注质量。关键步骤是计算框的8个角点:

def compute_3d_box(dim, loc, ry):
    h, w, l = dim
    x, y, z = loc
    
    # 计算相对坐标
    x_corners = [l/2, l/2, -l/2, -l/2, l/2, l/2, -l/2, -l/2]
    y_corners = [0, 0, 0, 0, -h, -h, -h, -h]
    z_corners = [w/2, -w/2, -w/2, w/2, w/2, -w/2, -w/2, w/2]
    
    # 应用旋转
    R = np.array([[np.cos(ry), 0, np.sin(ry)],
                  [0, 1, 0],
                  [-np.sin(ry), 0, np.cos(ry)]])
    
    corners = np.vstack([x_corners, y_corners, z_corners])
    corners = np.dot(R, corners).T
    
    # 平移
    corners += np.array([x, y, z])
    return corners

在Mayavi中绘制3D框时,我习惯用不同颜色区分不同类别:汽车用红色,行人用绿色,自行车用蓝色。这样一眼就能看出场景中的物体分布。

4. 高级可视化技巧

4.1 点云与图像融合可视化

将点云投影到图像上能获得更直观的感知。核心是坐标变换链:

  1. 激光雷达坐标系 → 相机坐标系
  2. 相机坐标系 → 图像平面
def project_velo_to_image(pts_3d_velo, calib):
    pts_3d_velo = np.hstack([pts_3d_velo, np.ones((pts_3d_velo.shape[0],1))])
    pts_3d_cam = np.dot(pts_3d_velo, calib.V2C.T)
    pts_3d_rect = np.dot(pts_3d_cam, calib.R0.T)
    pts_3d_rect = np.hstack([pts_3d_rect, np.ones((pts_3d_rect.shape[0],1))])
    pts_2d = np.dot(pts_3d_rect, calib.P2.T)
    pts_2d[:,0] /= pts_2d[:,2]
    pts_2d[:,1] /= pts_2d[:,2]
    return pts_2d[:,:2]

实际使用时要注意过滤掉图像外的点,否则会导致显示异常。我通常会添加一个简单的边界检查:

mask = (pts_2d[:,0] >= 0) & (pts_2d[:,0] < img_width) & \
       (pts_2d[:,1] >= 0) & (pts_2d[:,1] < img_height)
pts_2d = pts_2d[mask]

4.2 BEV鸟瞰图可视化

鸟瞰图(BEV)是自动驾驶中常用的视角,能清晰展示物体在地面上的分布。创建BEV的关键步骤:

  1. 将点云限制在感兴趣区域(ROI)
  2. 量化到2D网格
  3. 计算高度特征
def create_bev(point_cloud, x_range=(-40,40), y_range=(0,70), 
               grid_size=0.1, max_height=2.5):
    # 创建网格
    x_grid = np.arange(x_range[0], x_range[1], grid_size)
    y_grid = np.arange(y_range[0], y_range[1], grid_size)
    
    # 过滤点云
    mask = (point_cloud[:,0] >= x_range[0]) & \
           (point_cloud[:,0] < x_range[1]) & \
           (point_cloud[:,1] >= y_range[0]) & \
           (point_cloud[:,1] < y_range[1])
    pc_roi = point_cloud[mask]
    
    # 量化到网格
    x_idx = ((pc_roi[:,0] - x_range[0]) / grid_size).astype(int)
    y_idx = ((pc_roi[:,1] - y_range[0]) / grid_size).astype(int)
    
    # 计算高度特征
    height_map = np.zeros((len(y_grid), len(x_grid)))
    intensity_map = np.zeros((len(y_grid), len(x_grid)))
    
    for i in range(len(pc_roi)):
        if pc_roi[i,2] < max_height:
            height_map[y_idx[i], x_idx[i]] = max(
                height_map[y_idx[i], x_idx[i]], 
                pc_roi[i,2]
            )
            intensity_map[y_idx[i], x_idx[i]] = pc_roi[i,3]
    
    return height_map, intensity_map

在可视化BEV时,我习惯用热图表示高度,用灰度图表示反射强度,这样能同时利用两种信息。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐