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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user