ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

nuScenes数据集实战:从devkit到3D目标检测的完整指南

nuScenes数据集实战:从devkit到3D目标检测的完整指南 简介面向自动驾驶研究者和算法工程师这份源码包聚焦nuScenes数据集的深度讲解与动手实践。内容围绕数据集下载、开发工具包安装以及利用专用代码库对场景、样本、三维标注框、传感器标定等核心对象进行可视化与分析能够帮助读者快速建立对大规模多传感器数据集的整体认知并掌握基础工程操作方法。资源共五个文件包含说明文档、网页演示、配置文件与代码工程配置等压缩包仅八KB体积精简属于配套代码与文档而非原始数据。其中重点展示了数据集中各类实体场景、样本、样本数据、标注、实例、类别、属性、可见度、传感器、校准传感器、车辆姿态、日志和地图的调用与展示方法适合正在入门自动驾驶感知或需要解析nuScenes数据结构的开发者参考。目前已有118人学习下载。 入行自动驾驶感知这几年我先后碰过 KITTI、Waymo、nuScenes真正让我静下心来反复读源码的是 nuScenes 数据集。这套数据的传感器配置、标注体系、任务覆盖都太贴近工业落地了——6 个环视相机、5 个毫米波雷达、1 个激光雷达还带高精地图和跨模态标注很多论文里聊的 BEV、多模态融合、轨迹预测最终都要落到这套数据上。这篇文章不打算复述官方文档我按自己实际跑代码的顺序把数据集组织、devkit 接口、可视化、训练数据流和踩坑记录串起来讲一遍适合正在做 3D 目标检测、BEV 感知或者轨迹预测的同学参考。1. 先搞清楚你下载的几百G文件到底是什么1.1 为什么 nuScenes 能成为多模态感知的默认基准如果你想跑一个多模态融合模型KITTI 数据量太小Waymo 又不完全开放nuScenes 几乎是当前最合适的公共基准。它提供了 1000 个场景每个场景约 20 秒采样频率 2Hz标注了 40 万帧图像和 130 万个 3D 目标框。采集地点覆盖波士顿和新加坡道路类型、天气、光照、交通密度差异很大模型在这个数据集上不容易过拟合到单一环境。更关键的是传感器配置。6 个相机覆盖 360 度环视1 个 32 线激光雷达5 个毫米波雷达分布在车身四周。这种“视觉为主、雷达补盲、激光做精确几何”的传感器组合和当前很多量产车的感知硬件方案非常接近。所以你会发现最近两年关于 BEV 感知、Occupancy Network、端到端驾驶的论文几乎都在 nuScenes 上做实验。1.2 目录结构与 v1.0-mini 的选择逻辑下载解压之后你会看到这样的目录结构nuscenes/ ├── v1.0-mini/ # 元数据 JSON 表 ├── maps/ # 高精地图栅格图 ├── samples/ # 关键帧数据 │ ├── CAM_FRONT/ │ ├── CAM_FRONT_LEFT/ │ ├── LIDAR_TOP/ │ ├── RADAR_FRONT/ │ └── ... ├── sweeps/ # 非关键帧数据 ├── lidarseg/ # 点云语义分割标签可选 └── nuScenes-map-expansion/ # 地图矢量数据samples是采样频率 2Hz 的关键帧sweeps是相邻时刻的补帧。模型训练时主要用samples但做时序预测或多帧检测时sweeps里的历史帧同样重要。如果你只是想先跑通代码我建议直接下载 v1.0-mini它包含 10 个场景解压后大概 20GB 左右足够验证代码逻辑。全量 trainval 解压后超过 300GB如果空间不够或者只是想复现一个小实验没必要一上来就下全量。1.3 元数据 JSON 表之间的外键关系很多人第一次接触 nuScenes 代码时最懵的不是点云处理而是那些 JSON 表之间的 token 关系。简单说整个数据集的核心是一个叫sample的实体它表示某个时刻所有传感器采集到的“一帧数据”。scene是连续采样的场景集合一个scene包含多个sample。另外几个关键表sample_data每个传感器在某一时刻的实际文件路径和位姿引用ego_pose自车在世界坐标系下的位置和朝向calibrated_sensor传感器相对于自车坐标系的安装位姿annotation3D 目标框标注包括类别、尺寸、朝向、速度等用代码加载一个 sample 后你会看到类似这样的字段sample nusc.get(sample, sample_token) print(sample.keys()) # dict_keys([token, timestamp, scene_token, next, prev, # data, anns, metadata])data字段里存的是所有传感器的sample_datatokenanns字段是所有 3D 标注的 token。理解了这种由 token 相互引用的结构后面遍历数据、取标注、做可视化都会顺手很多。2. devkit代码的正确打开方式2.1 环境安装与版本搭配nuScenes 官方提供了一个 Python 工具包叫nuscenes-devkit安装非常直接pip install nuscenes-devkit但我强烈建议你在安装前先确认 Python 版本。nuscenes-devkit 对 Python 3.7~3.10 支持得比较好到 3.11 之后某些依赖尤其是pyquaternion可能会遇到编译问题。另外如果你后面要用 mmdetection3d 这类框架它内部也会依赖 nuscenes-devkit两个库的版本需要搭配。我踩过的一个典型坑是mmdetection3d 的某个旧版本只兼容特定版本的 devkit升级 devkit 后NuScenes类初始化时多了一个verbose参数导致调用报错。所以建议按你的训练框架文档来锁定 devkit 版本而不是一股脑装最新版。2.2 NuScenes 类的懒加载机制初始化 NuScenes 类的代码很简单from nuscenes import NuScenes nusc NuScenes( versionv1.0-mini, dataroot/data/nuscenes, verboseTrue )verboseTrue时初始化过程会在控制台打印每个表加载了多少条记录。这里要注意NuScenes 初始化只是把各 JSON 表加载进内存并不会直接读取图像和点云文件。所以即使你的磁盘空间比较大也不用担心内存直接爆炸。真正的图像和点云数据是在调用nusc.get(sample_data, token)或者渲染函数时才按需读取的。这种懒加载设计对多模态数据集来说很合理也意味着你可以放心地用同一份初始化代码遍历几千个 sample。2.3 一条数据链路的遍历方法我经常需要遍历某一个 sample 的所有传感器数据并取到对应的点云、图像、标定参数和自车位姿。这里有一个非常高频的写法sample nusc.get(sample, sample_token) for sensor in [LIDAR_TOP, CAM_FRONT, CAM_BACK, ...]: sd_token sample[data][sensor] sd nusc.get(sample_data, sd_token) ego_pose nusc.get(ego_pose, sd[ego_pose_token]) calib nusc.get(calibrated_sensor, sd[calibrated_sensor_token]) # sd[filename] 是相对路径sd[timestamp] 是采集时间关键点在于sample_data本身不自带位姿信息而是通过ego_pose_token和calibrated_sensor_token引用另外两张表。所以你在写多模态融合代码时一定要把这三样东西同时拿出来才能把点云转换到相机坐标系下。很多新手会把ego_pose和calibrated_sensor混为一谈其实前者是自车在地图里的姿态后者是传感器装在车上的外参两个矩阵缺一不可。3. 可视化代码实战先让点云和图像对得上3.1 官方接口渲染在写训练代码之前建立对数据质量的直观感受非常重要。nuScenes 官方提供了渲染接口一行代码就能画出一帧点云投影到图像上的结果nusc.render_pointcloud_in_image( sample_tokensample_token, pointsensor_channelLIDAR_TOP, camera_channelCAM_FRONT, render_intensityTrue, out_pathlidar_on_camera.png )这段代码会从雷达点云中随机采样一部分点根据标定参数投影到图像上用颜色区分深度。如果标定没有问题你会看到建筑边缘、车辆轮廓在图像上贴合得非常好。如果投影出现明显错位要么是标定文件有问题要么是你取错了传感器 token这个后面会提到。官方渲染接口还有一个配套的render_sample_data可以直接渲染某一传感器的完整视图包括 3D box 在图像上的投影。但说实话render_sample_data内部逻辑很重调试时反而不好用我更推荐自己写几十行绘图代码。3.2 手动 BEV 可视化做 3D 目标检测几乎绕不开 BEV 视角的可视化。从annotation里拿到的 3D box 默认是世界坐标系下的要先转换到自车坐标系再做一个简单的俯视投影。这个过程的代码逻辑如下import numpy as np from pyquaternion import Quaternion import matplotlib.pyplot as plt ann nusc.get(sample_annotation, ann_token) box nusc.get_box(ann[token]) # 转换到自车坐标系 ego_pose nusc.get(ego_pose, sample[data][LIDAR_TOP][ego_pose_token]) box.translate(-np.array(ego_pose[translation])) box.rotate(Quaternion(ego_pose[rotation]).inverse) # 取各自坐标 yaw box.orientation.yaw_pitch_roll[0] corners box.corners() # 3x8 矩阵 plt.plot(corners[0, [0,1,2,3,0]], corners[1, [0,1,2,3,0]], r-)这里有一个容易被忽略的细节box.corners()返回的 8 个角点顺序是有规律的前 4 个是底面、后 4 个是顶面而且底面 4 个点本身就是按顺时针或逆时针排列的。直接取前四个点画成矩形就能得到该目标在 BEV 下的轮廓。加上类别标签、速度箭头之后一帧 BEV 可视化基本就完成了。3.3 用可视化检查标定质量我在实际项目里还会刻意用可视化去检验标定结果是否真的可靠。具体做法是把激光雷达点云投影到多个相机图像上观察车身周围的地面点和远处物体是否对齐。重点检查两个地方地面点是否和图像中道路边缘重合如果点云“飘”在路面上方说明激光雷达的外参高程可能有偏差远处的路灯杆、交通标志牌是否出现重影如果出现明显重影说明不同传感器之间的时间同步或位姿外参有问题这类检查其实可以在 1 分钟内完成但很多人会跳过。等到模型训练完发现检测框偏了才发现数据不对那时候排查成本就高很多了。4. 训练数据流3D检测模型里被代码隐藏的坐标变换4.1 从 sample 到模型输入张量模型训练和可视化不同可视化只需要画一帧训练则要高效地把 sample 处理成张量。以单帧 3D 检测为例标准流程是原始点云 - 体素化voxelization - 稀疏卷积 - BEV 特征 - 检测头在代码层面输入模型的张量通常包括voxels体素化后的点云特征coors每个体素的空间坐标points原始点云有些模型直接吃 point-based 特征img_metas标定参数、图像尺寸、数据增强信息等如果你用 mmdetection3d数据加载器会帮你完成大部分预处理。但预处理里的坐标系变换逻辑每个做感知的人都应该理解否则调 bug 时根本无从下手。4.2 坐标系变换链一帧激光雷达点云从采集到送入模型需要经过一次全局坐标变换。具体来说雷达坐标系点云原始坐标自车坐标系通过calibrated_sensor外参转换全局坐标系通过ego_pose转换到世界坐标如果要做跨模态融合还要用相机外参把全局点云投影到图像平面对应的矩阵乘法是T_sensor_to_ego Quaternion(calib[rotation]).rotation_matrix T_sensor_to_ego np.concatenate( [np.concatenate([T_sensor_to_ego, np.array(calib[translation])[:, None]], axis1), np.array([[0, 0, 0, 1]])], axis0)ego_pose的rotation和translation也是同理再组合一次就得到从传感器到全局坐标的变换矩阵。很多框架实现里会把sensor - ego - global合成一个矩阵省去连续矩阵乘法的开销但理解时还是拆开比较好。4.3 mmdetection3d 中的 NuScenes 数据模块如果你用 mmdetection3d 训练 nuScenes 数据核心配置项是这个dataset_type NuScenesDataset data_root data/nuscenes/ ann_file data/nuscenes/nuscenes_infos_train.pklnuscenes_infos_train.pkl是提前准备好的一份清单里面存了每个 sample 的关键路径、标注、位姿等。训练循环会根据这份 pkl 文件按 batch 去读点云和图像。生成这份 pkl 的脚本是tools/create_data.py nuscenes但你可以先用 mini 数据集测试整个流程再切回全量数据。这里给一个建议如果你的显存有限可以把voxel_size调大一点比如从默认的[0.1, 0.1, 0.2]调整为[0.2, 0.2, 0.4]模型精度会有一点损失但训练速度会有明显提升。先跑通流程再回来优化精度这是比较稳妥的路子。5. 亲手跑完一遍之后值得记住的几个坑5.1 磁盘与版本两个最容易被低估的问题第一个坑是磁盘空间。很多人以为下载完成解压就好结果发现samples、sweeps、lidarseg加起来体积超过预期。lidarseg是点云语义分割标签可能一个场景就占几十 GB。如果你只是做 3D 检测这个目录可以先不下载。全量数据尽量用外置固态盘加载机械硬盘在读取几百 KB 小文件时会明显拖慢训练和可视化速度。第二个坑是版本。nuscenes-devkit的接口并不完全向后兼容。我遇到过render_pointcloud_in_image在旧版本里不叫这个名字、NuScenes初始化时没有verbose参数的情况。如果你从 GitHub 上拉了一个两年前的代码库直接跑大概率会报错。我的处理方式是用项目自带的requirements.txt锁定所有依赖没有的话先把 devkit 固定在某一个明确版本README 里一般会注明。5.2 box 参数里的中心点陷阱3D 标注的annotation中每个 box 有center、size和orientation三个关键字段。新手最容易踩的坑是center 是物体几何中心的坐标而不是底面中心的坐标。在 KITTI 里很多标注习惯用底部中心在 nuScenes 里center 是三维中心包含高度信息。所以你在做高度估算或可视化时要记得把 z 分量当作物体中心而不是地面位置。另外size的格式是[width, length, height]注意不是常见的[length, width, height]这两个顺序在代码里很容易搞反。orientation以四元数存储转换成 yaw 角时方向取决于你用的是yaw_pitch_roll还是yaw_pitch_roll的[0]索引。不同工具的约定可能不同尤其当你要把 nuScenes 的标注迁移到其他模型时一定要先确认 yaw 的定义。5.3 时间戳与 CAN bus 数据缺失nuScenes 提供了一些车辆的 CAN bus 数据包括速度、转向角等这些信息对轨迹预测和时序模型很有用。但实际使用时会发现这部分数据并不是每个sample都有的。有些场景的CAN bus数据缺失或者时间戳与当前帧对不齐。代码里处理时先检查sample的metadata字段里有没有speed信息缺失时要么用前后帧插值要么把该样本从训练集剔除。另外要注意雷达和相机的时间戳虽然有同步但并不是绝对精确到微秒。如果你在做多模态融合尤其是要计算目标在相机图像中的运动速度时间戳误差会导致速度估计偏大或偏小。如果模型最终要输出速度建议在数据预处理阶段对速度标注做平滑或者直接对速度做回归时引入时间一致性损失。5.4 小技巧先用 mini 数据集把代码跑通再切全量这个建议非常重要。整个 nuScenes 全量数据集的预处理时间非常长有人会直接在全量数据上做可视化、验证代码结果每次改 bug 都要重新加载大量文件效率极低。我的习惯是第一步用 v1.0-mini 把 devkit 的加载、可视化、数据保存脚本全部跑通第二步生成 mini 数据集的nuscenes_infos_mini.pkl用它调试训练框架第三步确认模型能正常收敛到某个 loss 值后再切换到全量数据训练这个过程看着多花了一点时间但实际上能省下大量反复等待磁盘 IO 和预处理的时间。如果你的服务器有多块卡也可以用 mini 数据在多卡上试试分布式训练是否配置正确这一步在正式训练前非常有用。最后分享一个我自己的习惯每次拿到新数据集我都会先写一个 50 行左右的脚本把数据集的类别分布、目标尺寸分布、每帧目标数量统计出来存成一张统计表。这一步能帮你快速发现数据里的不平衡问题比如某个类别样本特别少、某些场景目标数量极端稀疏。在 nuScenes 上做 3D 检测类别不平衡是非常常见的现象提前有个底后续加数据增强或类别权重时就不会手忙脚乱。本文还有配套的精品资源点击获取
返回列表