使用 Rerun 可视化 nuScenes 自动驾驶数据集:多传感器同步回放与 3D 标注渲染实战
【免费下载链接】rerunVisualize, query, and stream to train on multimodal robotics data.项目地址: https://gitcode.com/GitHub_Trending/re/rerun
导读
本文基于 Rerun 仓库中的官方示例 examples/python/nuscenes_dataset/README.md,完整讲解如何用 Rerun SDK 读取并可视化 nuScenes 自动驾驶数据集:在同一时间轴内同步呈现 6 路相机图像、LiDAR 与 RADAR 点云、GPS/IMU 轨迹以及 3D 目标检测框。读完本文,你将掌握Transform3D、Pinhole、EncodedImage、Points3D、Boxes3D、GeoPoints等 Rerun 核心 archetype 的组合用法,学会用 Blueprint API 按传感器数量动态生成视图布局,并了解数据自动下载与 GPS 坐标换算的实现细节。
nuScenes 数据集与示例背景
nuScenes 是一个面向自动驾驶研究的大型公开数据集。示例读取的场景数据来自自动驾驶车辆上一整套传感器组合,包括:
- 6 个相机(
CAM_FRONT、CAM_FRONT_LEFT、CAM_FRONT_RIGHT、CAM_BACK、CAM_BACK_LEFT、CAM_BACK_RIGHT); - 1 个 LiDAR(激光雷达);
- 5 个 RADAR(毫米波雷达);
- GPS 与 IMU传感器。
因此,数据集在任意时刻都同时提供车辆位姿(ego pose)、相机图像、原始传感器数据和目标检测结果。该示例在 examples/manifest.toml 中被归类在 “robotics, autonomous systems, and interfacing with sensor hardware”(机器人、自动驾驶系统与传感器硬件交互)类别下,是理解多模态传感器数据在 Rerun 中如何对齐到统一时间轴的最佳入门示例。
示例的核心入口为 examples/python/nuscenes_dataset/nuscenes_dataset/main.py,辅助模块还包括自动下载 minisplit 数据集的 download_dataset.py 和将全局坐标换算为经纬度的 export_gps.py。
环境准备与运行
安装依赖
示例依赖定义在 examples/python/nuscenes_dataset/pyproject.toml,包含matplotlib(用于点云按距离着色)、numpy、nuscenes-devkit(nuScenes 官方 Python SDK)、requests(下载数据)以及rerun-sdk。
确保满足 Rerun 要求的 Python 版本并安装最新 SDK,然后克隆仓库并安装示例依赖:
pip install --upgrade rerun-sdk # 安装最新 Rerun SDK git clone git@github.com:rerun-io/rerun.git # 克隆 Rerun 仓库 cd rerun git checkout latest # 检出与最新 SDK 版本匹配的提交安装示例所需的库:
pip install -e examples/python/nuscenes_dataset运行示例
直接执行主脚本即可运行:
python -m nuscenes_dataset # 运行示例如果想自定义行为、探索更多特性或保存结果,可通过--help查看完整的命令行参数说明:
python -m nuscenes_dataset --help除pip install -e安装出的nuscenes_dataset命令行入口(见 pyproject.toml 的[project.scripts]段)外,直接以模块方式运行的效果相同。
核心命令行参数
结合main.py 中的argparse定义,示例支持的参数如下:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
--root-dir | 路径 | examples/python/nuscenes_dataset/dataset | nuScenes 数据集的根目录 |
--scene-name | 字符串 | scene-0061 | 要可视化的场景名,形如scene-xxxx |
--dataset-version | 字符串 | v1.0-mini | 数据集版本,如v1.0-mini、v1.0-trainval |
--seconds | 浮点数 | inf | 限制只记录前 N 秒数据,CI 中常使用--seconds=5 |
此外,示例通过rr.script_add_args(parser)注入了 Rerun 脚本通用参数(定义于 rerun_py/rerun_sdk/rerun/_script_helpers.py),包括:
--headless:不弹出 GUI 窗口;--connect:连接外部 Rerun Viewer;--serve:启动 gRPC 与 Web 服务并在浏览器中打开 Web Viewer;--url:指定要连接的 Rerun URL;--save <path>:把数据保存为.rrd文件;--stdout:把数据输出到标准输出,便于管道送入 Rerun Viewer。
数据集自动下载机制
运行时会先调用ensure_scene_available()检查数据集是否就绪。当dataset-version为v1.0-mini且目标场景属于 minisplit 场景列表时,若本地无数据则自动从https://www.nuscenes.org/data/v1.0-mini.tgz下载并解压(见 download_dataset.py)。minisplit 共包含 10 个场景,覆盖多种驾驶工况,例如scene-0061(转弯)、scene-0103(城市道路直行)、scene-0796(匀速直行)、scene-0916(停车场绕圈)以及若干夜间场景。若请求的场景不在可自动下载范围内,脚本会提示并退出。
传感器标定:Pinhole 相机与传感器位姿
可视化前,需要为每个传感器初始化相机内参和空间位姿,以同时提供 3D 世界视角与相机透视视角。这通过Pinhole与Transform3D两个 archetype 实现。
先以静态(static=True)方式记录传感器相对 ego 车辆的位姿。示例将 nuScenes 的四元数从wxyz顺序转换为 Rerun 所需的xyzw顺序(np.roll(..., shift=-1)),并以ParentFromChild关系表达子坐标系到父坐标系的变换:
rr.log( f"world/ego_vehicle/{sensor_name}", rr.Transform3D( translation=calibrated_sensor["translation"], rotation=rr.Quaternion(xyzw=rotation_xyzw), relation=rr.TransformRelation.ParentFromChild, ), static=True, )再记录针孔相机内参。image_from_camera接收 3x3 相机内参矩阵,width与height对应图像分辨率(来自sample_data):
rr.log( f"world/ego_vehicle/{sensor_name}", rr.Pinhole( image_from_camera=calibrated_sensor["camera_intrinsic"], width=sample_data["width"], height=sample_data["height"], ), static=True, )从源码看,log_sensor_calibration()(main.py)仅在camera_intrinsic非空时才记录Pinhole,即该逻辑对相机与激光雷达/雷达传感器同样适用。另外在场景开头,示例还会记录rr.log("world", rr.ViewCoordinates.RIGHT_HAND_Z_UP, static=True),将世界坐标系约定为右手系、Z 轴朝上(对应自动驾驶常见的坐标约定)。
时间线:把多模态数据对齐到统一时间轴
所有后续数据在写入时都会绑定到具体时间点。Rerun 为每条日志数据分配时间戳,并挂载到timelines上。示例使用 nuScenes 的微秒时间戳,换算成秒后写入名为"timestamp"的时间线:
rr.set_time("timestamp", timestamp=sample_data["timestamp"] * 1e-6)这一行代码是整条时间轴同步的关键:由于所有传感器(相机、LiDAR、RADAR、标注)都基于各自sample_data的同一时刻调用set_time,Rerun Viewer 才能在拖动时间滑块时让六路图像、点云与 3D 框保持帧级对齐。
车辆位姿、GPS 与轨迹
车辆位姿
车辆行驶过程中位姿持续变化,因此相机与传感器的位置也要随之更新,这同样通过Transform3D完成,记录在world/ego_vehicle实体上:
rr.log( "world/ego_vehicle", rr.Transform3D( translation=ego_pose["translation"], rotation=rr.Quaternion(xyzw=rotation_xyzw), relation=rr.TransformRelation.ParentFromChild, ), )GPS 数据
GPS 坐标由场景的参考坐标加上变换(地图起始点 + 里程计)推导得出,以GeoPointsarchetype 记录:
rr.log( "world/ego_vehicle", rr.GeoPoints([[lat, long]]), )在源码实现中(main.py),每帧还会把当前经纬度追加到ego_trajectory_lat_lon,并用rr.GeoLineStrings记录完整的行驶轨迹,同时用红色点标注当前车辆位置:
rr.log( "world/ego_vehicle", rr.Transform3D( translation=ego_pose["translation"], rotation=rr.Quaternion(xyzw=rotation_xyzw), relation=rr.TransformRelation.ParentFromChild, ), rr.TransformAxes3D(10.0), # 可视化坐标轴的长度 rr.GeoPoints(lat_lon=position_lat_lon, radii=rr.Radius.ui_points(8.0), colors=0xFF0000FF), )GPS 坐标换算原理
经纬度换算逻辑位于 export_gps.py,其核心是derive_latlon()函数:
- 基于 nuScenes dev-kit 中的
export_poses.py改编; - 预先内置了 4 张地图的参考经纬度:
boston-seaport、singapore-onenorth、singapore-hollandvillage、singapore-queenstown; - 假设参考坐标与全局位姿原点均位于地图西南角;
- 取位姿平移量前两维
(x, y),计算相对参考点的方位角bearing = atan(x / y)与距离distance = sqrt(x² + y²); - 按地球半径
6.378137e6米做球面坐标推算,返回目标点经纬度。
这一换算保证了 Rerun 内置MapView能在地图上正确渲染车辆位置与轨迹。
LiDAR 点云可视化
LiDAR 数据以Points3Darchetype 记录。示例读取.bin点云文件后取前三通道(x、y、z),并按点到传感器原点的距离用turbo_r色带着色,近处偏暖、远处偏冷:
rr.log(f"world/ego_vehicle/{sensor_name}", rr.Points3D(points, colors=point_colors))对应的源码(main.py)为:
data_file_path = nusc.dataroot / sample_data["filename"] pointcloud = nuscenes.LidarPointCloud.from_file(str(data_file_path)) points = pointcloud.points[:3].T # 转置后形状为 (num_points, 3) point_distances = np.linalg.norm(points, axis=1) point_colors = cmap(norm(point_distances))着色使用matplotlib.colormaps["turbo_r"],归一化范围vmin=3.0、vmax=75.0(见文件头部定义),即 3 米以内与 75 米以外的距离都被截断到色带端点。由于历史原因(仓库 issue #4409),示例当前手动计算颜色而非直接使用 Rerun 内置的距离着色。
相机图像可视化
相机数据以编码图像形式记录,使用EncodedImagearchetype。直接传入磁盘上的图像文件路径即可,无需提前解码,Rerun 会按需加载:
rr.log(f"world/ego_vehicle/{sensor_name}", rr.EncodedImage(path=data_file_path))在源码的log_cameras()(main.py)中,对每路相机沿sample_data["next"]指针遍历该场景所有帧,逐帧设置时间戳并记录图像,直到超过max_timestamp_us或遍历结束。这保证了相机流与 LiDAR 流在时间上严格同步。
RADAR 点云可视化
RADAR 数据与 LiDAR 类似,同样以Points3D记录,并按距离着色:
rr.log(f"world/ego_vehicle/{sensor_name}", rr.Points3D(points, colors=point_colors))对应源码使用nuscenes.RadarPointCloud.from_file()读取雷达点云(main.py),处理方式与 LiDAR 一致。5 路 RADAR 各自拥有独立的实体路径(world/ego_vehicle/<RADAR_通道名>),因此可在 3D 视图中单独开关。
3D 目标检测标注(Annotations)
标注以Boxes3Darchetype 记录,包含目标位置(centers)、尺寸(sizes)与旋转(quaternions),并通过class_ids关联类别:
rr.log( "world/anns", rr.Boxes3D( sizes=sizes, centers=centers, quaternions=quaternions, class_ids=class_ids, fill_mode=rr.components.FillMode.Solid, ), )源码实现(main.py)还包含两个关键细节:
- 坐标轴约定转换:nuScenes 的尺寸为
(width, length, height),示例转换为 Rerun 的(length, width, height)以对应 x、y、z 三个轴向; - 类别管理与标注上下文:首次出现的类别名会注册进
label2id字典并分配整数 id,随后用rr.AnnotationContext(annotation_context)以static=True记录类别名到 id 的映射,Viewer 因此能按类别显示不同颜色与图例。
此外,标注的 GPS 坐标与车辆一样通过derive_latlon()换算,并作为rr.GeoPoints一并记录在world/anns实体上,使地图视图也能呈现目标位置。
用 Blueprint 动态编排默认视图布局
默认蓝图(blueprint)由 Blueprint API 程序化生成。核心思路是按传感器数量动态创建视图并排布成网格——这对相机数量随数据集变化的场景非常实用:
sensor_views = [ rrb.Spatial2DView( name=sensor_name, origin=f"world/ego_vehicle/{sensor_name}", # 将所有相机视图的图像平面距离设为 5m defaults=[rr.Pinhole.from_fields(image_plane_distance=5.0)], overrides={"world/anns": rr.Boxes3D(fill_mode="solid")}, ) for sensor_name in nuscene_sensor_names(nusc, args.scene_name) ] blueprint = rrb.Vertical( rrb.Horizontal( rrb.Spatial3DView(name="3D", origin="world"), rrb.Vertical( rrb.TextDocumentView(origin="description", name="Description"), rrb.MapView( origin="world", name="MapView", zoom=rrb.archetypes.MapZoom(18.0), background=rrb.archetypes.MapBackground(rrb.components.MapProvider.OpenStreetMap), ), row_shares=[1, 1], ), column_shares=[3, 1], ), rrb.Grid(*sensor_views), row_shares=[4, 2], )这段布局体现了 Rerun Blueprint 的容器机制:
Spatial3DView:以world为原点展示 3D 世界,包含 LiDAR/RADAR 点云、车辆位姿与 3D 标注框,并通过defaults将图像平面距离(image plane distance)统一设为 5 米,使相机画面在 3D 空间中呈现在距中心 5m 的平面上;Spatial2DView:每路相机一个 2D 视图,origin指向对应传感器实体,overrides强制world/anns使用实心填充显示 3D 框在图像上的投影;MapView:以 OpenStreetMap 为底图、缩放级别 18 渲染world上的 GPS 轨迹与车辆位置;TextDocumentView:在左上区域渲染 Markdown 格式的示例说明。
row_shares用于给垂直容器的每个子元素分配相对尺寸,水平容器对应column_shares,而Grid网格容器两者都支持。这里垂直方向 3D 视图与相机网格按 4:2 分配,水平方向 3D 视图与右侧面板按 3:1 分配。
值得注意,nuscene_sensor_names()(main.py)会遍历首帧所有相机sample_data收集通道名,并按预设顺序(CAM_FRONT_LEFT → CAM_FRONT → CAM_FRONT_RIGHT → CAM_BACK_RIGHT → CAM_BACK → CAM_BACK_LEFT)排序,使网格中六路相机按车身环绕顺序排列,模拟“绕车一周”的观察视角。
实际运行时,蓝图通过rr.script_setup(args, "rerun_example_nuscenes", default_blueprint=blueprint)注入(main.py),同时会将description实体以静态 Markdown 文档形式记录。若应用已有激活的蓝图,新蓝图需用户点击 “reset blueprint” 才会生效;script_setup还会根据命令行参数自动决定数据去向——spawn 本地 Viewer、连接外部 Viewer、保存.rrd文件或启动 Web 服务(见 rerun_py/rerun_sdk/rerun/_script_helpers.py)。
运行效果与常见调参建议
运行python -m nuscenes_dataset后,Viewer 将呈现:
- 3D 视图:ego 车辆坐标系、LiDAR 点云(按距离着色)、RADAR 点云、带类别颜色的 3D 标注框、车辆行驶轨迹;
- 六路相机 2D 视图:每帧图像叠加 3D 框的 2D 投影;
- 地图视图:基于 OSM 底图的车辆位置与 GPS 轨迹;
- 描述面板:示例说明文档。
调参建议:
- 只关注点云可加
--headless --save output.rrd将数据保存为.rrd文件离线分析; - 想快速验证流程可加
--seconds=10仅记录前 10 秒,减少内存占用;CI 中该示例即使用--seconds=5; - 换场景可将
--scene-name设为 minisplit 中其他场景(如scene-0916停车场绕圈),对比不同驾驶工况下的传感器数据形态; - 使用完整数据集时,将
--dataset-version改为v1.0-trainval并指定包含完整数据的--root-dir(完整数据需在 nuScenes 官网申请下载,脚本不会自动下载)。
小结
nuScenes 示例是 Rerun 处理多传感器、多模态自动驾驶数据的一个完整范式:以set_time统一时间线、以Transform3D与Pinhole建立空间标定、以Points3D/EncodedImage/Boxes3D承载异构数据、以 Blueprint API 动态编排视图,最终在单个 Viewer 中实现“一条时间轴、多视角联动”的沉浸式回放。对希望用 Rerun 可视化自有机器人或自动驾驶数据的开发者,可直接复用本文涉及的 archetype 组合与 Blueprint 编排模式,将数据接入替换为自身传感器流即可。
【免费下载链接】rerunVisualize, query, and stream to train on multimodal robotics data.项目地址: https://gitcode.com/GitHub_Trending/re/rerun
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考