File size: 2,782 Bytes
d176ecd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
#!/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()