Detection and segmentation in practice
By the end: You can fine-tune a pretrained detector, compute IoU and mean average precision, and budget for labelling.
- Boxes, masks and annotation formats
- IoU
- Mean average precision
- Fine-tuning a pretrained detector
- Labelling cost and licences
New to convolutional networks? A convolutional network is a neural network built for images. Rather than relating every pixel to every other pixel, it slides a small window across the picture hunting for one local pattern — an edge, then a corner, then a wheel — and hunts for that same pattern everywhere in the frame. Stack those layers and edges become shapes and shapes become objects. Why it matters
Classification tells you what is in an image. Detection and segmentation tell you where, as boxes or as pixel masks. This section is about doing that with a pretrained model rather than from scratch: the annotation formats you will be handed, how the overlap between a predicted box and a true one is scored, what mean average precision is actually averaging, and how to fine-tune a detector on your own data. It also costs out the labelling, which is usually the real constraint.
The annotation formats you will be handed#
Three formats account for nearly everything, and they disagree about what a box is, which is the source of most wasted afternoons in this work.
COCO JSON. Boxes are [x, y, width, height] in pixels, top-left origin.
Classes are integer ids with a separate categories list.
Pascal VOC One XML file per image. Boxes are xmin, ymin, xmax, ymax in pixels.
YOLO One text file per image, one line per object:
class_id x_centre y_centre width height
All four numbers are fractions of the image size, not pixels.
torchvision detection models want xmin, ymin, xmax, ymax in pixels.Intersection over union#
def iou(a, b): # boxes as (x1, y1, x2, y2)
ix1, iy1 = max(a[0], b[0]), max(a[1], b[1])
ix2, iy2 = min(a[2], b[2]), min(a[3], b[3])
inter = max(0, ix2 - ix1) * max(0, iy2 - iy1)
area = lambda r: (r[2] - r[0]) * (r[3] - r[1])
return inter / (area(a) + area(b) - inter)
truth = (10, 10, 50, 50) # a 40 x 40 box
for pred in [(10, 10, 50, 50), (12, 12, 52, 52), (20, 20, 60, 60),
(30, 30, 70, 70), (10, 10, 90, 90)]:
print(pred, round(iou(truth, pred), 3))
# (10, 10, 50, 50) 1.0 exact
# (12, 12, 52, 52) 0.822 off by 2 pixels in each direction
# (20, 20, 60, 60) 0.391 off by 10, a quarter of the box
# (30, 30, 70, 70) 0.143 off by 30
# (10, 10, 90, 90) 0.25 correct corner, four times the areaA predicted box counts as correct if its IoU with a true box of the same class exceeds a threshold, often 0.5. Note the last row: a box with the right corner and four times the area scores 0.25, so a detector that consistently predicts boxes too large is penalised as severely as one that misses objects entirely.
Mean average precision, worked#
Sort all predictions for a class by confidence, walk down the list, and trace precision against recall. The area under that curve is the average precision for the class. Mean average precision averages it over classes. The COCO convention also averages over IoU thresholds from 0.5 to 0.95, which rewards tight boxes. That is the definition; here it is on three true objects and five predictions.
three objects, five predictions, matched at IoU 0.5, walking down by confidence:
conf best IoU verdict running precision running recall
0.95 0.822 TP 1/1 = 1.000 1/3 = 0.333
0.90 0.620 TP 2/2 = 1.000 2/3 = 0.667
0.80 0.000 FP 2/3 = 0.667 2/3 = 0.667
0.70 0.747 TP 3/4 = 0.750 3/3 = 1.000
0.60 0.681 FP 3/5 = 0.600 3/3 = 1.000
AP at IoU 0.50 = 0.917
AP at IoU 0.75 = 0.333
AP at IoU 0.90 = 0.000
AP averaged over 0.50 to 0.95 in steps of 0.05 (COCO mAP) = 0.442That collapse from 0.917 to 0.333 between IoU 0.5 and 0.75 is the honest state of most detectors: they find the objects and their boxes are loose. Whether that matters is a question about your application, not about the model. If the box drives a robot arm, tightness is the product. If it crops a thumbnail, 0.5 is plenty and reporting COCO mAP will make a perfectly adequate system look broken.
Do not write the matching logic yourself for a real evaluation
The version above is deliberately simple so you can see what it does. Real implementations handle crowd regions, ignore areas, per-class matching and the 101-point interpolation, and they disagree with a naive version by several points. Write it once to understand it, then evaluate with pycocotools or torchmetrics and compare your number with theirs.
Fine-tune, do not train from scratch#
Start from a detector pretrained on a large dataset such as COCO and fine-tune it on your images. A few hundred well-labelled images per class often go a long way. torchvision ships detection and segmentation models with pretrained weights; several popular libraries make the whole loop a few lines. The change you make is the same one as for classification — replace the head with one sized for your classes — it is just buried a little deeper.
import torch
import torchvision
from torchvision.models.detection.faster_rcnn import FastRCNNPredictor
num_classes = 4 # your classes PLUS one for background
model = torchvision.models.detection.fasterrcnn_resnet50_fpn(weights="DEFAULT")
in_features = model.roi_heads.box_predictor.cls_score.in_features
model.roi_heads.box_predictor = FastRCNNPredictor(in_features, num_classes)
# In training mode it takes targets and returns a dict of losses, not predictions.
model.train()
images = [torch.rand(3, 240, 320), torch.rand(3, 200, 300)] # any sizes, unbatched
targets = [{"boxes": torch.tensor([[10., 10., 90., 90.]]), "labels": torch.tensor([1])},
{"boxes": torch.tensor([[20., 30., 80., 120.]]), "labels": torch.tensor([2])}]
losses = model(images, targets)
print(sorted(losses))
loss = sum(losses.values()) # you add them up yourself
model.eval()
with torch.no_grad():
out = model([torch.rand(3, 240, 320)])
print(sorted(out[0]), tuple(out[0]["boxes"].shape))
# ['loss_box_reg', 'loss_classifier', 'loss_objectness', 'loss_rpn_box_reg']
# ['boxes', 'labels', 'scores'] (100, 4)Labels cost the most#
Cost this out before anything else, because it usually decides the project. Drawing a box takes on the order of ten seconds once a labeller is warmed up; a careful polygon mask takes a minute or more. Suppose you want 500 images per class across 4 classes, at an average of 3 objects per image.
2,000 images x 3 objects = 6,000 annotations
boxes at 10 s each 16.7 hours
masks at 70 s each 116.7 hours
Add a review pass at half the original rate, and a second labeller on a
10% sample to measure agreement:
boxes 16.7 + 8.3 + 1.7 = 26.7 hours
masks 116.7 + 58.3 + 11.7 = 186.7 hours- Boxes take seconds each; pixel masks take much longer. Choose the cheapest label that answers the business question.
- Write labelling guidelines with examples of edge cases, such as occluded or partly visible objects, and measure agreement.
- Use the model to pre-label and have people correct, once it is decent. Correcting a box is roughly three times faster than drawing one, so this pays for itself well before the model is good.
- Label your validation set first and label it more carefully than the training set. Noise in training data costs you a little accuracy; noise in the validation set means you cannot tell which of two models is better, which costs you every decision afterwards.
Check licences
Some widely used detection libraries are released under strong copyleft licences with obligations even for software offered as a network service. Check before building a product on one.
Where people go wrong
Reporting one overall mAP when the objects that matter are a small slice of the data.
If this comes up in an interview
Explain mean average precision for object detection.
For each class, rank predictions by confidence, match them to ground truth boxes by IoU above a threshold, and compute the area under the precision-recall curve: that is average precision. Mean over classes gives mAP. COCO-style mAP also averages over IoU thresholds from 0.5 to 0.95.
Then they push further
Your detector has good mAP but misses small objects the business cares about. What do you do?
Measure mAP by object size to confirm, then try higher input resolution, tiling large images, anchor or architecture settings suited to small objects, and more labelled small examples.
What gets marked up. Marked up for IoU matching, the precision-recall curve, and breaking the metric down by slice.
Build this: Label 200 images of something around you with boxes, fine-tune a pretrained torchvision detector, and report mAP overall and by object size, with your own IoU and AP implementation checked against a library's.
Watch it explained
This is one section of Convolutional networks, 21 hours across 5 sections. Read it in full to get the whole topic in order.