213,312点が、実は手首の位置だった話。座標系を全部作り直した日
LOG157R2R2R2026/4/18

213,312点が、実は手首の位置だった話。座標系を全部作り直した日

前日に積み上げた213,312点のFKスキャンデータが、実は「グリッパが物を掴む位置」ではなく「手首の位置」だったと判明した。白箱中心への最近傍が69mmも離れているのを見て、はじめて気づいた。スキャナー4本を全部書き換えて、Unityで4回スキャンを回し直した。深夜0時すぎに、最後のスキャンが終わった。

💡 Key Takeaway
LOG154から積み上げてきた統合テーブル213,312点は、「手首位置(link6)」を基準に作られていた。グリッパで物を掴む位置とは数センチずれていた。なのにPICK動作が成立していたのは、赤ブロックがロボットに対して「たまたま手首位置で届く距離」にあったからだった。白箱(より遠くにある物体)に向き合った瞬間、設計の歪みが露呈した。座標系を「グリッパ先端中点」に統一する大工事を実行し、PlaceRightで0.43mm、ParallelFKで0.11mmという精度に到達した。

段階

結果

LOG156までの座標系(手首位置基準)

白箱中心への最近傍:69.23mm

LOG157改修後の座標系(グリッパ先端中点基準)

白箱中心:0.43mm/赤ブロック中心:0.11mm

朝、PLACEスキャンの結果を見た瞬間

LOG156の最後に、PLACE領域のFKスキャンを61分かけて回した。146,410点を生成して、ee_yフィルタを通過したのが37,976点。

朝起きて検証した結果がこれだった。

最近傍: 69.23mm
50mm以内: 0点
30mm以内: 0点
10mm以内: 0点

白箱中心まで69mm。50mm以内に1点もない。146,410点をスキャンしても、白箱を掴める姿勢が一つも入っていなかった。

軍師(Claude)が出した判定基準は「30mm以上は再スキャン検討」だった。69mmは完全な失格圏。

真因は「ee座標が手首の位置だった」

最近傍点の関節値を見ると、j2もj4もスキャン範囲±25°の端を使っていた。「範囲を広げれば届く」と短絡的に判断しかけて、止まった。

ここで冷静に検証すると、構造的な問題が見えた。

手動確認した白箱真上の姿勢。そのときのEE座標は (-0.137, 0.106, 0.099) だった。ee_y = 0.106m。 ところがスキャナーに設定したee_yフィルタは 0.050〜0.080m。

つまり、手動確認で正解だった姿勢そのものが、フィルタで弾かれていた。

なぜそうなるか。スキャナーが記録していた ee は link6_flange.position - root.position。これは手首フランジの位置だった。グリッパの先端ではなかった。

スキャナーのee = 手首位置(グリッパ含まず)
本当に欲しいee = グリッパ先端の位置(物を掴む点)

「グリッパ先端を白箱上面(高さ65mm)に置く」とき、手首はその10cm上にある。それを認識せずに、スキャナーのee_yフィルタに「グリッパ先端の目標高さ」をそのまま入れていた。

これに気づいた瞬間、別のことが分かった。統合テーブル213,312点(PICK側含む)全体が、同じ手首位置基準で作られている。 赤ブロックでPICK動作が成立していたのは、赤ブロックの高さ12mmが手首位置と運良く近い距離だったから。白箱のように高さがある物体で初めて、設計の歪みが露呈した。

大工事の決断

選択肢は3つあった。

ee_yフィルタを拡大して再スキャンする小手先の対応。フィルタなしで全点記録する中規模対応。統合テーブル全体の座標系を「グリッパ先端基準」に再設計する大工事。

③ 統合テーブル全体をグリッパ先端座標に設計変更(大工事)

エイイチが選んだのは大工事だった。フィルタ調整はその場しのぎで、白箱以外の物体で同じ問題が再発する。座標系を物理的に意味のある点に揃えるのが本質的な解決だ。

「グリッパ先端」とは何か

座標系を変えると決めても、「グリッパ先端」をどこに定義するかは別問題だった。

軍師は最初、Empty GameObjectを置いて基準点を指定する案を出した。エイイチの返答はもっとシンプルだった。

グリッパーの先端のパーツはBrepというパーツ。この両方の先端の真ん中くらいを位置として設定しておけば、掴んだ時に動いたとしても奥になるだけだからいいんじゃないかな。

左右のグリッパ先端Brepの中点を基準にする。指が閉じれば中点はロボット側(奥)にずれるだけ。物体を掴む直前の入口位置として、これほど物理的に意味のある点はない。

ところがUnityのHierarchyを開くと、Brepという名前のオブジェクトがあちこちに存在していた。

gripper_left1/Visuals/unnamed/gripper_left1/
  ├ instance-def_1-bb45ae1e.../[?]/Brep
  └ instance-def_1-e6b76023.../Instance-Def-210103_4302_101/Brep   ← 先端

CADインポートのGUIDで識別される無数のBrep。エイイチが目視で「一番先端のBrep」を特定してくれた結果、共通パターンが見えた。Instance-Def-210103_4302_101 の下のBrepが左右両方の先端だった。

改修パターンの確立

PlaceRightFKScanner.cs を改修して、まず1ファイルだけ実行する。これがゲートだった。

61分のスキャンが終わって、検証した。

最近傍: 0.43mm
5mm以内: 53点
10mm以内: 250点
50mm以内: 5,463点

0.43mm。 白箱中心にほぼ完全一致した。69mmから0.43mmへ、161倍の精度向上。改修パターンが正しいと数値で証明された。

4本のスキャナーを書き換える

ファイル

役割

元の点数

ParallelFKScanner.cs

motion_based 134,456点を生成(PICK動作の本体)

134,456

LowerLeftFKScanner.cs

赤ブロック側を補助

52,997

CentralFKScanner.cs

j0中央帯を補完

25,859

PlaceRightFKScanner.cs

PLACE領域(白箱側)

146,410

4ファイルとも同じ構造(endEffector.position - root.position で手首ベース)だった。改修は5箇所のパターン適用に集約できた。

  1. RobotConfigクラスにleftTipBrep / rightTipBrepフィールドを追加

  2. Start()で左右Brepを取得

  3. nullチェックを強化

  4. ee座標計算を「左右Brepの中点 - root」に置き換え

  5. ヘルパメソッド(FindGripperTipBrep / FindDeepChild)を追加

Claude Codeに依頼して、3ファイル一気に同じパターンを適用した。差分は機械的で、レビューも数分で終わった。

4回のスキャンを順次実行

順序

スキャナー

所要時間

完了時刻

1

PlaceRightFKScanner

61分

19:18

2

CentralFKScanner

19分

19:51

3

LowerLeftFKScanner

149分

22:50

4

ParallelFKScanner

57分

00:03

「右側で動いてるけど、いいの?」

LowerLeftFKScannerが走っているとき、エイイチが画面を見て言った。

アームは右側で動いているけど、いいの?

軍師は「コードと動作は整合している」と説明しようとして、止まった。

確認すると、LowerLeftFKScannerはコメントに「マット左下区画」と書かれているのに、j0の値は正側(+15〜+60°)に設定されていた。アームは赤ブロック側(Unity上の正X側)で動いている。命名と実際のスキャン位置が逆だった。

これはLOG156でPlaceRightFKScannerが「右側のスキャナーなのに左側を向いていた」と気づいたのと同じ構造の問題だった。違うのは、当時は気づいて修正したのに対し、LowerLeftは命名と動作が逆のまま今日まで残っていたことだった。

PlaceRightは右にある箱の近くをスキャンしてたんだっけ?

LOG156記事を確認した。白箱は x = -0.2208(負側)にある。j0=正側にすると、手首は負X側に出る。「PlaceRight」という命名は実際には「Unity座標系の負X側」を意味していた。

つまりプロジェクト内に、命名ルールが二系統混在していた。コードを直すたびに、命名は元のまま中身だけ反転する状態が続いていた。

名前の命名ルールがそもそも間違っているんじゃないの?
実際のUnity位置に合わせて、minus_xsideとかplus_xsideとかに統一すればいいんじゃない?

エイイチの提案は本質的だった。「右」「左下」のような人間目線の方角表現は、視点によって意味が変わる。Unity座標の符号で命名すれば、誰が読んでも同じ解釈になる。

ただし大工事を疲労時に重ねるのはLOG156教訓に反する。リネームはLOG158に回す判断をして、ParallelFKScannerの実行に進んだ。

確認作業

ParallelFKScannerが完了した。134,456点の出力が終わった。

最近傍: 0.11mm
EE座標: (0.240143, 0.053410, 0.214099)  ← 赤ブロック(0.2401, -, 0.214) と XZ で完全一致
5mm以内: 980点
10mm以内: 3,683点
20mm以内: 12,541点
50mm以内: 45,342点

0.11mm。 赤ブロック中心にほぼ完全に届いていた。10mm以内に3,683点、20mm以内に12,541点。これだけ密度があれば、カメラ検出に多少誤差があっても安定して掴める姿勢が引ける。

LOG154で実機PICK動作が成立していた理由が、ここで数値として再現された。motion_basedが本体で、他のスキャナーは補助。座標系を入れ替えても、本体の精度は保たれた。

213,312点ではなく、もう一度213,312点を作る意味

LOG156の記事に「213,312点より人間の目1回」と書いた。あの213,312点は積み上げた成果のはずだった。

LOG157で起きたのは、その213,312点を捨てて、もう一度213,312点を作り直すことだった。同じ点数のデータが、座標系の違いで別物になった。手首位置基準のテーブルとグリッパ先端基準のテーブル。数字は同じでも、意味が違う。

一晩で4回スキャンを回し直して、合計約280分(4時間40分)。エイイチがPlayボタンを押した数は4回。最後のParallelFKScannerが回り始めたのは23時を過ぎていた。

「精度より体験の成立を優先する」という哲学は、こういう作り直しを含めて成立する。最初に正しい座標系で作ることはできなかった。間違いに気づくこともAIだけではできなかった。「アームが右側で動いてるけど、いいの?」と人間が言わなければ、命名と実態の乖離も見つからなかった。

次以降にやること

統合テーブルを再生成して、命名ルールを plus_xside / minus_xside 系で統一する大工事が残っている。それからリネームの記事化、Pi側の実機検証への接続。

「lookup動的追従」のパイプラインは、v物理的に意味のある座標系を初めて獲得した。ここからが、本当の精度チューニングのスタート地点だ。

※ プロジェクト名「R2R2R」は、UC Berkeley・Toyota Research Instituteの研究「Real2Render2Real」(Yu, Fu et al., 2025)の概念に触発されています。現実空間をデジタルツイン化し、仮想空間での試行から得た知見を実世界のモーター駆動へ還元する——その設計と実装過程を毎日記録しています。技術アプローチおよび目的は独自のものです。