TASK 51 blur 'heavy' fix: quadratic blur radius. BlurRadius(strength) = 1 + 5s^2, MaxBlurRadius 12 -> 6 downscaled px (~24 full px max). Default 0.5 is now radius 2 small px (was the old linear 6) - ~3x lighter perceived spread, matching the creator's real-camera finding. Range from the established tools: StreamFX defaults its Box blur Size to 5 and its author stops downscale-based blurs around 8px. One integration test pins the mapping (0 -> 1, 0.5 -> 2, 1 -> 6) and step-edge behaviour (light reaches 9px at default vs 26px at full); the existing slice-2 blur test passes unchanged. 419/419 pass. Refs: https://github.com/Vhonowslend/StreamFX-Public/wiki/Filter-Blur https://xaymar.com/articles/2020/04/26/optimizing-streamfx-in-your-obs-scenes/ (Unit: bg-blur heavy fix)

This commit is contained in:
2026-10-10 07:08:01 -07:00
parent 305706956e
commit c2d64dcd6d
7 changed files with 124 additions and 44 deletions
+19 -25
View File
@@ -1,4 +1,4 @@
# HANDOFF — 2026-10-09 (TASK 51 quality pass: jitter FIXED; blur-heavy is the next unit)
# HANDOFF — 2026-10-09 (TASK 51 quality pass COMPLETE: jitter + blur-heavy both fixed)
## Branch
- main (no feature branch). Pushed through `5685c5d` (TRAX toggle); everything after is local,
@@ -17,31 +17,25 @@
the not-mine files WILL flag. Pass them to the script as declared-known (checksums unchanged since
`5685c5d`) or let the script's output be reconciled before committing.
## What just happened — temporal jitter fix
- Creator check: bg Remove is "jittery", Blur is "heavy". User picked **jitter first**.
- Root cause: `OnnxPersonSegmenter` classifies each frame independently (256×144 mask, bilinear
upsample) → zero temporal coherence → edge flutter. There is no temporal smoothing upstream.
- **Fix: `MaskSmoother`** — per-session blend of each raw mask with the previous smoothed one,
0.75×new + 0.25×previous (`CurrentWeight`, internal). Wired at the ONE production point
(`CameraManager.RunEffectWorker` → `session.Smoother.Smooth(TrySegment(source))`); state on
`CameraSession`, NOT the segmenter (segmenter is shared app-wide and contract-pinned to raw output).
Null mask resets history; first frame/size-change passthrough; never aliases the caller's array; two
ping-pong buffers, no per-frame LOH alloc. Weights: OBS obs-backgroundremoval 0.85 default test, Jitsi
jitsi/jitsi-meet#17080 0.7/0.3 best (0.5/0.5 smears on motion).
- **Test:** `CameraManagerTests.Temporal_Smoothing_Damps_Segmentation_Jitter_Across_Frames` — PASSED.
A `SteppingMaskSegmenter` returns fresh masks per call: `{1,1,0,0}` then `{1,0.2,0,0}`; 16×4 frame,
Remove 0.5. Asserts x=6: frame1 = 255 (passthrough), frame2 smoothed = **58**, raw-unsmoothed = 0;
person core x=1 = 255, background x=14 = 0. ⚠️ Gotcha: the worker's latest-frame-drop guard treats a
re-queued IDENTICAL frame reference as "no new frame" — pump frame2 as a NEW `VideoFrame` instance.
- `Assert.Equal(255, first[...])`/`58`/`0` predictions were hand-verified; other pixels differ by
float rounding (e.g. x=5 Python 34 vs C# 36) — only assert x=6/x=1/x=14.
## What just happened — two commits
1. **Jitter fix (`3057069`) — `MaskSmoother`**: per-session blend 0.75×new + 0.25×previous at the
ONE worker (`CameraManager.RunEffectWorker` → `session.Smoother.Smooth(TrySegment(source))`).
State on `CameraSession`, NOT the segmenter (shared app-wide, contract-pinned to raw output). Null
resets history; first frame passes through; never aliases caller's array; ping-pong buffers (no
per-frame LOH alloc). Weights: OBS obs-backgroundremoval 0.85 default, Jitsi 0.7/0.3 best
(0.5/0.5 smears). Test `Temporal_Smoothing_Damps_Segmentation_Jitter_Across_Frames`: x=6 → first
255 / smoothed 58 / raw-unsmoothed 0. ⚠️ Gotcha: worker guards a re-queued identical frame
reference — pump frame2 as a NEW instance.
2. **Blur "heavy" fix (this commit) — quadratic radius**: `BlurRadius(strength)` = 1 + 5·s²,
`MaxBlurRadius` 12→6 small px (≈ 24 full px max). Default 0.5 = 2 small px (was 6 under the old
linear curve) — ~3× lighter spread. Rationale: StreamFX defaults Box blur Size to 5, author stops
downscaled blurs ~8px. Test `Blur_default_strength_is_gentle_and_full_strength_is_strong` pins the
mapping and step-edge behaviour (reach 9px default vs 26px max). Point-source probes vanish below
the 0.5 rounding (3 box passes dilute a spike) — use a step edge, not a spike, to measure blur.
## Next step
1. Run `./scripts/verify.sh` with the declared file list; reconcile the not-mine dirty files with
scope-check; commit the unit (message `…(Unit: bg-removal jitter fix)` + OBS/Jitsi citations).
2. Next unit = **Blur "heavy"** — soften the radius curve in `BackgroundEffectProcessor.ApplyBlur`
(`MinBlurRadius=1 / MaxBlurRadius=12` at ¼ res ≈ 26–50 full px; strength 0.5 → 6 small px). The
creator needs a retry pass on the real camera after both.
- Creator re-runs the app: webcam → Layers properties → Background → Remove / Blur + Strength, and
reports whether the jitter is gone and the blur feels right at the slider's default and full range.
## Also still open
- **Vertical-format regression (STALE, not fixed):** 1080p60 vertical selection no longer shrinks the
@@ -53,7 +47,7 @@
- **Vertical recording file closed:** the recording itself is 1080×1920 60fps correctly; the
"whitespace" was the app's preview UI, not the file. Audio loudness (`ebur128`) for the private video
was never run and the thread is closed unless the user reopens it.
- Blur-heaviness = next unit (above). Yellow-border regression = stale thread.
- Blur-heaviness = fixed (above). Yellow-border regression = stale thread.
- ⛔ Sudo password rotation pending (user; then update both password lines in `CREDENTIALS.md`).
- TASK 56 secret scan before `gramps/LlamaCasty` goes public. TASK 55 disk hygiene queued.