Gate chroma suppression by the matte's own confidence

suppress_alpha_by_chroma was the one stage where colour evidence could veto the
matting model outright, and every observed failure had the same shape: ViTMatte
was right (raw ~0.97 on a white shirt over pastel blue, on blush over pink) and
the bgc-keyed suppression crushed it, because bgc magnitude cannot tell a
background-coloured subject from background residue. The matte's raw alpha can:
it rates enclosed background pockets low (~0.28 median) and subject texture high.

Suppression is now scaled by (1 - smoothstep(raw_alpha, suppress_raw_lo,
suppress_raw_hi)) (defaults 0.85/0.98): colour evidence only vetoes the matte
where the matte itself is unsure. The gate applies only when alpha really comes
from ViTMatte; chroma-seeded alpha passes raw_alpha=None and keeps old behaviour.
Set suppress_raw_lo: 1.0 to disable (verified byte-identical to the old output).

Validated on both screen colours: pink #F4D8E8 face restored (skin alpha 0.841 ->
0.993, holes 17.2% -> 0.6%), pastel blue #CFEFFF unchanged-to-better (shirt +0.01,
wisps identical, gap clearing 99.8%). Also fixes the long-deferred chroma-only
pipeline defects (white shirt 0.900, light skin 0.989). CLI output verified
byte-identical to the reviewed prototype on both images.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-03 23:54:28 +08:00
parent cf0ceec496
commit 639fdf01b6
4 changed files with 31 additions and 3 deletions
+11
View File
@@ -23,6 +23,7 @@ def suppress_alpha_by_chroma(
bg_confidence: np.ndarray,
trimap: np.ndarray,
settings: AlphaPostSettings,
raw_alpha: np.ndarray | None = None,
) -> np.ndarray:
"""Pull alpha toward 0 where background-colour confidence is high.
@@ -30,6 +31,12 @@ def suppress_alpha_by_chroma(
pixels are never thinned. Background colour that survived in hair gaps / hole
pockets becomes transparent; this also clears the colour fringe that
over-unmixing leaves on those low-alpha pixels, since alpha ~ 0 hides it.
``raw_alpha`` is the matting model's own prediction before any constraint.
When given, suppression fades where the model is confident the pixel is
opaque (ramp ``suppress_raw_lo -> suppress_raw_hi``): colour evidence may
only veto the matte where the matte is unsure. Background pockets it rates
low stay suppressible; a background-coloured subject it rates ~1 is kept.
"""
if not settings.chroma_suppress:
return alpha
@@ -38,6 +45,10 @@ def suppress_alpha_by_chroma(
settings.chroma_suppress_bg_low,
settings.chroma_suppress_bg_high,
)
if raw_alpha is not None and settings.suppress_raw_lo < 1.0:
suppress = suppress * (
1.0 - _smoothstep(raw_alpha, settings.suppress_raw_lo, settings.suppress_raw_hi)
)
factor = 1.0 - settings.chroma_suppress_strength * suppress
out = alpha.astype(np.float32).copy()
band = trimap == 128
+8 -3
View File
@@ -103,10 +103,15 @@ def _run_image(
else:
seg_mask = None
trimap, trimap_stats = generate_trimap(bg_confidence, settings.trimap)
alpha, alpha_source = pipeline._predict_alpha(rgb, trimap, bg_confidence)
raw_alpha, alpha_source = pipeline._predict_alpha(rgb, trimap, bg_confidence)
alpha = enforce_trimap(alpha, trimap)
alpha = suppress_alpha_by_chroma(alpha, bg_confidence, trimap, settings.alpha_post)
alpha = enforce_trimap(raw_alpha, trimap)
alpha = suppress_alpha_by_chroma(
alpha, bg_confidence, trimap, settings.alpha_post,
# The matte-confidence gate only makes sense for a real matting prediction;
# a chroma-seeded alpha is itself colour evidence, so no gate there.
raw_alpha=raw_alpha if alpha_source == "vitmatte" else None,
)
alpha = clean_alpha(alpha, trimap, settings.alpha_post)
foreground = estimate_foreground_rgb(
rgb, alpha, bg_confidence, model, settings.foreground
+7
View File
@@ -54,6 +54,13 @@ class AlphaPostSettings:
chroma_suppress_bg_low: float = 0.35
chroma_suppress_bg_high: float = 0.80
chroma_suppress_strength: float = 1.0
# Matte-confidence gate: fade the suppression where ViTMatte itself is confident
# the pixel is opaque (raw alpha ramps lo -> hi), so colour evidence only vetoes
# the matte where the matte is unsure. Protects background-coloured subjects
# (white shirt on pastel blue, blush on pink) that chroma alone cannot tell from
# background residue. Set suppress_raw_lo >= 1.0 to disable the gate.
suppress_raw_lo: float = 0.85
suppress_raw_hi: float = 0.98
@dataclass(frozen=True)
+5
View File
@@ -40,6 +40,11 @@ alpha_post:
chroma_suppress_bg_low: 0.35
chroma_suppress_bg_high: 0.80
chroma_suppress_strength: 1.0
# Matte-confidence gate: suppression fades where ViTMatte's raw alpha is high
# (ramp lo -> hi), so colour evidence only vetoes the matte where it is unsure.
# Set suppress_raw_lo: 1.0 to disable.
suppress_raw_lo: 0.85
suppress_raw_hi: 0.98
foreground:
enabled: true