第4章:数据预处理:NuScenes数据集介绍、数据加载与可视化、标注格式解析(JSON)、生成BEV视角的GT

各位同学,欢迎来到第四章。说实话,这一章是整个BEV感知的基石。你想想看,没有高质量的数据,再牛的模型也是白搭。我在做这个课程之前,踩过不少数据处理的坑,今天我把这些经验都揉碎了讲给你听。

4.1 NuScenes数据集:自动驾驶界的“ImageNet”

NuScenes数据集,全称是nuScenes autonomous driving dataset。它由nuTonomy公司(现在被Aptiv收购)发布。我个人觉得,它是目前最接近工业级标准的开源数据集之一。

为什么这么说?因为它有以下几个硬核特点:

  • 传感器配置全:6个摄像头(覆盖360°)、5个毫米波雷达、1个激光雷达、IMU/GPS。这配置,跟很多量产车已经非常接近了。
  • 场景丰富:波士顿和新加坡两个城市,白天、黑夜、雨天、晴天都有。我记得有一次我在处理新加坡雨天的数据,那个点云噪点真是让人头疼。
  • 标注精细:23个类别,40万+个标注框,还有8秒长的视频片段(每0.5秒一帧)。
  • 数据量大:1000个场景,每个场景约20秒。总数据量超过1TB。

核心数据量一览

项目 数值
场景数 1000
关键帧数 40,000+
标注框数 1,400,000+
类别数 23
传感器数 12(6相机+5雷达+1激光)

4.2 数据加载与可视化:先看看我们手里有什么牌

拿到数据集,第一件事不是训练,而是可视化。我习惯先看看数据长什么样,心里有个底。

NuScenes官方提供了一个Python库——nuscenes-devkit。安装很简单:

pip install nuscenes-devkit

加载数据的核心代码,我一般这么写:

from nuscenes.nuscenes import NuScenes

# 初始化,注意版本
nusc = NuScenes(version='v1.0-trainval', 
                dataroot='/path/to/nuscenes',
                verbose=True)

# 查看第一个场景
my_scene = nusc.scene[0]
print(f"场景名称: {my_scene['name']}")
print(f"场景描述: {my_scene['description']}")
print(f"关键帧数量: {my_scene['nbr_samples']}")

嗯,这里要注意。版本号一定要写对。v1.0-trainval是完整版,还有mini版(v1.0-mini)适合快速测试。我刚开始用的时候,就搞混过版本,结果加载了半天报错。

可视化一个关键帧,我推荐用官方自带的渲染工具:

# 获取第一个关键帧
my_sample = nusc.sample[0]

# 渲染激光雷达点云
nusc.render_sample(my_sample['token'])

# 渲染某个相机的图像
camera_token = my_sample['data']['CAM_FRONT']
nusc.render_sample_data(camera_token)

我的小技巧:可视化时,建议先看CAM_FRONT和LIDAR_TOP这两个传感器。前者给你直观的视觉感受,后者让你理解3D空间分布。两个一起看,你就能建立起“图像-点云”的对应关系。

4.3 标注格式解析:JSON里的秘密

NuScenes的标注信息,全部存在JSON文件里。说白了,就是一堆嵌套的字典和列表。但别小看它,结构设计得非常精巧。

核心的数据表(table)有这些:

  • scene:场景信息,包含场景名称、描述、关键帧token列表
  • sample:关键帧,每个关键帧包含所有传感器的时间戳和数据token
  • sample_data:传感器数据,比如某张图片、某帧点云
  • sample_annotation:标注框,包含3D框的位置、尺寸、朝向、类别
  • instance:物体实例,同一个物体在不同帧里是同一个instance

我们最关心的,当然是sample_annotation。一个典型的标注框长这样:

{
  "token": "a1b2c3d4...",
  "sample_token": "e5f6g7h8...",
  "instance_token": "i9j0k1l2...",
  "translation": [1.2, 0.5, 0.0],  # 中心点坐标 (x, y, z)
  "size": [1.8, 4.5, 1.5],         # 尺寸 (宽, 长, 高)
  "rotation": [0.0, 0.0, 0.7, 0.7], # 四元数 (w, x, y, z)
  "category_name": "vehicle.car",
  "num_lidar_pts": 125,
  "num_radar_pts": 0
}

这里有几个关键点,我当年可是吃了亏的:

  • translation:是全局坐标系下的坐标,单位是米。不是像素坐标,也不是图像坐标。
  • size:顺序是宽、长、高。宽对应车的左右,长对应车的前后。别搞反了,否则你的框会旋转90度。
  • rotation:四元数。如果你不熟悉四元数,建议先补一下。我习惯把它转成欧拉角,更直观。

避坑指南:我曾经在解析rotation时,直接用了四元数的前三个分量当欧拉角,结果画出来的框全部歪了。后来才发现,NuScenes的四元数顺序是 (w, x, y, z),而很多库默认是 (x, y, z, w)。一定要用 nusc.quaternion_to_rotation_matrix() 来转换。

4.4 生成BEV视角的GT:从3D到2D的降维打击

BEV感知的核心,就是把3D空间投影到鸟瞰图上。说白了,就是从上往下看。这样,我们就把3D检测问题,变成了2D检测问题。

生成BEV GT的步骤,我总结为三步:

  1. 确定BEV范围:比如 x 方向 [-50m, 50m],y 方向 [-50m, 50m]
  2. 确定分辨率:比如每个像素代表 0.1m,那么BEV图就是 1000x1000 像素
  3. 投影标注框:把3D框的8个角点投影到BEV平面,然后画多边形

代码实现,我一般这么写:

import numpy as np
import cv2

def generate_bev_gt(annotation, bev_range=50.0, resolution=0.1):
    """
    生成BEV视角的GT
    annotation: 单个标注框的字典
    """
    # 1. 获取3D框参数
    center = np.array(annotation['translation'])
    size = np.array(annotation['size'])  # w, l, h
    rotation = annotation['rotation']
    
    # 2. 计算8个角点(在物体坐标系下)
    w, l, h = size
    corners_3d = np.array([
        [-l/2, -w/2, -h/2],
        [ l/2, -w/2, -h/2],
        [ l/2,  w/2, -h/2],
        [-l/2,  w/2, -h/2],
        [-l/2, -w/2,  h/2],
        [ l/2, -w/2,  h/2],
        [ l/2,  w/2,  h/2],
        [-l/2,  w/2,  h/2],
    ])
    
    # 3. 旋转+平移,转到全局坐标系
    rot_matrix = nusc.quaternion_to_rotation_matrix(rotation)
    corners_global = corners_3d @ rot_matrix.T + center
    
    # 4. 只取BEV平面(x, y),忽略z
    corners_bev = corners_global[:, :2]
    
    # 5. 只保留底部的4个角点(z最小的4个点)
    # 实际上,对于BEV,我们只需要底部的矩形
    bottom_corners = corners_global[:4, :2]
    
    # 6. 坐标映射到像素
    # 注意:BEV坐标系:x向前,y向左
    # 图像坐标系:u向右,v向下
    u = (bottom_corners[:, 0] + bev_range) / resolution
    v = (bev_range - bottom_corners[:, 1]) / resolution
    
    # 7. 画多边形
    bev_img = np.zeros((int(bev_range*2/resolution), 
                        int(bev_range*2/resolution), 3), dtype=np.uint8)
    pts = np.int32(np.stack([u, v], axis=1))
    cv2.fillPoly(bev_img, [pts], (0, 255, 0))
    
    return bev_img

核心要点:BEV GT生成时,最关键的是坐标系转换。NuScenes的全局坐标系是右手系,x向前,y向左,z向上。而图像坐标系是u向右,v向下。所以y方向要取反。这个细节,我当年调试了整整一个下午才想明白。

生成后的BEV GT,你可以直接用来训练。但要注意,不同类别的物体,在BEV图上应该用不同的颜色或通道来表示。我个人习惯用多通道的one-hot编码,每个类别一个通道。

4.5 本章知识体系

为了让你更直观地理解本章的知识结构,我画了一张流程图:

第4章:数据预处理知识体系 NuScenes数据集 数据集介绍 数据加载与可视化 标注格式解析 传感器配置 场景与标注 nuscenes-devkit 可视化渲染 JSON结构 标注框解析 生成BEV视角的GT 核心:坐标系转换 + 投影 + 多边形绘制 关键:理解全局坐标系、传感器坐标系、图像坐标系之间的转换关系

这张图把本章的知识点串起来了。从NuScenes数据集出发,经过数据加载、可视化、标注解析,最终生成BEV GT。每一步都有坑,但每一步也都有收获。

好了,这一章的内容就到这里。数据预处理是BEV感知的“地基”,地基打不牢,房子盖不高。希望你能亲手跑一遍代码,把NuScenes的数据结构摸透。下一章,我们会开始搭建真正的BEV感知模型。

课后练习:

  1. 下载NuScenes mini版,加载并可视化一个场景的所有关键帧
  2. 解析一个标注框的JSON,手动计算它的8个角点在BEV平面上的坐标
  3. 生成一张包含多个物体的BEV GT图,不同类别用不同颜色

可可精品资料库