rosbag2 と MCAP
記録 (record) / 再生 (play) / 変換 (convert) / 検査 (info) の CLI と、SQLite → MCAP への移行理由。QoS override、latched の落とし穴、post-mortem 解析ワークフロー。
要点: 新規プロジェクトは --storage mcap で録る。MCAP は self-contained (msg 定義を焼き込む) ので ROS 未インストールの解析機でも読める + seek が速い。SQLite (.db3) は残っているが実運用は劣化案。latched (transient_local) topic は QoS override yaml で明示的に録る。post-mortem は Foxglove / PlotJuggler / Python (mcap package) の 3 択。
30 秒サマリ
- rosbag2: ROS 2 標準の記録/再生ツール群 (
ros2 bag ...)。storage backend が抜き差しできる (sqlite3/mcap) - MCAP: 汎用シリアル-independent なコンテナ。msg 定義を bag 内に埋め込むので、他 ROS distro でも読める。Foxglove が主導、CMake から使える単独 lib もある
- 既定: ROS 2 Humble/Jazzy 系で
--storage mcapが推奨、明示指定なしだとsqlite3が既定 (distro による)
一発記録・再生
# 全 topic 記録 (MCAP)
ros2 bag record -a --storage mcap -o /tmp/run1
# 特定 topic
ros2 bag record --storage mcap /joint_states /camera/image_raw/compressed -o /tmp/run2
# regex で選ぶ
ros2 bag record --storage mcap -e '/salamander/(joint_states|arm/.*)' -o /tmp/run3
# 再生
ros2 bag play /tmp/run1 # 1x
ros2 bag play -r 0.5 /tmp/run1 # 0.5x
ros2 bag play --start-offset 30 /tmp/run1 # 30 秒から
ros2 bag play --loop /tmp/run1
ros2 bag play --clock 100 /tmp/run1 # /clock を 100 Hz で publish (sim time)情報を見る
ros2 bag info /tmp/run1 # duration, count, topic 一覧
ros2 bag info --verbose /tmp/run1 # msg type と QoS もros2 topic hz /whatever は play 中の bag に対しても動く (別 terminal で play しながら)。
変換 (convert)
# SQLite → MCAP (旧 bag を新 tool で扱う)
ros2 bag convert -i in_folder -o out_folder \
--output-options '{output_bags: [{uri: out, storage_id: mcap}]}'
# または mcap CLI (単独)
mcap convert in.db3 out.mcap旧 bag が sqlite でも、まず MCAP に変換してから分析するのが速い。SQLite は seek が O(N) で 10 分超のログは辛い。
QoS override (latched を録りこぼさない)
問題: transient_local (latched) の topic は subscribe 時点で最後のメッセージが 1 発届いて終わり。record が subscribe した瞬間の 1 発だけ残るのが正解だが、publisher/reader の QoS が違うと届かない。
ros2 bag record --qos-profile-overrides-path qos_override.yaml -a ...qos_override.yaml:
/robot_description:
history: keep_last
depth: 1
reliability: reliable
durability: transient_local
/salamander/field_state:
reliability: reliable
durability: transient_local同じ override を再生時にも掛けられる: ros2 bag play --qos-profile-overrides-path qos_override.yaml。
分割・回転
# サイズで分割 (毎 500 MB で新ファイル、bag は複数 mcap を持つ)
ros2 bag record -a --storage mcap -b 500000000 -o /tmp/long_run
# 時間で分割 (60 秒ごと)
ros2 bag record -a --storage mcap -d 60 -o /tmp/long_run
# 最大数を制限 (circular buffer 相当)
ros2 bag record -a --storage mcap --max-cache-size 1000000000 --max-bag-size 500000000 --max-bag-duration 60長時間 record ではファイル 1 個は 1〜2 GB が扱いやすい (それ以上は Foxglove の初回スキャンが重い)。
メタデータ
- bag ディレクトリ内:
metadata.yaml+*.mcap(or*.db3) metadata.yaml: topic 一覧、msg 型、msg 数、QoS、compression、split info- 手書き編集は避ける。壊すと
ros2 bag infoが読めなくなる
圧縮
--compression-mode file # ファイル単位圧縮 (デフォルト off)
--compression-mode message # メッセージ単位 (seek しやすい、少し重い)
--compression-format zstd # or lz4 (zstd の方が高圧縮、実用は zstd)- camera / pointcloud を含むなら圧縮必須。zstd で 5〜10 倍
- MCAP は message 単位圧縮でも seek 高速 (chunk 分割前提)
Python API (post-mortem 解析)
mcap package (ROS 独立、pure python):
from mcap.reader import make_reader
from mcap_ros2.reader import read_ros2_messages
# msg 型定義も MCAP 内にあるので、ROS 未インストールでも decode 可
for msg in read_ros2_messages("run1.mcap"):
if msg.channel.topic == "/joint_states":
print(msg.log_time_ns, msg.ros_msg.position)rosbag2_py (ROS 2 が要る):
import rosbag2_py
from rclpy.serialization import deserialize_message
from rosidl_runtime_py.utilities import get_message
reader = rosbag2_py.SequentialReader()
reader.open(rosbag2_py.StorageOptions(uri="/tmp/run1", storage_id="mcap"),
rosbag2_py.ConverterOptions("", ""))
type_map = {t.name: get_message(t.type) for t in reader.get_all_topics_and_types()}
while reader.has_next():
topic, data, t = reader.read_next()
msg = deserialize_message(data, type_map[topic])mcap CLI (単独ツール、便利)
mcap info run.mcap # topic 一覧、chunk 数
mcap cat run.mcap --topic /joint_states # JSON で吐く
mcap filter run.mcap -y '/salamander/.*' -o out.mcap # regex で切り出し
mcap merge a.mcap b.mcap -o merged.mcap # 結合
mcap convert in.db3 out.mcap # SQLite → MCAPapt でも brew でも入る (go install github.com/foxglove/mcap/go/cli/mcap@latest)。
Post-mortem 解析ワークフロー
| 用途 | ツール |
|---|---|
| 画面で見る | Foxglove (3D、Plot、Diagnostics 全部込み) |
| 数値を細かく見る | PlotJuggler (曲線比較、微分、correlate) |
| 統計・fit | Python (mcap or rosbag2_py) → pandas/numpy |
| 切り出し | mcap filter で範囲・topic を絞ってから再解析 |
| CI で検査 | Python テストで期待値 assert (test_e2e.py みたいに) |
| 他の言語 | mcap package は Go / Rust / TypeScript / Swift も |
落とし穴
-a(all) で録ると camera / pointcloud を含み 10 GB / 分。まず regex で絞る/tf_staticの record を忘れる: 再生時に URDF 全部が原点に来て「変」になる。-aか明示指定/camera_infoを録らない: image 単体では intrinsics が無い → Foxglove の unrectify や単眼 SLAM が動かない- latched を単に
-aで録る: 上の QoS override 参照。latched を含む場合は明示 override - SQLite に固執: 5 分超のログで seek が重い。convert しておく
- Play 時に
--clockを忘れる:use_sim_time:=trueのノードが動かない。必要な時だけ渡す - 時刻源が混ざる: header.stamp と log_time が別。分析は log_time (record 時刻) が基準、header.stamp は sensor 時刻。header 無いメッセージ (String 等) は log_time が全て
- compression=none で 100 GB: すぐ zstd に変える
便利パターン
1. 部分書き出し (時刻 t1〜t2)
# 時間で filter (現状 CLI 一発の option は薄い、mcap filter で:)
mcap filter run.mcap --start 30s --end 60s -o cut.mcap2. TF だけ再生 (RViz デバッグ)
ros2 bag play run --topics /tf /tf_static /robot_description3. Event tag (chapter marker)
MCAP は「注釈 event」を書き込める (Foxglove の Events panel と対応)。
foxglove-cli add-event ... # 外部注釈を打つrun.mcap 内に events を焼くのは Python API 経由。試合の「ここで缶を落とした」等を後で見返すのに強力。
4. Snapshot record (rolling buffer + trigger)
ros2 bag record --snapshot-mode ...
# 事件が起きた瞬間だけ save (メモリ上に常時、trigger で flush)長時間モニタで「異常が起きた前後 30 秒だけ残す」用途。
rosbag2 vs MCAP まとめ
| 項目 | rosbag2 (sqlite) | MCAP |
|---|---|---|
| ROS 独立読み取り | × | ○ |
| Seek 速度 | 遅い | 速い (chunk index) |
| msg 型埋込み | × (別途 .msg が要る) | ○ |
| Foxglove 対応 | ○ | ◎ |
| 圧縮 | ○ (file) | ○ (file / message) |
| 分割 | ○ | ○ |
| events / annotations | × | ○ |
| ecosystem | ROS 2 only | 汎用 (Rust/Go/TS/Swift/Python) |
MCAP 一択。SQLite に fallback するのは古い bag をロードする時だけ。
関連
- foxglove — MCAP を開いて解析する定番 GUI
- rosbag-analysis-workflow — 実際の解析パイプライン
- rosbag-manifest-launch-wrapper — record の launch 化
- foxglove-mcp-bag-analysis — Claude Code の MCP で bag を触る
- image-topic-cost — record の帯域を圧迫する主因