Back to work

Case study / CV SERVING PIPELINE

football-detect-serve

A YOLO26 football detector taken from checkpoint to endpoint: trained on SoccerNet broadcast footage, exported to ONNX and quantized to INT8, then served behind a batching FastAPI service — with the same evaluator run against every backend so accuracy and latency claims are measured, not asserted.

PythonUltralytics YOLO26PyTorchONNX RuntimeFastAPIDockerLocust

Architecture

TrainEvaluate (gate)Export ONNXQuantize INT8Serve (batch)Benchmark+ load testtorch vs onnx-fp32 vs onnx-int8, same evaluator

How it works

01

Data

MOT-format tracking labels from SoccerNet broadcast footage are converted to YOLO format, then verified for class balance and bad labels before training starts.

02

Train

YOLO26 nano/small trained on broadcast football video to detect ball, goalkeeper, player and referee, with mixup added specifically to help the rare, tiny ball class.

03

Evaluate (gate)

A single evaluator computes COCO-style mAP for every backend through the same code path — cross-checked against ultralytics’ own YOLO.val() so accuracy claims are never backend-specific.

04

Export & quantize

The checkpoint is exported to ONNX fp32 and INT8, calibrated on a stratified, cross-sequence sample of real training images rather than a naive slice that would collapse onto one stadium.

05

Serve (batched)

A FastAPI service holds torch, onnx-fp32 and onnx-int8 backends at once, dynamically batching requests within a 15ms window; load-tested with Locust across concurrency and batching on/off.

Engineering highlights

ONNX-fp32 gave 1.5x throughput for free; INT8 cost accuracy without a CPU speedup on ARM or x86 — measured, not assumed

Load testing exposed a real thread-safety bug (unsynchronized concurrent access to a non-thread-safe model) and drove a per-backend predict lock

One evaluator runs every backend through the same code path, cross-checked against ultralytics’ own evaluator to ~0.02 mAP agreement

Next project

Football Tactical Metrics →