From f1a083349b14f72c81067d0e960607ebfb505fc0 Mon Sep 17 00:00:00 2001 From: Linghong Date: Fri, 24 Jul 2026 14:10:08 +0000 Subject: [PATCH] Drop the silhouette band overwrite in the directional trimap The band (dilate-minus-erode ring on the seg silhouette, added in 5f48f7a) was meant to give ViTMatte room to anti-alias the boundary. Measured on a warm-background sample (fix_image02, #FFD6C2): ViTMatte outputs near-binary alpha in the band on crisp-edged anime images (96% hard values, 82% of silhouette crossings with zero soft pixels), so the band delivered no anti-aliasing -- it only let ViTMatte relocate the boundary (chewed edges) and wholly confiscated structures thinner than the ring: 74% of lost hair strand pixels were rule-FG demoted by the overwrite, then dropped by ViTMatte. Without the band, 88% of those strands survive, edges follow the rule-decided contour, and cross-check no longer bites strand edges because strand cores stay anchored at 255 (re-destroyed: 0 px). The rules own unknown pixels (background-hued blends, low-seg transitions) remain ViTMatte territory. fuse_trimap (seg mode) keeps its band untouched -- its rules were designed around it and the removal is only validated for the directional path. Co-Authored-By: Claude Fable 5 --- bgfilter/trimap.py | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/bgfilter/trimap.py b/bgfilter/trimap.py index 2e2813f..78648da 100644 --- a/bgfilter/trimap.py +++ b/bgfilter/trimap.py @@ -123,16 +123,14 @@ def fuse_trimap_directional( strongly displaced toward it (e.g. blue between hair strands) is left unknown for ViTMatte / chroma-suppress. Lower-seg unknowns stay unknown. - A thin unknown band at the segmentation silhouette is preserved so ViTMatte can - anti-alias the boundary (it is deliberately small so interior holes survive). + No silhouette band is forced back to unknown: structures thinner than the band + ring (flying hair strands) got wholly confiscated by dilate-minus-erode and then + dropped by ViTMatte, which on crisp-edged (anime) images outputs near-binary + alpha in the band instead of anti-aliasing (measured: 96% hard values, and 74% + of lost strand pixels were rule-FG demoted by the band). The rules' own unknown + pixels (background-hued blends, low-seg transitions) remain ViTMatte's domain. """ - cv2 = require_cv2() shape = seg_mask.shape - fg_safe_radius = radius_from_ratio( - shape, settings.fg_safe_radius_ratio, settings.min_fg_safe_radius - ) - band_radius = max(settings.min_fg_safe_radius, fg_safe_radius // 2) - chroma_bg = bg_confidence >= settings.sure_bg_threshold chroma_fg = bg_confidence <= settings.sure_fg_threshold chroma_unknown = ~chroma_bg & ~chroma_fg @@ -161,16 +159,10 @@ def fuse_trimap_directional( trimap[bg] = 0 trimap[fg | rule3_fg | force_fg] = 255 - loose = (seg_mask >= settings.seg_loose_threshold).astype(np.uint8) - kernel = elliptical_kernel(band_radius) - band = cv2.dilate(loose, kernel).astype(bool) & ~cv2.erode(loose, kernel).astype(bool) - trimap[band & ~chroma_bg] = 128 - stats = { "sure_bg_pixels": int((trimap == 0).sum()), "unknown_pixels": int((trimap == 128).sum()), "sure_fg_pixels": int((trimap == 255).sum()), - "band_radius": int(band_radius), "rule3_fg_pixels": int(rule3_fg.sum()), "force_fg_pixels": int((force_fg & ~fg & ~rule3_fg).sum()), "bg_hued_pixels": int((bg_hued & chroma_unknown & core).sum()),