13.1 単眼カメラ vs デュアルカメラ構成
13.1 単眼カメラ vs デュアルカメラ構成
- 単眼カメラ構成: オーバーヘッドカメラのみを接続します。最初のパイプライン実行や環境確認に適しています。カメラが 1 台少ないということは、デバッグ時の変数が 1 つ減るということです。
- デュアルカメラ構成(本コースのメインパス、正式な収集用):オーバーヘッド + リスト。ACT モデルもデフォルトで 2 本の入力ストリームを想定しています。オーバーヘッド + サイドビューという構成も選べます。
3 台または 4 台のカメラを使って学習・収集することもできます。ACT 自体にはカメラ台数のハードな上限はありません。各画像ストリームは共有の ResNet18 バックボーンを通って特徴トークン列を生成し、それらを連結して Transformer エンコーダに入力します。元の ACT 論文(ALOHA デュアルアーム)では 4 台(オーバーヘッド 2 台 + リスト 2 台)のカメラが使われています。
- コスト: カメラを 1 台追加するごとに、VRAM と計算量はほぼ線形に増加し、データ要求も増えます(視点が増えるほど学習すべきことが増える)。各ストリームは同期と固定された位置関係を維持する必要があります。
13.2 オーバーヘッドカメラとリストカメラ
13.2 オーバーヘッドカメラとリストカメラ
- オーバーヘッドカメラ(正面): 作業空間全体を見下ろすようにマウントに固定します。モデルに「ターゲットがどこにあり、アーム全体がどのような状態か」を伝えます。
- リストカメラ(手首): アーム先端に取り付けられ、グリッパーに追従します。モデルに「グリッパーとターゲットの相対位置、および閉じるべきかどうか」を伝えます。

13.3 カメラデバイス名の確認
13.3 カメラデバイス名の確認
カメラインデックスを確認するには、まず次を実行します:
lerobot-find-cameras opencv
Id: 0 のような表示が見えます。この 0 がカメラインデックスです。
--- Detected Cameras ---
Camera #0:
Name: OpenCV Camera @ 0
Type: OpenCV
Id: 0
Backend api: AVFOUNDATION
Default stream profile:
Format: 16.0
Width: 1920
Height: 1080
Fps: 15.0
--------------------
(more cameras ...)
各カメラの名前、ID、デフォルト解像度が一覧表示されます。ディレクトリ内の ~/rebot_lerobot/outputs/captured_images/ を開くと、各カメラで撮影された画像を確認でき、カメラの位置が正しく適切かどうかを検証できます。
また注意点として、ノート PC を使用している場合は内蔵カメラもスキャンされます。正しいオーバーヘッドカメラとリストカメラのインデックスを見つけるには、USB を抜き差しして確認する必要があります。ノート PC の内蔵カメラは通常インデックス 0 です。
- 接続順によってインデックスは変わります。 今日はオーバーヘッドが 0 でも、明日差し直すと変わるかもしれません。録画セッションの前に毎回 10 秒かけて
lerobot-find-camerasを再実行し、確認してください。 - USB カメラはドック経由ではなく、必ず PC 本体に直接接続してください。 パッシブハブでの帯域競合は、そのまま画像が読めない・フレーム落ちといった形で現れます。理想的には、2 台のカメラを別々の USB コントローラに接続してください。
13.4 画像とアクションの同期
13.4 画像とアクションの同期
単眼カメラ
RS バージョン:
lerobot-teleoperate \
--robot.type=seeed_b601_rs_follower \
--robot.port=can0 \
--robot.id=follower1 \
--robot.can_adapter=socketcan \
--robot.cameras="{ front: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30, fourcc: "MJPG"}}" \
--teleop.type=rebot_arm_102_leader \
--teleop.port=/dev/ttyUSB0 \
--teleop.id=rebot_arm_102_leader \
--display_data=true
DM バージョン:
lerobot-teleoperate \
--robot.type=seeed_b601_dm_follower \
--robot.port=/dev/ttyACM0 \
--robot.id=follower1 \
--robot.can_adapter=damiao \
--robot.cameras="{ front: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30, fourcc: "MJPG"}}" \
--teleop.type=rebot_arm_102_leader \
--teleop.port=/dev/ttyUSB0 \
--teleop.id=rebot_arm_102_leader \
--display_data=true
カメラがさらにある場合は、--robot.cameras パラメータを変更して追加できます。index_or_path の形式に注意してください。これは python -m lerobot.find_cameras opencv コマンドの出力におけるカメラ ID の末尾の数字によって決まります。
デュアルカメラ
RS バージョン:
lerobot-teleoperate \
--robot.type=seeed_b601_rs_follower \
--robot.port=can0 \
--robot.id=follower1 \
--robot.can_adapter=socketcan \
--robot.cameras="{ front: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30, fourcc: "MJPG"}, side: {type: opencv, index_or_path: 2, width: 640, height: 480, fps: 30, fourcc: "MJPG"}}" \
--teleop.type=rebot_arm_102_leader \
--teleop.port=/dev/ttyUSB0 \
--teleop.id=rebot_arm_102_leader \
--display_data=true
DM バージョン:
lerobot-teleoperate \
--robot.type=seeed_b601_dm_follower \
--robot.port=/dev/ttyACM0 \
--robot.id=follower1 \
--robot.can_adapter=damiao \
--robot.cameras="{ front: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30, fourcc: "MJPG"}, side: {type: opencv, index_or_path: 2, width: 640, height: 480, fps: 30, fourcc: "MJPG"}}" \
--teleop.type=rebot_arm_102_leader \
--teleop.port=/dev/ttyUSB0 \
--teleop.id=rebot_arm_102_leader \
--display_data=true
これは、データ収集ボックス内部からの映像であることが分かります:

推奨パラメータ:640 × 480 @ 30 fps, fourcc: "MJPG"。この 3 つのパラメータはそれぞれ重要です。
- 解像度 640×480: 画質とリアルタイム性のバランスです。解像度を 2 倍にすると USB 帯域とストレージの負荷は 4 倍になりますが、モデル入力側では結局リサイズされるため、得られるメリットは限定的です。
- FPS 30: 収集 FPS と一致させます。カメラの FPS が低いと、録画時に古いフレームが繰り返し再利用されます。
fourcc: "MJPG": 送信前に画像を圧縮することで、USB 帯域の負荷を桁違いに減らします。もちろんYUYV形式の画像を試すこともできますが、その場合は解像度と FPS が下がり、アームの動きがカクつきます。現在、MJPG形式であれば 1920×1080 解像度のカメラ 3 台でも 30 FPS を維持できます。
13.5 LeRobot データセットの作成
13.5 LeRobot データセットの作成

以下のコマンドを実行する前に、データを記録できるよう準備しておいてください。録画フェーズに入るときに音声による合図があります。聞こえない場合は、ターミナルのプロンプトを確認して開始しているかどうかを確認してください。
RS バージョン:
lerobot-record \
--robot.type=seeed_b601_rs_follower \
--robot.port=can0 \
--robot.id=follower1 \
--robot.can_adapter=socketcan \
--robot.cameras="{ front: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30, fourcc: "MJPG"}, side: {type: opencv, index_or_path: 2, width: 640, height: 480, fps: 30, fourcc: "MJPG"}}" \
--teleop.type=rebot_arm_102_leader \
--teleop.port=/dev/ttyUSB0 \
--teleop.id=rebot_arm_102_leader \
--display_data=true \
--dataset.repo_id=seeed_rebot_b601_rs/test \
--dataset.num_episodes=5 \
--dataset.single_task="Grab the crayfish into the box" \
--dataset.push_to_hub=false \
--dataset.episode_time_s=30 \
--dataset.reset_time_s=20
DM バージョン:
lerobot-record \
--robot.type=seeed_b601_dm_follower \
--robot.port=/dev/ttyACM0 \
--robot.id=follower1 \
--robot.can_adapter=damiao \
--robot.cameras="{ front: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30, fourcc: "MJPG"}, side: {type: opencv, index_or_path: 2, width: 640, height: 480, fps: 30, fourcc: "MJPG"}}" \
--teleop.type=rebot_arm_102_leader \
--teleop.port=/dev/ttyUSB0 \
--teleop.id=rebot_arm_102_leader \
--display_data=true \
--dataset.repo_id=seeed_rebot_b601_dm/test \
--dataset.num_episodes=5 \
--dataset.single_task="Grab the crayfish into the box" \
--dataset.push_to_hub=false \
--dataset.episode_time_s=30 \
--dataset.reset_time_s=20
データセット自体に関係するパラメータはいくつかあります:
| Parameter | 意味 | 推奨 |
|---|---|---|
--dataset.repo_id | データセット名(ローカルフォルダ名も兼ねる) | テスト用と本番用で名前を分ける。例:rebot_b601/grab_cube_test / rebot_b601/grab_cube_v1 |
--dataset.single_task | タスクの説明(データセット内に保存) | タスク説明に対応した英語 |
--dataset.num_episodes | 何エピソード録画するか | テスト:5、本番:50(デフォルトは 50) |
--dataset.push_to_hub | 録画後に Hub へアップロードするかどうか | false = アップロードしない |
--dataset.episode_time_s=30 | 1 エピソードあたりの録画時間 | タスクの複雑さに応じて調整 |
--dataset.reset_time_s=20 | 次の録画に向けてシーンをリセットする時間 | シーンリセットに必要な時間に応じて調整 |
--display_data=true | カメラ映像をリアルタイム表示するか | - |
その後、データセットはホームディレクトリ内の ~/.cache/huggingface/lerobot に保存されます。上記のフォルダは seeed_rebot_b601_rs/test の下に作成されます。
13.6 エピソードの記録、一時停止、再記録
13.6 エピソードの記録、一時停止、再記録
記録
記録中のキーボード操作:
| キー | 動作 |
|---|---|
| →(右矢印) | 現在のエピソードを早期終了し、リセット/次へ進む |
| ←(左矢印) | 現在のエピソードを破棄し、このエピソードを再記録する |
| ESC | 収集セッション全体を終了:動画をエンコードし、統計量を計算し、データセットを保存 |
キーが反応しない場合は pynput のバージョンの問題です。ダウングレードしてください:pip install pynput==1.6.8。
再記録
5エピソードを記録して再生を確認したら、本番収集では:repo_id を本番用の名前に変更し、num_episodes=50 を本番用の値に変更し、第12章の鉛筆五点法に従います — 1点につき1エピソード、1周あたり5点、合計10周です。
記録の一時停止
記録を一時停止するのに Ctrl+C を押さないでください — Esc を押してください。そうしないと異常終了します。
13.7 データセットの可視化と再生
13.7 データセットの可視化と再生
データセットの可視化
データをアップロードした場合、ローカルでも次のコマンドで可視化できます:
echo ${HF_USER}/rebot_test
lerobot-dataset-viz \
--repo-id ${HF_USER}/rebot_test \
--episode-index 0 \
--display-compressed-images=false
--dataset.push_to_hub=false を使用してデータをアップロードしていない場合でも、次のコマンドでローカル可視化できます:
RS バージョン:
lerobot-dataset-viz \
--repo-id seeed_rebot_b601_rs/test \
--episode-index 0 \
--display-compressed-images=false
DM バージョン:
lerobot-dataset-viz \
--repo-id seeed_rebot_b601_dm/test \
--episode-index 0 \
--display-compressed-images=false
ここで、seeed_rebot_b601_rs/test は収集時のカスタムデータセット名(repo_id)です。
データセットの再生
ここでは、ロボット上で最初のデータセットを再生してみます:--dataset.episode=0 は最初に収集したデータセットを再生することを意味し、以降も同様です。
RS バージョン:
lerobot-replay \
--robot.type=seeed_b601_rs_follower \
--robot.port=can0 \
--robot.can_adapter=socketcan \
--robot.id=follower1 \
--dataset.repo_id=seeed_rebot_b601_rs/test \
--dataset.episode=0
DM バージョン:
lerobot-replay \
--robot.type=seeed_b601_dm_follower \
--robot.port=/dev/ttyACM0 \
--robot.can_adapter=damiao \
--robot.id=follower1 \
--dataset.repo_id=seeed_rebot_b601_dm/test \
--dataset.episode=0
これで、ロボットはテレオペレーション中に記録したのと同じ動作を実行するはずです。
13.8 データの追加と削除
13.8 データの追加と削除
- 記録中にチェックポイントが自動的に作成されます。
- 元のコマンドに
--resume=trueを追加して、データの追加収集を続行します。 - 再開時は、
--dataset.num_episodesを追加で記録するエピソード数(データセット内の最終的な合計ではない)に設定します。 - 最初からやり直す場合は、データセットディレクトリを手動で削除します。
次のコマンドを使用してエピソードを削除します:--operation.episode_indices "[0]" は最初のエピソードを削除し、以降も同様です。削除には時間がかかるので待機し、データセット名を適宜変更してください。
lerobot-edit-dataset \
--repo_id rebot_b601/grab_cube_v1 \
--operation.type delete_episodes \
--operation.episode_indices "[0]"