Ahmed Doghri Logo Image
Ahmed Doghri

videoprivacy

A missed frame shouldn't reveal a face. videoprivacy tracks across detector blinks and blurs the gap too, after I measured the pixels and found the filled box was covering where the face used to be.

videoprivacy tracked face redaction across frames
videoprivacy working browser demo

Frame-by-Frame Blur Leaks

Face detectors miss motion blur, profiles, occlusion, and compression artifacts. Treat every frame independently and one missed box reveals the person the pipeline was meant to protect.

videoprivacy associates detections with intersection-over-union tracking, pads and clips boxes, fills short detection gaps, and applies real OpenCV blur to the output video.

Ship the Redaction Report

The pipeline records every frame, track identifier, box, and inferred gap fill, so a redaction can be audited rather than trusted. Detection sequences can also be inspected through the CLI, the JSON API, the workbench, and the tests.

The Demo

Across five frames, two identities produce ten redacted regions, and one missing detection is filled from track continuity so neither identity is exposed during the gap.

The default local Haar detector keeps setup light. Production use needs a detector evaluated for the actual camera domain, demographic coverage, pose, occlusion, and recall target.

The Filled Gap Was Covering Where the Face Used to Be

I'd already stress-tested the tracker: across 2,100 simulated frames on a 40-seed sweep and a disjoint 30-seed holdout, no real detection was ever dropped from the report, and gap-filling added about 27 to 30 points of coverage over naive per-frame detection. Greedy matching can swap two identities when people cross, but every detection still resolves to some box, so a swapped label never costs a redaction.

What none of that caught is that I was checking reports, not pixels. So I wrote a real mp4, put a moving high-frequency patch in it as a stand-in for a face, forced the detector to blink for three frames, and measured how much detail survived inside the true face region. On the second missed frame, 20% of the original detail was still there. The gap fill reused the last known box, and the subject had walked out of it.

Tracks now carry their measured velocity. A filled box is predicted forward and grown by the distance travelled into the gap, because the velocity estimate decays too, and matching uses the predicted position so a track survives a gap it moved during. Residual detail on that frame went from 0.20 to 0.001. A subject standing still gets the identical box as before, with no needless inflation.

Past max_gap frames the track is dropped rather than guessed about forever, which is the honest boundary rather than a bug. That knob is now a CLI flag, because it's the real tradeoff here: raising it covers longer blinks and over-redacts more. For privacy work, over-covering is the right direction to be wrong in. 12 tests pass across Python 3.10 through 3.13.

Implementation Basis

The video path uses OpenCV's established detection and DNN tooling rather than inventing a media decoder or a blur pipeline. Read the OpenCV DNN face detection docs.

Try It

Needs OpenCV; everything runs locally:

git clone https://github.com/ahmeddoghri/videoprivacy && cd videoprivacy
pip install -e .
videoprivacy demo                                  # the tracked redaction report
videoprivacy redact in.mp4 out.mp4 --max-gap 5     # real blur, longer blink coverage
python -m videoprivacy.eval_v2                     # 2,100 frames: no detection dropped

Tools Used

Python
OpenCV
Video Processing
Face Detection
IoU Tracking
Privacy
Property-Based Testing
Docker