mechArm 270 Pi 実機データを土台にデジタルツインを作る Log#01 | 綺麗に動くデジタルツインには無い「ズレ」が、実機にはある
デジタルツインを実機に近づける作業をしていて、順番を取り違えていたことに気づいた。ツインの都合でデータの形を決めて、あとから実機を合わせようとすると、合わない。合わないたびに直す。直すたびに別のところがずれる。
正しい順番は逆だった。実機が取れるデータの形を先に決めて、ツインをそれに合わせる。この回は、その順番に行き着くまでに実際に確かめたことを、コードとログをそのまま残しながら書く。
ツインと実機は、同じ角度なら同じ姿勢になる
最初に確かめたのは、デジタルツインが実機の物差しとして使えるかどうかだった。同じ関節角を入れたとき、ツインと実機が同じ姿勢になるのか。ここが合っていなければ、ツインで何を測っても実機の参考にならない。
実機側(Python・pymycobot)と Unity 側(C#・URDF)で、関節番号の振り方を突き合わせた。実機は j0〜j5(0始まり)、Unity の motion_log CSV ヘッダは J1〜J6(1始まり)と、表記が食い違っていた。これが「ずれているように感じる」原因になっていた。だが中身を調べると、表記が違うだけで値の並びは完全に一致していた。
seq2 という教示姿勢 [-45.35, 90.00, -6.94, 0.87, -86.48, -29.53] を、実機と Unity の両方に送って対応を取った全6軸の表が以下になる。
# | 実機表記 | Python 配列 | MotionLog 列 | Unity angles[] | URDF link | 物理軸 | seq2 値 |
|---|---|---|---|---|---|---|---|
1 | j0 |
| J1 |
| link1 | 肩旋回 | -45.35 |
2 | j1 |
| J2 |
| link2 | 肩ピッチ | 90.00 |
3 | j2 |
| J3 |
| link3 | 肘 | -6.94 |
4 | j3 |
| J4 |
| link4 | 手首ピッチ | 0.87 |
5 | j4 |
| J5 |
| link5 | 手首ロール | -86.48 |
6 | j5 |
| J6 |
| link6 | 手首回転 | -29.53 |
配列のインデックスは実機・Unity とも 0始まりで統一されていて、交差や並び替えはない。表記が揺れていたのは motion_log CSV のヘッダだけだった。FK テーブルの実レコードでも並びの一致を確認した。pose_1 = [-46.93, 102.74, -43.33, 3.6, -60.11, -30.0] を J1..J6 順で送ったものが、CSV の j0..j5 列に同順で入っている。
ee_x ee_y ee_z j0 j1 j2 j3 j4 j5
0.238328, 0.025111, 0.208041, -46.930, 102.740, -43.330, 3.600, -60.110, -30.000motion_log の J1 が CSV の j0、J2 が j1、と完全に1対1で対応している。表記の食い違いだけで、姿勢のずれは無い。ツインは実機の物差しとして使える。
同じ角度を入れても、ツインと実機の「値」は一致しない
物差しになるとは確かめた。だが、ここで最初の差分が顔を出す。
関節の向きが実機と合っているかを、符号で裏取りした。実機側で校正済みなのは j0 だけで、MM_PER_DEG_J0_X = -6.63 という値を持っている。実機の j0 を +1° 動かすと、爪先の x が -6.63 mm 動く。マイナス符号。
同じことを Unity の FK テーブルから算出した。j0 以外を固定して j0 だけ振った3点を抜き出す。
j0 (°) | j1 (°) | j2 (°) | j3 (°) | j4 (°) | j5 (°) | ee_x (m) | Δj0 (°) | Δee_x (mm) | mm/deg |
|---|---|---|---|---|---|---|---|---|---|
-37.0 | 95.0 | -49.0 | 18.5 | -70.0 | -30.5 | 0.231374 | baseline | — | — |
-32.0 | 95.0 | -49.0 | 18.5 | -70.0 | -30.5 | 0.210665 | +5 | -20.71 | -4.14 |
-27.0 | 95.0 | -49.0 | 18.5 | -70.0 | -30.5 | 0.188352 | +10 | -43.02 | -4.30 |
結果は次の通り。
項目 | 実機 | Unity(FK テーブル実測) |
|---|---|---|
j0 +方向時の ee.x 符号 | マイナス(-6.63 mm/deg) | マイナス(-4.2 mm/deg) |
符号一致 | ✅ 一致 | |
大きさ | -6.63 mm/deg | -4.2 mm/deg |
大きさの差 | 約 36% 差 |
符号は一致した。向きは合っている。Unity の joints[0] の回転軸と実機 j0 の回転軸が同じ方向を向いている、という裏取りは取れた。
だが大きさは -6.63 と -4.2 で、36% 違う。これは校正した姿勢が違うためで(実機は PICK 直前の姿勢、FK 抽出は別の姿勢)、ヤコビアンが違うので当然ではある。説明はつく。それでも、ここに出ているのは「向きは合うが、値は一致しない」という事実だ。同じ関節を同じだけ動かしても、ツインの予測と実機の実測は、ぴったりにはならない。物差しとしては使えるが、目盛りが完全に同じわけではない。
実機が一回の試行で記録する項目
順番を取り違えていたのは、この後だった。ツイン側にも、実機と並べられる記録を作りたい。シミュレーションと現実の差を見るためだ。ここで「ツインで作りやすい項目」から決めると、実機を繋いだとき項目がずれて作り直しになる。
だから実機が取れる項目を先に確定した。実機は一回の試行を、次の36項目で一行に記録している。
timestamp, run_stem, trial_no,
pick_verdict, pick_reason, pick_confidence,
place_verdict, place_reason, place_confidence,
block_pick_x, block_pick_z, block_place_x, block_place_z,
box_x, box_z, in_box_geom, in_box_dist_mm,
final_j1, final_j2, final_j3, final_j4, final_j5, final_j6,
gripper, place_capture_path,
tip_pick_x, tip_pick_z,
tip_block_dx_mm, tip_block_dz_mm, tip_block_dist_mm,
run_dir, tip_capture_path, pick_capture_path,
align_j0_final, align_red_x_mm, align_converged実際の一行はこうなる。把持に成功した試行の記録から、値の入り方が分かる。
2026-06-18T14:44:00.224119, run_0001_20260618_144228, 1,
yes, 爪の先端部分に赤い四角いブロックが見えており、グリッパーの指がブロックを囲むように位置していることから把持していると判断できる, 0.85,
unknown, swept_retreat_failed, 0.0,
191.1, 258.2, , , -208.1, 207.5, , ,
, , , , , ,
0, , , , , , -0.26, 1.66, 2.02, 0.17, -2.37, -29.61, ...ここには、姿勢(final_j1〜j6)、物体の位置(block・box の x/z)、成否の判定(pick_verdict と、その理由のテキスト、確信度 0.85)、爪先と物体の距離(tip_block_dist_mm)が入っている。判定の理由が自然文で残っているのは、カメラ画像を見て成否を判断させているからだ。
重要なのは、これらが「実機が取れるもの」だという点だ。カメラがあるから物体の位置が取れる。把持判定ができるから verdict が取れる。実機のセンサーとスクリプトが取れる範囲が、この36項目を決めている。ツインの都合ではない。
ツイン側の記録は、この36項目に揃える。同じ列・同じ並びにする。ツインで取れる項目(姿勢、先端位置)は埋める。実機にしかない項目(カメラ判定、実機写真の経路)は空にするか印を置く。項目を同じにしないと、そもそも実機の行とツインの行を並べられない。並べられなければ、差は測れない。
ツインが同じ角度で出力する値
ツイン側で、実機と同じ8つの教示姿勢を Unity 内で取らせて、各個体がどの姿勢を取っているかと、出力される先端位置を記録した。9体(HOME と seq2〜9)を並列で動かし、それぞれの関節角を出力させたログが以下になる。
[FKScanner_MotionBased][LayerB] robot[0]=base worldPos=(0.000,0.000,0.000) angles=[0.00,0.00,0.00,0.00,0.00,-30.00]
[FKScanner_MotionBased][LayerB] robot[1]=base worldPos=(0.000,0.000,1.000) angles=[-45.35,90.00,-6.94,0.87,-86.48,-29.53]
[FKScanner_MotionBased][LayerB] robot[2]=base worldPos=(0.000,0.000,2.000) angles=[-50.88,89.73,0.00,-3.60,-91.66,-30.14]
[FKScanner_MotionBased][LayerB] robot[3]=base worldPos=(0.000,0.000,3.000) angles=[-55.45,85.95,5.88,-6.85,-91.58,-29.79]
[FKScanner_MotionBased][LayerB] robot[4]=base worldPos=(0.000,0.000,4.000) angles=[-55.89,81.65,13.53,-5.18,-91.84,-29.88]
[FKScanner_MotionBased][LayerB] robot[5]=base worldPos=(-1.000,0.000,0.000) angles=[-56.16,77.34,20.91,-1.93,-91.93,-29.00]
[FKScanner_MotionBased][LayerB] robot[6]=base worldPos=(-1.000,0.000,1.000) angles=[-64.07,77.78,20.30,-10.28,-92.37,-33.39]
[FKScanner_MotionBased][LayerB] robot[7]=base worldPos=(-1.000,0.000,2.000) angles=[-65.74,70.13,28.38,-6.32,-88.76,-29.00]
[FKScanner_MotionBased][LayerB] robot[8]=base worldPos=(-1.000,0.000,3.000) angles=[-63.98,59.85,35.68,3.42,-78.39,-29.00]robot[1] の angles [-45.35,90.00,-6.94,0.87,-86.48,-29.53] は、先ほどの Rosetta 表の seq2 と完全に一致している。実機の台帳に入る final_j1〜j6 と、ツインのこの angles は、同じ姿勢を指す。つまり実機の一行とツインの一行を、同じ姿勢で並べられる状態になった。
ツインはこの通りに、誤差なく動く。指定した角度で、指定した姿勢になる。実機は同じ角度を送っても、たわみやガタやキャリブのずれで、先端が予測した位置からずれる。先ほどの j0 で見た「向きは合うが値は違う」が、すべての姿勢で起きる。ツインには無く、実機にはある。この差分こそが、並べて初めて見えるものだ。
ツインは綺麗に動きすぎる
ここまで手を動かして、はっきりしたことがある。
ツインは関節角を入れたら、その通りに動く。誤差がない。だからツイン同士を並べても、差は出ない。全部が予測どおりだからだ。差分という概念が、そもそも要らない。
実機は違う。正しい角度を送っても、たわみがある。ガタがある。キャリブレーションのわずかなずれがある。同じ指令でも、実機の先端は予測した位置からずれる。この「正しく入れても出るずれ」は、ツインには存在しない。実機にしかない。
デジタルツイン | 実機 | |
|---|---|---|
同じ指令への応答 | 予測どおり・誤差なし | たわみ・ガタ・ずれが出る |
差分データ | 出ない(要らない) | 必ず出る(情報になる) |
役割 | 理想を示す | 現実を示す |
データ設計上の位置 | 土台に合わせる側 | 土台 |
ここまで来て、外の世界で起きていることが腑に落ちた。模倣学習でも、大規模なロボット基盤モデルでも、実機データの収集に大きなコストがかけられている。シミュレーションだけでは足りないとされ、実機で動かして集めたデータが重視される。理由は、手を動かして分かったことと同じだ。シミュレーションは綺麗に動きすぎる。綺麗すぎて、現実にあるずれが消えてしまう。そのずれこそが、現実で動かすために必要な情報だ。だから実機データを集める。シミュレーションで代用できない部分が、そこにある。
iPhone と Unity で個人がデジタルツインを作る。その作業の中でも、同じ構造に突き当たった。ツインは土台にならない。実機が土台で、ツインはそこに合わせる。実機データの形を正として、ツインをそこへ寄せていく。
このシリーズで作っていくもの
この回でやったのは、実機とツインを同じ形で並べられる状態に近づけることだった。関節が一対一で対応していることを確かめ、ツインが実機の物差しとして使えることを確認した。実機が一回の試行で取る36項目を正として確定し、ツイン側にも同じ項目の記録を作る準備をした。そして j0 の符号照合で、向きは合うが値は一致しないという、最初の差分を確認した。
並べる器ができれば、次は差分そのものを見ていく。どの姿勢で、どれだけずれるか。そのずれをどう記録し、どう使うか。実機データを土台に、ツインをそこへ寄せていく作業を、これから一つずつ積んでいく。
実機が土台で、ツインはそこに合わせる。綺麗に動くツインには無いずれが、実機にはある。そのずれを残すために、まず形を揃える。
