Objektdetektion
Kunna förklara bounding boxes, IoU, NMS och använda en detektor.
Förkunskaper
Intuition
Klassificering svarar «vad finns i bilden?». Detektion svarar «vad finns var?» — en lista av rutor med klass och konfidens.
IoU (intersection over union) mäter hur väl två rutor överlappar:
| IoU | Betyder |
|---|---|
| 1,0 | identiska rutor |
| 0,5 | tröskeln för «träff» i klassiska mått |
| 0,0 | ingen överlappning |
NMS (non-maximum suppression) löser att detektorn hittar samma objekt flera gånger: sortera rutorna efter konfidens, behåll den bästa, och kasta alla som överlappar den med mer än en tröskel. Upprepa.
Utan NMS får du tjugo rutor runt varje bil. Med NMS får du en.
Formellt
Två familjer:
| Tvåstegs (Faster R-CNN) | Enstegs (YOLO, RetinaNet, DETR) | |
|---|---|---|
| Hur | föreslå regioner, klassificera dem | förutsäg rutor direkt |
| Precision | historiskt högre | numera likvärdig |
| Hastighet | långsammare | realtid |
| Används | forskning, hög precision | i praktiken nästan alltid |
DETR är en tredje väg: en transformer som förutsäger en fast mängd rutor och matchar dem mot facit med ungersk matchning. Ingen NMS behövs — modellen lär sig att inte duplicera.
mAP (mean average precision) är standardmåttet, och det missförstås ofta:
- För varje klass, sortera detektioner efter konfidens.
- Räkna precision och recall vid varje tröskel → precision-recall-kurva.
- AP är arean under den kurvan.
- mAP är medelvärdet över klasserna.
- mAP@[.5:.95] (COCO-standard) medelvärdesbildar dessutom över IoU-trösklarna 0,50 till 0,95.
Den sista punkten är viktig: [email protected] och mAP@[.5:.95] skiljer sig ofta med 15–20 procentenheter, och en jämförelse som blandar dem är meningslös.
Klassobalans är detektionens grundproblem: en bild har typiskt några objekt och tiotusentals bakgrundsregioner. Två lösningar:
| Lösning | Idé |
|---|---|
| Focal loss | nedvikta lätta exempel så att de svåra dominerar gradienten |
| Hard negative mining | välj ut de svåraste bakgrundsexemplen |
Praktiska val:
| Behov | Val |
|---|---|
| Realtid på kant | YOLO i liten variant |
| Hög precision, inte tidskritiskt | tvåstegs eller stor DETR-variant |
| Små objekt | högre indataupplösning — viktigare än modellvalet |
| Få märkta bilder | förtränad modell + kraftig augmentering |
Raden om små objekt är den som oftast löser problemet i praktiken: att fördubbla upplösningen hjälper mer än att byta arkitektur.
Kod
import torch, torchvision
from torchvision.ops import box_iou, nms
# IoU för hand
def iou(a, b):
"""a, b: (x1, y1, x2, y2)"""
x1, y1 = max(a[0], b[0]), max(a[1], b[1])
x2, y2 = min(a[2], b[2]), min(a[3], b[3])
snitt = max(0, x2 - x1) * max(0, y2 - y1)
area_a = (a[2] - a[0]) * (a[3] - a[1])
area_b = (b[2] - b[0]) * (b[3] - b[1])
return snitt / (area_a + area_b - snitt)
print(round(iou((0, 0, 10, 10), (5, 5, 15, 15)), 4)) # 0.1429
print(round(iou((0, 0, 10, 10), (0, 0, 10, 10)), 4)) # 1.0
print(round(iou((0, 0, 10, 10), (20, 20, 30, 30)), 4)) # 0.0
# NMS: samma objekt hittat flera gånger → behåll den bästa
rutor = torch.tensor([[10., 10., 50., 50.], [12., 12., 52., 52.],
[11., 9., 49., 51.], [100., 100., 140., 140.]])
poang = torch.tensor([0.92, 0.88, 0.85, 0.79])
behall = nms(rutor, poang, iou_threshold=0.5)
print(behall.tolist()) # [0, 3] — tre överlappande blev en
# Använd en förtränad detektor
modell = torchvision.models.detection.fasterrcnn_resnet50_fpn(weights="DEFAULT").eval()
with torch.no_grad():
ut = modell([bild])[0]
for ruta, etikett, p in zip(ut["boxes"], ut["labels"], ut["scores"]):
if p > 0.7:
print(f" klass {int(etikett)} konfidens {float(p):.2f} {ruta.tolist()}")
# mAP: skilj på [email protected] och mAP@[.5:.95]
def ap_vid_troskel(pred_rutor, pred_poang, facit_rutor, iou_troskel=0.5):
ordning = pred_poang.argsort(descending=True)
pred_rutor = pred_rutor[ordning]
matchad = torch.zeros(len(facit_rutor), dtype=torch.bool)
tp = torch.zeros(len(pred_rutor))
for i, p in enumerate(pred_rutor):
if len(facit_rutor) == 0:
break
iou_rad = box_iou(p[None], facit_rutor)[0]
iou_rad[matchad] = -1 # redan använda facit kan inte matchas igen
basta = int(iou_rad.argmax())
if float(iou_rad[basta]) >= iou_troskel:
tp[i] = 1.0
matchad[basta] = True
kum_tp = tp.cumsum(0)
precision = kum_tp / torch.arange(1, len(tp) + 1)
recall = kum_tp / max(len(facit_rutor), 1)
# 101-punkts interpolerad AP, som COCO
ap = 0.0
for r in torch.linspace(0, 1, 101):
p_max = precision[recall >= r].max() if (recall >= r).any() else torch.tensor(0.0)
ap += float(p_max) / 101
return ap
def map_coco(pred_rutor, pred_poang, facit_rutor):
trosklar = torch.arange(0.5, 1.0, 0.05)
return {
"[email protected]": round(ap_vid_troskel(pred_rutor, pred_poang, facit_rutor, 0.5), 4),
"mAP@[.5:.95]": round(float(sum(
ap_vid_troskel(pred_rutor, pred_poang, facit_rutor, float(t))
for t in trosklar) / len(trosklar)), 4),
}
Skillnaden mellan de två talen i map_coco är ofta 15–20 procentenheter. Att jämföra en modells [email protected] med en annans mAP@[.5:.95] är ett av de vanligaste felen i detektionslitteraturen.
Behärskning innebär
- Förklarar IoU och NMS
- Tolkar mAP korrekt
- Väljer detektorfamilj efter krav
Logga in för att göra övningarna och bygga upp din behärskning.
Källor
- arXiv — Focal Loss for Dense Object Detection — arXiv (öppen åtkomst; licens per artikel)
- arXiv — End-to-End Object Detection with Transformers (DETR) — arXiv (öppen åtkomst; licens per artikel)
- PyTorch — tutorials (BSD-3) — BSD-3-Clause