Skip to main content

Module 3 — One-stage detection: YOLO and SSD

Module 2 built Faster R-CNN for its accuracy and paid the throughput price. The crossroads camera streams at 25 frames per second, day and night, on a single edge GPU. At that budget every millisecond counts, and we now switch to detectors designed from the start to answer where and what in a single forward pass.

The one-stage idea

Two-stage detectors work in image-then-region logic: propose, then classify. One-stage detectors reverse it. They lay a dense grid over the image and let each cell predict, all at once, whether an object of some class is centred there and how big its box is.

The saving is architectural, not just algorithmic. There is no proposal generator to run, no RoI pooling to compute, no per-region head to iterate: one forward pass produces every prediction. The price is that dense predictions must handle massive class imbalance — the vast majority of grid cells contain no object — and this drives most of the design choices in the family.

YOLO in one paragraph, then five years

YOLO — You Only Look Once — divides the input image into a grid of cells. Each cell predicts BB bounding boxes with their confidence, plus a class distribution. Boxes are parameterised relative to the cell centre and to a set of anchor shapes (module 4). Everything is regressed in a single tensor, whose shape encodes the grid, the anchors, the box coordinates and the classes.

Then five years:

VersionYearChange that matters for us
v32018Three detection scales, Darknet-53 backbone, still anchor-based
v42020Bag of training tricks (Mosaic, CIoU loss), first "engineer's" YOLO
v52020Ultralytics rewrite in PyTorch, ergonomic CLI
v72022Extended aggregation network, best precision-speed trade-off at release
v82023Anchor-free head, unified tasks (detection, segmentation, pose)
v9 to v112024–2025Programmable Gradient Information, incremental gains

For the crossroads, YOLOv8 is a reasonable default: pretrained on COCO, anchor-free (module 4 will explain why this simplifies training), fast on modest hardware, and its Ultralytics tooling handles both training and inference in a few lines.

Running YOLOv8 on the crossroads

from ultralytics import YOLO

model = YOLO("yolov8n.pt") # nano version, fastest
results = model.predict(
source="crossroads_frame_0001.jpg",
conf=0.5,
iou=0.5,
classes=[0, 2, 3, 5, 7], # person, car, motorcycle, bus, truck (COCO ids)
)

for box in results[0].boxes:
xyxy = box.xyxy[0].cpu().numpy() # [x1, y1, x2, y2] in pixels
cls = int(box.cls[0])
score = float(box.conf[0])
print(f"class={cls} score={score:.2f} box={xyxy}")

The API is deliberately minimal. Behind it, the model resizes the image (default 640 pixels on the long side), runs one forward pass, decodes predictions, applies NMS, and returns the survivors. Filtering by COCO class ids restricts detections to the ones we actually care to count.

Video runs the same way, streaming frames without loading them all into memory:

results = model.predict(source="crossroads.mp4", stream=True, conf=0.5)
for r in results:
frame = r.orig_img
boxes = r.boxes.xyxy.cpu().numpy()

SSD and RetinaNet: the other lineage

Two other one-stage detectors deserve a mention because they contributed ideas that YOLO absorbed.

SSD (Single Shot Detector, 2016) added the crucial idea of multi-scale detection. Instead of predicting boxes from a single feature map, SSD reads several feature maps of decreasing resolution and predicts small objects on the fine ones, large objects on the coarse ones. Modern YOLO does the same, with three or four detection scales connected by a feature pyramid.

RetinaNet (2017) tackled the class imbalance head-on with focal loss. Standard cross-entropy is dominated by the tens of thousands of easy background cells: their contribution washes out the few hard positives. Focal loss down-weights confident predictions by a factor (1pt)γ(1 - p_t)^\gamma, letting the network focus on the ambiguous cases. The formula is worth memorising:

FL(pt)=α(1pt)γlog(pt)\text{FL}(p_t) = -\alpha (1 - p_t)^\gamma \log(p_t)

With γ=0\gamma = 0 focal loss reduces to standard cross-entropy; γ=2\gamma = 2 is the recommended default. The idea has spread beyond RetinaNet: several modern anchor-free detectors include a variant.

The precision-speed trade-off, revisited

On COCO, a rough comparison at 640-pixel input:

ModelmAP@[0.5:0.95]Inference on a mid-range GPU
Faster R-CNN, ResNet-50~408 to 12 fps
YOLOv8-n~37200+ fps
YOLOv8-m~5060 to 100 fps
YOLOv8-x~5425 to 40 fps

Two lessons. First, the modern medium YOLO matches or exceeds Faster R-CNN in accuracy while being an order of magnitude faster. Second, one-stage does not mean "worse but faster" any more; it means "a different point on the same curve", and often a better one for real-time constraints.

Real time is a system property, not a model property

A model that runs at 100 fps on a laboratory GPU can drop to 5 fps in production when disk reads, decoding, colour conversion and network transmission are added. Measure the full pipeline end to end before choosing a model size, otherwise you optimise a component that was not the bottleneck.

Where one-stage detectors still lose

Small objects, dense crowds and heavy occlusion remain harder for one-stage detectors than for two-stage ones. The reason is the same as their strength: one dense pass has a fixed budget per spatial location, whereas a two-stage detector spends more compute where it matters. On the crossroads, distant pedestrians on the far pavement are the typical case where a large Faster R-CNN outperforms YOLOv8-n even at ten times the latency.

Choose your YOLO size from a latency budget, not from a paper

Ultralytics ships five sizes (n, s, m, l, x). Pick the largest that fits your latency budget in the worst realistic case, not the best. On our crossroads, "worst realistic" is a foggy night frame with 40 vehicles: NMS becomes non-trivial, decoding takes longer, and the extra 20 ms you had in reserve are eaten in one frame.

In summary

  • One-stage detectors predict densely on a grid in a single forward pass, trading a proposal stage for massive class imbalance to manage.
  • YOLOv8 is a reasonable default for the crossroads: pretrained on COCO, anchor-free, fast, and driven by an ergonomic Python and CLI toolkit.
  • Focal loss (RetinaNet) rescues one-stage training from being dominated by trivial background cells, and its idea now lives in most modern anchor-free heads.
  • Modern medium one-stage models often beat two-stage on both accuracy and speed; two-stage still wins on small, occluded or crowded objects, which the next modules will revisit.

Next module: anchors, non-maximum suppression and the thresholds that turn raw predictions into a usable detection list.