Spaces:
Sleeping
Sleeping
File size: 11,414 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 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 | """P/R/F1 cua PREDICT-pipeline tai conf VAN HANH β "so luc deploy" (ban giao 19, 05/08/2026).
Do dung nhung gi CV worker deploy (scripts/cv_worker.py) lam voi MOT anh,
tren 49 anh val pix2pockets:
1. resize max-dim 1280 INTER_AREA (chep nguyen block cua ``scan_once`` β
anh val 1920x1080 va 3360x2100 deu vuot tran nen buoc nay CO tac dung),
2. ``model.predict(conf=<opconf.json>, imgsz=640)`` β NMS
``multi_label=False`` (chi class argmax), dung pipeline worker goi,
roi merge any-ball (Black+Cue+Solid+Striped -> "ball"; Dot LOAI khoi ca
pred lan GT β khong phai bi) va match GREEDY voi GT: duyet pred theo conf
GIAM DAN, moi pred lay GT chua-match co IoU cao nhat >= 0.5; pred khong
match = FP, GT thua = FN -> precision / recall / F1 toan split.
MATCHER TU VIET LA DUNG O DAY (khac ban giao 17): lan do can identity voi
val-pipeline nen matcher port bi loai; lan nay muc tieu la do THUOC DEPLOY
β khong co reference nao de identity, chi can khai phuong phap ro rang.
HAI CAI THUOC (bai hoc ban giao 17): so o day la P/R/F1 tai MOT diem conf
tren PREDICT-pipeline. KHONG phai AP, KHONG duoc so voi any-ball AP50
0.9383 (val-pipeline, NMS multi_label=True). Cap so hop le duy nhat:
P/R/F1@conf cua val-pipeline (opconf.json) vs P/R/F1@conf o day β ky vong
predict-pipeline THAP hon (multi_label=False mat cac box "duoc cuu"; Cue
tung lech -0.20 AP giua hai thuoc).
KHONG tai lap cac khau SAU predict cua worker (homography, kep mep, dedupe
tam ban, cue-dung-1): chung can corners + toa do ban, con GT o day la bbox
pixel β do o tang bbox, TRUOC homography.
Guard (BRIEF "Neu bi"): P hoac R < 0.5 -> nghi sai matcher/resize, DUNG
khong ghi so. Neo them: tong GT ball tu file nhan phai khop counters
``gt_kept`` cua opconf.json (563) β lech la parse nhan sai, DUNG.
Chay tren venv CV (can ultralytics; GPU nhu worker):
python scripts/cv/eval_deploy_pr.py --artifact-dir "D:/Khoa luan/cv_full_20260805"
Console ASCII-only (bay cp1252). Ket qua ghi ``opconf_predict.json`` vao
artifact dir, canh ``opconf.json`` de dat hai thuoc canh nhau.
"""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2] # poolcoach-rl/
sys.path.insert(0, str(ROOT / "src"))
DATA_YAML = ROOT / "datasets" / "pix2pockets" / "yolo" / "data.yaml"
BALL_NAMES = ("Black", "Cue", "Solid", "Striped")
EXCLUDE_NAME = "Dot"
IMGSZ = 640 # = cv_worker.IMGSZ β cung imgsz train/val/worker
MAX_DIM = 1280 # = cv_worker.MAX_DIM
IOU_THR = 0.5
def resize_like_worker(img, max_dim: int = MAX_DIM):
"""Chep NGUYEN block resize cua cv_worker.scan_once β doi o do thi doi o day."""
import cv2
h, w = img.shape[:2]
scale = 1.0
if max(h, w) > max_dim:
scale = max_dim / max(h, w)
img = cv2.resize(img, (round(w * scale), round(h * scale)),
interpolation=cv2.INTER_AREA)
return img, scale
def load_gt_boxes(lbl_path: Path, w: int, h: int,
ball_ids: set[int]) -> tuple[list[list[float]], int]:
"""Nhan YOLO txt (class cx cy bw bh, normalized) -> list xyxy pixel tren
anh DA resize (cung he voi pred). Tra (boxes_ball, n_dot_dropped)."""
boxes: list[list[float]] = []
n_dot = 0
if not lbl_path.exists():
return boxes, n_dot
for line in lbl_path.read_text(encoding="utf-8").splitlines():
parts = line.split()
if not parts:
continue
cid = int(parts[0])
if cid not in ball_ids:
n_dot += 1
continue
cx, cy, bw, bh = (float(v) for v in parts[1:5])
boxes.append([(cx - bw / 2) * w, (cy - bh / 2) * h,
(cx + bw / 2) * w, (cy + bh / 2) * h])
return boxes, n_dot
def iou_xyxy(a: list[float], b: list[float]) -> float:
ix1, iy1 = max(a[0], b[0]), max(a[1], b[1])
ix2, iy2 = min(a[2], b[2]), min(a[3], b[3])
iw, ih = max(0.0, ix2 - ix1), max(0.0, iy2 - iy1)
inter = iw * ih
if inter <= 0.0:
return 0.0
area_a = (a[2] - a[0]) * (a[3] - a[1])
area_b = (b[2] - b[0]) * (b[3] - b[1])
return inter / (area_a + area_b - inter)
def greedy_match(preds: list[tuple[float, list[float]]],
gts: list[list[float]],
thr: float = IOU_THR) -> tuple[int, int, int]:
"""Match 1 anh: pred theo conf GIAM DAN, moi pred lay GT chua-match co
IoU cao nhat >= thr. Tra (tp, fp, fn). Mot GT match toi da MOT lan β
double-detection cung mot bi thanh FP, trung thuc voi detector."""
used = [False] * len(gts)
tp = fp = 0
for _conf, pb in sorted(preds, key=lambda t: -t[0]):
best_i, best_iou = -1, thr
for i, gb in enumerate(gts):
if used[i]:
continue
v = iou_xyxy(pb, gb)
if v >= best_iou:
best_i, best_iou = i, v
if best_i >= 0:
used[best_i] = True
tp += 1
else:
fp += 1
fn = used.count(False)
return tp, fp, fn
def main() -> None:
ap = argparse.ArgumentParser(
description="P/R/F1 predict-pipeline (deploy ruler) at operating conf")
ap.add_argument("--artifact-dir", type=Path, required=True,
help="chua opconf.json (doc conf + weights); ghi opconf_predict.json")
ap.add_argument("--weights", type=Path, default=None,
help="mac dinh: truong weights cua opconf.json")
ap.add_argument("--conf", type=float, default=None,
help="mac dinh: truong conf cua opconf.json")
ap.add_argument("--device", default=None,
help="mac dinh: khong truyen (model tu chon, nhu worker)")
args = ap.parse_args()
import torch
import yaml
import cv2
from ultralytics import YOLO
opconf = json.loads((args.artifact_dir / "opconf.json").read_text(encoding="utf-8"))
conf = args.conf if args.conf is not None else float(opconf["conf"])
weights = args.weights or Path(opconf["weights"])
predict_kw = {"device": args.device} if args.device else {}
print(f"[env] torch={torch.__version__} cuda_available={torch.cuda.is_available()}"
f" conf={conf:g} weights={weights}")
data = yaml.safe_load(DATA_YAML.read_text(encoding="utf-8"))
names = {i: n for i, n in enumerate(data["names"])}
name_to_id = {n: i for i, n in names.items()}
missing = [n for n in (*BALL_NAMES, EXCLUDE_NAME) if n not in name_to_id]
if missing:
sys.exit(f"[FAIL] data.yaml thieu class {missing}")
ball_ids = {name_to_id[n] for n in BALL_NAMES}
img_dir = Path(data["path"]) / data["val"]
lbl_dir = Path(str(img_dir).replace("images", "labels"))
imgs = sorted(img_dir.glob("*.jpg"))
if not imgs:
sys.exit(f"[FAIL] khong thay anh val o {img_dir}")
model = YOLO(str(weights))
if dict(model.names) != names:
sys.exit(f"[ERROR] names checkpoint {model.names} khac data.yaml {names}")
tot = {"tp": 0, "fp": 0, "fn": 0, "gt_ball": 0, "gt_dot": 0,
"pred_ball": 0, "pred_dot": 0}
for p in imgs:
img = cv2.imread(str(p))
if img is None:
sys.exit(f"[FAIL] khong doc duoc anh {p}")
img, _scale = resize_like_worker(img)
h, w = img.shape[:2]
res = model.predict(img, conf=conf, imgsz=IMGSZ, verbose=False,
**predict_kw)[0]
preds: list[tuple[float, list[float]]] = []
for b in res.boxes:
if names[int(b.cls)] not in BALL_NAMES:
tot["pred_dot"] += 1
continue
preds.append((float(b.conf), [float(v) for v in b.xyxy[0]]))
gts, n_dot = load_gt_boxes(
lbl_dir / (p.stem + ".txt"), w, h, ball_ids)
tp, fp, fn = greedy_match(preds, gts)
tot["tp"] += tp
tot["fp"] += fp
tot["fn"] += fn
tot["gt_ball"] += len(gts)
tot["gt_dot"] += n_dot
tot["pred_ball"] += len(preds)
# --- neo: tong GT ball tu file nhan phai khop validator da nghiem thu ---
ref_gt = opconf.get("counters", {}).get("gt_kept")
if ref_gt is not None and tot["gt_ball"] != ref_gt:
sys.exit(f"[FAIL] GT ball tu file nhan = {tot['gt_ball']} khac "
f"counters.gt_kept = {ref_gt} cua opconf.json - parse nhan "
f"sai, DUNG khong ghi so.")
prec = tot["tp"] / (tot["tp"] + tot["fp"]) if tot["tp"] + tot["fp"] else 0.0
rec = tot["tp"] / (tot["tp"] + tot["fn"]) if tot["tp"] + tot["fn"] else 0.0
f1 = 2 * prec * rec / (prec + rec) if prec + rec else 0.0
print(f"[deploy-pr] {len(imgs)} anh val; GT ball {tot['gt_ball']} "
f"(Dot loai {tot['gt_dot']}); pred ball {tot['pred_ball']} "
f"(pred Dot bo {tot['pred_dot']})")
print(f"[deploy-pr] TP {tot['tp']} FP {tot['fp']} FN {tot['fn']}")
print(f"[deploy-pr] conf {conf:g}: P {prec:.4f} R {rec:.4f} F1 {f1:.4f}"
f" (predict-pipeline)")
print(f"[deploy-pr] val-pipeline cung conf (opconf.json): "
f"P {opconf['precision']:.4f} R {opconf['recall']:.4f} "
f"F1 {opconf['f1']:.4f} - HAI CAI THUOC, ky vong predict thap hon")
# BRIEF "Neu bi": so vo ly la dau hieu sai matcher/resize -> DUNG, bao lai
if prec < 0.5 or rec < 0.5:
sys.exit(f"[FAIL] P {prec:.4f} / R {rec:.4f} < 0.5 - nghi sai "
f"matcher/resize, DUNG khong ghi opconf_predict.json, "
f"bao lai Cowork (BRIEF).")
out = {
"date": "2026-08-05",
"weights": str(weights),
"split": "val",
"conf": conf,
"method": (
"worker predict-pipeline: resize max-dim 1280 INTER_AREA (chep "
"block scan_once cua cv_worker.py) + model.predict imgsz=640 "
"NMS multi_label=False tai conf van hanh; any-ball merge "
"(Black+Cue+Solid+Striped -> ball), Dot loai ca pred lan GT; "
"greedy match theo conf giam dan, IoU >= 0.5, moi GT match "
"toi da 1 lan. Day la P/R/F1 tai MOT diem conf - KHONG phai "
"AP, KHONG so voi anyball AP50 val-pipeline (hai cai thuoc, "
"ban giao 17). Khong tai lap cac khau sau predict cua worker "
"(homography, dedupe tam ban, cue-dung-1)."),
"predict_pipeline": {"precision": round(prec, 4),
"recall": round(rec, 4),
"f1": round(f1, 4),
"tp": tot["tp"], "fp": tot["fp"], "fn": tot["fn"]},
"val_pipeline_ref": {"precision": opconf["precision"],
"recall": opconf["recall"],
"f1": opconf["f1"],
"source": "opconf.json (cung conf, val-pipeline)"},
"counters": {"n_images": len(imgs),
"gt_ball": tot["gt_ball"],
"gt_dot_excluded": tot["gt_dot"],
"pred_ball": tot["pred_ball"],
"pred_dot_dropped": tot["pred_dot"]},
"env": {"torch": torch.__version__,
"cuda_available": torch.cuda.is_available(),
"device_arg": args.device},
}
out_path = args.artifact_dir / "opconf_predict.json"
out_path.write_text(json.dumps(out, indent=2), encoding="utf-8")
print(f"[artifact] -> {out_path}")
if __name__ == "__main__":
main()
|