1. nuScenes数据集简介与安装指南

nuScenes是自动驾驶领域最受欢迎的公开数据集之一,由Motional(前身为nuTonomy)团队于2019年发布。这个数据集采集自波士顿和新加坡的复杂城市道路场景,包含1000个场景,每个场景时长约20秒。数据集最吸引人的特点是其多模态传感器配置:6个摄像头(覆盖360°视野)、1个激光雷达、5个毫米波雷达,以及完整的IMU和GPS数据。

我第一次接触这个数据集时,被它的规模震撼到了——完整版约400GB,包含140万张图像、39万帧激光雷达扫描数据。不过别担心,官方提供了mini版本(约4GB),非常适合快速验证想法。数据集的核心价值在于其精细的3D标注,包括23类物体的3D边界框、属性和可见度信息,这在同类数据集中非常罕见。

安装过程其实很简单,只需两步:

pip install nuscenes-devkit

然后下载数据集(建议从官网注册后获取mini版本)。解压后你会看到这样的目录结构:

/data/sets/nuscenes
    ├── samples       # 关键帧传感器数据
    ├── sweeps        # 非关键帧传感器数据  
    ├── maps          # 高精地图(PNG+JSON)
    └── v1.0-mini     # 元数据与标注(JSON)

遇到过一个小坑:如果自定义数据路径,需要修改devkit源码中的默认路径设置。建议初学者先用官方推荐路径,等熟悉了再调整。

2. 数据集结构深度解析

2.1 核心数据表关系

nuScenes的数据组织像一个小型数据库,所有信息通过token(唯一ID)关联。主要表格包括:

  • scene:场景单元(如"雨天十字路口左转")
  • sample:场景中的时间采样点(每0.5秒一帧)
  • sample_data:具体传感器数据(如图像、点云文件路径)
  • sample_annotation:3D物体标注

用个生活化的比喻:如果把scene比作一部电影,sample就是电影中的关键帧,sample_data是每帧的画面和声音,sample_annotation则是字幕里标注的人物信息。

2.2 传感器数据详解

数据集包含三类传感器:

  1. 摄像头:6个(前/后/左前/右前/左后/右后),1600×900分辨率
  2. 激光雷达:1个32线雷达(车顶),每秒20次扫描
  3. 毫米波雷达:5个(前/左前/右前/左后/右后),每秒13次扫描

特别要注意的是数据存储方式:

  • samples/文件夹存放关键帧数据
  • sweeps/存放中间帧数据(无标注)

实际使用时有个技巧:如果只做目标检测,可以只下载samples数据;若要做时序分析,才需要完整的sweeps数据。

3. 多传感器数据可视化实战

3.1 基础可视化方法

devkit自带的NuScenesExplorer类提供了开箱即用的可视化工具。最常用的三个方法:

from nuscenes.nuscenes import NuScenes
nusc = NuScenes(version='v1.0-mini', dataroot='/data/sets/nuscenes')

# 渲染单帧传感器数据
nusc.render_sample_data('37091c75b9704e0daa829ba56dfa0906')  # 传入sample_data token

# 渲染3D标注框
nusc.render_annotation('ef63a697930c4b20a6b9791f423351da')

# 渲染整个场景的追踪轨迹
nusc.render_instance('9cba9cd8af85487fb010652c90d845b5')

实测发现一个小技巧:在Jupyter Notebook中使用%matplotlib notebook可以获得交互式可视化窗口,方便旋转查看3D点云。

3.2 多传感器同步可视化

要实现多传感器数据叠加显示(如相机图像+雷达点云),需要处理坐标系转换。devkit已经封装好了相关方法:

from nuscenes.utils.data_classes import LidarPointCloud
import matplotlib.pyplot as plt

# 获取样本数据
sample = nusc.sample[10]
cam_token = sample['data']['CAM_FRONT']
lidar_token = sample['data']['LIDAR_TOP']

# 将激光雷达点云投影到图像
points = LidarPointCloud.from_file(nusc.get_sample_data(lidar_token)[0])
img = Image.open(nusc.get_sample_data(cam_token)[0])

# 可视化
plt.figure(figsize=(12,6))
plt.imshow(img)
points.render_height(plt.gca())  # 在图像上渲染点云高度信息

这里有个常见问题:点云在图像上位置不准?通常是没考虑传感器外参。devkit的get_sample_data()方法会自动完成坐标系转换,比自己手动计算更可靠。

4. 高级技巧与性能优化

4.1 高效数据加载方案

处理完整数据集时,I/O容易成为瓶颈。我的经验是:

  1. 预加载元数据:初始化时设置verbose=False可加速加载
  2. 使用生成器:批量处理样本而非单帧处理
  3. 缓存常用数据:如校准参数、地图等
# 批量处理示例
for scene in nusc.scene:
    first_sample_token = scene['first_sample_token']
    while first_sample_token:
        sample = nusc.get('sample', first_sample_token)
        process_sample(sample)  # 自定义处理函数
        first_sample_token = sample['next']

4.2 自定义可视化技巧

官方可视化有时不够灵活,我们可以用pyviz或open3d实现更复杂的渲染:

from nuscenes.utils import visualization as viz
import open3d as o3d

# 创建带标注的3D场景
fig, ax = viz.visualize_sample(nusc, sample_token='ca9a282c9e77460f8360f564131a8af5')
plt.show()

# 使用Open3D交互式查看点云
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(points.points[:3,:].T)
o3d.visualization.draw_geometries([pcd])

遇到过的一个坑:open3d默认坐标系与nuScenes不同(y轴向上 vs z轴向上),需要做坐标变换。

5. 实际项目中的应用建议

在真实项目中,我通常会这样使用nuScenes:

  1. 算法原型阶段:用mini版快速验证idea
  2. 模型训练:使用完整版的trainval split(约700场景)
  3. 测试评估:用test split(约150场景)提交到官方评测服务器

对于计算资源有限的团队,建议:

  • 优先使用关键帧数据(samples)
  • 从单一模态开始(如仅用摄像头或仅用激光雷达)
  • 利用官方提供的baseline代码(如CenterPoint、BEVFusion)

最后分享一个实用脚本——批量导出所有场景的鸟瞰图:

from nuscenes.utils.geometry_utils import view_points

def export_bev(scene_token, output_dir):
    scene = nusc.get('scene', scene_token)
    sample_token = scene['first_sample_token']
    
    while sample_token:
        sample = nusc.get('sample', sample_token)
        lidar_data = nusc.get_sample_data(sample['data']['LIDAR_TOP'])[0]
        
        # 转换为BEV坐标
        points = LidarPointCloud.from_file(lidar_data)
        points = points.points[:3,:]  # 取xyz
        points[2,:] = 0  # 忽略高度
        
        # 绘制并保存
        plt.scatter(points[0,:], points[1,:], s=1)
        plt.savefig(f"{output_dir}/{sample_token}.png")
        plt.clf()
        
        sample_token = sample['next']

更多推荐