mechArm 270 Pi 動的追従の限界を探る Log#3 | TIP検出ノイズの正体を特定して初めて赤ブロックを掴んだ
LOG184R2R2R2026/5/2

mechArm 270 Pi 動的追従の限界を探る Log#3 | TIP検出ノイズの正体を特定して初めて赤ブロックを掴んだ

視覚サーボのループは「CONVERGED」と表示されていた。なのにアームは赤ブロックを掴めなかった。今日はその原因を1日かけて特定した。

💡 Key Takeaway

青シール3点をY座標降順でwrist/claw_a/claw_bに割り当てていたロジックが、姿勢によってY座標の順序が入れ替わることで37mmのジャンプを生んでいた。3点間の最短辺で爪先2点を特定する幾何学的アルゴリズムに変更し、TIP検出 stdev を19.42mmから0.08mmへ改善。グリッパー制御を実装し、Trial 3・6で赤ブロックの把持に初成功した。

発生した問題

現場判断

結果

noise_test で赤ブロックが全10回 None を返し続けた

_mat_px_range によるマット範囲チェックが X=[994, 1594] と右半分だけに破綻しており、赤ブロックをマット外と判定していた。テスト目的に限り vsp._mat_px_range = None でバイパス

赤ブロック検出が正常動作。red_x_mm stdev=0.00mm、range=0.01mmを確認

TIP検出が10回中8回同一値(247.42mm)を返し続けた

Y座標降順ソートによる青シール識別ロジックの不安定が原因。3点間の最短辺で爪先を特定する幾何学ベースのアルゴリズムに変更

TIP stdev 19.42mm → 0.08mm。2値間ジャンプ現象が消滅

ループCONVERGEDなのに物理最終位置が最大52mmズレた

APPROACH_PICK姿勢でj0を合わせてから降下するとj1・j4の変化でTIP_Xが連鎖的にズレることが原因。視覚サーボをPICK姿勢(降下後)で実行するよう再設計

Trial 3(diff=+7.5mm)・Trial 6(diff=-0.8mm)で赤ブロックの把持に成功

問いの設定

Log#1・Log#2では視覚サーボの制御周期と振幅の問題を扱った。今回のテーマは「なぜ視覚サーボはCONVERGEDと報告しているのに、アームが赤ブロックを掴めないのか」だ。

前回テストのCSVデータにはすでに奇妙な数字があった。

# 前回テストCSV(視覚サーボ改版)の一部
trial,converged,loop_count,final_tip_x_mm,final_diff_x_mm
1,True,3,174.7,-19.3
4,True,2,-147.2,-7.4
5,True,1,245.8,14.9
6,True,2,202.3,-52.2

Trial 6はループ最終diff=-3.3mmでCONVERGEDと判定しているのに、物理最終位置が-52.2mmズレている。「収束した」と「実際に合った」は別の話だ。

デバッグの入り口として、まず検出系そのものが正常かどうかを確かめることにした。

検出系の切り分け:noise_test

アームを手動で把持姿勢に固定し、10回連続でTIPと赤ブロックの位置を取得する detection_noise_test.py を実行した。

第1回:全10回がNone

# noise_test_20260502_130707.csv(全データ)
i,tip_x_mm,tip_z_mm,red_x_mm,red_z_mm,fixed_angles
1,,,,,"[-35.77, 99.05, -38.75, 0.43, -59.94, -31.46]"
2,,,,,"[-35.77, 99.05, -38.75, 0.43, -59.94, -31.46]"
3,,,,,"[-35.77, 99.05, -38.75, 0.43, -59.94, -31.46]"
4,,,,,"[-35.77, 99.05, -38.75, 0.43, -59.94, -31.46]"
5,,,,,"[-35.77, 99.05, -38.75, 0.43, -59.94, -31.46]"
6,,,,,"[-35.77, 99.05, -38.75, 0.43, -59.94, -31.46]"
7,,,,,"[-35.77, 99.05, -38.75, 0.43, -59.94, -31.46]"
8,,,,,"[-35.77, 99.05, -38.75, 0.43, -59.94, -31.46]"
9,,,,,"[-35.77, 99.05, -38.75, 0.43, -59.94, -31.46]"
10,,,,,"[-35.77, 99.05, -38.75, 0.43, -59.94, -31.46]"

全部Noneだった。スクリプトにデバッグ画像保存を追加した。

# detection_noise_test.py への追加(1回目のフレームで保存)
if i == 0:
    cv2.imwrite(f"{DEBUG_DIR}/raw_frame.png", frame)
    hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
    lower1 = (0, 100, 50);   upper1 = (10, 255, 255)
    lower2 = (160, 100, 50); upper2 = (180, 255, 255)
    m1 = cv2.inRange(hsv, lower1, upper1)
    m2 = cv2.inRange(hsv, lower2, upper2)
    red_mask = cv2.bitwise_or(m1, m2)
    cv2.imwrite(f"{DEBUG_DIR}/red_mask.png", red_mask)

生フレームには赤ブロックがはっきり映っていた。赤マスクには赤ブロックの位置に白い塊が光っていた。つまりHSV閾値は正常。それでもNoneが返ってくる。

detect_red_block() のコードを確認した。

# visual_servo_pick.py(マット範囲チェック箇所)
if _mat_px_range is not None:
    min_x, max_x, min_y, max_y = _mat_px_range
    if not (min_x <= cx <= max_x and min_y <= cy <= max_y):
        continue  # ここで弾かれていた

ターミナルのログには マット範囲: X=[994, 1594] と出ていた。正常時は X=[585, 1863] 程度になるはずで、今回は右半分だけの異常な範囲になっている。マット四隅検出が破綻し、homographyも誤っていた。赤ブロックは画像のX=400〜500ピクセル付近にあったため、この範囲チェックで全部弾かれていた。

検出ノイズを計測するというテストの目的に限り、範囲チェックを無効化した。

# detection_noise_test.py に追加(setup後)
vsp._mat_px_range = None
print("[DEBUG] マット範囲チェック無効化(全画面で検出する)")

第2回:TIPが2値の間をジャンプ

# ターミナル出力(noise_test 第2回)
[検出開始] 10 回測定(間隔 0.5s)
  [ 1/10] TIP=(218.43, 259.732)  RED=(215.442, 257.54)  red_px=(515, 785)
  [ 2/10] TIP=(218.43, 259.732)  RED=(215.442, 257.54)  red_px=(515, 785)
  [ 3/10] TIP=(180.831, 230.411) RED=(215.442, 257.54)  red_px=(515, 785)
  [ 4/10] TIP=(180.831, 230.411) RED=(215.442, 257.54)  red_px=(515, 785)
  [ 5/10] TIP=(180.831, 230.411) RED=(215.442, 257.54)  red_px=(515, 785)
  [ 6/10] TIP=(180.831, 230.411) RED=(215.442, 257.54)  red_px=(515, 785)
  [ 7/10] TIP=(180.831, 230.411) RED=(215.442, 257.54)  red_px=(515, 785)
  [ 8/10] TIP=(180.831, 230.411) RED=(215.442, 257.54)  red_px=(515, 785)
  [ 9/10] TIP=(218.43, 259.732)  RED=(215.442, 257.54)  red_px=(515, 785)
  [10/10] TIP=(218.43, 259.732)  RED=(215.442, 257.54)  red_px=(515, 785)

統計サマリー (mm単位)  固定姿勢: [-38.23, 101.16, -45.26, -1.93, -55.81, -29.26]
  tip_x_mm: n=10  mean=+195.87  stdev=19.42  range=37.60  (+180.83 〜 +218.43)
  tip_z_mm: n=10  mean=+242.14  stdev=15.14  range=29.32  (+230.41 〜 +259.73)
  red_x_mm: n=10  mean=+215.44  stdev= 0.00  range= 0.00  (+215.44 〜 +215.44)
  red_z_mm: n=10  mean=+257.54  stdev= 0.00  range= 0.00  (+257.54 〜 +257.54)

赤ブロックはstdev 0.00mmで完全に安定している。TIPだけが大きく揺れている。

10回の内訳を見ると、2つの値の間を行き来していた。

[1, 2, 9, 10] : TIP=(218.43, 259.732)   ← 4回
[3〜8]        : TIP=(180.831, 230.411)  ← 6回

ランダムなノイズではなく、離散的な2値間のジャンプだった。37.60mmというrangeはまさに2つのTIP値の差そのものだ。

原因特定:青シール識別ロジックの脆弱性

2値間ジャンプが起きているということは、「どのシールをwrist/claw_a/claw_bに割り当てるか」がフレームごとに入れ替わっていることを意味する。

現状の identify_blue_markers はY座標降順でソートしていた。

# 旧実装(visual_servo_pick.py L269-280)
def identify_blue_markers(blue_points, homography):
    sorted_by_y = sorted(blue_points, key=lambda p: p[1], reverse=True)
    claw_a_px = sorted_by_y[0]   # Y座標が最大
    claw_b_px = sorted_by_y[1]   # Y座標が2番目
    wrist_px  = sorted_by_y[2]   # Y座標が最小
    ...

今回の姿勢でwristに近い2点のY座標が拮抗していた。撮影タイミングのわずかなノイズで順序が入れ替わる。wristとclawが混ざるとTIPの計算結果(claw_aとclaw_bの中点)が大きく動く。37mmのジャンプはwristとclawが入れ替わったときのTIP移動距離と一致していた。

修正:幾何学ベースの割り当てに変更

claw_aとclaw_bは物理的に爪先で近接している。wristは離れた位置にある。この構造は姿勢に関係なく成立する。3点間の最短辺の両端をclaw_a/claw_b、残りをwristと割り当てる。

# 新実装(DAY184改 visual_servo_pick.py L268-310)
def identify_blue_markers(blue_points, homography):
    """
    3点を wrist/claw_a/claw_b に割り当てる。
    3点間の最短辺で爪先2点を特定。X座標で claw_a/claw_b を区別。
    """
    import math
    p0, p1, p2 = blue_points

    d01 = math.hypot(p0[0] - p1[0], p0[1] - p1[1])
    d02 = math.hypot(p0[0] - p2[0], p0[1] - p2[1])
    d12 = math.hypot(p1[0] - p2[0], p1[1] - p2[1])

    if d01 <= d02 and d01 <= d12:
        wrist_px = p2;  claw_pts = [p0, p1]
    elif d02 <= d01 and d02 <= d12:
        wrist_px = p1;  claw_pts = [p0, p2]
    else:
        wrist_px = p0;  claw_pts = [p1, p2]

    claw_pts.sort(key=lambda p: p[0])
    claw_a_px = claw_pts[0]
    claw_b_px = claw_pts[1]

    def to_unity(px, py):
        pt = np.array([[[float(px), float(py)]]], dtype=np.float32)
        r = cv2.perspectiveTransform(pt, homography)
        return float(r[0][0][0]) * 0.001, float(r[0][0][1]) * 0.001

    wrist_ux,  wrist_uz  = to_unity(*wrist_px)
    claw_a_ux, claw_a_uz = to_unity(*claw_a_px)
    claw_b_ux, claw_b_uz = to_unity(*claw_b_px)

    return {
        "wrist":  (wrist_px[0],  wrist_px[1],  wrist_ux,  wrist_uz),
        "claw_a": (claw_a_px[0], claw_a_px[1], claw_a_ux, claw_a_uz),
        "claw_b": (claw_b_px[0], claw_b_px[1], claw_b_ux, claw_b_uz),
    }

修正後のnoise_test結果(第3回)

# ターミナル出力(noise_test 第3回 / 新ロジック適用後)
  [ 1/10] TIP=(265.481, 195.972)  RED=(258.277, 192.764)  red_px=(421, 665)
  [ 2/10] TIP=(265.481, 195.972)  RED=(258.277, 192.764)  red_px=(421, 665)
  [ 3/10] TIP=(265.737, 195.477)  RED=(258.277, 192.269)  red_px=(421, 664)
  [ 4/10] TIP=(265.481, 195.972)  RED=(258.277, 192.764)  red_px=(421, 665)
  [ 5/10] TIP=(265.481, 195.972)  RED=(258.277, 192.764)  red_px=(421, 665)
  [ 6/10] TIP=(265.481, 195.972)  RED=(264.069, 201.798)  red_px=(410, 683)
  [ 7/10] TIP=(265.481, 195.972)  RED=(264.069, 201.798)  red_px=(410, 683)
  [ 8/10] TIP=(265.481, 195.972)  RED=(264.069, 201.798)  red_px=(410, 683)
  [ 9/10] TIP=(265.481, 195.972)  RED=(264.069, 201.798)  red_px=(410, 683)
  [10/10] TIP=(265.481, 195.972)  RED=(264.069, 201.798)  red_px=(410, 683)

統計サマリー (mm単位)  固定姿勢: [-52.55, 96.15, -34.27, -4.57, -60.55, -28.38]
  tip_x_mm: n=10  mean=+265.51  stdev= 0.08  range= 0.26  (+265.48 〜 +265.74)
  tip_z_mm: n=10  mean=+195.92  stdev= 0.51  range= 1.50  (+195.48 〜 +195.97)
  red_x_mm: n=10  mean=+258.27  stdev= 0.00  range= 0.01  (+258.27 〜 +258.28)
  red_z_mm: n=10  mean=+192.71  stdev= 0.16  range= 0.50  (+192.27 〜 +192.76)

  diff_x (TIP-RED): mean=+7.24  stdev=0.08  range=0.25
  diff_z (TIP-RED): mean=+3.21  stdev=0.00  range=0.00

全項目stdev < 0.2mm。2値間ジャンプが消えた。

「見えているのに合わせられない」の構造

検出系が安定した状態で本番の視覚サーボを再テストした。ループ収束率は5/6まで改善した(前回3/6)。しかし「ループはCONVERGEDなのに物理最終位置がズレる」問題は残っていた。

カメラはマット平面(XZ)を真上から見ている。赤ブロックのX座標もTIPのX座標も見える。しかし6軸アームは、同じTIP位置(X, Z)に対して無数の関節角度の組み合わせを持つ。視覚サーボはAPPROACH_PICK姿勢でj0だけを調整してTIP_Xを目標に合わせる。その後PICK姿勢に降りるとき、j1とj4が変化することでTIP_Xも連鎖的に動いてしまう。

実際のテストデータで確認できる。

# DAY184改テスト(APPROACH_PICK姿勢でサーボ実行)
Trial 3:
  ループ最終: TIP_X=+150.1mm  Target=+155.4mm  diff=+5.3mm  [CONVERGED]
  物理最終:   TIP_X=+174.7mm  目標との差=-19.3mm  乖離=24.6mm

Trial 6:
  ループ最終: TIP_X=+153.4mm  Target=+150.1mm  diff=-3.3mm  [CONVERGED]
  物理最終:   TIP_X=+202.3mm  目標との差=-52.2mm  乖離=48.9mm

「APPROACH姿勢でのTIP_X」と「PICK姿勢でのTIP_X」は別物だった。

動作シーケンスの再設計

視覚サーボの実行タイミングをPICK姿勢(降下後)に変えた。同時にグリッパー制御とLIFT動作を追加して動作シーケンスを確定した。

# 動作シーケンス(DAY184改で確定)

1. HOME
   HOME = [0, 0, 0, 0, 0, -30]

2. GRIPPER OPEN
   mc.set_gripper_state(0, 100)  # state=0: 開く

3. APPROACH_PICK へ移動
   approach_angles = [j0_lookup, 87.74, -38.33, 3.6, -70.11, -30.0]

4. DESCEND(j0はAPPROACH時のまま・j1を固定値で降下)
   PICK_J1_FIXED = 90.87
   descend_angles = [j0_lookup, PICK_J1_FIXED, j2_lookup, j3_lookup, j4_lookup, j5_lookup]

5. PICK_VISUAL_SERVO(PICK姿勢のままj0だけ微調整)
   CONVERGE_THRESHOLD_MM = 10
   MAX_DELTA_DEG = 5.0
   J0_RELATIVE_LIMIT = 15.0

6. 最終TIP測定(GRIP前・評価用)

7. GRIP
   mc.set_gripper_state(1, 100)  # state=1: 閉じる

8. LIFT(APPROACH_PICK姿勢に戻る・j0は視覚サーボの最終値)
   lift_angles = [final_j0, 87.74, -38.33, 3.6, -70.11, -30.0]

9. HOME復帰(爪は閉じたまま・赤ブロックを保持)

j1を90.87°に固定したのは、赤ブロックが常にマット平面に置かれているためだ。lookupで決まるj1が90.61〜90.96°でバラつくより固定値の方が安定する。LIFT動作はPICK失敗時でも必ず実行する設計にした。グリッパーを閉じたままHOMEに戻ると、赤ブロックをマット上で引きずってしまうためだ。

グリッパー制御関数を実装した。

# visual_servo_pick.py に追加(L373-403)
def ssh_set_gripper(state, speed=100, wait=2):
    """
    グリッパー開閉制御。
    state=0: 開く / state=1: 閉じる
    DAY175既知問題: result=-1 は動作の成否と無関係。連続送信時は2秒以上sleep。
    """
    cmd = (
        f"python3 -c \""
        f"from pymycobot.mycobot import MyCobot; import time; "
        f"mc = MyCobot('/dev/ttyAMA0', 1000000); time.sleep(2); "
        f"result = mc.set_gripper_state({state}, {speed}); "
        f"time.sleep({wait}); "
        f"print('GRIPPER:', 'OPEN' if {state} == 0 else 'CLOSE', 'result=', result)\""
    )
    client = paramiko.SSHClient()
    client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    client.connect(SSH_HOST, username=SSH_USER, key_filename=SSH_KEY_PATH)
    stdin, stdout, stderr = client.exec_command(cmd)
    output = stdout.read().decode("utf-8", errors="ignore").strip()
    error  = stderr.read().decode("utf-8", errors="ignore").strip()
    client.close()
    if output:
        print(f"  [Pi] {output}")
    if error and "no longer maintained" not in error:
        print(f"  [Pi stderr] {error}")

本番テスト結果(7 trial)

新しいシーケンスとグリッパー制御を実装して7 trialを実行した。

# visual_servo_20260502_181427.csv(全データ)
trial,pick_x_mm,pick_z_mm,initial_j0,final_j0,converged,loop_count,final_tip_x_mm,final_diff_x_mm
1, 227.68,243.51,-50.0,-35.077,True, 6, 247.42,-19.74
2, 156.52,256.12,-32.0,-17.0,  False,4, 219.45,-62.94
3,-191.83,256.36, 41.0, 41.0,  True, 1,-199.37,  7.54   ← 把持成功
4, 265.7, 152.83,-45.0,-57.499,False,5, 244.73, 20.97
5,-257.79,148.14, 72.0, 72.0,  False,2,       ,
6,  18.81,306.11,  3.0,  3.0,  True, 1,  19.59, -0.78   ← 把持成功
7, 157.01,255.62,-32.0,-17.0,  False,4, 219.45,-62.44

Trial 3とTrial 6で赤ブロックの把持に成功した。Trial 6はdiff=-0.8mm、loops=1。1ループで収束し、0.8mmの誤差でグリッパーが閉じた。

一方、Trial 1・2・4・7ではループが回っているのにTIP_Xが動かない現象があった。

# Trial 1のhistory(CSVのhistoryカラムより)
loop 1: tip_x=247.67  diff=-19.99  j0=-50.00
loop 2: tip_x=247.42  diff=-19.74  j0=-46.985  ← j0が3°変わってもTIPが動かない
loop 3: tip_x=247.42  diff=-19.74  j0=-44.008  ← さらに3°変えてもTIP変わらず
loop 4: tip_x=247.42  diff=-19.74  j0=-41.031
loop 5: tip_x=247.42  diff=-19.74  j0=-38.054
loop 6: tip_x=229.83  diff=-2.15   j0=-35.077  ← 最後だけ動いた

j0を15°変えているのにTIP_Xがほぼ変わっていない。

j0送信の切り分けテスト

j0_send_test.py でAPPROACH姿勢(j1=87.74)でのj0動作を確認した。

# j0_send_test.py 結果サマリー
send_j0  | measured_j0 |   diff
---------+-------------+--------
  -30.00 |      -29.79 |  +0.21 ✓
  -25.00 |      -24.60 |  +0.40 ✓
  -20.00 |      -19.59 |  +0.41 ✓
  -15.00 |      -14.76 |  +0.24 ✓
  -10.00 |       -9.93 |  +0.07 ✓
   -5.00 |       -4.92 |  +0.08 ✓
   +0.00 |       +0.00 |  +0.00 ✓

全送信値±0.41°以内で一致。APPROACH姿勢では ssh_send_angles は正常動作している。

次に visual_servo_loop 内にデバッグprintを追加してPICK姿勢でのj0を確認した。

# visual_servo_pick.py のvisual_servo_loop内に追加
ssh_send_angles(angles_to_send, wait=LOOP_WAIT_SEC)
measured_angles = ssh_get_angles()
if measured_angles is not None:
    print(f"  [DEBUG] 送信j0={angles_to_send[0]:+.2f} → 実測j0={measured_angles[0]:+.2f}")
tip_unity, markers, frame = detect_gripper_tip_auto(cap, trial=None)
if tip_unity is not None:
    print(f"  [DEBUG] TIP検出 raw_unity=({tip_unity[0]:.4f}, {tip_unity[1]:.4f})")
# デバッグログ出力(Trial 1・2)
=== 視覚サーボループ開始 (PICK姿勢) ===
Target X: +156.8mm  初期 j0: -32.00
  [DEBUG] TIP検出 raw_unity=(0.2171, 0.2556)
[Loop 1] TIP_X=+217.1mm  Target_X=+156.8mm  diff=-60.3mm  j0=-32.00
  [DEBUG] 送信j0=-27.00 → 実測j0=-31.55   ← ★ アームが動いていない
  [DEBUG] TIP検出 raw_unity=(0.2151, 0.2571)
[Loop 2] TIP_X=+215.1mm  Target_X=+156.8mm  diff=-58.3mm  j0=-27.00
  [DEBUG] 送信j0=-22.00 → 実測j0=-31.55   ← ★ 同じ値のまま
  [DEBUG] TIP検出 raw_unity=(0.2151, 0.2571)
[Loop 3] TIP_X=+215.1mm  Target_X=+156.8mm  diff=-58.3mm  j0=-22.00
  [DEBUG] 送信j0=-17.00 → 実測j0=-31.55   ← ★ 同じ値のまま
  [DEBUG] TIP検出 raw_unity=(0.2151, 0.2571)
[Loop 4] TIP_X=+215.1mm  Target_X=+156.8mm  diff=-58.3mm  j0=-17.00
[ABORT] j0=-12.00 が 初期j0=-32.00から +/-15.0度を超過。視覚サーボ中断。

=== 視覚サーボループ開始 (PICK姿勢) ===
Target X: -151.8mm  初期 j0: +45.00
  [DEBUG] TIP検出 raw_unity=(-0.2076, 0.2707)
[Loop 1] TIP_X=-207.6mm  Target_X=-151.8mm  diff=+55.8mm  j0=+45.00
  [DEBUG] 送信j0=+40.00 → 実測j0=+44.64   ← ★ アームが動いていない
  [DEBUG] TIP検出 raw_unity=(-0.2083, 0.2700)
[Loop 2] TIP_X=-208.3mm  Target_X=-151.8mm  diff=+56.5mm  j0=+40.00
  [DEBUG] 送信j0=+35.00 → 実測j0=+44.64   ← ★ 同じ値のまま
  [DEBUG] TIP検出 raw_unity=(-0.2083, 0.2700)
[Loop 3] TIP_X=-208.1mm  Target_X=-151.8mm  diff=+56.3mm  j0=+35.00
  [DEBUG] 送信j0=+30.00 → 実測j0=+44.64   ← ★ 同じ値のまま
  [DEBUG] TIP検出 raw_unity=(-0.2081, 0.2700)
[ABORT] j0=+25.00 が 初期j0=+45.00から +/-15.0度を超過。視覚サーボ中断。

送信j0は変化しているのに、実測j0がPICK降下後の値から動かない。APPROACH姿勢(j1=87.74)では問題なく動くが、PICK姿勢(j1=90.87)では固まる。原因の特定は次のセッションへ持ち越した。

判断ログ

迷った点: 視覚サーボをAPPROACH姿勢で実行するか、PICK姿勢で実行するか。

選んだ: PICK姿勢(降下後)で実行する。

理由: APPROACH姿勢でTIP_Xを合わせても、降下動作でj1・j4が変化すると連鎖的にTIP_Xが動く。「見えている位置に合わせた」と「実際に掴む位置に合わせた」が一致しない。視覚サーボはできる限り最終動作に近い姿勢で実行すべきだ。

迷った点: j1固定値をlookupの平均値にするか、実測で安定していた90.87にするか。

選んだ: 90.87固定。

理由: LOG184初回テストでCONVERGEDだったTrialの実測j1が90.87と90.96で安定していた。lookupの平均より実績値を優先した。

今日の原則

「CONVERGED」と「物理的に位置が合った」は異なる検証レイヤーだ。ループが収束したことと、実際に物体を掴める位置に到達したことは別の問いとして検証しなければならない。

分類: #workflow

→ principles.md にP009として転記すること

メタ情報

Phase: #phase/PICK Tool: #tool/Python #tool/mechArm #tool/iPhone

ハンドオフ

パイプライン状態

Layer

Status

Last Confirmed DAY

Camera detection

✅

LOG184

Blue marker identification

✅

LOG184

Red block detection

✅

LOG184

PICK lookup

✅

LOG184

Visual servo convergence

✅

LOG184

j0 execution in servo loop

❌

LOG184

PICK physical motion

⚠️ 2/5 (lookup成功trial中)

LOG184

Gripper open/close

✅

LOG184

LIFT motion

✅

LOG184

今日達成されたこと

  • 青シール識別ロジックを幾何学ベースに変更(TIP stdev 19.42mm → 0.08mm)

  • 動作シーケンス確定(APPROACH → DESCEND → PICK_VISUAL_SERVO → GRIP → LIFT → HOME)

  • PICK_J1_FIXED = 90.87 確定

  • グリッパー制御実装(state=0:開く / state=1:閉じる)

  • Trial 3・6で赤ブロック把持に成功

  • arm_kinematics.md 作成・ローカルGitとClaudeプロジェクト知識に両所配置

今日達成されなかったこと

  • PICK姿勢でのj0送信問題の原因特定(DEBUGログで現象は確認済み)

  • 視覚サーボの安定した収束率(現状2/5、lookup成功trial中)

未検証の前提

  • PICK姿勢(j1=90.87)でj0が動かない原因(原因特定が未完)

  • PICK_J1_FIXED=90.87がマット全域の位置で通用するか

次にやること

  1. PICK姿勢でj0が動かない問題の原因特定

  2. 原因特定後、収束率の安定化

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