Add chroma-guided alpha suppression for background holes
Pull alpha toward 0 in the unknown band where bg_confidence is high, so green that survives in hair gaps and hole pockets goes transparent (doc section 10). Runs between trimap enforcement and alpha cleanup; sure-foreground pixels are never touched, keyed by the bg_confidence we already compute. On the two samples this clears the faint outer green halo (edge pixels 19.3k -> 16.0k) but has little *visible* effect, because their residual is no longer green: it is magenta from the pymatting unmix on genuine semi-transparent hair (vis-magenta p95 ~0.07, identical pre/post despill), which this pass deliberately leaves alone. The magenta needs a separate colour fix. Knobs live in AlphaPostSettings (chroma_suppress*, default on). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,38 @@ def enforce_trimap(alpha: np.ndarray, trimap: np.ndarray) -> np.ndarray:
|
||||
return out
|
||||
|
||||
|
||||
def _smoothstep(x: np.ndarray, edge0: float, edge1: float) -> np.ndarray:
|
||||
t = np.clip((x - edge0) / max(edge1 - edge0, 1e-6), 0.0, 1.0)
|
||||
return t * t * (3.0 - 2.0 * t)
|
||||
|
||||
|
||||
def suppress_alpha_by_chroma(
|
||||
alpha: np.ndarray,
|
||||
bg_confidence: np.ndarray,
|
||||
trimap: np.ndarray,
|
||||
settings: AlphaPostSettings,
|
||||
) -> np.ndarray:
|
||||
"""Pull alpha toward 0 where background (green) confidence is high.
|
||||
|
||||
Only the unknown band (trimap == 128) is touched, so confidently-foreground
|
||||
pixels are never thinned. Green that survived in hair gaps / hole pockets
|
||||
becomes transparent; this also clears the magenta fringe that over-unmixing
|
||||
leaves on those low-alpha pixels, since alpha ~ 0 hides the foreground colour.
|
||||
"""
|
||||
if not settings.chroma_suppress:
|
||||
return alpha
|
||||
suppress = _smoothstep(
|
||||
np.clip(bg_confidence, 0.0, 1.0),
|
||||
settings.chroma_suppress_bg_low,
|
||||
settings.chroma_suppress_bg_high,
|
||||
)
|
||||
factor = 1.0 - settings.chroma_suppress_strength * suppress
|
||||
out = alpha.astype(np.float32).copy()
|
||||
band = trimap == 128
|
||||
out[band] = out[band] * factor[band]
|
||||
return np.clip(out, 0.0, 1.0)
|
||||
|
||||
|
||||
def _remove_small_components(mask: np.ndarray, min_area: int) -> np.ndarray:
|
||||
cv2 = require_cv2()
|
||||
count, labels, stats, _ = cv2.connectedComponentsWithStats(mask.astype(np.uint8), 8)
|
||||
|
||||
@@ -6,7 +6,7 @@ from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
|
||||
from .alpha_post import clean_alpha, enforce_trimap
|
||||
from .alpha_post import clean_alpha, enforce_trimap, suppress_alpha_by_chroma
|
||||
from .chroma import compute_bg_confidence
|
||||
from .despill import despill_green
|
||||
from .foreground import estimate_foreground_rgb
|
||||
@@ -71,6 +71,7 @@ def _run_image(
|
||||
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 = clean_alpha(alpha, trimap, settings.alpha_post)
|
||||
foreground = estimate_foreground_rgb(
|
||||
rgb, alpha, bg_confidence, model, settings.foreground
|
||||
|
||||
@@ -33,6 +33,12 @@ class AlphaPostSettings:
|
||||
fill_hole_area_ratio: float = 0.00002
|
||||
alpha_floor: float = 0.002
|
||||
alpha_ceil: float = 0.998
|
||||
# Chroma-guided alpha suppression: in the unknown band, pull alpha toward 0
|
||||
# where background (green) confidence is high, clearing spill in gaps/holes.
|
||||
chroma_suppress: bool = True
|
||||
chroma_suppress_bg_low: float = 0.35
|
||||
chroma_suppress_bg_high: float = 0.80
|
||||
chroma_suppress_strength: float = 1.0
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
|
||||
@@ -10,6 +10,12 @@ trimap:
|
||||
unknown_radius_ratio: 0.012
|
||||
fg_safe_radius_ratio: 0.006
|
||||
|
||||
alpha_post:
|
||||
chroma_suppress: true
|
||||
chroma_suppress_bg_low: 0.35
|
||||
chroma_suppress_bg_high: 0.80
|
||||
chroma_suppress_strength: 1.0
|
||||
|
||||
foreground:
|
||||
enabled: true
|
||||
method: ml
|
||||
|
||||
Reference in New Issue
Block a user