![[S] Use YOLO! Not today - a 131k-param net I wrote in two days beats it in small blurry object detection](https://external-preview.redd.it/V5PM_HlTLJaPd6NlXBviYmetsh2I-iJKqqpVsJniuro.jpeg?width=140&height=105&auto=webp&s=d215c9d6f29df13b8713750789df91b2879eff8a)
[S] Use YOLO! Not today - a 131k-param net I wrote in two days beats it in small blurry object detection
TL;DR: Cropping in action with some extra algebraic and statistical magic applied: https://youtu.be/SetiZDbc8iE
I recently worked on determining the ball's position and reshaping the video from landscape to portrait based on that position. It often s looks like a layup: fixed camera, one class, find the thing. Then you look at what you're actually asking for: a small, blurry object is a handful of pixels, smeared across a few more, changing shape between consecutive frames. Not a crisp circle - a faint streak you can barely point at when the video is paused.
The part that tends to get skipped in the YOLO family is that those architectures downsample 32× before they reason. At stride 32, an 8-pixel object is a quarter of one feature cell. There is nothing left to detect. Fine-tune forever, buy a bigger GPU, adopt whatever dropped last week — the model is being asked to localize something it structurally cannot see. The extra-small heads help and still aren't built for this.
I believe great data and a simple model always beat poor data and a sophisticated model. Before this approach, I tried TrackNet v2/3/4, and the quality was awful; the public data used for training is not even close to what you meet in real practice.
What worked instead:
- Detector, ~131k params. Fully convolutional, dilated, max stride 2. In: 4 channels - RGB plus frame-difference. Out: a heat map and a size map at half resolution. No pretrained backbone: ImageNet features are the wrong prior for a faint smear.
- Verifier, ~48k params. The detector has the target in its top 20 about 90% of the time, but ranks it first only 77% of the time. This scores 64×64 crops and asks, "Is it a ball?"
- Then no ML at all. A reach limit measured from labeled footage - how far it can plausibly move between frames, scaled by apparent size - then link the surviving runs. Never link by direction of travel: anything that bounces reverses direction without going anywhere.
So, ~180k parameters total, ~125 fps on a 4090, ~4× realtime. Not fully optimized: custom Rust server with a CPU-bound FFmpeg decoder, ORT+TensorRT, and Rayon to speed things up a bit. Yet cannot use 100% of the GPU, capped by CPU-GPU PCIe transfers. Probably can reach 250-300 FPS with a more optimized inference design and int8.
The insight that mattered wasn't architectural. Blur is a signal, not a defect. The object is nearly invisible against a busy background and is almost always the fastest thing in the frame, so the frame-difference channel carries more information than any choice of backbone.