0. 摘要

本文深入介绍 ros2_unbag 这款由德国亚琛工业大学 ika 团队开发的开源工具,该工具专门用于将 ROS 2 的 bag 文件(.db3 或 .mcap 格式)导出为 CSV、JSON、PCD、PNG、MP4 等常用数据格式。文章涵盖工具的设计理念、安装配置、GUI 与 CLI 双模式使用、内置导出例程、处理器链机制、时间重采样策略、自定义插件开发以及性能优化等内容,帮助读者全面掌握该工具的使用方法和扩展能力。

项目地址:https://github.com/ika-rwth-aachen/ros2_unbag

1. 问题背景:ROS 2 Bag 文件处理的困境

在机器人操作系统 ROS 2 的开发过程中,bag 文件是记录传感器数据、话题消息和系统状态的核心载体。无论是自动驾驶车辆的路测数据采集,还是机器人实验室的算法验证,开发者都依赖 bag 文件来保存和回放运行时的完整数据流。然而,当需要将这些数据导出用于离线分析、模型训练或学术论文撰写时,ROS 2 官方提供的工具链显得捉襟见肘。

与 ROS 1 时代相比,ROS 2 的 bag 文件格式从单一的 .bag 演变为 .db3(基于 SQLite3)和 .mcap 两种格式。这种底层变化虽然带来了更好的性能和扩展性,但也意味着原有的处理脚本和工具链需要重新适配。官方的 ros2 bag 命令虽然支持基本的录制和回放操作,但在数据导出方面功能有限。开发者往往需要编写大量的 Python 脚本,手动解析消息结构,处理时间戳对齐,再逐一转换为目标格式。这个过程不仅繁琐,而且容易出错,尤其是在处理包含多个传感器数据流的大型 bag 文件时。

德国亚琛工业大学(RWTH Aachen)的 ika 研究团队深谙这一痛点,他们开发了 ros2_unbag 这款开源工具,旨在提供一站式的 bag 文件数据导出解决方案。该项目自发布以来,在 ROS 社区获得了广泛关注,其设计理念和实现方式值得深入探讨。

2. 项目定位与核心设计理念

ros2_unbag 的核心定位是一个通用的 ROS 2 bag 文件数据格式转换器。它的设计目标并非替代官方的 ros2 bag 工具,而是专注于解决数据导出这一特定场景下的需求。从架构层面来看,该项目遵循以下几个核心设计原则:

可扩展的插件体系:不同于硬编码的导出逻辑,ros2_unbag 采用插件化架构。导出例程(Export Routine)和处理器(Processor)均可通过装饰器机制动态注册,开发者能够在不修改核心代码的情况下扩展对新消息类型或输出格式的支持。

GUI 与 CLI 双模式并行:项目同时提供基于 Qt 的图形界面和完整的命令行接口。GUI 模式适合交互式探索和配置调试,CLI 模式则便于集成到自动化流水线和批处理脚本中。两种模式共享相同的底层逻辑和配置格式,确保工作流的一致性。

多进程并行处理:考虑到 bag 文件动辄数十 GB 的数据量,单线程处理效率低下。ros2_unbag 默认启用多进程并行导出,充分利用多核 CPU 资源,同时提供 CPU 占用率控制参数,避免在共享计算环境中过度抢占资源。

配置持久化与复用:所有导出参数(话题选择、输出格式、处理器链、重采样设置等)均可保存为 JSON 配置文件。这种设计不仅便于团队协作时共享配置,也为实现可重复的数据处理流程奠定了基础。

下图展示了 ros2_unbag 的整体架构设计:

在这里插入图片描述

图1:ros2_unbag 整体架构 - 从 Bag 文件输入到多格式输出的完整数据流


3. 安装与环境配置

3.1 系统依赖

ros2_unbag 基于 Python 开发,依赖 ROS 2 运行时环境。在安装之前,需要确保系统已正确配置 ROS 2 发行版(支持 Humble、Iron、Jazzy 及更新版本),并完成环境变量的加载:

source /opt/ros/<distro>/setup.bash

其中 <distro> 需替换为实际使用的 ROS 2 发行版名称,如 humblejazzy

由于 GUI 模块基于 Qt 框架构建,还需安装以下系统依赖库:

sudo apt update
sudo apt install libxcb-cursor0 libxcb-shape0 libxcb-icccm4 libxcb-keysyms1 libxkbcommon-x11-0

3.2 通过 pip 安装

最简便的安装方式是直接从 PyPI 获取:

pip install ros2-unbag

该命令会自动解析并安装所有 Python 依赖,安装完成后即可通过 ros2 unbag 命令调用。

3.3 从源码安装

对于需要修改源码或贡献代码的开发者,可以选择从 GitHub 仓库克隆并安装:

git clone https://github.com/ika-rwth-aachen/ros2_unbag.git
cd ros2_unbag
pip install .

3.4 Docker 部署

项目提供了预构建的 Docker 镜像,适用于不希望在本地系统安装额外依赖的场景:

docker pull ghcr.io/ika-rwth-aachen/ros2_unbag:latest

该镜像基于 ROS 2 Jazzy 构建,已预装所有必要组件。如需使用 GUI 功能,需要在启动容器前配置 X11 转发:

xhost +local:
docker-compose -f docker/docker-compose.yml up

4. 图形界面模式详解

4.1 界面启动与布局

不带任何参数直接执行 ros2 unbag 命令,即可启动图形界面:

ros2 unbag

GUI 界面采用经典的主从布局设计。左侧为 bag 文件加载区和话题树视图,展示 bag 文件中包含的所有话题及其消息类型;右侧为导出配置面板,用于设置每个话题的输出格式、处理器链和其他参数。

4.2 话题选择与格式配置

加载 bag 文件后,话题树会按照话题名称的层级结构展示。每个话题节点显示其消息类型和消息数量。通过复选框可以选择需要导出的话题,选中后会在右侧配置面板生成对应的配置卡片。

每个配置卡片包含以下核心设置项:

  • 导出格式:下拉菜单列出当前消息类型支持的所有导出格式
  • 子目录:可选的输出子目录名称,用于组织导出文件
  • 处理器链:可添加多个预处理步骤,按顺序执行

4.3 配置保存与加载

完成配置后,可通过界面上的"Save Config"按钮将当前设置导出为 JSON 文件。这个配置文件可以在后续的 CLI 模式中直接加载使用,实现 GUI 调试与 CLI 批处理的无缝衔接。


5. 命令行接口深度使用

5.1 基础导出命令

CLI 模式的核心命令格式如下:

ros2 unbag <bag_path> --export </topic:format[:subdir]> [options]

其中 <bag_path> 为 bag 文件路径,--export 参数指定话题到格式的映射关系,可重复使用以导出多个话题。

一个典型的多话题导出示例:

ros2 unbag recording.mcap \
    --output-dir ./exported_data \
    --export /camera/image_raw:image/png:camera \
    --export /lidar/points:pointcloud/pcd:lidar \
    --export /vehicle/odom:table/csv:odom

该命令将相机图像导出为 PNG 格式存放于 camera 子目录,点云数据导出为 PCD 格式存放于 lidar 子目录,里程计数据导出为 CSV 格式存放于 odom 子目录。

5.2 命名模式控制

--naming 参数用于控制输出文件的命名规则,支持以下占位符:

占位符 说明
%name 话题名称(去除斜杠后的最后一段)
%index 消息序号(从 0 开始)
%timestamp 消息时间戳
%master_timestamp 主话题时间戳(仅在重采样模式下有效)

此外还支持 strftime 格式的时间占位符,如 %Y-%m-%d_%H-%M-%S

示例:

ros2 unbag recording.mcap \
    --export /camera/image:image/png \
    --naming "%Y%m%d_%H%M%S_%index"

5.3 使用配置文件

当导出参数较为复杂时,推荐使用配置文件方式:

ros2 unbag recording.mcap --config export_config.json

配置文件的 JSON 结构示例:

{
  "output_dir": "./exported_data",
  "naming": "%name_%index",
  "exports": [
    {
      "topic": "/camera/image_raw",
      "format": "image/png",
      "subdir": "camera",
      "processors": [
        {"name": "normalize", "args": {}},
        {"name": "apply_color_map", "args": {"color_map": "2"}}
      ]
    },
    {
      "topic": "/lidar/points",
      "format": "pointcloud/pcd",
      "subdir": "lidar"
    }
  ],
  "resample": {
    "master": "/lidar/points",
    "association": "last",
    "discard_eps": 0.1
  },
  "cpu_percentage": 80.0
}

6. 内置导出例程一览

ros2_unbag 内置了覆盖主流消息类型的导出例程,按数据类型分类如下:

6.1 图像类数据

格式标识 支持的消息类型 输出说明
image/png sensor_msgs/msg/Image, sensor_msgs/msg/CompressedImage 单帧 PNG 图片
image/jpeg 同上 单帧 JPEG 图片
video/mp4 同上 图像序列合成为 MP4 视频
video/avi 同上 图像序列合成为 AVI 视频

6.2 点云类数据

格式标识 说明
pointcloud/pcd 标准 PCD 格式(二进制存储)
pointcloud/pcd_compressed 压缩二进制 PCD 格式
pointcloud/pcd_ascii ASCII 文本 PCD 格式
pointcloud/xyz 纯文本 XYZ 坐标格式
pointcloud/pkl Python pickle 序列化格式

6.3 通用格式

以下格式适用于任意消息类型:

格式标识 单文件模式 多文件模式
table/csv 所有消息展开为表格,写入单个 CSV 文件 每条消息一个 CSV 文件
text/json 所有消息以时间戳为键,写入单个 JSON 文件 每条消息一个 JSON 文件
text/yaml 所有消息写入单个 YAML 文件 每条消息一个 YAML 文件

默认使用多文件模式,可通过 @single_file@multi_file 后缀显式指定模式:

ros2 unbag recording.mcap --export /odom:table/csv@single_file

下图汇总了 ros2_unbag 支持的主要导出格式:

在这里插入图片描述

图2:ros2_unbag 支持的导出格式分类汇总


7. 处理器链机制

处理器(Processor)是 ros2_unbag 的核心扩展机制之一,用于在导出前对消息进行预处理。多个处理器可以串联形成处理器链,按指定顺序依次执行。

7.1 内置处理器

处理器名称 适用消息类型 参数说明
field_mapping PointCloud2 field_mapping: 字段重映射规则,格式为 old:new,old:new
remove_fields PointCloud2 fields_to_remove: 待删除字段列表,逗号分隔
transform_from_yaml PointCloud2 custom_frame_path: 包含变换矩阵的 YAML 文件路径
apply_color_map Image, CompressedImage color_map: OpenCV 颜色映射索引值

7.2 处理器链配置

在 CLI 模式下,通过重复 -p 参数来构建处理器链:

ros2 unbag recording.mcap \
    -e /camera/depth:image/png \
    -p /camera/depth:normalize \
    -p /camera/depth:apply_color_map:color_map=2

上述命令对深度图像先执行归一化处理,再应用伪彩色映射,最后导出为 PNG 格式。处理器按照命令行参数的顺序依次执行,执行顺序对最终结果有直接影响。


8. 时间重采样策略

多传感器数据的时间对齐是机器人和自动驾驶领域的常见需求。不同传感器的采样频率往往不一致,例如激光雷达通常以 10-20Hz 工作,相机可能达到 30Hz 或更高,而 IMU 采样率可能高达数百 Hz。ros2_unbag 提供了两种重采样策略来处理这一问题。

8.1 Last 模式

Last 模式以指定的主话题(master topic)为基准,对于主话题的每条消息,从其他话题中选取在该时间点之前最近的一条消息。这种策略适用于传感器频率差异较大、但只需要获取各传感器最新状态的场景。

ros2 unbag recording.mcap \
    --export /lidar/points:pointcloud/pcd \
    --export /camera/image:image/png \
    --resample /lidar/points:last

可选的 discard_eps 参数指定最大允许时间差,超过该阈值的帧将被丢弃:

--resample /lidar/points:last,0.1

…详情请参照古月居

Logo

更多推荐