Despill across full foreground to cut edge green spill
Green spill bleeds into pixels ViTMatte marks as solid foreground (alpha~1), but despill was gated to the soft edge band (0.005<alpha<0.995) and weakened exactly there: the alpha tent peaked at 0.5 and bg_confidence (low on the character edge) further suppressed the correction. - (1) Apply despill to the whole foreground (alpha > edge_low) instead of the soft band only; neutral pixels stay untouched via the green-excess term. - (2) Weight by alpha at full strength across the opaque range (alpha>=0.5), easing off only where mostly transparent, instead of a tent peaking at 0.5. - (3) Drop bg_confidence as a despill gate; localise purely by green excess. Controlled comparison (same alpha, only despill changed), edge_green_excess: ViTMatte TestImage p95 0.239->0.176, mean 0.131->0.061 ViTMatte TestImage2 p95 0.231->0.165, mean 0.115->0.052 Removes now-unused DespillSettings fields edge_high and bg_confidence_weight. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+16
-10
@@ -9,7 +9,6 @@ from .settings import DespillSettings
|
||||
def despill_green(
|
||||
rgb: np.ndarray,
|
||||
alpha: np.ndarray,
|
||||
bg_confidence: np.ndarray,
|
||||
model: BackgroundModel,
|
||||
settings: DespillSettings,
|
||||
) -> tuple[np.ndarray, np.ndarray]:
|
||||
@@ -17,7 +16,11 @@ def despill_green(
|
||||
if not settings.enabled:
|
||||
return rgb.copy(), np.zeros(alpha.shape, dtype=np.float32)
|
||||
|
||||
edge = (alpha > settings.edge_low) & (alpha < settings.edge_high)
|
||||
# (1) Despill across the whole foreground, not only the soft edge band.
|
||||
# Green frequently bleeds into pixels ViTMatte marks as solid foreground
|
||||
# (alpha ~ 1); the old upper bound left those uncorrected. Neutral pixels
|
||||
# stay untouched because the green-excess term below is zero for them.
|
||||
foreground = alpha > settings.edge_low
|
||||
if settings.edge_expand_radius > 0:
|
||||
from .deps import require_cv2
|
||||
|
||||
@@ -26,23 +29,26 @@ def despill_green(
|
||||
kernel = cv2.getStructuringElement(
|
||||
cv2.MORPH_ELLIPSE, (radius * 2 + 1, radius * 2 + 1)
|
||||
)
|
||||
edge = cv2.dilate(edge.astype(np.uint8), kernel).astype(bool)
|
||||
foreground = cv2.dilate(foreground.astype(np.uint8), kernel).astype(bool)
|
||||
|
||||
r = rgb_f[..., 0]
|
||||
g = rgb_f[..., 1]
|
||||
b = rgb_f[..., 2]
|
||||
neutral_green = np.maximum(r, b) + settings.green_excess_margin
|
||||
excess = np.maximum(g - neutral_green, 0.0)
|
||||
|
||||
# (2) Keep full strength across the whole opaque range (alpha >= 0.5) and
|
||||
# ease off only where the pixel is mostly transparent. The previous tent
|
||||
# peaked at alpha 0.5 and starved exactly the solid hair that needs despill.
|
||||
alpha_weight = np.maximum(
|
||||
np.clip((1.0 - np.abs(alpha - 0.5) * 2.0), 0.0, 1.0),
|
||||
settings.alpha_weight_floor,
|
||||
)
|
||||
bg_weight = (
|
||||
settings.bg_confidence_weight * np.clip(bg_confidence, 0.0, 1.0)
|
||||
+ (1.0 - settings.bg_confidence_weight)
|
||||
np.clip(alpha * 2.0, 0.0, 1.0), settings.alpha_weight_floor
|
||||
)
|
||||
|
||||
# (3) Localise purely by green excess. bg_confidence is intentionally not a
|
||||
# gate: it is low on the character edge where the spill lives, so using it
|
||||
# suppressed the very correction we want.
|
||||
green_weight = np.clip(excess / max(settings.green_excess_margin * 8.0, 1e-6), 0.0, 1.0)
|
||||
mask = edge.astype(np.float32) * alpha_weight * bg_weight * green_weight
|
||||
mask = foreground.astype(np.float32) * alpha_weight * green_weight
|
||||
|
||||
out = rgb_f.copy()
|
||||
target_green = np.minimum(g, neutral_green)
|
||||
|
||||
@@ -76,7 +76,7 @@ def _run_image(
|
||||
rgb, alpha, bg_confidence, model, settings.foreground
|
||||
)
|
||||
corrected_rgb, despill_mask = despill_green(
|
||||
foreground.rgb, alpha, bg_confidence, model, settings.despill
|
||||
foreground.rgb, alpha, model, settings.despill
|
||||
)
|
||||
save_rgba(output_path, corrected_rgb, alpha)
|
||||
|
||||
|
||||
@@ -54,11 +54,9 @@ class ForegroundSettings:
|
||||
class DespillSettings:
|
||||
enabled: bool = True
|
||||
edge_low: float = 0.005
|
||||
edge_high: float = 0.995
|
||||
strength: float = 0.92
|
||||
green_excess_margin: float = 0.015
|
||||
edge_expand_radius: int = 2
|
||||
bg_confidence_weight: float = 0.65
|
||||
alpha_weight_floor: float = 0.35
|
||||
|
||||
|
||||
|
||||
@@ -26,9 +26,7 @@ foreground:
|
||||
despill:
|
||||
enabled: true
|
||||
edge_low: 0.005
|
||||
edge_high: 0.995
|
||||
strength: 0.92
|
||||
green_excess_margin: 0.015
|
||||
edge_expand_radius: 2
|
||||
bg_confidence_weight: 0.65
|
||||
alpha_weight_floor: 0.35
|
||||
|
||||
Reference in New Issue
Block a user