
FIG. 01
RECORD DATA
Category
Project Diary
TARİH
There's a well-known fix for small-object detection: sliced inference (tiling). Instead of handing the model the whole frame, you cut it into overlapping tiles and run each tile on its own. That way a distant small target appears proportionally large inside the tile, and the model can pick it up.
That is exactly how it worked for us. Then we moved it onto the Jetson and dropped to 1-3 frames per second.
This post is the story of the gap between "a solution that works" and "a solution you can use in the field."
Tiling really did work
Proof first. We took two real aerial frames on which our old model produced no detections at all and ran nothing but sliced inference over them — no change to the model, the data, or the training.
Frame | Full-frame inference | Sliced inference |
|---|---|---|
Frame 1 | 0 detections | 5 detections (max confidence 0.74) |
Frame 2 | 0 detections | 1 detection |
That result was a turning point for us, because it showed us where the problem wasn't. The model could recognize those objects; it simply couldn't see them. The difference was how many pixels the object arrived at the network as.
(A note: ground truth was never formally labeled on those two frames, so we report this as a recovery signal rather than a "recall measurement." What we care about is the gap.)
That finding kicked off two things: rebuilding the dataset and the scale policy from scratch (that post is here), and pulling sliced inference into the flight software.
Moving it to the Jetson: 1-3 FPS
When we ran the sliced pipeline on the Jetson Orin NX, what we measured was 1 to 3 frames per second.
Our first instinct would have been to say "the GPU can't keep up." But once we profiled it, we saw the bottleneck wasn't on the GPU: the time was going into the CPU-side pre- and post-processing done for every tile. Splitting the frame into tiles, getting each tile into the shape the model wants (letterbox, normalization), then mapping each tile's output back into global coordinates and merging them (NMS) — all of it gets multiplied by the tile count.
So the problem wasn't "the model is heavy," it was "we were doing the same work N times for every frame".
That distinction matters: if we had diagnosed it as "not enough GPU," we would have gone looking for a smaller model or beefier hardware and headed off in the wrong direction.
The real question: is FPS a requirement or a preference?
This is where we stopped and asked: how many FPS do we actually need?
It's a very easy question to skip. "Real-time" tends to get used as if it unquestionably means 30 FPS. But the requirement comes out of the mission:
In our flow, the aircraft detects the target, records its position, then flies to it and releases the payload while centered on it.
The centering stage is closed-loop control: the aircraft corrects its own position by looking at the image.
In closed-loop control, a low frame rate translates directly into latency. A control loop running at 2 FPS makes decisions on information that is 500 ms stale while the aircraft keeps moving — it starts oscillating around the target. 2 FPS might have been fine for the search stage; for the centering stage it wasn't.
So the answer: in our scenario, FPS really was a requirement.
The trade: we spent resolution to buy speed
We changed the flight configuration:
Sliced mode | Flight mode | |
|---|---|---|
Inference | 1280 px tiles + full frame | Single full-frame pass, 640 px |
Frame rate (Orin NX) | 1-3 FPS | 15-20 FPS |
Target size at ~20 m | large within the tile | ~32 px |
Target size at ~46 m | reasonable within the tile | ~14 px |
Note what bought the speedup: it wasn't a smaller model or more aggressive optimization. It was removing the tiling itself — once the per-tile CPU work went away, the frame rate jumped straight to 15-20.
There is a band in which this trade holds up: at our validated ~20 m working altitude, the mannequin still comes to roughly 32 pixels after the downscale. The scale policy of the model we retrained covers that size.

What the trade honestly cost
The easy way to tell a story like this is to write up what you gained and gloss over the price. Here's the price:
At 46 meters (the competition's airdrop ceiling), the mannequin falls to roughly 14 pixels at a 640-pixel input, and we have not yet run a detection trial in that band.
This is a known, accepted gap. Our fallback is ready: we keep sliced mode and the compiled TensorRT FP16 engine in the codebase as a disabled option. If the full-frame budget doesn't hold up in the 46 m test, we'll switch sliced mode back on and accept the lower frame rate — and at that point we'll have to change the centering strategy to match.
But "a plan on the shelf" is not "a measured result." We're writing it down as exactly that.
One design decision, changed twice
Looking back, here's the interesting part: the same test changed the design twice.
The sliced inference trial → showed that scale was the cause of the lost detections → the dataset and training policy got rebuilt.
Profiling that same pipeline on the Jetson → exposed the CPU bottleneck → the flight configuration moved to full-frame mode.
A good test doesn't say "pass/fail"; it determines the next decision. In our case tiling turned out to be one of the most valuable experiments in the project even though we don't fly it — because it showed us where the problem was and where it wasn't.
What we took away
1. "It works" and "it works in the field" are different claims. Don't commit a solution to your architecture before you've measured it on the target hardware.
2. Profile before you optimize. Our bottleneck was CPU pre/post-processing, not the GPU; the wrong diagnosis would have led straight to the wrong fix.
3. Question the phrase "real-time." The mission decides how many FPS you need; a search pass and a closed-loop controller don't ask for the same thing.
4. Write down what the trade cost you. Describing your win while hiding your loss misleads your own team as well.
5. Don't delete the solution you rejected. Sliced mode is still sitting in our codebase as a disabled option; if conditions change, it's ready.
