Mục tiêu của bài này
Trong bài 1, chúng ta chia vai operator và data supervisor cho một ca pilot humanoid VLA. Trong bài 2, chúng ta chọn teleop stack để tạo action có thể học được. Bài 3 xử lý lớp nằm giữa robot và dataset training: raw log.
Raw log là bản ghi gốc, chưa bị ép vào schema training. Nó phải trả lời được ba câu hỏi: robot đã thấy gì, robot đã làm gì, và từng tín hiệu xảy ra lúc nào. Nếu raw log sai, mọi bước phía sau đều phải đoán. LeRobot có thể lưu observation.state, action, camera và timestamp. Robo-DM nhắm tới lưu trữ và tải robot trajectories đa phương thức ở quy mô lớn. Nhưng trước khi chuyển sang hai định dạng đó, bạn cần một chuẩn ghi thô có thể replay, inspect và audit. Với ROS 2, lựa chọn thực dụng nhất hiện nay là MCAP qua rosbag2_storage_mcap.
Nếu bạn mới xây ROS graph hoặc chưa quen topic/service/action, đọc thêm giới thiệu ROS 2 cho robotics trước sẽ giúp phần record dễ theo hơn. Nếu mục tiêu của bạn là đưa log lên một hệ vận hành đội robot, bài monitor ROS 2 robots từ xa là lớp bổ sung tốt sau khi raw log đã ổn định.
Bài này đi từ lệnh đơn giản:
ros2 bag record -s mcap --all
đến file YAML cấu hình McapWriterOptions, rồi tới bảng map topic humanoid sang schema dataset. Sau bài này, bạn sẽ có một design raw log có thể:
| Việc cần làm | Công cụ |
|---|---|
Ghi toàn bộ ROS 2 topics ra file .mcap |
ros2 bag record -s mcap |
| Replay lại vào ROS graph | ros2 bag play -s mcap |
| Kiểm tra nhanh topic, type, timestamp | ros2 bag info -s mcap, mcap info, Foxglove |
| Xem camera, joint state, TF, action theo timeline | Foxglove |
| Export sang dataset training | Script đọc MCAP, giữ timestamp gốc, ghi LeRobot/Robo-DM |
Điểm quan trọng nhất: đừng biến MCAP thành dataset training ngay từ đầu. Hãy coi MCAP là "hộp đen" của episode. Training schema có thể đổi, model có thể đổi, camera có thể thêm, nhưng raw log phải đủ trung thực để bạn quay lại điều tra.
Vì sao MCAP hợp với humanoid VLA
MCAP là container cho các message pub/sub có timestamp và serialization khác nhau. Theo MCAP spec, một message record có channel_id, sequence, log_time, publish_time và data. Với ROS 2, data thường là payload CDR của message, còn channel chứa topic và message encoding. Đây là cấu trúc rất hợp với humanoid vì robot không chỉ có một stream video. Một episode có thể có camera đầu, camera cổ tay trái/phải, joint state, IMU, force/torque, TF, teleop command, action đã retarget, safety state và prompt ngôn ngữ.
rosbag2_storage_mcap là storage plugin cho rosbag2. Tài liệu ROS 2 cho biết bạn có thể dùng MCAP với lệnh rosbag thường bằng cách thêm --storage mcap hoặc -s mcap: record, play và info đều dùng được. Tài liệu MCAP cũng nhấn mạnh Foxglove có thể mở MCAP chứa ROS 2 data, trong khi SQLite .db3 cũ không tự chứa đầy đủ message definitions cho nhiều workflow bên ngoài ROS.
Với data center cho VLA, MCAP có bốn lợi ích rất cụ thể:
| Lợi ích | Ý nghĩa trong thu dữ liệu humanoid |
|---|---|
Self-contained hơn .db3 |
Tool như Foxglove có nhiều thông tin hơn để decode message, đặc biệt khi có custom messages |
| Có schema, channel, message | Dễ tách topic thành sensor/action/meta khi export |
Có log_time và publish_time |
Giữ được thời điểm record và thời điểm publish nếu pipeline cung cấp |
| Có chunk, index, compression | Có thể tối ưu giữa throughput trên robot và truy vấn sau ca thu |
Beginner thường hỏi: "Tôi có cần tự học binary format MCAP không?" Câu trả lời là không cho bước đầu. Bạn dùng ros2 bag record -s mcap, sau đó dùng ros2 bag play, ros2 bag info, Foxglove hoặc thư viện đọc MCAP. Nhưng bạn cần hiểu vài khái niệm để không cấu hình sai.
Bắt đầu với ros2 bag record -s mcap
Trước tiên cài plugin, thay $ROS_DISTRO bằng distro của bạn:
sudo apt-get install ros-$ROS_DISTRO-rosbag2-storage-mcap
Kiểm tra nhanh:
ros2 bag record --help | rg "storage|mcap"
Nếu hệ của bạn không có rg, dùng grep:
ros2 bag record --help | grep -E "storage|mcap"
Lệnh ghi toàn bộ topics:
mkdir -p ~/humanoid_logs
ros2 bag record -s mcap \
-o ~/humanoid_logs/episode_000123_raw \
--all
Lệnh này tiện để smoke test, nhưng production không nên luôn dùng --all. Với humanoid, --all có thể kéo theo debug image, point cloud nặng, TF phụ, log text tần số cao và topic test không ổn định. Bắt đầu bằng --all trong pilot, sau đó chốt danh sách topic rõ ràng:
ros2 bag record -s mcap \
-o ~/humanoid_logs/episode_000123_raw \
/clock \
/tf \
/tf_static \
/robot_description \
/humanoid/head_camera/image_raw \
/humanoid/left_wrist_camera/image_raw \
/humanoid/right_wrist_camera/image_raw \
/humanoid/joint_states \
/humanoid/imu \
/humanoid/teleop/raw_command \
/humanoid/control/action \
/humanoid/safety/state \
/humanoid/task/prompt
Quy tắc naming nên ổn định từ ngày đầu:
logs/
2026-06-10/
robot_g1_001/
episode_000123_raw/
episode_000123_raw_0.mcap
metadata.yaml
operator_notes.md
calibration/
metadata.yaml là metadata của rosbag2, không phải nơi bạn nhét toàn bộ annotation. Annotation của episode nên nằm cạnh bag hoặc trong topic riêng như /humanoid/task/prompt, /humanoid/task/result, /humanoid/operator/event. Lý do: nếu annotation cũng có timestamp trong ROS graph, bạn có thể nhìn nó trong Foxglove và align với video/action.
Replay và inspect trước khi nói về dataset
Sau khi record, làm ba bước kiểm tra trước khi export:
ros2 bag info -s mcap ~/humanoid_logs/episode_000123_raw
Bạn cần nhìn thấy duration, message count và topic list. Nếu topic camera không có message hoặc action count bằng 0, đừng export. Sửa recorder trước.
Replay:
ros2 bag play -s mcap ~/humanoid_logs/episode_000123_raw
Nếu bag là một file .mcap đơn lẻ thay vì folder rosbag2, một số môi trường cũng cần chỉ rõ storage:
ros2 bag play -s mcap ./episode_000123_raw_0.mcap
Khi replay custom message trong ROS 2, máy replay vẫn cần có package message/type support phù hợp trong workspace. MCAP chứa schema hữu ích cho tool bên ngoài như Foxglove, nhưng ROS 2 player vẫn publish serialized messages qua type support của ROS 2. Vì vậy data center nên version hóa Docker image hoặc workspace dùng để thu dữ liệu.
Inspect bằng Foxglove:
- Mở Foxglove.
- Chọn open local file hoặc kéo thả folder/file MCAP.
- Mở
Topicsđể kiểm tra topic có đủ không. - Thêm
Raw Messagescho/humanoid/control/action. - Thêm
Imagepanels cho camera. - Thêm
Plotcho joint positions, velocities, command norm, safety state. - Thêm
3Dnếu bạn có TF và robot model đủ tốt.
Foxglove docs nói có thể mở MCAP local và xem timeline topic. Đây là lý do MCAP nên là chuẩn raw log: data supervisor không cần chờ script export chạy xong mới biết episode hỏng.
McapWriterOptions: YAML nên dùng thế nào
rosbag2_storage_mcap cho phép truyền --storage-config-file để cấu hình mcap::McapWriterOptions. Một file production vừa phải có thể bắt đầu như sau:
# mcap_writer_options.yaml
noChunkCRC: false
noAttachmentCRC: false
enableDataCRC: false
noSummaryCRC: false
noChunking: false
noMessageIndex: false
noSummary: false
chunkSize: 4194304
compression: "Zstd"
compressionLevel: "Fast"
forceCompression: false
Record với file này:
ros2 bag record -s mcap \
-o ~/humanoid_logs/episode_000123_raw \
--storage-config-file mcap_writer_options.yaml \
/tf /tf_static /humanoid/joint_states /humanoid/control/action
Các option cần hiểu:
| Option | Beginner nên hiểu | Gợi ý mặc định |
|---|---|---|
noChunking |
Nếu true, writer không gom message vào chunk. Ghi có thể nhẹ hơn, nhưng mất nhiều lợi ích index/compression |
false cho raw log dài hạn |
chunkSize |
Kích thước chunk chưa nén. Chunk lớn hơn thường nén tốt hơn, nhưng cần bộ nhớ/CPU hơn | 4 MB là điểm khởi đầu tốt |
compression |
None, Lz4, Zstd |
Zstd cho lưu lâu, Lz4 nếu CPU yếu |
compressionLevel |
Nén nhanh hay nhỏ | Fast hoặc Fastest trên robot |
noChunkCRC |
Tắt CRC của chunk | false nếu muốn kiểm tra hỏng dữ liệu |
enableDataCRC |
Tính CRC toàn Data section, hữu ích khi không chunking | false khi đã chunking |
noMessageIndex |
Tắt index message | false, vì export theo topic/time cần index |
noSummary |
Tắt summary section | false, vì info/seek/tooling cần summary |
forceCompression |
Ép nén mọi chunk | false, để writer bỏ qua chunk không đáng nén |
Tài liệu ROS 2 cũng có preset fastwrite và zstd_small. fastwrite tối ưu throughput bằng cách cấu hình kiểu không chunking và không summary CRC, nhưng tài liệu cảnh báo không nên coi file đó là long-term storage nếu chưa post-process vì một số tác vụ như seek hoặc đọc subset topic có thể kém. Với humanoid data center, cách dùng thực dụng là:
| Tình huống | Cấu hình |
|---|---|
| Robot CPU yếu, đang pilot safety | --storage-preset-profile fastwrite, sau đó convert/compress ngay sau ca |
| Thu production bình thường | YAML Zstd/Fast, chunking/index bật |
| Archive sau QA | Convert sang zstd_small hoặc nén offline |
Ví dụ convert sau khi thu nhanh:
# convert_to_archive.yaml
output_bags:
- uri: episode_000123_archive
storage_id: mcap
storage_preset_profile: zstd_small
ros2 bag convert \
-i ~/humanoid_logs/episode_000123_raw \
-o convert_to_archive.yaml
Map topic humanoid sang schema dataset
Đừng bắt đầu bằng code exporter. Bắt đầu bằng bảng topic. Một schema tốt nói rõ topic nào là observation, action, instruction, metadata và quality signal.
| ROS 2 topic | Message type ví dụ | Vai trò trong raw log | Field dataset gợi ý |
|---|---|---|---|
/humanoid/head_camera/image_raw |
sensor_msgs/msg/Image |
Camera ego/head | observation.images.head |
/humanoid/left_wrist_camera/image_raw |
sensor_msgs/msg/Image |
Camera tay trái | observation.images.left_wrist |
/humanoid/right_wrist_camera/image_raw |
sensor_msgs/msg/Image |
Camera tay phải | observation.images.right_wrist |
/humanoid/joint_states |
sensor_msgs/msg/JointState |
Proprioception | observation.state |
/humanoid/imu |
sensor_msgs/msg/Imu |
Base orientation/accel | observation.imu hoặc thêm vào state |
/tf, /tf_static |
tf2_msgs/msg/TFMessage |
Frame tree | Metadata/calibration, không nhất thiết train trực tiếp |
/humanoid/teleop/raw_command |
custom/msg | Input người điều khiển | teleop.raw hoặc debug-only |
/humanoid/control/action |
custom/msg hoặc Float32MultiArray |
Action thật gửi controller | action |
/humanoid/safety/state |
custom/msg | E-stop, mode, fault | episode.quality, filter |
/humanoid/task/prompt |
std_msgs/msg/String |
Lệnh ngôn ngữ | task, language_instruction |
/humanoid/task/event |
custom/msg | Start, success, fail, discard | Episode boundaries/labels |
LeRobot v3 mô tả dataset robot learning đa phương thức với time-series, sensorimotor signals, multi-camera video và metadata. API sample của LeRobot trả về các key như observation.state, action, observation.images.front_left và timestamp. Vì vậy khi export từ MCAP, bạn không cần phát minh tên field lạ nếu mục tiêu là train với LeRobot. Hãy map về convention phổ biến trước.
Ví dụ một frame training sau export:
sample = {
"episode_index": 123,
"frame_index": 42,
"timestamp": 8.400, # seconds from episode start
"observation.state": joint_state_vector,
"observation.images.head": head_rgb,
"observation.images.left_wrist": left_wrist_rgb,
"observation.images.right_wrist": right_wrist_rgb,
"action": action_vector,
"task": "Pick up the blue bin and place it on the shelf.",
}
Điểm dễ sai là timestamp. MCAP message có log_time và publish_time ở nanoseconds. ROS message cũng có thể có header.stamp. Bạn phải chọn policy rõ:
| Timestamp | Dùng khi | Rủi ro |
|---|---|---|
header.stamp |
Sensor driver đã timestamp đúng tại capture time | Một số custom action không có header |
MCAP publish_time |
Muốn thời điểm node publish message | Có thể lệch capture nếu pipeline xử lý lâu |
MCAP log_time |
Muốn thời điểm recorder nhận message | Bị ảnh hưởng network/recorder load |
Khuyến nghị cho beginner:
- Nếu message có
header.stampđáng tin, dùng nó làm timestamp semantic. - Luôn lưu thêm MCAP
log_timeđể debug recorder delay. - Chuẩn hóa
timestamp = (t - episode_start_t) / 1e9khi ghi LeRobot. - Không nội suy phá hủy dữ liệu gốc trong raw log. Nội suy chỉ làm ở exporter.
Episode boundaries: đừng chỉ dựa vào tên file
Một lỗi phổ biến là mỗi lần bấm record tạo một episode, rồi coi toàn bộ bag là demo tốt. Với humanoid, operator có thể mất 10 giây chuẩn bị, thử gripper, reset stance, sau đó mới làm nhiệm vụ. Bạn cần boundary.
Cách đơn giản nhất là publish event:
/humanoid/task/event
stamp: 2026-06-10T10:01:02.123Z
episode_id: "episode_000123"
event_type: "START" | "SUCCESS" | "FAIL" | "DISCARD" | "RESET"
note: "left wrist camera bumped at 00:13"
Exporter sẽ cắt đoạn START đến SUCCESS, bỏ đoạn trước/sau, và đánh dấu episode failed nếu có FAIL hoặc DISCARD. Nếu bạn chưa có custom message, có thể dùng std_msgs/msg/String chứa JSON trong pilot. Production nên có message type rõ ràng để Foxglove và exporter validate tốt hơn.
Export sang LeRobot mà không mất timestamp
Pipeline tối thiểu:
MCAP raw log
-> reader đọc theo topic/time
-> chọn episode window
-> decode camera/state/action
-> align theo action hoặc camera clock
-> ghi Parquet/MP4/metadata theo LeRobot
Pseudo-code:
episode_start_ns = find_event("START").timestamp_ns
episode_end_ns = find_event("SUCCESS").timestamp_ns
frames = []
for action_msg in iter_topic("/humanoid/control/action", episode_start_ns, episode_end_ns):
t_ns = choose_timestamp(action_msg)
frame = {
"timestamp": (t_ns - episode_start_ns) / 1e9,
"observation.state": nearest("/humanoid/joint_states", t_ns),
"observation.images.head": nearest_frame("/humanoid/head_camera/image_raw", t_ns),
"observation.images.left_wrist": nearest_frame("/humanoid/left_wrist_camera/image_raw", t_ns),
"observation.images.right_wrist": nearest_frame("/humanoid/right_wrist_camera/image_raw", t_ns),
"action": decode_action(action_msg),
"task": current_prompt(t_ns),
}
frames.append(frame)
Ở đây action là clock chính vì imitation learning thường học "observation tại thời điểm t -> action tại thời điểm t". Nếu camera 30 FPS và control 20 Hz, bạn có thể lấy nearest camera frame cho mỗi action. Nếu policy train ở 10 Hz, downsample action sau khi align, không downsample raw log.
Checklist chống mất timestamp:
| Check | Pass khi |
|---|---|
| Monotonic timestamp | timestamp tăng đều trong mỗi episode |
| Max camera-action gap | Nearest image không lệch quá ngưỡng, ví dụ 50 ms |
| Header vs log delta | Sensor header.stamp không lệch recorder log_time bất thường |
| Dropped frames | Frame index không nhảy quá nhiều |
| Safety filter | Episode có E-stop hoặc fault không vào train set mặc định |
Export sang Robo-DM: coi MCAP là nguồn sự thật
Robo-DM paper mô tả bài toán lớn: dataset robot có video, text, numerical modalities và nhiều camera rất khó curate, distribute và load. Robo-DM hướng tới framework quản lý data robot hiệu quả, có lưu trữ self-contained dựa trên EBML, compression mạnh và retrieval nhanh. Paper cũng nhấn mạnh khả năng giữ original timestamps để tránh heuristic alignment.
Điều đó không có nghĩa bạn phải record trực tiếp bằng Robo-DM ngay ngày đầu. Với ROS 2 humanoid, thiết kế sạch là:
Robot ROS 2 graph
-> MCAP raw log, immutable
-> QA report
-> Robo-DM trajectory store for large-scale train/retrieval
MCAP giữ bản gốc để replay trong ROS 2 và inspect bằng Foxglove. Robo-DM hoặc LeRobot là bản dẫn xuất cho training. Khi model lỗi ở eval, bạn quay lại MCAP để xem action gốc, camera gốc, TF gốc và safety event gốc.
Cấu hình đề xuất cho pilot 10 episode
Nếu bạn đang ở giai đoạn beginner, hãy dùng cấu hình này:
# pilot_mcap_writer_options.yaml
noChunkCRC: false
noAttachmentCRC: false
enableDataCRC: false
noSummaryCRC: false
noChunking: false
noMessageIndex: false
noSummary: false
chunkSize: 4194304
compression: "Zstd"
compressionLevel: "Fast"
forceCompression: false
Lệnh record:
ros2 bag record -s mcap \
-o ~/humanoid_logs/$(date +%Y%m%d_%H%M%S)_pilot \
--storage-config-file pilot_mcap_writer_options.yaml \
/clock /tf /tf_static /robot_description \
/humanoid/head_camera/image_raw \
/humanoid/left_wrist_camera/image_raw \
/humanoid/right_wrist_camera/image_raw \
/humanoid/joint_states \
/humanoid/imu \
/humanoid/teleop/raw_command \
/humanoid/control/action \
/humanoid/safety/state \
/humanoid/task/prompt \
/humanoid/task/event
Sau mỗi episode:
ros2 bag info -s mcap ~/humanoid_logs/20260610_100102_pilot
ros2 bag play -s mcap ~/humanoid_logs/20260610_100102_pilot --rate 0.5
Mở trong Foxglove và data supervisor đánh dấu:
| Mục | Pass/Fail |
|---|---|
| Ba camera có hình trong toàn bộ đoạn task | |
| Joint state và action có tần số ổn định | |
| Prompt đúng nhiệm vụ | |
| Không có E-stop trong đoạn train | |
| Replay không crash do thiếu message type | |
| Exporter giữ timestamp tăng đều |
Những lỗi cần tránh
Chỉ lưu video MP4 mà không lưu action. Video giúp demo đẹp, nhưng policy cần action. Hãy record action topic thật gửi controller.
Chỉ lưu action đã normalize. Raw log nên lưu action có đơn vị rõ ràng, ví dụ radian, meter, Newton, normalized gripper kèm metadata. Normalize là việc của exporter.
Đổi tên topic giữa các ngày pilot. Nếu hôm nay là /humanoid/control/action và mai là /action, exporter sẽ phải có nhiều nhánh đặc biệt. Chốt naming sớm.
Tắt index để tiết kiệm chút dung lượng rồi quên post-process. Data center cần seek theo time/topic. noMessageIndex: false và noSummary: false là mặc định hợp lý.
Không lưu event start/success/fail. Không có boundary thì bạn sẽ train cả đoạn chuẩn bị, đoạn operator nói chuyện, hoặc đoạn reset.
Tin một timestamp duy nhất mà không audit. Hãy lưu cả semantic time từ header.stamp nếu có và recorder time từ MCAP. Khi alignment lỗi, hai clock này cho bạn manh mối.
Nguồn kỹ thuật nên đọc
- ROS 2
rosbag2_storage_mcapdocumentation - MCAP ROS 2 guide
- MCAP format specification
- Foxglove ROS 2 docs
- LeRobotDataset v3.0 docs
- Robo-DM paper
Kết luận
MCAP không thay thế LeRobot hoặc Robo-DM. Nó đứng trước hai định dạng đó. Vai trò của nó là giữ raw truth: topic nào được publish, message type gì, timestamp nào, action nào, camera nào, safety event nào. Nếu bạn làm đúng, một episode humanoid có thể được replay bằng ros2 bag play -s mcap, inspect bằng Foxglove, export sang LeRobot để train nhanh, hoặc chuyển sang Robo-DM để quản lý quy mô lớn.
Trong bài 4, chúng ta sẽ đi sâu hơn vào bước sau MCAP: thiết kế exporter LeRobot/Robo-DM, chia train/val, lưu video, metadata và statistics sao cho không biến raw log sạch thành dataset khó debug.