How it works: the method, in public

Which engine ran on your photo, how the diagnosis reached its verdict, how sure it is, and — for the learned pass this site is built to offer — the licence chain, the corpus and the host, written down before anything is downloaded.

Pixels stay in this tab; only the file you save leaves it.

Which engine ran

Every result names its engine, and the label, the work done and the export receipt all read the same value, so they cannot disagree. Today two engines are available:

Classical passAvailable. Deconvolution against a kernel of the measured shape and length, then an artifact guard that clamps each pixel to the local range of the original. Kernels under 33 taps run the iterative spatial path (eight rounds by default, each correction held inside a bounded band); kernels of 33 taps or more run the frequency-domain path, a regularised (Wiener) deconvolution computed with an FFT in tiles anchored to the full frame, whose cost does not grow with the kernel. The receipt names the path.
Learned passNot available. No model is published for this build, so nothing offers it and no result is ever labelled as learned.
SharpenAvailable, for photos that are soft rather than smeared: an unsharp mask (radius 0.3–5 px, amount 0–300%, threshold 0–20 levels, brightness only by default) with the artifact guard as its halo limiter. Offered when the measurement finds little or nothing to undo; it warns when the photo actually smeared in one direction.
De-blockNot available in this build. A block grid gets the verdict “can't be saved” and no pass.

How the diagnosis reaches a verdict

The photo is measured on a working copy no longer than 1,024 pixels before anything runs. Gradient energy is read in twelve directions; the axis that lost the most is the axis of motion. How far the smear ran is where the autocorrelation of differences along that axis first turns negative, and the same is read across it to tell movement from missed focus. A two-axis grid test looks for block periods of 4, 8 and 16 pixels.

MovementThe extent along the smear is clearly longer than across it, or gradient energy has collapsed onto one axis.
Missed focusThe spread is about equal in every direction.
Block gridA regular grid of block edges was found. Detail inside a block was averaged away; no pass is offered.
Nothing to undoThe smear is two pixels or less on the working copy.
Beyond reachThe smear is longer than the classical pass handles at this size, or longer than the measurement can read (24 px on the working copy). The original is left untouched.

Each verdict carries a confidence band — clear, likely or uncertain — derived from how directional the loss was and how decisively the along- and across-smear extents differ. At uncertain, Auto starts from a shorter, more conservative kernel and the page invites you to probe or adjust it. The bands are thresholds set by hand on synthetic smears; they have not yet been calibrated against a labelled corpus, so no precision or recall figure is published and no percentage is ever printed beside a verdict.

The synthetic harness behind the two paths

Both classical paths are checked against a drawn scene whose sharp state is known (scripts/harness-deblur.mjs). Mean absolute error per channel against the sharp scene, 640 × 448, 8 rounds, measured 20 October 2026:

Motion 15 tapsblurred 4.58 · spatial 4.19 · FFT 4.50 — the spatial path runs
Motion 30°, 21 tapsblurred 7.76 · spatial 6.67 · FFT 6.38 — the spatial path runs
Motion 31 tapsblurred 6.60 · spatial 5.65 · FFT 5.98 — the spatial path runs
Motion 60°, 41 tapsblurred 13.06 · spatial 10.16 · FFT 8.75 — the FFT path runs, about 2× faster
Motion 61 tapsblurred 9.77 · spatial 7.26 · FFT 8.43 — the FFT path runs, about 3× faster
Defocus 33 tapsblurred 9.83 · spatial 8.21 · FFT 7.47 — the FFT path runs

The same harness asserts that a banded render, a cropped render with its documented margin and a whole-frame render are byte-identical on every path, and that strength 0 returns the original exactly. The FFT path is not always closer to the sharp scene than the spatial one — on a long straight smear it trails — and it is chosen for long kernels because it stays fast where the spatial path slows down.

The blur check’s figures

Edge widthThe median 10–90% rise distance across the strongest edges, taken separately for four edge orientations; the widest orientation is the reading, because camera shake only widens the edges that cross the movement. In pixels of the 1,024-pixel working copy, with the full-size equivalent beside it. Up to 2.5 px reads as sharp, up to 4 px as soft, above that as blurred.
Laplacian varianceThe variance of the Laplacian divided by the variance of the image itself, so a low-contrast photo is not marked blurry for being grey.
High-frequency ratioEnergy left after removing a 5-pixel box blur, against the energy of the image about its mean.
What they are notNone of these is a quality score or a probability that any portal will accept a photo. The thresholds above are provisional, set by hand, and will change once they are calibrated.

What this build actually has

No published export. The loader, the byte-length check, the Cache Storage bucket, the session builder and the control that deletes the whole thing are all written and typed against a release constant, and that constant is null. Every surface that would offer a download says so instead of drawing a control that cannot complete.

What runs today is the classical pass (or the Sharpen engine) with no download behind it: the smear is measured off your frame, a kernel of that shape is built, and the frame is walked back through it — eight rounds by default — under a bounded correction. It is weaker than a trained pass and it is honest about the difference. It also downloads nothing.

The learned model it is built for, and the licence chain

NetworkNAFNet, in the single-image motion-deblurring form. Fully convolutional, which is what makes a frame of any size feasible in one pass rather than a fixed square.
ExportThe ONNX conversion published as opencv/deblurring_nafnet, licensed MIT.
UpstreamThe original implementation is offered under MIT and Apache-2.0 together.
Training pairsThe GoPro set, under CC BY 4.0 — sharp and motion-blurred frames from the same hand-held sequences.
AttributionThe GoPro corpus and both upstream licences are reproduced on this page because that is where the obligation sits, not in a file nobody opens.
Precisionfp32 as exported. An int8 or fp16 conversion would halve the transfer and is the single largest improvement available, but only if the quality can be measured to hold.

The chain matters practically rather than aesthetically: a corpus or a model licensed against commercial use would make the whole site unshippable however well it scores.

What was rejected, and on what grounds

  • MPRNet is out on licence alone. Its terms are a custom academic public licence that forbids commercial use outright, which is an absolute bar here rather than a trade-off to weigh against quality.
  • Restormer is MIT and clean, but it is a transformer with no ready ONNX export, and a transformer on single-threaded WebAssembly is near-certainly far slower than a convolutional stack of the same capability.
  • SwinIR is Apache-2.0 and clean, and fails on the same two grounds as Restormer.
  • All three are worth revisiting the day a maintained quantised export appears, and not before.

What a fetch would look like

HostOur model file server. Never a public model hub, because a request to one tells that hub which of this site's visitors is deblurring something.
RuntimeThe ONNX WebAssembly build, just under 14 MB, served from this origin rather than from anywhere else. Single-threaded: threads need cross-origin isolation, which this static site does not switch on.
TriggerA control naming the exact byte length. Nothing is requested before it is pressed, and Escape stops a transfer in flight and leaves nothing behind.
StorageOne Cache Storage bucket whose name carries the release, so a new set of weights is a new bucket rather than an invalidated path. Written only after a complete body of the right length has been read.
RemovalOne control under the rail. It clears the bucket, drops the session and its memory arena, and reports what it freed.
Private windowsThe Cache API is refused outright in some profiles. The pass still runs; it pays for the fetch again next session, and the rail says so rather than failing quietly.

Figures that are estimates, marked as estimates

Two numbers on this domain have been measured and the rest have not. The runtime’s length is the length of the file in this repository. The seconds quoted in the rail are timed on your own processor, running the identical loop the pass runs, which is why they are given as a band rather than as a figure.

Everything about the learned pass is an estimate until a model is published: a size of roughly 90 megabytes, the ten to twenty seconds for a 1024-pixel pass, and the assumption that the published graph has dynamic spatial axes at all. If it turns out to be frozen at 512 by 512 then every frame becomes a tiled job, the overhead roughly doubles, and the ceiling has to be rethought before anything else is built on top of it.

Elsewhere on this domain

Photographic visual for Model

Model