Build the mental model
Object detection differs from classification in a fundamental way: instead of producing a single label for the whole image, it must predict both where each object is (location) and what it is (class) simultaneously. Location is typically represented as a bounding box (x1, y1, x2, y2) — the top-left and bottom-right corner coordinates. When you need to quantify how well a predicted box matches a ground-truth box, you use Intersection over Union (IoU): the area where the two boxes overlap, divided by the total area the two boxes occupy together (their union).
IoU ranges from 0 (no overlap at all) to 1 (the boxes are identical). A plain intersection-area metric alone would be misleading — two large boxes could have a large overlapping area purely because they're both big, regardless of how well-aligned they actually are. Normalizing by the union accounts for box size, making the comparison fair no matter how large or small the boxes are. That's why IoU is the fundamental building block underlying essentially all object detection evaluation (like mAP), and it's also the core operation inside non-maximum suppression, which uses it to discard redundant overlapping candidate boxes.
Connect it to a real scenario
Suppose the Tutorial Platform is building a feature that auto-detects the code-block region within a lesson screenshot to auto-crop a tightly-framed thumbnail around it. To decide whether the model's predicted code-block box is trustworthy, you'd use exactly this lesson's IoU function to quantify how well the predicted box matches a hand-labeled ground-truth box, and only trust the auto-crop in production once IoU is consistently high.
Try the working example
import torch
def compute_iou(box1, box2):
x1 = torch.max(box1[0], box2[0])
y1 = torch.max(box1[1], box2[1])
x2 = torch.min(box1[2], box2[2])
y2 = torch.min(box1[3], box2[3])
inter_width = (x2 - x1).clamp(min=0)
inter_height = (y2 - y1).clamp(min=0)
intersection = inter_width * inter_height
area1 = (box1[2] - box1[0]) * (box1[3] - box1[1])
area2 = (box2[2] - box2[0]) * (box2[3] - box2[1])
union = area1 + area2 - intersection
return intersection / union
# Two 10x10 boxes that overlap in a 5x10 strip
box_a = torch.tensor([0.0, 0.0, 10.0, 10.0])
box_b = torch.tensor([5.0, 0.0, 15.0, 10.0])
iou = compute_iou(box_a, box_b)
print(f"IoU: {iou.item():.4f}")
Prints exactly 'IoU: 0.3333'. Both boxes have area 100 (10×10); the overlap region spans x: 5-10, y: 0-10, giving intersection = 5×10 = 50, union = 100+100-50 = 150, so IoU = 50/150 = 0.3333.5-minute try-it
Modify compute_iou to be batch-aware — accept box_a of shape (N, 4) and box_b of shape (N, 4), and use torch.max/torch.min with an appropriate dim argument so it returns element-wise IoU of shape (N,) via broadcasting instead of handling one pair at a time.
One important caution
Skipping clamp(min=0) on inter_width/inter_height means two non-overlapping boxes produce a negative width or height, which can make the computed IoU negative.
Mistaking a (x1, y1, width, height)-format dataset (common in some COCO-style annotations) for (x1, y1, x2, y2) silently breaks the entire IoU calculation.
Wikipedia — Object detection — Computer Vision