"""依頼110 F-3: 上の端が切れている日に, 本線のホモグラフィ (上の2点を (±310, −89) とする) で測った位置が どれだけずれるかを計算する (読むだけ). 正しい対応: 上の2点は「マットの左右の縁 x=±310 の, 方眼で数えた z (f_days.csv の image_top_z_mm)」. 1920×1080 のコマがない日 (384×216 だけの日) は, 下の辺の幅と高さの比から z を推し量る (1920×1080 の切れている日で係数を合わせる. 粗い見積もり). 左右の上の点の z は同じとした (依頼107 D のカメラ合わせでは左右で約 4mm 違った). 本線の対応: 上の2点を (±310, −89). ずれ = 本線で出る位置 − 本当の位置 (x, z mm. z は + が手前 = 根元から遠い向き). 出力: f_shift.csv (日 × 点) / f_ztop.csv (日ごとの上の端の z) """ import csv from pathlib import Path import cv2 import numpy as np OUT = Path("/Volumes/KIOXIA/r2r2r-data/lab/twin/20261107_失敗の再生と置き場所/F_上カメラの上の端") MAIN_TOP_Z = -89.0 POINTS = [ # 名前, x, z (本線のマット座標 mm. Unity の x・z と同じ向き) ("教えた位置5 (T5)", -70.0, 253.0), ("π0.5 のブロックのあたり (依頼107 B)", -55.0, 248.0), ("白箱の中心 (依頼107 B)", -204.0, 236.0), ("教えた位置1 (T1)", 161.6, 212.8), ("マットの中央", 0.0, 136.0), ("手前の中央", 0.0, 330.0), ("手前の左", 250.0, 330.0), ("手前の右", -250.0, 330.0), ("奥の中央", 0.0, 0.0), ] def H_of(c, ztop_l, ztop_r): src = np.array(c, np.float32) dst = np.array([[310, ztop_l], [-310, ztop_r], [-310, 361], [310, 361]], np.float32) return cv2.getPerspectiveTransform(src, dst) def apply(H, p): return cv2.perspectiveTransform(np.array([[p]], np.float32), H)[0][0] def main(): days = [r for r in csv.DictReader(open(OUT / "f_days.csv")) if r.get("status") == "切れている"] # 1920×1080 の日で, 高さ/幅 の比から z を出す係数を合わせる def ratio_est(r): w = float(r["LR_u"]) - float(r["LL_u"]); h = float(r["LL_v"]) - 0.0 return h * 620.0 / w fit = [(ratio_est(r), 361 - float(r["image_top_z_mm"])) for r in days if r.get("image_top_z_mm")] fit = [(a, b) for a, b in fit if b < 440] # 7月のわずかに切れた日 (上の端がほぼマットの縁) は外す k = float(np.mean([b / a for a, b in fit])) resid = [b - k * a for a, b in fit] zrows, srows = [], [] for r in days: c = [(float(r[f"{n}_u"]), float(r[f"{n}_v"])) for n in ("UL", "UR", "LR", "LL")] if r.get("image_top_z_mm"): zt, how = float(r["image_top_z_mm"]), "方眼を数えた (1920×1080)" else: zt, how = 361 - k * ratio_est(r), f"幅と高さの比から推し量った (係数 {k:.3f}・合わせた{len(fit)}日の残り最大 {max(abs(x) for x in resid):.1f}mm)" note = "" if r.get("other_cut"): note = f"上のほかに {r['other_cut']} も画像の端で切れている (下の2隅も本当の角ではない)" zrows.append({"source": r["source"], "day": r["day"], "kind": r["kind"], "image_top_z_mm": round(zt, 1), "top_shift_vs_main_mm": round(zt - MAIN_TOP_Z, 1), "how": how, "note": note}) if note: continue Hm = H_of(c, MAIN_TOP_Z, MAIN_TOP_Z) Ht = H_of(c, zt, zt) Ht_inv = np.linalg.inv(Ht) for name, x, z in POINTS: px = apply(Ht_inv, (x, z)) xm, zm = apply(Hm, px) srows.append({"source": r["source"], "day": r["day"], "image_top_z_mm": round(zt, 1), "point": name, "true_x_mm": x, "true_z_mm": z, "main_x_mm": round(float(xm), 1), "main_z_mm": round(float(zm), 1), "dx_mm": round(float(xm - x), 1), "dz_mm": round(float(zm - z), 1), "dist_mm": round(float(np.hypot(xm - x, zm - z)), 1), "pixel_u": round(float(px[0]), 1), "pixel_v": round(float(px[1]), 1), "in_image": bool(px[1] >= 0)}) for name, rows in (("f_ztop.csv", zrows), ("f_shift.csv", srows)): keys = [] for r in rows: keys += [kk for kk in r if kk not in keys] with open(OUT / name, "w", newline="") as f: w = csv.DictWriter(f, fieldnames=keys); w.writeheader(); w.writerows(rows) print("k", round(k, 4), "fit days", len(fit), "resid", [round(x, 1) for x in resid]) for r in zrows: print(r) for r in srows: if r["point"] in ("教えた位置5 (T5)", "マットの中央", "手前の中央", "奥の中央"): print(r["source"][:8], r["day"], r["image_top_z_mm"], r["point"], r["dx_mm"], r["dz_mm"]) if __name__ == "__main__": main()