nuScenes转ROS2 MCAP
把nuScenes自动驾驶数据集转成ROS2消息格式的MCAP文件
做自动驾驶相关开发的人,大概率接触过nuScenes——nuTonomy开源的城市场景数据集。它提供了丰富的传感器数据(6路相机、5路雷达、LIDAR、GPS、IMU、CAN总线),外加语义标注和地图信息,在学术界和工业界都很常用。
但这个数据集有个使用门槛:它的标准格式是nuScenes自己的JSON结构,不能直接用常见的ROS2工具链打开。开发中你想回放一段数据、验证某个算法、或者用Foxglove看看效果,都得先自己写一把转换脚本。
所以AI写了个工具:nuscenes2mcap,把nuScenes mini数据集直接转成ROS2消息格式的MCAP文件。
为什么用ROS2消息格式?
市面上已有其他nuScenes → MCAP转换工具,但它们通常使用Foxglove自家的Protobuf格式(Foxglove Schema)。这本身没问题,Foxglove解析得很好。
但如果你更习惯ROS2生态——比如你想用 ros2 bag play 回放数据、评估你的感知算法,或者把数据导入已有的ROS2 pipeline——那 ROS2消息(CDR编码) 就是最直接的桥接方式。
这个转换脚本做的事情很简单:把nuScenes的20Hz关键帧数据、非关键帧传感器数据、CAN总线消息,全部重新打包为标准ROS2消息,写入MCAP文件。
输出了哪些消息?
转换覆盖了nuScenes的主要数据源:
| `sensor_msgs/PointCloud2` | LIDAR点云(LIDAR_TOP) |
|---|---|
| `sensor_msgs/CompressedImage` | 六路相机图像 |
| `sensor_msgs/CameraInfo` | 相机内参 / 畸变系数 |
| `sensor_msgs/PointCloud2` | 5路雷达数据 |
| `sensor_msgs/NavSatFix` | GPS经纬度 |
| `sensor_msgs/Imu` | IMU(来自CAN总线) |
| `nav_msgs/Odometry` | 自车位姿 + 速度(来自CAN总线) |
| `nav_msgs/OccupancyGrid` | 地图底图 + 可驾驶区域 |
| `geometry_msgs/PoseStamped` | 自车实时位姿 |
| `tf2_msgs/TFMessage` | 所有传感器到车体的坐标变换 |
| `visualization_msgs/MarkerArray` | 3D标注框 + 车道线 |
| `diagnostic_msgs/DiagnosticArray` | CAN总线状态(转向角、刹车、油门等) |
每个 sample(~2Hz关键帧)生成一个完整的时间步,中间的非关键帧传感器数据按时间戳排序后插入,确保时间轴连续。
值得一提的是 语义地图 的处理:nuScenes的地图是矢量格式,转换脚本把底图渲染为OccupancyGrid,同时把车道中心线投影为MarkerArray,随数据一起输出。在Foxglove里打开,地图、自车、传感器数据都是对齐的。
使用方法
三步走:
# 1. 下载数据
# 从 nuScenes 官方 S3 下载 v1.0-mini,解压
# can_bus.zip → data/
# nuScenes-map-expansion-v1.3.zip → data/maps/
# v1.0-mini.tgz → data/
# 2. 安装
pip install -e .
# 3. 运行
python convert_to_mcap_ros2.py默认扫描 data/ 目录,每个场景输出一个 .mcap 文件,保存在 output_ros2/。也支持指定场景ID:
python convert_to_mcap_ros2.py --scene scene-0061技术要点
转换过程中有几个值得注意的点:
时间同步。 nuScenes里不同传感器的时间戳独立,脚本以LIDAR_TOP关键帧为基准,将同一sample内的所有传感器数据对齐到同一时间步。CAN总线消息有独立的时间戳,按其自身时间戳插入,确保CAN数据和传感器数据时间线一致。
CDR序列化。 使用 rosbags 库的typesys获取标准ROS2消息定义,通过 serialize_cdr 序列化,再写入MCAP。这样输出的文件 ros2 bag 可以直接读。
TF树。 构建了 map → base_link → sensor_id 的TF树,每个时间步发布完整的坐标变换。在Foxglove里打开,所有传感器的位姿都是正确的。
地图渲染。 nuScenes的MapExpansion提供了矢量地图,脚本用 get_map_mask 按自车位置动态裁剪可驾驶区域,以自车为中心的OccupancyGrid实时更新。
一些感想
写这个工具的过程挺有意思的。nuScenes的数据结构不算复杂,但因为它用自己那套schema组织(sample、sample_data、ego_pose、calibrated_sensor这些表互相token关联),想要完整映射到ROS2的消息体系,需要对两边的数据模型都熟悉。
最折腾的部分是CAN总线数据。nuScenes的CAN消息是单独通过 NuScenesCanBus 加载的,格式和主数据不同步。需要手动解析 ms_imu、pose、steeranglefeedback 等消息,映射到 sensor_msgs/Imu、nav_msgs/Odometry、diagnostic_msgs/DiagnosticArray。
项目在GitHub上开源: