# F. 本線の上カメラの上の端のずれを調べる（2026-11-07・依頼110）

**読むだけ。** 本線のコード（`visual_servo_pick.py`・`mat_corner_detection.py`）は変えていない（四隅の検出の関数を読み取り専用で借りただけ）。`runs/`・`datasets/`・`diag/` は読むだけ。実機には触れていない。

## 先に読むこと（結論）

| 問い | 答え |
|---|---|
| 1 本線の四隅の扱い | `mat_corner_detection.detect_corners` が、黒いマットのいちばん大きい輪郭の凸包を4頂点に近似し、x+y・x−y で左上・右上・右下・左下に並べる。`visual_servo_pick.setup_homography_and_mat_range` が、その**左上・右上を (+310, −89)・(−310, −89) mm**、右下・左下を (−310, +361)・(+310, +361) mm に対応させてホモグラフィを作る。**検出した点が画像の端（v=0）にあるかは見ていない**。マットが画像の上の端で切れていると、凸包の頂点は「マットの縁と画像の上の端が交わる点」になり、それがそのまま z=−89（マットの物理的な上の端）として扱われる |
| 2 切れている日 | 見た 30 日のうち **11 日**（大きく切れている 8 日: 10/19・10/21・10/22・10/24・10/30・10/31・11/01・11/06／わずか 3 日: 7/11・7/13・10/29）。写っている日 19 日（6/19〜7/20 のうち 7/11・7/13 以外の 16 日と 10/15・10/16・10/17）。**10/19 からはずっと切れている**（10/19 の教えたデモから π0.5 の推論の日まで） |
| 3 ずれの大きさ | 大きく切れている日、画像の上の端は本当は z=−54〜−73mm（本線の決めより 16〜35mm 根元寄り）。本線で出す位置は**根元の側（z が小さい側）へずれる**。教えた位置5のあたりで 4〜9mm、マットの中央で 8〜19mm、手前（z=330）で 1〜3mm、奥（z=0）で 13〜31mm。x は 0（左右対称と置いたため） |
| 4 補正記憶・台帳 | **補正記憶（`diag/correction_memory.json`）・置く高さの記憶・置き場所の補正は、どれも 10/16 が最後の更新で、大きく切れている日の値は入っていない**。わずかに切れた 7/11・7/13 の値は入っている見込み（ずれは 2mm 以下）。**台帳（`runs/pick_place_ledger_v3.csv`）の 10/19 の 39 行・10/22 の 4 行のブロックと箱の座標は、切れた画のままの値**（ブロック 5.5〜22.3mm・箱 7.2〜10.0mm、いずれも根元寄り）。依頼107 の B のブロック・白箱の位置も同じ |
| 5 直し方の案 | ①上の端が切れていたら、上の2点の z をマットの方眼から出して対応させる（このフォルダのやり方）。②上の端が切れていたら止める・警告し、カメラをマット全体が写る位置に付け直す（ただし π0.5 の学習データは 10/19 から切れた画）。どちらも本線の変更なので、外部レビューを挟む |

## 1. 本線で、マットの四隅をどう検出し、どの点を (±310, −89) に対応させているか

| ファイル:行 | 中身 |
|---|---|
| `mat_corner_detection.py:42-141` `detect_corners` | 灰色にして大津の二値化（マット=黒を前景）→ 15×15 の閉じ・開き → いちばん大きい輪郭 → 凸包（`:98`）→ `approxPolyDP` で 4 頂点（`:109`、ならなければ係数を変えて `:119`）→ `sort_corners` |
| `mat_corner_detection.py:144-158` `sort_corners` | x+y 最小=左上・x−y 最大=右上・x+y 最大=右下・x−y 最小=左下 |
| `mat_corner_detection.py:161-191` `remove_outliers_and_average` | 複数コマの四隅を中央値 ±2σ（最低 5px）で外れを除いて平均 |
| `visual_servo_pick.py:262-290` `setup_homography_and_mat_range` | 四隅を検出して平均 → **`:274-279` で 左上=(+310, −89)・右上=(−310, −89)・右下=(−310, +361)・左下=(+310, +361)** → `:281` `cv2.getPerspectiveTransform`。`:284-290` で四隅の画素の範囲を `_mat_px_range` に残す（切れている日は上が 0） |
| `visual_servo_pick.py:293-301` `pixel_to_unity` | 画素 → このホモグラフィ → mm → Unity の m |
| `visual_servo_pick.py:818-821` `mm_to_px`・`:848-867` `capture_rectified` | 同じ (±310, −89)・(±310, +361) で真上から見た画を作る |
| `dynamic_tracking_limits/place/place_modules/run_pick_place.py:136-151・682・791・810` | 本線の `setup_homography_and_mat_range`・`pixel_to_unity` をそのまま使う（独自のホモグラフィは持たない） |

−89・+361 の出どころは「アームの根元はマットの左右の中央・上の端から 89mm・下の端から 361mm」（`r2r2r-content/methodology/arm_kinematics.md:194`、DAY164 の記事。マットは 620×450mm）。つまり (±310, −89) は**マットの物理的な上の2隅**で、画像に上の端が写っている前提の値。

同じ (±310, −89) を使う古いスクリプトも残っている（`calibrate_visual_servo.py`・`recalibrate_homography.py`・`verify_dynamic_pick_auto_correct.py`・`debug_detection.py`）。

## 2. マットの上の端が写っている日と、切れている日

日ごとの最初のコマ（それぞれの置き場で最も早い試行。真っ暗なコマは飛ばしてその日の次の試行）に本線の `detect_corners` をかけ、左上・右上の点の v が 6px 以下なら「切れている」とした。384×216 のコマは 5 倍に広げてから検出した。

一覧画像: **`f_days_montage.jpg`**（緑の枠=写っている・赤の枠=切れている。黄の丸が検出した四隅）。1枚ずつは `f_first_frames/{置き場}_{日}.jpg`。値はすべて `f_days.csv`。

| 日 | 置き場（使ったコマ） | 左上 v・右上 v（px） | 判定 | 画像の上の端の z（mm） |
|---|---|---|---|---|
| 6/19〜7/7（12 日） | run_pick_place（上カメラの jpg） | 30〜137 | 写っている | — |
| 7/10 | run_pick_place | 27・42 | 写っている | — |
| **7/11** | run_pick_place | **0**・45 | 切れている（左上の隅だけ。方眼では上の端が −94 で、ほぼマットの縁） | −93.7 |
| 7/12 | run_pick_place | 26・28 | 写っている | — |
| **7/13** | run_pick_place | **0・0** | 切れている（わずか） | −87.4 |
| 7/15・7/20 | run_pick_place | 96〜116 | 写っている | — |
| 10/15 | run_pick_place（jpg）・pi05_policy（384×216）・datasets | 79〜105 | 写っている | — |
| 10/16 | run_pick_place（top.mp4）・datasets | 86〜104 | 写っている | — |
| 10/17 | pi05_policy（384×216） | 96・107 | 写っている | — |
| **10/19** | run_pick_place（top.mp4）・datasets（teach_v1） | **0・0** | 切れている | −64.0（方眼）／−60.9（比） |
| **10/21** | pi05_policy（384×216。最初の試行は真っ暗で次の試行） | **0・0** | 切れている | −54.4（比） |
| **10/22** | pi05_policy（384×216） | **0・0** | 切れている | −71.2（比） |
| **10/24** | pi05_policy（384×216） | **0・0** | 切れている | −72.8（比） |
| **10/29** | pi05_policy（384×216） | **0・0** | 切れている（わずか） | −83.1（比） |
| **10/30** | pi05_policy（top.mp4） | **0・0** | 切れている | −56.7（方眼） |
| **10/31** | pi05_policy（top.mp4）・datasets（corr_v1） | **0・0** | 切れている | −55.8（方眼）／−54.0（比） |
| **11/01** | pi05_policy（top.mp4）・datasets（corr_v4） | **0・0** | 切れている | −53.8（方眼）／−54.8（比） |
| **11/06** | pi05_policy（top.mp4）・datasets（corr_v5） | **0・0** | 切れている | −63.5（方眼）／−64.5（比） |

- 同じ日の置き場どうしで判定は食い違わなかった
- 左・右・下の隅が画像の端にかかった日はなかった
- 6〜7月は腕がつかむ途中のコマ（上カメラの jpg）だが、四隅は検出できている

### 画像の上の端が、マットの本当はどこに当たるか

**方眼を数えた（1920×1080 のコマ）**: マットには 1cm の方眼と 5cm ごとの数字が刷ってあり、いちばん下の線が「0」（マットの下の端から約 10mm）。左の縁の内側（縁から 70〜220px）の帯で横線を拾って下から数え、画像の上の端（v=0）と検出した左上の点が何 cm の線に当たるかを出した。「k cm」の線は z = 351 − 10k mm。

- **確かめ**: 上の端が写っている 1920×1080 の日（7/10 を除く 17 日）で、検出した左上の隅は 43.6〜44.0cm 相当 = **z −85〜−89mm**（本線の決め −89mm と 4mm 以内）。7/10 だけは線の数え違いで 40.9cm になった（下の端から「0」の線まで 38mm と出ていて、ほかの日の 7〜11mm と合わない）
- 下の端から「0」の線までは 7〜11mm（7/10 の 38mm・7/20 の 20.5mm を除く）

**比から推し量った（384×216 だけの日）**: 下の辺の画素の幅（= 620mm）と、下の端から画像の上の端までの画素の高さの比に、1920×1080 の切れている 5 日で合わせた係数（1.009）をかけた。合わせた 5 日での残りは最大 2.7mm。同じ日に方眼の値もある 10/19・10/31・11/01・11/06 では、方眼との差は 1〜3mm。

依頼107 の D（カメラの位置と向きを解いた方）では 11/01 の上の端が z≈−58・−54mm（左・右）で、方眼の −53.8mm（左）と 4mm 以内。

## 3. 切れている日に、本線で測った位置がどれだけずれるか

本当の対応（上の2点 = マットの左右の縁 x=±310mm の、上の端の z）と、本線の対応（上の2点 = (±310, −89)）でホモグラフィを2つ作り、マットの上の点を「本当の対応で画素へ → 本線の対応で mm へ」戻したときの差（本線 − 本当）。左右の上の点の z は同じとした（D では左右で約 4mm 違った）。x のずれは、この置き方では 0。値は `f_shift.csv`、上の端の z は `f_ztop.csv`。

z のずれ（mm。− は根元の側＝画像の上の側へずれる）:

| 日（上の端の z） | 教えた位置5 (−70, 253) | π0.5 のブロックのあたり (−55, 248) | 白箱の中心 (−204, 236) | 教えた位置1 (162, 213) | マットの中央 (0, 136) | 手前 (0・±250, 330) | 奥の中央 (0, 0) |
|---|---|---|---|---|---|---|---|
| 7/11（−93.7） | +1.1 | +1.2 | +1.3 | +1.5 | +2.3 | +0.3 | +3.7 |
| 7/13（−87.4） | −0.4 | −0.4 | −0.4 | −0.5 | −0.8 | −0.1 | −1.3 |
| 10/19（−64.0） | −6.4 | −6.6 | −7.4 | −8.7 | −13.2 | −1.8 | −21.2 |
| 10/21（−54.4・比） | −9.0 | −9.4 | −10.4 | −12.4 | −18.8 | −2.6 | −30.1 |
| 10/22（−71.2・比） | −4.5 | −4.7 | −5.2 | −6.1 | −9.3 | −1.3 | −14.9 |
| 10/24（−72.8・比） | −4.0 | −4.2 | −4.7 | −5.5 | −8.4 | −1.2 | −13.5 |
| 10/29（−83.1・比） | −1.4 | −1.5 | −1.6 | −2.0 | −3.0 | −0.4 | −4.8 |
| 10/30（−56.7） | −8.4 | −8.7 | −9.7 | −11.5 | −17.4 | −2.4 | −27.9 |
| 10/31（−55.8） | −8.6 | −9.0 | −10.0 | −11.8 | −17.9 | −2.5 | −28.8 |
| 11/01（−53.8） | −9.2 | −9.6 | −10.6 | −12.6 | −19.1 | −2.6 | −30.6 |
| 11/06（−63.5） | −6.5 | −6.8 | −7.5 | −8.9 | −13.5 | −1.9 | −21.7 |

- 下の2隅（z=361）は本当の角なので、手前ほどずれが小さく、奥ほど大きい
- 本線は「画像の上の端 = −89」として上下に引き伸ばすので、マットの上の点は**根元の側に寄って**出る。依頼107 の README は「ブロックのあたりでは本線の座標が約 9mm 手前にずれる」と書いているが、計算では z が小さい側（根元の側）へのずれ。大きさ（約 9mm）は合っている
- ブロックの上面（高さ 24mm）や爪の印（さらに高い）を床の点として変換するときの、高さによるずれはこの表に入っていない（依頼107 で「さらに数 mm」としたもの）

## 4. 補正記憶・台帳にこのずれが入り込んでいそうか（読むだけ）

| ファイル | 最後の更新 | 切れている日の値 |
|---|---|---|
| `diag/correction_memory.json`（つかむ位置の補正。マス目ごとの dx・dz） | 10/16 19:43 | 大きく切れている日（10/19 以降）は**入っていない**。7/11（補正のマスの行 37）・7/13（32 行）は入っている見込みだが、その日のずれはつかむ位置のあたりで 2mm 以下 |
| `diag/place_height_memory.json` | 10/16 17:53 | 同上 |
| `diag/place_release_offset.json` | 10/16 19:15 | 同上 |
| `runs/pick_place_ledger_v3.csv`（台帳） | 10/22 17:34 | **10/19 の 39 行・10/22 の 4 行の `block_pick_x/z`・`box_x/z` は切れた画のホモグラフィの値**。この日は補正（`tip_block_dx_mm`・`corr_cell_key`）は使っていない。その日の最初のコマの四隅で計算し直すと、ブロックの位置（10/19 は z=−41〜241 とばらつく）は 10/19 が 6.6〜22.3mm・10/22 が 5.5〜5.7mm、箱（z=178〜191）は 10/19 が 9.5〜10.0mm・10/22 が 7.2mm、いずれも根元の側にずれている |
| `runs/handover_ledger_v1.csv` | 7/20 | 写っている日だけ |

- 補正記憶の dx・dz は、同じ画の中の爪先とブロックの差から作っているので、切れた日のものでも差の部分のずれは打ち消し合う方向（打ち消しきるかは高さが違うので分からない）。ただし、どのマスに入れるか（ブロックの位置）はずれる
- π0.5（`pi05/run_policy_client.py`）は画をそのまま渡していて、このホモグラフィは使っていない。影響を受けたのは、本線（`run_pick_place.py`）を 10/19 以降に走らせた試行と、依頼107 の B（ブロック・白箱の位置）
- **これから本線を今のカメラの付け方で走らせると**、ブロックの位置が教えた位置5のあたりで 6〜9mm 根元寄りに出て、補正記憶（写っている日に作ったもの）のマスと目標がその分ずれる見込み

## 5. 直し方の案（直すのは別の依頼。本線の変更は外部レビューを挟む）

1. **上の端が切れていたら、上の2点の z をマットの方眼から出す**: `setup_homography_and_mat_range` で、左上・右上の v が数 px 以下なら、(±310, −89) ではなく、方眼を数えて出した z（このフォルダの `f_corners.py` の `grid_lines`・`cm_at` と同じ考え）を上の2点に対応させる。カメラの付け直しがそのまま使え、π0.5 の学習データの画角も変えない。方眼の数え違い（7/10 のように 3cm 外れる）を見つける検査が要る（下の端から「0」の線までが 7〜11mm に入るか、など）
2. **上の端が切れていたら止める（または警告する）**: 検出した四隅のどれかが画像の端にかかっていたら、ホモグラフィを作らずに止め、カメラをマット全体が写る位置（6/19〜10/17 の付け方）に付け直す。仕組みは簡単だが、10/19 以降の π0.5 の学習データ（教えたデモ・補正のデータ）は切れた画角なので、π0.5 を使う日と本線を使う日でカメラの付け方を変えることになる

## やり直し方

```bash
cd "/Volumes/KIOXIA/r2r2r-data/lab/twin/20261107_失敗の再生と置き場所/F_上カメラの上の端"
python3 -I f_corners.py   # f_days.csv・f_first_frames/（本線の detect_corners を r2r2r-scripts から読み取り専用で import）
python3 -I f_shift.py     # f_ztop.csv・f_shift.csv
python3 -I f_montage.py   # f_days_montage.jpg
```

`f_corners.py` は `python3 -I` でも `sys.path` に `/Users/shimazueiichi/r2r2r-scripts` を足して `mat_corner_detection` を読む（関数を借りるだけで、ファイルは変えない）。
