face-model / code /recall_at_conf.py
Banaxi-Tech's picture
Upload face detector (YOLO11n/s), ONNX exports, scripts, model card
d176ecd verified
Raw History Blame Contribute Delete
2.78 kB
#!/usr/bin/env python3
"""Face recall at a fixed confidence threshold on WIDER val (what fraction of GT faces get a box with IoU>=0.5)."""
import argparse
from pathlib import Path
import numpy as np
from ultralytics import YOLO
def iou(a, b):
x1 = np.maximum(a[:, None, 0], b[None, :, 0]); y1 = np.maximum(a[:, None, 1], b[None, :, 1])
x2 = np.minimum(a[:, None, 2], b[None, :, 2]); y2 = np.minimum(a[:, None, 3], b[None, :, 3])
inter = np.clip(x2 - x1, 0, None) * np.clip(y2 - y1, 0, None)
aa = (a[:, 2] - a[:, 0]) * (a[:, 3] - a[:, 1]); ab = (b[:, 2] - b[:, 0]) * (b[:, 3] - b[:, 1])
return inter / (aa[:, None] + ab[None, :] - inter + 1e-9)
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--model", default="export/face_yolo11n_fp16.onnx")
ap.add_argument("--conf", type=float, default=0.25)
ap.add_argument("--iou", type=float, default=0.5)
ap.add_argument("--val", default="dataset/images/val")
ap.add_argument("--device", default="0", help="0 for GPU, cpu for CPU")
ap.add_argument("--imgsz", type=int, default=640, help="inference size (.pt models only; ONNX exports are fixed)")
a = ap.parse_args()
model = YOLO(a.model, task="detect")
files = sorted(Path(a.val).glob("*.jpg"))
hits, sizes, n_pred, n_fp = [], [], 0, 0
for i in range(0, len(files), 1): # ONNX export is fixed batch=1
batch = files[i:i + 1]
for f, r in zip(batch, model.predict([str(x) for x in batch], conf=a.conf, imgsz=a.imgsz, device=a.device, verbose=False)):
h, w = r.orig_shape
gt = np.array([[float(v) for v in l.split()[1:]] for l in
Path(str(f).replace("images", "labels").replace(".jpg", ".txt")).read_text().split("\n") if l.strip()])
g = np.stack([(gt[:, 0] - gt[:, 2] / 2) * w, (gt[:, 1] - gt[:, 3] / 2) * h,
(gt[:, 0] + gt[:, 2] / 2) * w, (gt[:, 1] + gt[:, 3] / 2) * h], 1)
p = r.boxes.xyxy.cpu().numpy()
n_pred += len(p)
if len(p):
m = iou(g, p)
hits += list(m.max(1) >= a.iou)
n_fp += int((m.max(0) < a.iou).sum())
else:
hits += [False] * len(g)
sizes += list(gt[:, 3] * h) # face height in px at the image's stored (<=640) scale
hits, sizes = np.array(hits), np.array(sizes)
print(f"RES faces={len(hits)} overall recall@conf{a.conf}/IoU{a.iou} = {hits.mean():.1%} (false-positive boxes: {n_fp}/{n_pred})")
for lo, hi in [(0, 10), (10, 20), (20, 40), (40, 80), (80, 1e9)]:
m = (sizes >= lo) & (sizes < hi)
print(f"RES face height {lo:>3}-{int(min(hi, 999)):>3}px: {hits[m].mean():6.1%} (n={m.sum()})")
if __name__ == "__main__":
main()