notes
Ros

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 → MCAP

apt でも brew でも入る (go install github.com/foxglove/mcap/go/cli/mcap@latest)。

Post-mortem 解析ワークフロー#

用途ツール
画面で見るFoxglove (3D、Plot、Diagnostics 全部込み)
数値を細かく見るPlotJuggler (曲線比較、微分、correlate)
統計・fitPython (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.mcap

2. TF だけ再生 (RViz デバッグ)#

ros2 bag play run --topics /tf /tf_static /robot_description

3. 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×○
ecosystemROS 2 only汎用 (Rust/Go/TS/Swift/Python)

MCAP 一択。SQLite に fallback するのは古い bag をロードする時だけ。

関連#

出典#

最終更新:

On this page