The method
What we blur, what we do not, and what happens when it fails
Faces are found and blurred before anything else in the pipeline runs, and the cut that turns a Recording into Clips reads the redacted copy — so anyone who reviews that cut sees blurred footage rather than the original. Two detectors can do the blurring, and which one runs is a property of the host. When neither loads, the stage raises and nothing moves past it.
What runs
Redaction is the first stage a Recording walks: a Recording job runs BLUR, then SEGMENT, the stage that cuts it into Clips. Each Clip that SEGMENT mints enters the queue already held — hand tracking, captioning and export are deferred until somebody releases that cohort by name, so they do not begin on arrival. Nothing that runs after BLUR reads the original file: each later stage takes the redacted copy and raises when it is absent rather than reaching past it.
Which detector runs is a property of the host, not a claim about the product. YuNet — a small OpenCV face-detection network — runs when the installed OpenCV exposes FaceDetectorYN and the weights file sits in the models directory. When either condition fails, a Haar cascade runs: OpenCV's older classical detector, available when the library still ships CascadeClassifier and the frontal-face XML exists where OpenCV points. We prefer YuNet for the faces egocentric capture actually produces, which are off-axis, motion-blurred and partial. We have not scored either detector against a labelled set of our own, and that gap is stated at the end of this page.
A detected box is padded outward by 15% before the blur lands, because a hairline, a jaw and an ear carry identity as a face does. The blur is a 99×99 Gaussian at sigma 30 — large and odd on purpose, since a small kernel over a face-sized region decorates rather than removes.
Each pass returns a record rather than a verdict: which detector ran, how many frames it decoded, how many regions it blurred, what it covers and what it does not. What the pipeline writes down from that is narrower. It records the redacted copy's SHA-256, its byte size and its path, against the capture the file came from, and refuses to record any of it when no capture record exists. The detector name, the frame count and the region count are handed back to the stage that asked for them and are not stored anywhere — the last section says so, because it is the reason the published run carries no redaction record.
A caller cannot switch this off. The two annotation routes accept a form field asking to skip redaction, and the server redacts anyway and logs the refusal. Only the deployment can honour a skip, through a setting that ships false and exists so a local batch run can spend its CPU elsewhere. The upload path a Capture Partner's app uses carries no such field at all.
What it covers
Coverage here means one thing, stated narrowly: a face detector ran over every decoded frame, and the boxes it returned were blurred.
- Human faces, in every decoded frame of the video track.
- The whole file. The pass decodes and re-encodes each frame, so what leaves this stage is a new file rather than an edited original.
- Sound, by removal rather than by blurring. The encoder writes video only, and the service then reads the written file's own track table and raises if it finds an audio track — so the absence is checked, not inherited from whichever encoder happens to be installed.
What it does not cover
The service names three gaps in the record it returns: licence plates, screens and documents. The rest of this list is ours — it is what we know this pass does not reach, written down here rather than left for a buyer to discover.
The one no extra detector closes is the fourth line, a face the detector misses. It is measurable, we have not measured it, and the last section says what measuring it would take.
- Licence plates are not covered. No plate detector runs anywhere in this pipeline.
- Screens and documents are not covered. A laptop display, a printed page or a whiteboard in frame reaches a buyer as it was recorded, unless a face appears on it.
- Name badges, tattoos, house numbers and door plates. Nothing looks for them.
- A face the detector misses stays unblurred, and neither detector reports what it failed to find.
- The retained original keeps its audio. This pass reads the video track alone, so a background conversation survives in the file we hold, though it does not reach the copy we deliver.
- What a Capture Partner tells us about a Recording. That text sits in the consent record, and the consent-ledger page describes how it is handled.
What happens when redaction cannot run
The detector is built before anything is opened or written, so a host carrying neither one produces no output file at all, and the error names the OpenCV version it found, the path it searched, and the script that fetches the weights. Five conditions stop this pass, and each one raises rather than returning a path:
A stage that fails retries, and stops at its fourth attempt. Nothing advances behind it either: the cut refuses to run without the redacted copy, the tracker refuses to fall back to the original, and the captioner checks for the redacted file again before any frame leaves this machine for an external model.
- No detector. When neither YuNet nor the cascade loads, the stage raises before a frame is read.
- A source that will not open. If the decoder cannot open the recording, the stage raises rather than reporting a pass over a file it never read.
- A writer that will not open. OpenCV's video writer returns a working-looking object when the codec is missing and silently drops every frame it is given. It is checked now, so a missing encoder produces a stopped stage rather than a zero-byte file described as redacted.
- Nothing decoded. If the pass reaches the end of the file having read no frames, it raises instead of reporting a redaction over zero of them.
- Sound in the output. The written file is read back for an audio track, and the stage raises if it finds one.
What we corrected, and when
Until 13 August 2026 this service could pass footage through unredacted and report success. The frame loop sat behind a check on the cascade, so a detector that failed to load meant every frame was written unmodified, and the method still returned its output path as though the work had happened. A caller could not tell one output from the other.
That was live rather than theoretical. OpenCV 5.0 had removed CascadeClassifier along with the bundled cascade files, so on the installed library the detector never loaded at all. Both halves were fixed the same day: YuNet became the first choice, the cascade became the fallback, and the service began raising. Redaction happens, or the stage stops.
A day later we corrected a claim rather than a defect. We do not redact licence plates, and until 14-08-2026 this codebase said in several places that we did — including the policy string written into a Capture Partner's consent receipt and hashed into their ledger entry. The wording came out of every one of them.
Receipts issued before that correction still carry the old text, and we are not rewriting them. Each entry's SHA-256 covers the policy field, and every later entry's prev_hash covers that hash, so editing the history would either break the chain or quietly re-hash it — which is the tamper the ledger exists to expose. The correction runs forward. What we owe the holders of those receipts is a founder's decision, and it is open.
Four days after that, an audit found three more ways this path could report a redaction it had not performed, and closed all three: the unchecked video writer, the fallback to the raw original in the stage after this one, and the form field that let a caller ask for the pass to be skipped. All three are described above. Each was defensible on its own, which is how they survived review together.
One line of the 14-08 correction is still owed, and it is easier to state than to fix. The policy text hashed into every new receipt names YuNet. The code runs YuNet only when the installed OpenCV exposes it and the weights file is present, and falls back to the older cascade when either condition fails — so on a host that falls back, the receipt names a detector that did not run. Either that text becomes conditional or the fallback goes.
Where the measurements stop, and what closes each one
Every limit on this page is a measurement limit, and each one names the thing that closes it.
The miss rate is the one to press us on. It closes the day a cohort records against a commissioned brief, and not before.
- No miss rate. Scoring a detector needs cohort footage and a labelled sample drawn from it. Nothing has been recorded against a commissioned brief yet, so we hold neither.
- No comparison between the two detectors on our own material. The preference for YuNet rests on the kind of faces this capture produces, rather than on a score we ran.
- No published throughput. We have committed no machine-readable redaction timing, so any number printed here would be typed by hand.
- No redaction record on the published run. One recording has been annotated end to end and is published in full, with its annotation and its checksum. Its redaction record is not among those files, and there is none to publish: the detector name, the frame count and the region count are returned by the pass and never written down.
- The original is kept rather than deleted. Redaction produces a second file, and the unedited recording stays under a stated ceiling that the privacy notice carries.
- The sweep enforcing that ceiling reports by default. Deletion is a deliberate action somebody has to take, because the target is the unredacted original and no undo exists.