mechArm 270 Pi 動的追従の限界を探る Log#17 | 視覚サーボ Y 補正の最短解。2関節を比べて j4 単独に落ち着いた
XYZ_TRACK の最後の設計判断は、Y 補正にどの関節を使うかだった。机上では決めきれなかったので、3 姿勢で実測した。
💡 Key Takeaway
Y 補正を j2 と j4 のどちらに担わせるか。3 姿勢での実測の結果、j4 の Z 干渉は j2 の 1/5 以下と判明。j4 を「Y 専用関節」として単独起用し、当初予定の異符号連動設計を破棄。
発生した問題 | 現場判断 | 結果 |
|---|---|---|
Y 補正に j2 と j4 のどちらを使うか机上で決められない | 3 姿勢で単独動作のキャリブをスクリプト化して実測 | j4 単独で Y 補正可能と確定。当初の j2+j4 連動設計は不要に |
最初のテスト Trial 1 で j1 が +1.11° 重力ドリフトして ABORT | 「動かす関節」と「動かさない関節」で到達許容差を分離(ACTIVE 1.5°/STATIC 2.0°) | 重力ドリフトに耐えるロジックに変更、テスト続行 |
±1° 指令で実機が動かない(サーボの不感帯) | Δ角度を ±5°/±3° に変更、検出を 3 回平均化して range をログ出力 | データのばらつきが消え、線形性が見えた |
XYZ_TRACK の最後の設計判断
視覚サーボピックの全体フローは HOME → OPEN → XYZ_TRACK → GRIP → LIFT → HOME の 6 段。中核の XYZ_TRACK は上カメラ(XZ 平面)と横カメラ(Y)の両方を同時に見て、爪先とブロックの 3 軸差分を 1 つのループで閉じるフェーズだ。
前セッションまでに、3 軸の補正方針は 2 軸分が決まっていた。X 補正は j0 単独。Z 補正は LOG189 で確立した j1 + j2 + j4 の連動。残る Y 補正だけが未確定だった。
事前の見立てはこうだ。「j2 と j4 はどちらも垂軸(ピッチ系)の関節で、両方とも Y に効く。ただし j2 は Z にも強く効くから、j2 と j4 を異符号で連動させて Z 干渉を打ち消しながら Y を動かす設計になる」。
机上では筋が通って見えた。しかしこの連動比率を決めるには、j2 と j4 が「1° あたり Y にどれだけ・Z にどれだけ動かすか」を実測する必要がある。机上の前提(j2 も j4 も同程度に Y に効く)が間違っていれば、ループ全体が動かない。
実測することにした。
y_calibration.py を作る
新規スクリプト y_calibration.py の仕様はシンプルだ。
mechArm を APPROACH 姿勢に戻す。j2 を ±5°, ±3° の 4 段階で単独動作させ、上カメラ TIP(X, Z)と横カメラ TIP(Y pixel)の動きを記録する。j4 を ±5°, ±3° で同じことをする。合計 8 試行を 1 セッションで取って CSV に保存する。
メインループの骨格はこうだ。
TRIALS = [
(2, +5.0), (2, +3.0), (2, -3.0), (2, -5.0),
(4, +5.0), (4, +3.0), (4, -3.0), (4, -5.0),
]
for trial_idx, (joint_idx, delta_deg) in enumerate(TRIALS, start=1):
# (a) ベース姿勢に戻す
daemon_send_angles(APPROACH_PICK_BASE, speed=SPEED, wait=WAIT_SEC)
# (b) before 計測(上カメラXZ + 横カメラY)
x_bef, z_bef, y_pix_bef = measure_both_with_retry(cap_top, cap_side, tag="before")
# (c) Δ加算して目標姿勢に動かす
target_angles = list(APPROACH_PICK_BASE)
target_angles[joint_idx] += delta_deg
daemon_send_angles(target_angles, speed=SPEED, wait=WAIT_SEC)
# (d) after 計測
x_aft, z_aft, y_pix_aft = measure_both_with_retry(cap_top, cap_side, tag="after")
# (e) 実関節角度取得 + 到達確認
measured = daemon_get_angles()
# ... ACTIVE/STATIC TOLでチェック
# (f) 差分計算してCSV行に追加
dx_mm = x_aft - x_bef
dz_mm = z_aft - z_bef
dy_pix = y_pix_aft - y_pix_bef既存の visual_servo_pick.py から daemon_send_angles、detect_tip_from_frame をインポートして再利用。横カメラ TIP は前セッションで作った side_camera_utils.detect_tip_side() を使う。新規実装は最小限にした。
このスクリプトに辿り着くまでに、外部レビュー(codex / gemini)で 3 ラウンドの指摘と修正があった。物理リスクのあるコードは必ずレビューを挟むという LOG200 で実証したルールを守った結果だ。
1 回目のレビュー:HOME 戻り・到達確認・ESC 検出
最初に書き上げた版に対して、レビューで以下 3 点が指摘された。
HOME 戻りの wait が短い。return_to_home() で WAIT_SEC=2 を使っていたが、APPROACH から HOME への移動量は大きい。既存の visual_servo_pick.send_home() は 4 秒待ちだった。HOME_WAIT_SEC=4 を別定数として定義し直した。
目標角度到達のチェックがない。daemon_get_angles() で実関節角度は取得していたが、目標との差を確認していなかった。daemon が遅延したり指令が届いていなかった場合でも、データが CSV に残ってしまう。目標角度との誤差が許容を超えたら中断するロジックを入れた。
ESC 検出が試行の先頭でしか働かない。1 試行は約 15 秒かかる。その間に押した ESC は次のループまで反映されない。base 送信後・before 検出後・target 送信後・after 検出後の 4 箇所で ESC チェックを追加した。
修正版で実機テストに入った。
2 回目のレビュー:daemon の wait 引数が機能していない
修正版を再レビューしたところ、より深い問題が判明した。daemon_send_angles(..., wait=N) の wait 引数を Pi 側 mecharm_daemon.py が無視していた。引数として受け取ってはいるが time.sleep(wait) を呼んでいない。
これに気づかず HOME 復帰の wait=4 を渡しても、実際には 0 秒で次の処理に進んでしまっていた。アームが動き終わる前に次の検出が走る可能性がある。
修正は Mac 側で time.sleep() を明示的に入れる形にした。daemon 側を変えると visual_servo_pick.py 等の既存スクリプトに副作用が出るリスクがある。y_calibration.py 単独で完結させた。
daemon_send_angles(HOME_ANGLES, speed=SPEED, wait=HOME_WAIT_SEC)
time.sleep(HOME_WAIT_SEC) # daemon 側 wait 未実装のため Mac 側で明示待機同じパターンを base 姿勢送信後と target 姿勢送信後にも入れた。target 後は到達確認ロジックがあるので必須ではないが、安全側に倒した。
daemon の wait 未実装は中期 TODO として残す。今は visual_servo_pick.py が動作中だから触らない。
1 回目の実機テスト:j1 の重力ドリフトで即 ABORT
修正版を実機で走らせた。1 試行目で即 ABORT。
--- Trial 1/8 j2 delta=+3.0° ---
目標姿勢: [0.0, 87.74, -17.0, 3.6, -90.0, -30.0]
[ABORT] trial 1 目標角度未到達 max_err=1.110° > 0.5°
target=[0.0, 87.74, -17.0, 3.6, -90.0, -30.0]
measured=[-0.26, 88.85, -16.52, 3.25, -89.73, -29.79]軸ごとの誤差を並べる。
軸 | target | measured | err |
|---|---|---|---|
j0 | 0.0 | -0.26 | 0.26 |
j1 | 87.74 | 88.85 | 1.11 |
j2 | -17.0 | -16.52 | 0.48 |
j3 | 3.6 | 3.25 | 0.35 |
j4 | -90.0 | -89.73 | 0.27 |
j5 | -30.0 | -29.79 | 0.21 |
j1 が 1.11° ズレている。j1 は 87.74° という大きな値を保持する関節で、重力負荷を強く受ける。サーボの保持力が重力に負けて、目標値より少し上にドリフトしていた。
肝心の j2 自体は 0.48° で TOL 内に収まっている。測りたい関節は止まっているが、測らない関節が止まれずに ABORT になったという構造だ。
到達許容差を関節ごとに分けることにした。動かす関節(active)は厳密に、動かさない関節(static)は重力ドリフトを許容して緩く。
TOL_REACH_DEG_ACTIVE = 0.5 # 動かす関節(j2 or j4)の到達許容差
TOL_REACH_DEG_STATIC = 2.0 # 動かさない関節(重力ドリフト許容)ループ内のチェックも書き直した。動かす関節の誤差だけ ACTIVE TOL で見て、それ以外は STATIC TOL で見る。
2 回目の実機テスト:±1° で実機が反応しない
ACTIVE/STATIC 分離の修正後、再実行した。Trial 1, 2 は通った。Trial 3 (j2 -1°) で再び ABORT。
--- Trial 3/8 j2 delta=-1.0° ---
目標姿勢: [0.0, 87.74, -21.0, 3.6, -90.0, -30.0]
[ABORT] trial 3 動かす関節 j2 未到達 err=1.230° > 0.5°
measured=[-0.26, 88.85, -19.77, 3.33, -90.43, -29.79]j2 を -20° から -21° に動かそうとしたら、measured は -19.77° で止まっていた。指令と実測の差 1.23°。これは「動こうとして力が足りなかった」のではなく、「動く指令と認識しなかった」ように見える。
3 回再現した。±1° の指令では実機が動かない。パルスを送ってもサーボがピクリともしない。mechArm 270 Pi のサーボにある不感帯(dead band)の存在。数値をいじるだけの机上計算が弾かれた瞬間だった。
加えて、それまでに取れた数試行のデータを見直すと、同じ姿勢・同じ指令でも検出値がばらついていることに気づいた。j2 +3° の試行を 3 回繰り返したときの dy(横カメラ Y 方向の動き)を並べる。
Trial | Δ | run 1 dy | run 2 dy | run 3 dy |
|---|---|---|---|---|
1 | j2 +3.0° | +3 | +11 | +13 |
2 | j2 +1.0° | 0 | -1 | +1 |
3 | j2 -1.0° | ABORT | ABORT | ABORT |
j2 +3° で dy が 3 / 11 / 13 と 4 倍以上ばらついている。1 試行 1 回の検出では信頼性が足りない。横カメラの緑シールが姿勢で影に入ったり光が当たったりして、検出位置が ±10 pix ほどゆらいでいた。
修正は 2 つ入れた。第一に Δ角度を ±5° / ±3° の 2 点に変更(±1° は捨てる)。第二に、各検出を 3 回行って平均化する。ログには range(最大値 − 最小値)も吐き出し、ばらつきの大きい試行を後からスジの悪いデータとして弾けるようにした。
DETECT_AVERAGE_COUNT = 3
DETECT_AVERAGE_INTERVAL_SEC = 0.1
def measure_both_with_retry(cap_top, cap_side, tag=""):
x_samples, z_samples, y_samples = [], [], []
for sample_idx in range(DETECT_AVERAGE_COUNT):
# ... 検出してサンプルを溜める ...
avg_x = sum(x_samples) / len(x_samples)
avg_z = sum(z_samples) / len(z_samples)
avg_y = int(round(sum(y_samples) / len(y_samples)))
print(f" [AVG] {tag} samples={DETECT_AVERAGE_COUNT}: "
f"x={avg_x:.2f}mm (range={max(x_samples)-min(x_samples):.2f}) "
f"y={avg_y}pix (range={max(y_samples)-min(y_samples)})")
return avg_x, avg_z, avg_yTOL_REACH_DEG_ACTIVE も 0.5° → 1.0° に緩めた。±5° の動きでも未到達リスクがあるため。
3 回目の実機テスト:高姿勢で 7/8 完走
修正版で走らせた。Trial 1〜7 が通り、Trial 8 (j4 -5°) で ABORT。
[Trial 1/8] j2 +5.0deg | dx=-0.3mm dz=-8.1mm dy=+34pixel
[Trial 2/8] j2 +3.0deg | dx=-1.0mm dz=-4.8mm dy=+30pixel
[Trial 3/8] j2 -3.0deg | dx=+1.2mm dz=+5.3mm dy=-33pixel
[Trial 4/8] j2 -5.0deg | dx=+1.9mm dz=+7.8mm dy=-55pixel
[Trial 5/8] j4 +5.0deg | dx=-1.2mm dz=+0.7mm dy=+56pixel
[Trial 6/8] j4 +3.0deg | dx=-0.1mm dz=+0.2mm dy=+12pixel
[Trial 7/8] j4 -3.0deg | dx=+1.2mm dz=-1.7mm dy=-42pixel
--- Trial 8/8 j4 delta=-5.0° ---
[ABORT] trial 8 動かす関節 j4 未到達 err=1.050° > 1.0°
measured=[-0.26, 88.85, -19.42, 3.51, -93.95, -29.79]Trial 8 は j4 を -90° から -95° に動かそうとして -93.95° で止まった。err 1.05°、TOL 1.0° を 0.05° だけ超過。実機の動作限界近くで起こる微小な未到達だ。TOL をさらに緩めても良かったが、7 試行で十分なデータが取れているので、ここで止めて分析に入った。
CSV ファイル: ~/r2r2r-scripts/logs/y_calibration_20260516_202140.csv
このデータから 1° あたりの動きベクトルを抽出する。j2 の 4 試行を平均すると:
関節 | dx mm/° | dz mm/° | dy pix/° |
|---|---|---|---|
j2(高姿勢、j2=-20°基準) | -0.3 | -1.6 | +10 |
j4(高姿勢、j4=-90°基準) | -0.2 | +0.3 | +10 |
ここで重要な発見があった。j2 も j4 も Y には同じくらい効く(約 +10 pix/°)が、Z への干渉が桁違いに違う。j2 は Z に -1.6 mm/° と強く効くのに対して、j4 は +0.3 mm/° しか動かない。約 5 倍の差だ。
これは事前の見立てを覆す結果だった。「j2 と j4 を異符号連動で Z 干渉を打ち消す」という設計の前提は、j2 と j4 の Z 干渉が同程度であることだった。実際は片方だけが Z に強く効く。だったら Z 干渉が小さい j4 単独で Y を補正すればいい。
ただし、ここで判断を確定させなかった。APPROACH 姿勢(j2=-20、爪先がマットから離れた高い位置)でのデータでしかない。実際の XYZ_TRACK ループは、爪先がブロックに近づくにつれて姿勢が変わる。姿勢が変わると感度も変わる可能性が高い。
姿勢依存性を確認するため、もう 2 つの姿勢でもキャリブを取ることにした。
低姿勢の決定:実機を目視で動かして探す
XYZ_TRACK の収束ゴールは「爪先がブロック直上にある」状態。GRIP に移行できる高さだ。この姿勢を机上ではなく実機で目視判定して決めることにした。
横カメラのプレビューを開きながら、daemon_send_angles() で j2 を 5° ずつ動かして爪先の高さを観察した。
ステップ | j2 | 観察 |
|---|---|---|
1 | -15° | 爪先がブロック上面より少し上 |
2 | -10° | さらに下がる |
3 | -5° | ブロック上面付近 |
4 | -2° | ブロック上面と同じ高さ。掴める高さ |
4 ステップ目(j2 = -2°)を採用。これを新しい BASE 姿勢として APPROACH_PICK_BASE_LOW = [0.0, 87.74, -2.0, 3.6, -90.0, -30.0] と定義した。y_calibration.py の定数を書き換えて再実行。
ここで直感とコードが完全に逆を向く。j2 = elbow ピッチ のマイナス方向は、肘を伸ばす動き。数字の上では「-20° から -2° へ増えている」ように見えるのに、物理的には「肘を曲げて爪先が下がる」。実機を見ずにコードだけで組むと、高確率で逆方向に動かして激突させる罠だ。
低姿勢キャリブ:8/8 完走
低姿勢で y_calibration.py を実行した。全 8 試行が完走した。
[Trial 1/8] j2 +5.0deg | dx=+0.2mm dz=-6.7mm dy=-4pixel
[Trial 2/8] j2 +3.0deg | dx=-0.7mm dz=-6.5mm dy=+21pixel
[Trial 3/8] j2 -3.0deg | dx=+1.0mm dz=+7.2mm dy=-23pixel
[Trial 4/8] j2 -5.0deg | dx=+1.7mm dz=+10.0mm dy=-36pixel
[Trial 5/8] j4 +5.0deg | dx=-0.5mm dz=-0.9mm dy=+25pixel
[Trial 6/8] j4 +3.0deg | dx=-0.1mm dz=-0.5mm dy=+7pixel
[Trial 7/8] j4 -3.0deg | dx=+1.0mm dz=+1.4mm dy=-34pixel
[Trial 8/8] j4 -5.0deg | dx=+1.0mm dz=+0.9mm dy=-28pixelCSV ファイル: ~/r2r2r-scripts/logs/y_calibration_low_20260516_203456.csv
Trial 1 (j2 +5°) で dy が -4 pix と他と方向が逆になっているのは検出ノイズ。range をログで確認すると、横カメラの緑シールが姿勢で影に入る瞬間があった試行で大きい値が出ている。3 回平均化が効いてはいるが、完全には消しきれない。
平均値を出す。
関節 | dx mm/° | dz mm/° | dy pix/° |
|---|---|---|---|
j2(低姿勢、j2=-2°基準) | -0.2 | -2.0 | +7.3 |
j4(低姿勢、j4=-90°基準) | -0.2 | -0.25 | +6 |
低姿勢でも j4 の Z 干渉は -0.25 mm/° と小さい。j2 の Z 干渉は -2.0 mm/° と高姿勢よりも強くなっている。さらに、j4 の Y 感度(dy/°)が高姿勢の +10 から低姿勢の +6 に下がっている。姿勢が低くなると j4 の Y 感度が落ちる——これは想定通りだが、定量的に確認できた。
中間姿勢でのキャリブ:線形性の確認
高姿勢 (j2=-20) と低姿勢 (j2=-2) の間に、中間姿勢 (j2=-11) を入れた。高姿勢と低姿勢の中点だ。線形補間が成立するかを見るためだ。
中間姿勢は Trial 6 (j4 +3°) で ABORT(err=1.59° > 1.5°)。j2 4 試行 + j4 1 試行が取れた。
[Trial 1/8] j2 +5.0deg | dx=-0.1mm dz=-8.6mm dy=+23pixel
[Trial 2/8] j2 +3.0deg | dx=-0.7mm dz=-5.3mm dy=+21pixel
[Trial 3/8] j2 -3.0deg | dx=+1.0mm dz=+6.2mm dy=-29pixel
[Trial 4/8] j2 -5.0deg | dx=+1.9mm dz=+9.2mm dy=-47pixel
[Trial 5/8] j4 +5.0deg | dx=-1.0mm dz=-1.0mm dy=+41pixelCSV ファイル: ~/r2r2r-scripts/logs/y_calibration_mid_20260516_204525.csv
j4 は中間姿勢で 1 点しか取れなかったが、結果は dy=+8.2 pix/°。高姿勢 (+10) と低姿勢 (+6) の中点が +8 で、ほぼピッタリ。線形補間が成立しそうだと判断して、ここでキャリブを終わらせた。j4 の追加データは、実装に入ってから XYZ_TRACK の中で観察できる。
3 姿勢の比較
集まったデータを 1 つの表にまとめる。
関節 | 量 | 高姿勢 (j2=-20) | 中姿勢 (j2=-11) | 低姿勢 (j2=-2) | パターン |
|---|---|---|---|---|---|
j2 | dx/° | -0.3 | -0.2 | -0.2 | ほぼ一定 |
j2 | dz/° | -1.6 | -1.85 | -2.0 | 線形に強くなる |
j2 | dy/° | +10 | +7.7 | +7.3 | 急変→安定 |
j4 | dx/° | -0.2 | -0.19 | -0.2 | ほぼ一定 |
j4 | dz/° | +0.3 | -0.19 | -0.25 | 符号反転 |
j4 | dy/° | +10 | +8.2 | +6 | 線形に弱くなる |
ここから設計判断に行く。「Y 効率に対する Z 干渉比」を計算する。Z 干渉の絶対値を Y 効果(pix/°)で割って、mm/pix 単位にする。この値が小さいほど Y 専用関節として優秀だ。
関節 | 高姿勢 |dz|/|dy| | 中姿勢 |dz|/|dy| | 低姿勢 |dz|/|dy| |
|---|---|---|---|
j2 | 0.16 mm/pix | 0.24 mm/pix | 0.27 mm/pix |
j4 | 0.03 mm/pix | 0.02 mm/pix | 0.04 mm/pix |
j4 は j2 より 5〜13 倍も Y 専用に近い。
結論が出た。Y 補正は j4 単独で行う。j2 は使わない。当初想定していた「j2 と j4 を異符号連動で Z 干渉を打ち消す」設計は不要だった。設計が当初想定の半分くらいの複雑さで済むことになった。
面白かったのは、「どの関節が動くか」ではなく「どの関節を Y 専用関節として扱えるか」が問題だったことだ。関節は 6 軸ある。でも制御ループの中では、役割を固定できた瞬間にシステムが単純化する。「j4 = Y」「j0 = X」のように 1 軸 1 関節を割り当てられれば、ループは収束しやすくなる。j4 は元々 Z 補正の連動にも入っていて、Y も足すと役割の重複が起きるが、それは Δ の合算で吸収できる。
j4 感度の線形補間モデル
j4 の Y 感度 (dy/°) は姿勢で線形に変化する(+10 → +8.2 → +6)。XYZ_TRACK ループ中に j2 値が変わるたびに、j4 の感度も変える必要がある。固定値で計算すると、姿勢が低い領域では補正量が過剰になり、ループが振動するリスクがある。
線形補間式:
dy_per_degree_j4(j2) = 6.0 + (10.0 - 6.0) × (j2 - (-2)) / ((-20) - (-2))
= 6.0 - 0.222 × (j2 + 2)検算:
j2 | 計算値 | 実測 | 差 |
|---|---|---|---|
-20° | 10.00 | 10.0 | 0.00 |
-11° | 8.00 | 8.2 | 0.20 |
-2° | 6.00 | 6.0 | 0.00 |
実測との差は最大 0.2 pix/°。誤差 2.5% くらい。XYZ_TRACK の収束条件は ±5 pix なので、ここはもう十分だと思った。3 点で線形性が成立していれば、内挿は安全に使える。範囲外の j2 値(j2 < -20° や j2 > -2°)は実装でクリップする。
XYZ_TRACK の設計が決まった
今回のキャリブ結果から、XYZ_TRACK ループの設計は以下に確定した。
[XYZ_TRACK ループ]
↓
上カメラから現在 TIP (x, z) を取得
横カメラから現在 TIP y_pixel を取得
上カメラから BLOCK (x_target, z_target) を取得
横カメラから BLOCK y_pixel_target を取得
↓
diff_x, diff_z, diff_y を計算
↓
収束判定(全軸が許容内なら GRIP へ)
↓
Δj0 ← diff_x から(X補正、既存ロジック)
Δj1, Δj2, Δj4_for_Z ← diff_z から(Z補正、既存連動)
Δj4_for_Y ← diff_y / dy_per_degree_j4(現在のj2) ← 今回確定
↓
Δj4_total = Δj4_for_Z + Δj4_for_Y
↓
new_angles = current_angles + [Δj0, Δj1, Δj2, 0, Δj4_total, 0]
clamp & safety check
daemon_send_angles(new_angles)
time.sleep(ループ周期)
↓
ループ継続XYZ_TRACK の中で X / Y / Z の 3 軸補正を同時並行で進める。j4 だけが Y と Z の両方に関わるが、Z 補正分の j4_for_Z と Y 補正分の j4_for_Y を合算して 1 つの指令にまとめる。j4 の Y 効率は j2 値(現在の姿勢)で動的に補間する。
収束許容値の初期案は X が ±2mm、Z が ±2mm、Y が ±5pix。ループ周期は LOG182 で実測したソフト制御周波数 91Hz と物理追従 5〜10Hz から、200ms (5Hz) スタートが安全。
実機で測ってから分かったこと
机上では予想できなかった現象がいくつも出てきた。
mechArm 270 Pi のサーボには ±1° の不感帯がある。小さな指令には反応しない。これを知らずに「もっと細かく制御すれば精度が上がる」と思い込んで設計を組むと、ループが振動する。
j1 は重力負荷で +1° 程度ドリフトする。静止しても完全には止まらない。「動かさない関節」も完全に止まっていると仮定すると、到達確認のロジックが ABORT を量産する。
j2 と j4 の Y 感度は姿勢で 30〜40% 変化する。固定値では XYZ_TRACK が安定収束しない。線形補間で姿勢ごとに感度を変える必要がある。
j4 の Z 干渉は姿勢で符号が反転する。高姿勢では +0.3 mm/°、低姿勢では -0.25 mm/°。絶対値が小さいので Y 専用関節として使える結論は変わらないが、Z 補正側の連動係数を姿勢で変える検討は実装段階で必要になる。
机上の計算では、j2 も j4 も同じ Y 補正の候補だった。実測データが提示した答えは「j4 の単独起用」という最短解。j2 を連動させる複雑なロジックは、組む必要すらなかった。
23 試行(高 7 + 低 8 + 中 5、加えてテスト中の ABORT 3 回)の積み重ねが、当初想定より半分シンプルな設計を引き出した。
次のセッションでやること
設計は確定した。実装に移る。
xyz_track_test.pyを新規作成(visual_servo_pick.py に統合する前の単体検証用)HOME → OPEN → XYZ_TRACK で 3 軸収束まで(GRIP は接続しない)
外部レビュー(codex / gemini)
実機実行
動作確認後、visual_servo_pick.py の REFINE フェーズを XYZ_TRACK で置き換え
GRIP / LIFT 接続
動画撮影
Y 軸補正は、関節 1 つで十分だった。それを確かめるために、関節 2 つを 3 姿勢で 23 試行測ることが必要だった。
Physical AI は「複雑な制御を作る競争」に見える。でも実機を触ると逆で、実測を積むほど不要な複雑さを削る作業になっていく。実機は、設計を複雑にするためではなく、設計を削るために触る。
