Add seg_force_fg: saturated seg confidence overrides the hue-split demotion

New rule 4 in fuse_trimap_directional: a chroma-unknown pixel whose seg
confidence is >= trimap.seg_force_fg (default 0.98) stays sure-FG even when
background-hued. Bright skin on a warm same-hue-family background (peach,
pale yellow) was being demoted to unknown by the hue split and then lost --
either ViTMatte itself misfires there (raw 0.07-0.13 measured) or the
chroma suppressor does (raw 0.89 halved to 0.45 via the RGB-proximity
confidence path). The segmenter meanwhile rates those pixels a saturated
1.0; that semantic certainty now outranks same-hue colour suspicion.

Safety: chroma_unknown excludes sure background, so a real flat backdrop
can never be forced foreground; the silhouette band still re-opens the
boundary; the cross-check veto still overrides trimap-FG. Set > 1.0 to
disable.

Verified (GPU bf16): yellow-bg frame face 0.84->1.00, peach-bg thigh
0.87->1.00 (residual softness only where chroma is near-sure-bg, by
design). Pastel-blue TestImage3 regression: 0.047% of pixels differ with
cross-check on, 0.058% without it (hair-gap suppression path), both
visually negligible.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-08 18:15:10 +08:00
parent e234a1a13d
commit 18c6918e45
3 changed files with 24 additions and 2 deletions
+8
View File
@@ -40,6 +40,14 @@ class TrimapSettings:
mode: str = "directional"
seg_low: float = 0.15 # seg below this -> background-eligible; at/above -> foreground-eligible
bg_hue_proj_min: float = 4.0 # Lab a*/b* projection onto bg direction above which a pixel is background-hued
# Semantic override for the directional hue split: a chroma-unknown pixel whose
# seg confidence is at/above this is kept sure-FG even when background-hued.
# A saturated seg signal outranks colour evidence that only says "same hue
# family as the background" (bright skin on a warm background, measured raw
# ViTMatte failures at proj ~7-10). Chroma sure-background is never overridden,
# and the silhouette band still re-opens the boundary for anti-aliasing.
# Set > 1.0 to disable. (seg masks are uint8-quantised: 0.98 = 250/255.)
seg_force_fg: float = 0.98
@dataclass(frozen=True)