Spaces:
Sleeping
Sleeping
File size: 7,581 Bytes
78738de | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | """Đo gợi ý 4 góc bàn tự động vs corner CHẤM TAY đã lưu (gate A.1, 14/08/2026).
Phép đo đầu tiên của ``poolcoach_cv.autocorner`` — BRIEF 14/08 không đặt
ngưỡng phải "đạt", chỉ yêu cầu số thật: **median + max lệch px per-corner**
và **tỉ lệ ca qua van sanity**. Mốc tự đặt để đọc kết quả: median > ~15px
thì kết luận "gợi ý chưa đáng tin" thay vì ship im lặng.
Nguồn chuẩn (corner tay CÓ THẬT trên đĩa, không dựng lại):
1. ``datasets/bb9_pilot/shot_XX/`` — mỗi cú một ``homography.json``
(``oriented_px``, Danh chấm 11/08 bằng ``click_homography.py``) kèm đúng
frame đã chấm ``frames/00000.jpg``. 10 cú.
2. VOD ``datasets/bb9_pilot/rack_a1/rack_5245_5805.mp4`` — corner tay là bộ
đã dùng THẬT cho run A2b ``c9fda71c`` (chép từ
``scripts/experiments/run_analyzer_a2b_vod.bat``, làm tròn 0.1px của
``shot_01``). Đây là rack KHÁC shot_01 nên bộ này là corner "dùng lại"
chứ không phải chấm riêng — ghi rõ để đừng đọc nhầm nó là chuẩn vàng.
Clip KHÔNG có corner tay (``--clips``) chỉ đóng góp **tỉ lệ qua van**, không
vào thống kê lệch — không có chuẩn thì không có lệch để đo.
Chạy (venv nào cũng được, chỉ cần cv2 + numpy):
python scripts/broadcast/eval_autocorner.py
python scripts/broadcast/eval_autocorner.py --clips "D:/Khoa luan/cu*.mp4"
Ghi CSV ra workspace (nếp bb9_*): ``autocorner_20260814.csv``.
"""
from __future__ import annotations
import argparse
import csv
import glob
import json
import statistics
import sys
from pathlib import Path
import numpy as np
ROOT = Path(__file__).resolve().parents[2]
if str(ROOT / "src") not in sys.path:
sys.path.insert(0, str(ROOT / "src"))
from poolcoach_cv.autocorner import (corner_errors_px, # noqa: E402
suggest_corners, suggest_from_video)
from poolcoach_cv.homography import orient_corners # noqa: E402
WORKSPACE = ROOT.parent
PILOT = ROOT / "datasets" / "bb9_pilot"
VOD = PILOT / "rack_a1" / "rack_5245_5805.mp4"
# Chép nguyên từ run_analyzer_a2b_vod.bat (run c9fda71c) — xem docstring.
VOD_CORNERS = [[527.5, 869.7], [1380.1, 864.0], [1232.3, 278.5],
[673.0, 272.8]]
def _rows_pilot() -> list[dict]:
out = []
for d in sorted(PILOT.glob("shot_*")):
hj, frame = d / "homography.json", d / "frames" / "00000.jpg"
if not (hj.exists() and frame.exists()):
continue
truth = json.loads(hj.read_text(encoding="utf-8"))["oriented_px"]
out.append({"case": d.name, "kind": "pilot", "frame": frame,
"truth": truth})
return out
def _read_image(path: Path):
import cv2
img = cv2.imread(str(path), cv2.IMREAD_COLOR)
if img is None:
raise SystemExit(f"không đọc được ảnh {path}")
return img
def _eval_case(row: dict) -> dict:
if row["kind"] == "video":
res = suggest_from_video(row["frame"])
else:
res = suggest_corners(_read_image(row["frame"]))
rec = {"case": row["case"], "kind": row["kind"],
"ok": res["ok"], "reason": res["reason"] or "",
"hue": (res["cloth"] or {}).get("hue"),
"area_frac": (res["cloth"] or {}).get("area_frac"),
"quad_fill": (res["cloth"] or {}).get("quad_fill"),
"touches_border": (res["cloth"] or {}).get("touches_border"),
"cam_h_m": (res["camera"] or {}).get("h_m"),
"cam_f_px": (res["camera"] or {}).get("f_px"),
"asym_pair0": (res["rails"] or {}).get("asym_pair0"),
"asym_pair1": (res["rails"] or {}).get("asym_pair1"),
"swapped": (res["rails"] or {}).get("swapped"),
"corners": json.dumps(res["corners"])}
truth = row.get("truth")
if res["ok"] and truth is not None:
t = orient_corners(np.asarray(truth, dtype=np.float64))
errs, rot = corner_errors_px(res["corners"], t)
# xoay 1 nhịp khớp hơn HẲN ⇒ máy gán nhầm cặp băng ngắn (lỗi nặng
# nhất của tầng này) — bắt riêng, không nuốt vào phép khớp
alt = min(
float(np.linalg.norm(np.roll(np.asarray(res["corners"]), -r,
axis=0) - t, axis=1).mean())
for r in (1, 3))
rec.update(err_px=[round(e, 1) for e in errs],
err_max=round(max(errs), 1),
err_mean=round(sum(errs) / 4, 1), rot=rot,
pairing_suspect=bool(alt < sum(errs) / 4))
return rec
def main() -> int:
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("--clips", default="",
help="glob video KHÔNG có corner tay — chỉ tính tỉ lệ van")
ap.add_argument("--out", default=str(WORKSPACE / "autocorner_20260814.csv"))
a = ap.parse_args()
rows = _rows_pilot()
if VOD.exists():
rows.append({"case": "rack_a1(VOD)", "kind": "video", "frame": VOD,
"truth": VOD_CORNERS})
for p in sorted(glob.glob(a.clips)) if a.clips else []:
rows.append({"case": Path(p).stem, "kind": "video", "frame": Path(p)})
if not rows:
raise SystemExit("không có ca nào để đo — kiểm datasets/bb9_pilot")
recs = [_eval_case(r) for r in rows]
fields = ["case", "kind", "ok", "reason", "err_px", "err_mean", "err_max",
"rot", "pairing_suspect", "cam_h_m", "cam_f_px", "hue",
"area_frac", "quad_fill", "touches_border", "asym_pair0",
"asym_pair1", "swapped", "corners"]
with open(a.out, "w", encoding="utf-8-sig", newline="") as f:
w = csv.DictWriter(f, fieldnames=fields, extrasaction="ignore")
w.writeheader()
for r in recs:
w.writerow(r)
print(f"{'ca':<16}{'van':<5}{'lech px per-corner':<30}"
f"{'max':>7}{'h_m':>8}{'f_px':>8} ghi chu")
print("-" * 92)
for r in recs:
ep = ("" if "err_px" not in r else
" ".join(f"{v:.0f}" for v in r["err_px"]))
note = r["reason"] if not r["ok"] else (
"GAN NHAM CAP BANG NGAN?" if r.get("pairing_suspect") else
("xoay 180 do" if r.get("rot") else ""))
cell = lambda k: ("" if r.get(k) is None else str(r[k])) # noqa: E731
print(f"{r['case']:<16}{'OK' if r['ok'] else 'CHAN':<5}{ep:<30}"
f"{cell('err_max'):>7}{cell('cam_h_m'):>8}"
f"{cell('cam_f_px'):>8} {note}")
n_ok = sum(1 for r in recs if r["ok"])
per_corner = [e for r in recs for e in r.get("err_px", [])]
print("-" * 92)
print(f"qua van sanity: {n_ok}/{len(recs)} ca "
f"({n_ok / len(recs):.0%})")
if per_corner:
n_gt = sum(1 for r in recs if "err_px" in r)
print(f"lech px PER-CORNER tren {n_gt} ca co corner tay "
f"({len(per_corner)} goc): median "
f"{statistics.median(per_corner):.1f}px · trung binh "
f"{statistics.fmean(per_corner):.1f}px · max "
f"{max(per_corner):.1f}px")
worst = max((r for r in recs if "err_max" in r),
key=lambda r: r["err_max"])
print(f"ca lech nhat: {worst['case']} (max {worst['err_max']}px)")
n_pair = sum(1 for r in recs if r.get("pairing_suspect"))
print(f"nghi gan nham cap bang ngan: {n_pair} ca")
print(f"CSV: {a.out}")
return 0
if __name__ == "__main__":
sys.exit(main())
|