はじめに
株式会社フィジカルAI総合研究所 取締役COOの林幸一です。
VLA(Vision-Language-Action)を実機ロボットで動かそうとすると、最初に必要になるのは「人がロボットを操作したお手本」データです。モデルやGPUは調達できても、このデモデータは現場ごとに自分たちで集める必要があります。
今回は、手元のiPhone 1台を「マスターアーム」のように使い、産業用ロボットアームUR3eをエンドエフェクタ(EE)空間で6DoFテレオペレーションする仕組みを作りました。iPhoneのARKitが返す位置・姿勢をロボットのTCP目標に変換し、VLAモデルの学習に使うデモデータを収集できるようにしています。
今回UR3eを選んだのは、VLAを最終的に工場や製造現場へ持ち込むことを想定しているためです。当初はGELLOという専用のテレオペ装置を使う予定でしたが、納期の都合で実験タイミングに間に合いませんでした。そこで、先にデータ収集ラインを立ち上げるため、iPhoneを操作デバイスとして使う方針に切り替えました。
以下の動画にて、テレオペしている様子が見れます。
https://youtu.be/ni0w_FXWLGc
1. iPhoneをマスターアームとして使う
まず、全体のパイプラインは次の構成です。
Phone.get_action() # iPhone(ARKit)の6DoFポーズ + ボタン
-> MapPhoneActionToRobotAction # iPhone座標系 -> UR座標系へ軸変換
-> EEReferenceAndDeltaFromTcp # 実TCPを基準に差分へ変換(クラッチ)
-> EEBoundsAndSafety # 作業領域クランプ・速度上限
-> UR3eRobot.send_action() # servoLでTCP目標を送る(IKはUR側)
PC側では、iPhoneのARKitポーズをHEBI Mobile I/O経由で受け取り、UR3eのTCP目標姿勢に変換します。IK(逆運動学)はUR側に任せ、PCはエンドエフェクタの目標位置・姿勢を送り続けるだけです。ここでは関節角を直接操作するのではなく、ロボットの手先をどこへ動かしたいかだけを与える構成にしています。
設計上のポイントは、相対ポーズ + クラッチ方式にしたことです。iPhoneの絶対位置をそのままロボットに対応させるのではなく、操作を有効にした瞬間のiPhoneポーズを基準として記録し、そこからの差分だけをロボットへ反映します。
これにより、車のクラッチのように「いったん操作を切って、手を持ち替えて、また操作を続ける」ことができます。人間の腕の可動範囲は限られているため、絶対ポーズ対応にしてしまうとすぐに操作範囲が足りなくなります。相対ポーズ方式にすると、持ち替えながら作業を続けられます。
操作モードは2種類用意しました。
- toggle: ボタンを一度押すとON、もう一度押すとOFF。長押しが不要で疲れにくい。
- hold: ボタンを押している間だけ追従する従来型の操作。
どちらのモードでも、操作が有効になった瞬間に基準ポーズを取り直します。再開時は常に「その瞬間の姿勢 = 差分ゼロ」から始まるため、急なジャンプを避けられます。
2. 座標軸合わせは実機で詰める
実装で最初に苦労したのは、iPhone座標系とUR座標系の対応づけです。やること自体は「iPhoneを動かした向きとロボットが動く向きを一致させる」だけですが、座標系の取り方によって符号や軸の対応が簡単に反転します。
最初は iPhone(X,Y,Z) -> UR(-Y,Z,-X) が自然だと考えました。しかし実機で動かすと、観測される挙動は (X,-Y,-Z) に近く見えました。すぐに決め打ちせず、複数の軸対応パターンをCLIで切り替えられるようにし、実機上で比較しながら詰めました。
最終的には、次の対応に落ち着きました。
-
位置: iPhone
(X,Y,Z)-> UR(-X,-Y,Z) -
回転: iPhone
(RX,RY,RZ)-> UR(-RX,RZ,RY)
回転については当初 (-RZ,-RX,RY) にしていましたが、手首の回り方が直感と合わず、実機での確認を経て差し替えました。座標変換は机上で完全に決め切るのが難しいため、複数マッピングを切り替えられるようにしておくと検証が楽になります。
3. 飛び値と速度上限への対策
無線で届くiPhoneのポーズには、欠落やジャンプが混ざります。その値をそのままロボットへ送ると、エンドエフェクタが不自然に跳ねる可能性があります。そこで、ポーズの飛び値を検知して平滑化するフィルタを挟みました。
通常時は、位置と回転にローパスフィルタをかけます。一方で、位置差分・回転差分・前回更新からの経過時間が大きい場合は「ジャンプ」とみなし、より強くなめらかにします。自然な操作には追従しつつ、不自然な段差だけを鈍らせるための処理です。
もう1つ注意が必要な点は、速度上限の解釈です。MAX_EE_STEP は「1 tickで動かせる最大距離」であり、速度そのものではありません。実効的な最高速度は次のようになります。
最高速度 = MAX_EE_STEP x fps
60Hzのテレオペ用に決めた値を20Hzの収録ループでそのまま使うと、最高速度が3分の1になります。その結果、ロボットの動きがかなり遅く見えました。収録時のfpsに合わせて上限値を調整する必要がありました。
4. 収録ループが落ちる原因を切り分ける
このプロジェクトで最も大きな問題は、収録中に制御ループが極端に遅くなり、最終的にプロセスごと落ちることでした。
現象としては、収録ループが大きく低下し、その後UR制御リンクのwatchdogがタイムアウトします。ログには Could not receive data や End of file が出て、再接続が繰り返されます。さらに、ur_rtde のC++バックグラウンドスレッドが未捕捉例外を投げ、Pythonプロセスごとabortします。
Python側で例外をcatchして復旧するのは難しく、根本的には「制御ループを止めない」ことが必要でした。
最初はWi-Fiのジッタを疑いました。実際、iPhoneとのWi-Fi通信は不安定でした。しかし、切り分けを進めると主因は別にありました。問題を起こしていたのは、ストリーミング動画エンコードでした。
保存時に動画ファイルのセカンドパス処理が走ると、ファイル全体の読み直しが発生します。さらに重いエンコードも重なり、次エピソードの収録ループとCPU・ディスク・GILを奪い合っていました。保存時にまとめてエンコードする構成へ変えると、Wi-Fi構成を変えずに各エピソードが安定して通るようになりました。
ここで重要だったのは、通信を疑い続けるのではなく、収録時に同時に走っている重い処理を1つずつ切り分けることでした。結果として、主因はWi-Fiではなくエンコード周りの競合だと判断しました。
対策は複数層で入れています。
| 対策 | 内容 |
|---|---|
| servoLの専用スレッド分離 |
servoL を収録ループから切り離し、専用スレッドが一定周期で最新目標を送り続ける |
| 画像エンコードの別プロセス化 | PNGエンコードをメインプロセスのスレッドから別プロセスへ逃がす |
| カメラfpsを録画レートに一致 | カメラを録画と同じfpsで開き、余計なフレームを減らす |
| ループ低速フェイルセーフ | ループHzが閾値未満の状態が続いたら、watchdogが切れる前に正常停止へ移る |
特に効いたのはservoLの専用スレッド化です。watchdogが見ているのは「手先目標が定期的に届いているか」です。収録ループが詰まっても、別スレッドから目標を送り続けられれば、RTDE接続の切断を防げます。
5. クラッシュ後のデータセット復旧
収録中にプロセスが落ちると、データセットも壊れます。parquetファイルは、クラッシュ時にfooter(末尾のメタ情報)が書かれず、読み込めなくなることがあります。
復旧では、主に次の作業を行いました。
-
エピソード境界へのロールバック: 破損したparquetと中途半端なファイルをバックアップへ退避し、
info.jsonのエピソード数・フレーム数を健全な境界に戻す。 - statsの再計算: 各エピソードの統計を集計し直し、データセット全体の正規化統計を再生成します。
- 無効エピソードの除去: RTDE切断中の無効データが保存されていたケースでは、該当エピソードだけを削除します。
ここで収録スクリプト側のバグも修正しました。フェイルセーフや q による中断で停止フラグが立っても、後続の保存パスが無条件にエピソードを保存していたため、中断したテイクが残っていました。停止フラグが立っている場合はエピソードバッファをクリアしてbreakするガードを追加し、「q は中断テイクを破棄する」という挙動を明確にしました。
データ収集システムでは、クラッシュしない設計だけでなく、壊れたときにどこまで巻き戻すかを手順化しておくことが重要です。
6. 環境構築で詰まったポイント
UR3eは固定IPで有線直結しました。ロボットへの通信は有線にしつつ、インターネット側のデフォルトルートはWi-Fiに残す必要があります。稼働中の接続へ設定が自動反映されない場合もあるため、設定後に接続を再適用する運用にしました。有線直結ではレイテンシが非常に安定しました。
カメラはudevルールでUSBシリアルに基づくシンボリックリンクを作り、収録・推論の両方でそのパスを参照するようにしました。OpenCVのカメラインデックスは接続順で変わるため、固定パスにしないと別のカメラを誤って開く事故が起きます。
USB帯域も重要でした。Webカメラを2台同じUSB2.0ハブに挿すと、帯域を奪い合ってfpsが落ちました。非圧縮フォーマットは帯域を使うため、圧縮ストリームを強制し、暗所でfpsを落とす露出制御も無効化しました。
iPhoneの通信は、Wi-FiよりUSBテザリングの方が安定しました。RTDE自体は有線直結なので、iPhoneのWi-Fiジッタが直接RTDEへ乗るわけではありません。ただし、HEBIフィードバックを受けるスレッドがブロックされると、GILや割り込みの競合を通じてメインスレッドに影響する可能性があります。実運用では、iPhoneはUSBテザリングにして、ディスカバリ対象インターフェースを固定する構成が安定しました。
まとめ
iPhone 1台を使ってUR3eを6DoFテレオペし、VLA学習用のデモデータを収集する仕組みを構築しました。
今回の主な学びは次の通りです。
- 専用マスターアームがなくても、iPhoneのARKitポーズを使えばEE空間の直感的なテレオペは実現できます。
- 相対ポーズ + クラッチ方式にすると、手を持ち替えながら安定して操作できます。
- 収録システムでは、動画エンコードやディスクI/Oが制御ループへ与える影響を無視できません。
- servoLを専用スレッドに分離すると、収録ループの遅延とRTDE watchdogを切り離せます。
- データ収集では、壊れたデータをエピソード境界まで巻き戻す手順と、stats再計算の仕組みが必須になります。
- 固定IP、udev、USB帯域、通信ジッタなど、周辺の足回りが収録品質に大きく効きます。
VLAの実機検証では、モデルの学習以前に、安定したデータ収集ラインを作ることが大きな比重を占めます。今回のiPhoneテレオペ構成は、専用装置がなくても収集を先に回し始められる現実的な選択肢になりました。