def iou_t(boxes1, boxes2):
"""IoU vectorisee : (N,4) x (M,4) en (x0,y0,w,h) -> (N,M)."""
b1, b2 = boxes1.to(DEVICE), boxes2.to(DEVICE)
ix0 = torch.maximum(b1[:, None, 0], b2[None, :, 0])
iy0 = torch.maximum(b1[:, None, 1], b2[None, :, 1])
ix1 = torch.minimum(b1[:, None, 0] + b1[:, None, 2], b2[None, :, 0] + b2[None, :, 2])
iy1 = torch.minimum(b1[:, None, 1] + b1[:, None, 3], b2[None, :, 1] + b2[None, :, 3])
iw = (ix1 - ix0).clamp(min=0)
ih = (iy1 - iy0).clamp(min=0)
inter = iw * ih
union = b1[:, None, 2] * b1[:, None, 3] + b2[None, :, 2] * b2[None, :, 3] - inter
return inter / (union + 1e-9)
# garde-fous : cas ou la reponse est connue
a = torch.tensor([[10.0, 10.0, 20.0, 20.0]])
assert abs(iou_t(a, a).item() - 1.0) < 1e-6 # identiques -> 1
assert iou_t(a, torch.tensor([[50.0, 50.0, 20.0, 20.0]])).item() < 1e-6 # disjoints -> 0
b = torch.tensor([[20.0, 10.0, 20.0, 20.0]]) # recouvrement horizontal 50 %
expected = 10.0 * 20.0 / (2 * 400.0 - 200.0)
assert abs(iou_t(a, b).item() - expected) < 1e-6
assert abs(iou_np((10, 10, 20, 20), (20, 10, 20, 20)) - expected) < 1e-9 # parite NumPy
print("IoU : garde-fous OK (identique=1, disjoint=0, moitie=%.4f)" % expected)
def predict_boxes(model, idx):
"""Boites (N,4) xyxy + scores d'une image de validation, seuils du 4.2c."""
r = model.predict(str(DS / "images" / "val" / f"{idx:05d}.png"),
conf=0.5, iou=0.45, imgsz=IMG, verbose=False,
device=DEVICE)[0].boxes
return (torch.tensor(r.xyxy.tolist(), dtype=torch.float32),
torch.tensor(r.conf.tolist(), dtype=torch.float32))
def collect_pr_yolo(model, B, iou_thr=0.5):
"""Flags TP/FP des detections YOLO, meme matching glouton que 4.2c."""
tp_s, fp_s, ngt = [], [], 0
for i in range(len(B)):
dets, dscores = predict_boxes(model, i)
gts = xyxy_yolo(B[i])
matched = torch.zeros(len(gts), dtype=torch.bool)
for d in dscores.argsort(descending=True).tolist():
if len(gts):
ious = iou_t(dets[d:d + 1], gts)[0]
ious[matched] = -1
g = int(ious.argmax())
if ious[g] >= iou_thr:
matched[g] = True
tp_s.append(float(dscores[d]))
continue
fp_s.append(float(dscores[d]))
ngt += len(B[i])
return tp_s, fp_s, ngt
def xyxy_yolo(b_xywh):
"""(x0, y0, w, h) numpy -> (x1, y1, x2, y2) torch : corners."""
t = torch.tensor(b_xywh, dtype=torch.float32)
return torch.stack([t[:, 0], t[:, 1], t[:, 0] + t[:, 2], t[:, 1] + t[:, 3]], dim=1)
def ap_voc(tp_s, fp_s, ngt):
flags = np.array([1] * len(tp_s) + [0] * len(fp_s), dtype=np.float64)
scores = np.array(tp_s + fp_s, dtype=np.float64)
order = np.argsort(-scores)
flags, scores = flags[order], scores[order]
ctp, cfp = np.cumsum(flags), np.cumsum(1 - flags)
rec = ctp / max(ngt, 1)
prec = ctp / np.maximum(ctp + cfp, 1e-9)
mrec = np.concatenate([[0], rec, [1]])
mpre = np.concatenate([[0], prec, [0]])
for i in range(len(mpre) - 2, -1, -1): # monotonie descendante (VOC10)
mpre[i] = max(mpre[i], mpre[i + 1])
ap10 = float(np.sum((mrec[1:] - mrec[:-1]) * mpre[1:]))
ap07 = 0.0
for t in np.linspace(0, 1, 11): # 11-point interpole (VOC07)
sel = rec >= t
ap07 += (prec[sel].max() if sel.any() else 0.0) / 11
return ap07, ap10, rec, prec