1. 初识nuScenes:自动驾驶研究的“瑞士军刀”

如果你刚开始接触自动驾驶相关的算法研究,比如目标检测、轨迹预测或者多传感器融合,那你大概率会听到一个名字:nuScenes。我第一次接触这个数据集的时候,感觉就像拿到了一本厚厚的、内容极其丰富的百科全书,但一开始却不知道从哪一页开始读起。它不像MNIST或者CIFAR-10那样,下载下来就是一张张规整的图片。nuScenes是一个大型的、多模态的自动驾驶数据集,里面包含了激光雷达点云、雷达数据、六个不同角度的摄像头图像,还有精确的3D物体标注、地图信息等等,数据量非常大,结构也相对复杂。

简单来说,你可以把nuScenes想象成一个自动驾驶汽车的“行车记录仪”全集,但它比行车记录仪专业得多。它记录了车辆在波士顿和新加坡城市道路中行驶的1000个场景,每个场景大约20秒。这20秒可不是连续的录像,而是以2Hz的频率(也就是每0.5秒)采集的“关键帧”快照。每一帧快照,都同步记录了当时车上所有传感器的数据,并对视野内的车辆、行人、自行车、交通锥等物体进行了精确的3D边界框标注。

对于开发者来说,nuScenes的价值在于它的“全”和“真”。它提供了学术界和工业界公认的、用于衡量算法性能的基准(Benchmark)。如果你想验证自己的3D目标检测算法好不好,或者你的多模态融合方案有没有效,在nuScenes上跑一跑,看看指标,心里就有底了。接下来,我就带你从零开始,一步步拆解这个数据集,从怎么把数据加载到Python环境里,到如何看懂里面各种让人眼花缭乱的数据结构,最后再到把数据可视化出来,亲眼看看算法要处理的世界到底是什么样子。整个过程,我会尽量用我踩过坑后总结的经验,让你少走弯路。

2. 环境搭建与数据准备:走好第一步

万事开头难,和nuScenes打交道的第一步,就是搭建好工作环境并把数据下载下来。这一步如果没做好,后面所有的代码都可能跑不起来。我强烈建议你使用Python 3.8或以上的版本,并且创建一个独立的虚拟环境,这样能避免各种包版本冲突的“玄学”问题。

首先,你需要安装nuScenes官方提供的开发工具包,也就是 nuscenes-devkit。打开你的终端,用pip安装就行:

pip install nuscenes-devkit

这个工具包是核心,它提供了加载数据、访问各种标注、进行可视化的所有API。安装过程通常很顺利。接下来就是下载数据了,这是个体力活。nuScenes数据集有几个版本,对于刚入门的研究者或开发者,我强烈建议从 “v1.0-mini” 这个迷你版开始。它包含了完整数据集的10个场景,数据量小(大约4GB),下载快,但数据结构、API的使用方式和完整版完全一致,非常适合用来学习和调试代码。

你可以去nuScenes的官方网站注册并下载。下载后,你会得到一个压缩包,解压后的文件夹结构大概是这样的:

/path/to/your/nuscenes_data/
├── v1.0-mini
│   ├── maps/          # 高清语义地图文件
│   ├── samples/       # 传感器数据(图像、点云等)
│   ├── sweeps/        # 中间帧的传感器数据(非关键帧)
│   └── v1.0-mini      # 核心的标注和元数据JSON文件

这里最重要的就是那个 v1.0-mini 文件(注意,它和文件夹同名,但这里指的是那个没有扩展名的文件)。它其实是一个包含了所有标注、索引和关系的大型JSON文件,是nuscenes-devkit读取数据的“地图”。准备好数据路径后,我们就可以在Python里初始化数据集对象了,这是所有操作的起点。

3. 核心数据结构深度解析:读懂nuScenes的“语言”

加载数据后,你会发现nuScenes不是简单的一堆图片和点云文件,它通过一套精心设计的数据结构把所有元素串联了起来。理解这些核心概念,是你能否灵活使用这个数据集的关键。官方文档里列了一堆名词,刚开始看确实头疼,我用一个更生活化的比喻帮你理解。

想象一下nuScenes在记录一部“公路电影”。log 就是拍摄这部电影所用的摄影车和行车日志,记录了在哪个城市、哪条路、什么天气下拍摄的。scene 就是电影中的一个连续片段,比如“主角驾车通过一个繁忙十字路口的20秒”。这是我们处理的基本单元。

在这个20秒的片段(scene)里,导演不会用连续录像,而是每半秒拍一张“定妆照”。这张定妆照就是 sample。每一张定妆照(sample)都记录了那一刻所有机位的情况:主摄像机(CAM_FRONT)、车顶的激光扫描仪(LIDAR_TOP)等等。这些机位拍下来的原始数据文件(比如一张JPG图片、一个.pcd点云文件),就是 sample_data

现在,照片里拍到了很多演员和道具,比如一辆特定的卡车、一个行走的行人。同一个演员在整个电影片段里会出现在多张定妆照中,这个演员本身就是一个 instance(实例)。而演员的类型,比如“行人”、“卡车”,就是他的 category(类别)。在某一刻,这个演员是“正在移动”还是“静止站立”,这个状态就是他的 attribute(属性)。

最后,在某一特定的定妆照(sample)里,我们需要用3D框精确标出某个演员(instance)的位置和姿态,这个具体的3D框标注,就是 sample_annotation。它包含了位置、大小、旋转角度,以及指向它属于哪个实例(instance)、哪个类别(category)、哪个属性(attribute)的链接。

所有这些元素,都不是通过文件名来关联的,而是通过一个全局唯一的字符串——token。Token就像每个元素的身份证号。在sample的数据里,你会找到一个data字典,里面CAM_FRONT键对应的值就是一个token,通过这个token,你就能找到对应的sample_data(那张前摄像头图片)。这种通过token钩稽的关系,是nuScenes API设计的精髓。

4. 数据加载实战:与数据集对话

理论说再多,不如动手写一行代码。让我们打开Python,真正开始和数据集对话。首先,初始化NuScenes类,这是通往数据世界的大门。

from nuscenes import NuScenes

# 请将 dataroot 替换成你解压数据的实际路径
nusc = NuScenes(version='v1.0-mini', dataroot='/path/to/your/nuscenes_data', verbose=True)

如果一切顺利,你会看到终端打印出数据集的基本信息,比如场景数量、样本数量等。verbose=True参数会让它多输出一些信息,方便确认加载成功。现在,我们可以开始探索了。比如,列出所有的场景:

scenes = nusc.scene
print(f"数据集共有 {len(scenes)} 个场景")
for i, scene in enumerate(scenes[:3]):  # 只看前三个
    print(f"场景 {i}: {scene['name']} - {scene['description']}")

你会看到类似“scene-0061, Parked truck, construction, intersection...”这样的输出,这是场景的名字和人工描述的文字概要,能帮你快速了解这个场景发生了什么。

让我们深入第一个场景,看看它的第一个样本(sample)。每个场景都有first_sample_tokenlast_sample_token,像书签一样标出了这个片段的起止。

first_scene = scenes[0]
first_sample_token = first_scene['first_sample_token']
first_sample = nusc.get('sample', first_sample_token)

print("第一个样本的token:", first_sample['token'])
print("该样本的时间戳:", first_sample['timestamp'])
print("该样本关联的传感器数据:", list(first_sample['data'].keys()))

first_sample['data']这个字典非常重要,它保存了当前这个时刻,所有传感器数据的token。键是传感器通道名,值就是对应sample_data的token。如果你想获取前摄像头的图片信息,就这样做:

cam_front_token = first_sample['data']['CAM_FRONT']
cam_front_data = nusc.get('sample_data', cam_front_token)
print("图片文件名:", cam_front_data['filename'])
print("图片尺寸: {}x{}".format(cam_front_data['width'], cam_front_data['height']))
print("是否为关键帧:", cam_front_data['is_key_frame'])

is_key_frame字段很重要,nuScenes的标注只提供在关键帧(key frame)上,也就是我们一直说的sample对应的帧。传感器数据(sample_data)则有关键帧和非关键帧(sweep)之分,非关键帧数据更多,用于补充信息,但没有标注。通过nusc.get方法,传入表名(如'sample', 'sample_data')和token,你就能查询到任何记录的详细信息,这是最常用的操作。

5. 可视化技巧大全:让数据“活”过来

看懂数据结构是一回事,能直观地看到数据是另一回事。nuScenes-devkit提供了强大的可视化工具,能把抽象的标注变成直观的图像,这对算法调试和理解数据分布至关重要。

5.1 渲染单张图片及其标注

这是最常用的功能。我们可以把某个样本(sample)中,某个摄像头的画面,以及画面中所有物体的2D投影框(从3D标注投影而来)画出来。

from nuscenes.utils.data_classes import Box
from PIL import Image
import matplotlib.pyplot as plt

# 选择第一个样本和前摄像头
sample_token = first_sample['token']
camera_channel = 'CAM_FRONT'

# 使用render_sample_data方法,它会自动获取标注并投影到图像上
nusc.render_sample_data(cam_front_token, nsweeps=0, out_path='./demo_cam_front.jpg')

nsweeps=0表示只渲染当前关键帧的数据。执行后,会在当前目录生成一张图片。你会看到图像上叠加了很多彩色的2D框,不同类别用不同颜色表示,框上还有类别名称。这是检查3D标注投影是否正确、标注质量如何的最快方法。

5.2 在点云中查看3D标注

对于激光雷达点云,我们同样可以渲染。点云的可视化能让你真切感受到3D空间中的物体分布。

# 获取激光雷达数据的token
lidar_token = first_sample['data']['LIDAR_TOP']
# 渲染点云和3D框
nusc.render_sample_data(lidar_token, nsweeps=0, out_path='./demo_lidar.jpg')

生成的图片是从上往下的俯视图。彩色点是激光雷达点云,不同的物体被不同颜色的3D立方体框住。你可以清晰地看到汽车的形状、行人的位置。有时候,点云稀疏或者物体被遮挡,标注框里点很少,这正好体现了真实世界的挑战。

5.3 多传感器融合可视化

nuScenes最强大的可视化之一是 render_sample 函数。它能把一个样本(sample)的所有传感器数据(通常是摄像头图像)和标注,以仪表盘的形式拼接在一起展示。

nusc.render_sample(sample_token, out_path='./demo_sample.jpg')

这张生成的图会被分成多个子图,六个摄像头的图像围成一圈,中间可能还有激光雷达的俯视图。所有检测到的物体,会在其出现的所有摄像头视图里,用同一个颜色的2D框标出。这让你一眼就能看出一个3D物体在不同视角下的成像情况,对于理解多摄像头几何和标注一致性非常有帮助。

5.4 查看地图信息

nuScenes还提供了高清的语义地图(如车道线、人行横道、可行驶区域)。你可以把车辆轨迹和标注画在地图上,从上帝视角看整个场景。

# 获取当前样本对应的日志(log)token,从而找到地图
log_token = first_scene['log_token']
nusc.render_egoposes_on_map(log_token, out_path='./demo_map.jpg')

这张图会显示车辆在这个日志记录期间的所有行驶轨迹(ego pose),以及地图元素。这对于研究预测、规划任务非常有用,因为算法需要理解道路结构。

在实际项目中,我经常把可视化代码嵌入到训练或验证循环里,定期保存一些样本的渲染结果。这样能非常直观地看到模型在哪里出了错:是没检测到远处的小物体?是把骑自行车的人误判成了行人?还是3D框的角度预测不准?眼睛看到的问题,比单纯的数字指标更有指导意义。

6. 高级操作与性能优化

当你熟悉了基本操作后,可能会遇到一些更实际的需求。比如,数据集这么大,如何高效地遍历?如何根据自定义条件筛选数据?这里分享几个进阶技巧。

6.1 高效遍历与查询

直接遍历 nusc.sample 列表是可以的,但如果你需要根据特定条件(比如只包含卡车的样本)来查找,使用 nusc.field2token 方法会更高效。不过,更常见的模式是,我们先获取一个样本的所有标注token,然后遍历这些标注,检查其类别。

# 查找包含“卡车”的样本
truck_samples = []
for sample in nusc.sample:
    ann_tokens = sample['anns']
    for ann_token in ann_tokens:
        ann = nusc.get('sample_annotation', ann_token)
        if 'truck' in ann['category_name']:
            truck_samples.append(sample['token'])
            break  # 这个样本已经包含卡车,跳出内层循环
print(f"找到 {len(truck_samples)} 个包含卡车的样本")

6.2 理解时间序列与前后帧

nuScenes中的数据是有时间顺序的。每个sample都有prevnext字段,指向相邻时间戳的样本token(如果没有则为空字符串)。这对于做跟踪(Tracking)或者时序预测(Prediction)的研究者至关重要。你可以轻松地获取一个物体(instance)在整个场景中所有时刻的标注序列。

# 获取一个实例(例如一辆特定的车)
instance_token = 'e91afa15647c4c4994f19aeb302c7179'  # 这里需要替换成真实的token
instance = nusc.get('instance', instance_token)

# 获取它的第一个和最后一个标注
first_ann_token = instance['first_annotation_token']
last_ann_token = instance['last_annotation_token']

# 遍历这个实例的所有标注
current_token = first_ann_token
track = []
while current_token != '':
    ann = nusc.get('sample_annotation', current_token)
    track.append(ann['translation'])  # 记录位置
    current_token = ann['next']  # 通过next指针走到下一帧
    if current_token == last_ann_token:
        # 处理最后一个标注
        ann = nusc.get('sample_annotation', current_token)
        track.append(ann['translation'])
        break

这样,你就得到了这辆车在整个场景中的运动轨迹点序列 track

6.3 处理雷达与相机数据同步

nuScenes提供了传感器之间的精确标定参数(calibrated_sensor)。当你需要将雷达点投影到图像上,或者将图像上的点反投影到3D空间时,就需要用到这些参数。nuscenes-devkit中的 Box 类和 view_points 函数封装了这些复杂的坐标变换。例如,将3D标注框投影到相机图像上的核心步骤,在 render_sample_data 内部就是通过调用这些函数完成的。当你需要自定义可视化或者进行传感器融合算法开发时,深入理解 nuscenes.utils.geometry_utils 模块是必不可少的。

7. 避坑指南与最佳实践

最后,结合我自己和同事们使用nuScenes的经验,总结几个容易踩坑的地方和最佳实践,希望能帮你节省大量调试时间。

7.1 路径与版本问题

确保初始化NuScenes对象时,dataroot参数指向的文件夹里包含正确版本的文件夹(如v1.0-mini)。经常出现的错误是路径指错了层级。另外,version参数必须和文件夹名严格一致。如果你下载的是完整版v1.0-trainval,那么版本号也要写对。

7.2 理解“token”的不可预测性

Token是随机生成的字符串,每次重新标注或生成数据集都可能不同。因此,千万不要在你的代码里硬编码任何token值。你从我这篇文章里复制的token,在你的数据集里肯定对应着不同的东西。所有对数据的访问,都应该通过查询和关系链来动态获取。

7.3 关注数据集的“Split”

nuScenes官方划分了训练集(train)、验证集(val)和测试集(test)。测试集的标注是不公开的,你需要将预测结果提交到官方评测服务器才能得到分数。在迷你版中,所有数据都混在一起。但在使用完整版时,要注意区分,用训练集训练,用验证集调试和验证模型效果。

7.4 可视化时的性能考虑

render_samplerender_sample_data 函数在渲染复杂场景时可能会比较慢,尤其是渲染点云。如果你在Jupyter Notebook中循环渲染很多样本,可能会导致内存不足或卡顿。建议将渲染结果保存为图片文件,而不是在Notebook中直接内联显示大量图片。对于批量可视化需求,可以编写脚本离线生成图片。

7.5 善用官方教程与论坛

nuScenes的官方GitHub仓库里有非常详细的教程(Tutorial)Notebook,涵盖了从基础到进阶的所有操作,并且会随着工具包更新。遇到问题时,先去查阅这些教程和API文档。此外,相关的学术社区和论坛(如GitHub Issues)也是寻找答案的好地方,你遇到的问题很可能别人已经遇到并解决了。

上手nuScenes的过程,就像学习一门新的方言。开始时觉得各种tokensample_annotation绕来绕去,但一旦掌握了它的核心逻辑——即通过token链接起场景、样本、传感器数据和标注——你就会发现它设计上的巧妙与严谨。这套结构能够高效地组织起海量、多模态的自动驾驶数据。我建议你不要只停留在阅读上,而是跟着步骤,亲自下载迷你数据集,把每一段示例代码都跑一遍,甚至尝试修改代码去查询不同的信息。亲手实现一遍,这些概念才会真正变成你的工具。当你能够流畅地提取所需数据,并生成清晰的可视化结果时,你就已经为基于nuScenes进行算法开发打下了最坚实的基础。

Logo

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

更多推荐