NuScenes数据集实战:从零搭建自动驾驶数据可视化环境(附避坑指南)
当你第一次打开NuScenes数据集,面对几十GB的传感器数据、复杂的JSON标注文件和陌生的目录结构,是不是感觉无从下手?我刚开始接触这个数据集时,花了整整两天时间才把可视化环境跑通,期间踩过的坑足够写一篇技术博客。今天,我就把这些实战经验整理出来,帮你绕过那些恼人的报错,快速搭建起一个稳定、高效的数据可视化环境。
对于自动驾驶领域的研究者和开发者来说,NuScenes是目前最主流的公开数据集之一。它包含了1000个精心采集的城市驾驶场景,配备了6个摄像头、1个激光雷达、5个毫米波雷达以及IMU/GPS等完备的传感器套件。但数据丰富也意味着复杂度高——如何快速理解数据结构、如何可视化多传感器融合结果、如何为后续的模型训练做好准备,这些都是摆在面前的现实问题。
本文的目标很明确:让你在30分钟内完成从零到一的环境搭建,并能够流畅地浏览和可视化数据集中的任意场景。我会从最基础的Python环境配置讲起,涵盖nuscenes-devkit库的安装、常见依赖冲突的解决方案,一直到编写自己的可视化脚本。更重要的是,我会分享那些官方文档里没有写的“坑”,比如特定版本NumPy导致的崩溃、OpenGL渲染问题、以及内存不足时的处理技巧。
1. 环境准备与依赖安装
搭建可视化环境的第一步,是创建一个干净、隔离的Python环境。我强烈建议使用conda或venv,避免与系统已有的Python包发生冲突。下面是我验证过的环境配置方案,适用于Ubuntu 20.04/22.04和Windows WSL2。
1.1 创建并激活虚拟环境
使用conda创建环境可以更好地管理CUDA和cuDNN版本,这对于后续可能进行的深度学习任务很有帮助。
# 创建名为nuscenes_env的Python 3.8环境
conda create -n nuscenes_env python=3.8 -y
# 激活环境
conda activate nuscenes_env
如果你没有安装conda,使用venv也是不错的选择:
python3.8 -m venv nuscenes_venv
source nuscenes_venv/bin/activate # Linux/Mac
# 或
nuscenes_venv\Scripts\activate # Windows
注意:Python 3.8是目前与
nuscenes-devkit兼容性最好的版本。Python 3.9+可能会遇到一些第三方库的兼容性问题。
1.2 安装核心依赖包
nuscenes-devkit的安装看似简单,但实际上有几个隐藏的依赖项需要特别注意。以下是我推荐的安装顺序:
# 首先升级pip,确保安装过程顺利
pip install --upgrade pip
# 安装NumPy和Matplotlib的特定版本
# 这是第一个坑:最新版的NumPy可能与devkit中的某些C扩展不兼容
pip install numpy==1.21.0 matplotlib==3.5.0
# 安装Pillow用于图像处理
pip install Pillow==9.0.0
# 安装OpenCV(headless版本,避免GUI依赖)
pip install opencv-python-headless==4.5.5.64
# 最后安装nuscenes-devkit
pip install nuscenes-devkit==1.1.10
为什么需要指定这些版本?让我分享一个实际案例:有一次我直接pip install nuscenes-devkit,结果在可视化点云时频繁出现段错误。经过半天调试才发现,是NumPy 1.22.0与pyquaternion库的兼容性问题。回退到NumPy 1.21.0后问题立即消失。
1.3 验证安装结果
安装完成后,不要急着下载数据集,先运行一个简单的验证脚本确保核心功能正常:
# test_installation.py
import sys
print(f"Python版本: {sys.version}")
try:
from nuscenes import NuScenes
print("✓ nuscenes-devkit 导入成功")
except ImportError as e:
print(f"✗ nuscenes-devkit 导入失败: {e}")
try:
import numpy as np
print(f"✓ NumPy 版本: {np.__version__}")
except ImportError as e:
print(f"✗ NumPy 导入失败: {e}")
try:
import matplotlib
print(f"✓ Matplotlib 版本: {matplotlib.__version__}")
except ImportError as e:
print(f"✗ Matplotlib 导入失败: {e}")
在终端运行这个脚本,如果所有检查都通过,说明基础环境已经就绪。如果遇到任何导入错误,请根据错误信息调整相应的包版本。
2. 数据集下载与结构解析
NuScenes数据集有多个版本,对于初次接触的用户,我建议从v1.0-mini开始。这个迷你版本只有约4GB,包含了10个完整场景,足够你熟悉数据结构和可视化流程。
2.1 获取数据集访问权限与下载
首先,你需要到NuScenes官方网站注册账号并申请数据使用许可。这个过程是免费的,但需要等待审核(通常几小时内会通过)。
审核通过后,你可以选择以下两种下载方式:
方式一:官方脚本下载(推荐)
官方提供了完整的下载脚本,可以自动下载并验证数据完整性:
# 克隆nuscenes-devkit仓库
git clone https://github.com/nutonomy/nuscenes-devkit.git
# 进入脚本目录
cd nuscenes-devkit/python-sdk
# 下载迷你数据集
python download_mini.py --out_dir /path/to/your/data
方式二:手动下载
如果你遇到网络问题,可以手动下载以下必要文件:
| 文件类型 | 文件名 | 大小 | 作用 |
|---|---|---|---|
| 元数据 | v1.0-mini.tgz | ~35 MB | 包含所有标注和元数据 |
| 摄像头数据 | v1.0-mini_blobs.tgz | ~3.8 GB | 6个摄像头的图像数据 |
| 激光雷达数据 | v1.0-mini_lidar.tgz | ~130 MB | 激光雷达点云数据 |
| 地图数据 | v1.0-mini_maps.tgz | ~20 MB | 语义地图图像 |
下载完成后,解压所有文件到同一个目录,结构应该如下:
/path/to/your/data/
├── v1.0-mini/
│ ├── maps/ # 地图文件
│ │ ├── 53992ee3023e5494b90c316c183be829.png
│ │ └── ...
│ ├── samples/ # 关键帧传感器数据
│ │ ├── CAM_BACK/
│ │ ├── CAM_FRONT/
│ │ ├── LIDAR_TOP/
│ │ └── ...
│ ├── sweeps/ # 非关键帧传感器数据
│ ├── v1.0-mini/ # 元数据
│ │ ├── attribute.json
│ │ ├── calibrated_sensor.json
│ │ ├── category.json
│ │ ├── ego_pose.json
│ │ ├── instance.json
│ │ ├── log.json
│ │ ├── map.json
│ │ ├── sample_annotation.json
│ │ ├── sample_data.json
│ │ ├── sample.json
│ │ ├── scene.json
│ │ └── sensor.json
│ └── README.txt
2.2 理解核心数据结构
NuScenes采用了一种基于token的关系型数据组织方式。刚开始可能会觉得复杂,但理解几个核心概念后就会清晰很多:
- Scene(场景):一次连续的驾驶记录,约20秒。迷你数据集中有10个场景。
- Sample(样本):场景中的一帧数据,每0.5秒采集一次。一个20秒的场景包含约40个样本。
- SampleData(样本数据):单个传感器在某个样本时刻采集的具体数据,比如一张图片或一帧点云。
- SampleAnnotation(样本标注):对场景中某个物体(车辆、行人等)在特定样本时刻的标注。
- Instance(实例):同一个物体在所有样本中的标注序列。
- Token(令牌):每个数据项的唯一标识符,用于建立数据间的关联。
这种设计的好处是,你可以通过token快速查询到任何相关联的数据。比如,从一个sample的token,可以找到这个时刻所有传感器的数据,也可以找到这个时刻所有物体的标注。
3. 基础可视化:从单传感器到多模态融合
现在进入最有趣的部分——可视化。我将从最简单的单传感器可视化开始,逐步过渡到复杂的多传感器融合显示。
3.1 初始化NuScenes对象
首先,让我们加载数据集并检查基本信息:
import matplotlib.pyplot as plt
from nuscenes import NuScenes
import os
# 设置数据路径
data_root = '/path/to/your/data' # 替换为你的实际路径
version = 'v1.0-mini'
# 初始化NuScenes对象
# verbose=True会打印加载进度,第一次运行时建议开启
nusc = NuScenes(version=version, dataroot=data_root, verbose=True)
print(f"数据集版本: {version}")
print(f"场景数量: {len(nusc.scene)}")
print(f"样本数量: {len(nusc.sample)}")
print(f"标注数量: {len(nusc.sample_annotation)}")
如果一切正常,你会看到类似这样的输出:
数据集版本: v1.0-mini
场景数量: 10
样本数量: 404
标注数量: 2306
3.2 可视化单个传感器的数据
让我们从最简单的摄像头图像开始:
def visualize_camera_sample(scene_idx=0, sample_idx=0):
"""
可视化指定场景和样本的摄像头图像
参数:
scene_idx: 场景索引
sample_idx: 样本在场景中的索引
"""
# 获取场景
scene = nusc.scene[scene_idx]
print(f"场景: {scene['name']}, 描述: {scene['description']}")
# 获取场景的第一个样本token
sample_token = scene['first_sample_token']
# 遍历到指定的样本位置
current_sample = nusc.get('sample', sample_token)
for i in range(sample_idx):
if current_sample['next']:
current_sample = nusc.get('sample', current_sample['next'])
else:
print(f"警告: 场景只有{i}个样本")
break
# 获取所有摄像头数据
camera_channels = ['CAM_FRONT', 'CAM_FRONT_LEFT', 'CAM_FRONT_RIGHT',
'CAM_BACK', 'CAM_BACK_LEFT', 'CAM_BACK_RIGHT']
# 创建子图
fig, axes = plt.subplots(2, 3, figsize=(20, 12))
axes = axes.flatten()
for idx, channel in enumerate(camera_channels):
# 获取该摄像头的sample_data token
sample_data_token = current_sample['data'][channel]
# 获取sample_data元数据
sample_data = nusc.get('sample_data', sample_data_token)
# 渲染图像
nusc.render_sample_data(sample_data_token, ax=axes[idx])
axes[idx].set_title(channel, fontsize=14)
axes[idx].axis('off')
plt.tight_layout()
plt.show()
return current_sample
# 可视化第一个场景的第一个样本
sample = visualize_camera_sample(scene_idx=0, sample_idx=0)
这段代码会显示6个摄像头的图像,让你直观感受车辆周围的360度视野。注意观察图像之间的重叠区域,这对于理解多摄像头标定很重要。
3.3 激光雷达点云可视化
点云可视化比图像稍微复杂一些,因为涉及到3D坐标变换:
def visualize_lidar_with_image(sample_token, camera_channel='CAM_FRONT'):
"""
将激光雷达点云投影到摄像头图像上
参数:
sample_token: 样本token
camera_channel: 要投影到的摄像头通道
"""
# 获取样本
sample = nusc.get('sample', sample_token)
# 获取激光雷达数据
lidar_token = sample['data']['LIDAR_TOP']
lidar_data = nusc.get('sample_data', lidar_token)
# 获取摄像头数据
camera_token = sample['data'][camera_channel]
camera_data = nusc.get('sample_data', camera_token)
# 创建图形
fig, axes = plt.subplots(1, 2, figsize=(20, 8))
# 左侧:单独显示点云
ax1 = axes[0]
nusc.render_sample_data(lidar_token, ax=ax1)
ax1.set_title('激光雷达点云(俯视图)', fontsize=16)
# 右侧:点云投影到图像
ax2 = axes[1]
nusc.render_sample_data(camera_token, ax=ax2)
# 将点云投影到图像平面
points, coloring, im = nusc.explorer.map_pointcloud_to_image(
pointsensor_token=lidar_token,
camera_token=camera_token
)
# 在图像上绘制点云
ax2.scatter(points[0, :], points[1, :], c=coloring, s=5, alpha=0.8)
ax2.set_title(f'点云投影到{camera_channel}', fontsize=16)
ax2.axis('off')
plt.tight_layout()
plt.show()
# 打印一些统计信息
print(f"激光雷达点数量: {len(nusc.get('sample_data', lidar_token)['filename'])}")
print(f"摄像头图像尺寸: {im.shape if im is not None else 'N/A'}")
# 使用之前获取的sample token
visualize_lidar_with_image(sample['token'])
这个可视化非常有用,因为它展示了3D点云如何与2D图像对齐。你可以看到,远处的点比较稀疏,近处的点很密集,而且点云的颜色通常表示反射强度。
3.4 标注信息可视化
NuScenes提供了丰富的标注信息,包括3D边界框、物体类别、属性等:
def visualize_annotations(sample_token, max_annotations=10):
"""
可视化样本中的标注(3D边界框)
参数:
sample_token: 样本token
max_annotations: 最多显示的标注数量
"""
# 获取样本
sample = nusc.get('sample', sample_token)
# 获取标注token列表
annotation_tokens = sample['anns']
print(f"该样本共有 {len(annotation_tokens)} 个标注")
# 创建图形
fig, axes = plt.subplots(1, 3, figsize=(24, 8))
# 1. 在点云中显示标注
ax1 = axes[0]
lidar_token = sample['data']['LIDAR_TOP']
nusc.render_sample_data(lidar_token, ax=ax1)
# 渲染前N个标注
for i, ann_token in enumerate(annotation_tokens[:max_annotations]):
nusc.render_annotation(ann_token, ax=ax1)
ax1.set_title('点云中的3D边界框', fontsize=16)
# 2. 在摄像头图像中显示标注
ax2 = axes[1]
camera_token = sample['data']['CAM_FRONT']
nusc.render_sample_data(camera_token, ax=ax2)
for i, ann_token in enumer

&spm=1001.2101.3001.5002&articleId=153548882&d=1&t=3&u=18c8b1a7f60c4d80b372765356e6cc75)
467

被折叠的 条评论
为什么被折叠?



