Skip to content

The blur gate

S edited this page Aug 20, 2026 · 3 revisions

The blur gate, and what 156 hand-labelled frames actually showed

A phone held by a walking person produces a lot of frames that are worthless for computer vision: smeared by an arm swing, or near-black while the exposure catches up after stepping out of a doorway.

Feeding those to a detector costs the same as feeding it a good frame and returns nothing. The cheapest optimisation available in any vision pipeline is deciding not to process. The interesting question is not whether to have such a gate — it is where to put the threshold, and that is a question most projects answer by guessing.

We stopped guessing, and the results were less tidy than the guess would have been.

The measurement

One recorded indoor session. Every captured frame — 156 of them — sorted by hand into three buckets:

  • blurry — clearly blurred: 51 frames
  • blurry-a-bit — usable but visibly degraded: 63 frames
  • sharp — ideal: 42 frames

That labelling was then persisted as a checked-in JSON file, keyed by frame name, so it outlives the folder it came from and can be replayed against any future change. Four candidate blur metrics were scored against it, with the frames decoded through the runtime-equivalent path — the same downscale, the same grayscale conversion the app performs — and then replayed through the real gate at thresholds from 0.10 to 1.20.

What it showed

No metric separates cleanly. Every one of the four has blurry frames scoring like sharp ones. In AUC terms — where 1.0 is perfect separation and 0.5 is a coin flip — the best result at the "clearly blurry vs everything else" boundary was 0.761, and nothing exceeded 0.9 at either boundary.

That is the finding that matters, and it is a negative one. There is no threshold that catches the bad frames and keeps the good ones. There is only a trade, and pretending otherwise would have produced a confident, wrong default.

Beyond that:

  • Variance-of-Laplacian — the metric already shipping — is the best at spotting the clearly blurry (AUC 0.761) and was not beaten at its own job.
  • A high-frequency energy ratio computed via FFT is clearly better at "ideal vs everything degraded" (0.838 against 0.750). It is the one to reach for if the goal ever becomes keep only pristine frames rather than drop the unusable ones. The two are alternatives selected by a toggle, not a pair used together.
  • Tenengrad was clearly worst (0.651) and was dropped from consideration.

The threshold trade, measured. The gate rejects a frame when its sharpness falls below k × the rolling median of recent sharpness:

  • k = 0.5 — rejects 7% of frames, and on this scene rejected no sharp frame at all. But it catches only about a fifth of the clearly blurry ones. Safe, and close to useless on its own.
  • k = 0.8 — rejects about 30%, still almost never rejects a sharp frame (precision ≈ 0.98), and catches 59% of the clearly blurry ones.
  • Above k ≈ 0.85 precision decays fast. That is the wall.

We ship k = 0.8, enabled by default. The safer setting was the tempting one, but a gate that catches a fifth of the problem is a gate whose main effect is to make you feel it is handled.

What we did not measure, and it matters

Whether the retry actually helps. A rejected frame triggers a retry, and a replay of a recording can only see the frames that were captured — never the better frame a retry might have produced. So "30% rejected" is a measured number and "and the replacement was sharper" is an assumption. The wait is bounded, and when nothing good arrives in time a frame is saved anyway rather than starving the capture.

Outdoor behaviour. The labelled session is indoors. This framework's whole subject is outdoor AR. If outdoor frames score systematically differently the reject rate moves, and the honest position is that nobody has checked yet.

Publishing a default tuned on one indoor scene is a deliberate choice: the alternative was leaving a measured improvement switched off indefinitely waiting for a second labelling session that kept not happening.

Two design notes worth stealing

Relative, not absolute. The gate compares each frame against a rolling median of the scene's own recent sharpness rather than a fixed number. Walk from a detailed street into a plain grass field and the absolute sharpness of every frame drops — with a fixed threshold you would suddenly reject everything. Self-calibrating against the scene is what makes one threshold work in more than one place.

Metrics and policy are separate. The measurements are pure functions over pixels; the drop-or-retry decision is a separate object that consumes their numbers. That split is why the thresholds could be swept offline at all — you can feed the policy synthetic scores with no images in sight, and you can look at the distribution of scores independently of the verdicts.

Where it runs, precisely

Today the gate runs in the recorder's capture path — deciding which frames are worth storing. It is not in front of the QR detector, though that is the obvious next place for it.

That distinction matters for the claim being made. The storage saving is real and measured. The "detection cost avoided" saving is available, not banked.

The general habit

Where a stage is expensive and its input quality varies, put a cheap test in front of it. And where you can, label a few hundred examples by hand before choosing the threshold — it took an evening, and it replaced a plausible guess with a number, plus the considerably more useful knowledge that no threshold was going to be clean.

Clone this wiki locally