Applied computer vision · Local processing · Public code
PrivacyGuard
A public Python implementation that turns compatible ONNX model output into bounded image regions, applies an explicit masking policy, and carries the transformed frames through image, recorded-video, or best-effort live-stream paths.
Technical implementation
PrivacyGuard separates three questions that are often blurred together: what the model reported, which reported classes should be acted on, and how the chosen pixels should be transformed. The public orchestrator, detector, and anonymizer make those boundaries inspectable.
One frame through the actual pipeline
- Copy the caller’s frame. The high-level frame API works on a copy, so masking does not silently mutate the array supplied by the calling application.
- Preserve the source geometry separately. The detector records the original width and height, resizes the BGR frame to the configured model dimensions, converts it to RGB, normalizes bytes to floating-point values, changes HWC layout to CHW, and adds the batch dimension expected by ONNX Runtime.
- Execute the selected local runtime provider. The model session receives the normalized tensor. Provider selection prefers an available accelerator and falls back to CPU; that choice affects performance, not the downstream detection contract.
- Decode one of the supported output layouts. YOLOv8-style center coordinates and class-score arrays, and the older six-value box format, are converted into the same immutable detection record: corners, confidence, class identifier, and optional label.
- Map coordinates back to the source frame. Horizontal and vertical scale factors are applied independently, coordinates are clipped to frame bounds, and low-confidence candidates are removed.
- Suppress duplicates within each class. Non-maximum suppression runs per class. An overlapping plate and face therefore do not eliminate one another merely because their boxes intersect.
- Apply deployment policy. The orchestrator can restrict masking to configured class identifiers. Model recognition and the decision to modify a region remain separate operations.
- Transform a bounded region. Optional padding is applied and clipped again. Empty boxes are skipped; valid regions receive the configured Gaussian blur, downsample-and-upsample pixelation, solid fill, or a class-specific override.
- Return or encode the result. A post-processing hook and monitor can observe the result. Image and video paths write it to a destination, while cleanup for recorded video is protected by a finalization block that releases both reader and writer.
Hard problem: normalize model output without moving the mask
A detector integration can appear correct while placing masks on the wrong pixels. Models disagree on tensor layout, box representation, labels, and coordinate space. PrivacyGuard normalizes its two supported output layouts before the policy or masking stages see them, then converts center-based boxes to corners, rescales to the original frame, clips to valid bounds, and performs per-class suppression.
The per-class rule matters. If suppression were applied across all labels, a high-confidence face box could remove an overlapping plate box even though both regions should remain eligible for masking. The detector tests use synthetic model arrays to verify confidence filtering, coordinate rescaling, empty output, both supported layouts, and retention of overlapping detections from different classes.
The design also has a visible tradeoff: preprocessing uses direct resize rather than letterboxing. Independent horizontal and vertical scales map boxes back to the correct source coordinates, but a non-square source can be geometrically distorted during inference. Whether that reduces recall is model- and footage-dependent and must be evaluated; coordinate correctness alone does not prove detection quality.
- Normalize before policy
- Every supported detector path must produce the same bounded detection record. This keeps class filtering and masking independent of a model’s native output layout.
- Clip at both model and masking boundaries
- Post-processing constrains boxes to the frame, and the anonymizer constrains padded regions again. Invalid or zero-area regions are skipped instead of being passed to OpenCV.
- Do not infer safety from a box
- A well-formed coordinate proves only that the pipeline can transform that region. It says nothing about regions the model missed or classified incorrectly.
Masking semantics and their limits
The anonymizer operates only on detected and policy-approved regions. Gaussian blur changes local frequency detail, pixelation reduces and re-expands the region, and solid fill replaces its visible pixels. Per-class overrides allow, for example, a solid treatment for one class and blur for another. The anonymizer tests verify region modification, padding, per-class selection, zero-size handling, shape preservation, and an unchanged result when there are no detections.
None of those transformations protects an original stored elsewhere. Blur and pixelation are not encryption, and even solid fill applies only to the pixels in the produced frame. Metadata removal is a separate utility rather than an automatic step in the core frame path. Operational logging is likewise a local software record, not tamper-evident proof that every required region was masked.
Live streams choose freshness over completeness
The stream reader captures frames on a background thread and exposes a lock-protected copy of the most recent frame. It deliberately does not build a queue. When capture runs faster than detection and masking, newer frames overwrite older ones instead of building latency and unbounded memory pressure.
That is a low-latency policy, not backpressure. Because reading does not consume or sequence a frame, scheduling can make processing skip intermediate frames or observe the same latest frame more than once. The component does not implement reconnect behavior for an interrupted network stream. A consuming application that needs complete archival processing should use the sequential recorded-video path or add its own bounded queue, drop accounting, retry policy, and health state.
The built-in frame timing covers detection and anonymization. It does not by itself constitute an end-to-end benchmark of decoding, capture, display, post-processing hooks, encoding, storage, or network transport.
What the public evidence verifies
The public test suite checks tensor shape and scaling, both detector output formats, confidence and class behavior, source-frame immutability, ROI transformation, target filtering, metric propagation through the file path, regional helpers, monitoring, batch operations, and fixed-region masking. The orchestrator tests mock inference so pipeline behavior can be tested without bundling a model.
That test strategy verifies software contracts; it does not establish face or plate recall on representative footage. The repository ships neither a fixed evaluation dataset nor a validated model artifact for every advertised class. Regional and ensemble modules should therefore be read as experimental extension points, not as published accuracy results.
No reproducible public FPS benchmark is claimed. The repository’s benchmark record notes that a historical internal run was described as approximately 25–30 FPS, but the model hash, device, runtime provider, source footage, warm-up, timing boundary, and run distribution were not retained. The number is not evidence for the current code or any named hardware.
Deployment context
I built PrivacyGuard independently and used it as one component inside a larger CCTV system for a freelance client. The client, footage, wider system, and operational details are confidential and are not reproduced here.