Simplify to single-segmenter pipelines; add screen_color prior; drop rescue + recolor

Strategic pivot: the dropout-fill rescue, the anime-seg fill backend, the
BiRefNet+anime-seg intersection, and the recolor pass all existed to repair
source images whose hair was already green-contaminated or broken at generation
time. Fixing the source instead (clean pastel-background generation) makes the
matte high-contrast, so that whole compensation layer is unnecessary. Remove it.

- Two pipelines via segmentation.enabled:
    false -> chroma-only (Chroma + ViTMatte)
    true  -> single segmenter (default anime-seg, switchable birefnet) -> trimap
  then ViTMatte refines, pymatting estimates foreground, despill cleans spill.

- Generalise the green-hardcoded colour logic to a screen_color prior (hex, e.g.
  "#CFEFFF"; default null = green auto-detect, byte-identical). chroma keys off
  Lab/RGB distance to the colour; despill removes chroma along its Lab direction.
  Added --screen-color CLI flag.

- Removed dead code: fill_seg_dropouts + seg_fill*/intersection params,
  SegmentationSettings.fill_backend/fill_model_name/sharpen, unsharp_mask,
  recolor.py + RecolorSettings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-01 15:33:58 +08:00
parent 87359ca304
commit 7555321cfc
10 changed files with 142 additions and 245 deletions
+11 -35
View File
@@ -42,20 +42,6 @@ class AlphaPostSettings:
chroma_suppress_bg_low: float = 0.35
chroma_suppress_bg_high: float = 0.80
chroma_suppress_strength: float = 1.0
# Segmentation dropout-fill: rescue alpha where ViTMatte cut a region the
# semantic mask asserts is foreground (green-contaminated wisps it cannot see).
seg_fill: bool = True
seg_fill_alpha_max: float = 0.10
seg_fill_seg_min: float = 0.10
seg_fill_strength: float = 0.70
seg_fill_min_area_ratio: float = 0.00002 # drop rescued specks below this area
# Intersection fill: gate the (anime-seg) dropout-fill by a second, reliable
# mask (BiRefNet). anime-seg proposes freely at a low seg_fill_seg_min; only
# rescues BiRefNet also endorses (>= seg_fill_gate_min, its threshold left
# unchanged) survive. Trims anime-seg's over-inclusive halo to the structure
# both backends agree on, without reintroducing BiRefNet's grainy value.
seg_fill_intersect: bool = False
seg_fill_gate_min: float = 0.10
@dataclass(frozen=True)
@@ -85,19 +71,10 @@ class DespillSettings:
green_excess_margin: float = 0.015
edge_expand_radius: int = 2
alpha_weight_floor: float = 0.35
@dataclass(frozen=True)
class RecolorSettings:
enabled: bool = True
person_threshold: float = 0.5 # clean reference = subject above this seg/alpha
coverage_soft: float = 0.08 # recolour ramps in over alpha 0..this
contam_low: float = 0.02 # residual off-colour where recolour starts
contam_high: float = 0.12 # residual off-colour for full recolour
clean_dom_max: float = 0.03 # reference = subject pixels below this off-colour
propagate_radius_ratio: float = 0.02
min_reference_weight: float = 0.05
luminance_strength: float = 0.5 # how far to pull luminance to the clean neighbour
# Custom-screen despill (when screen_color is set): margin, in Lab a*/b*
# units, of chroma allowed along the background colour direction before it is
# treated as spill. Larger keeps more of the subject's own colour.
color_margin_lab: float = 4.0
@dataclass(frozen=True)
@@ -110,15 +87,11 @@ class ModelSettings:
@dataclass(frozen=True)
class SegmentationSettings:
enabled: bool = True
backend: str = "birefnet" # "birefnet" or "anime-seg"
model_name: str = "ZhengPeng7/BiRefNet"
fill_backend: str = "" # "" = reuse `backend` for dropout-fill; else e.g. "anime-seg"
fill_model_name: str = ""
enabled: bool = True # False = chroma-only pipeline (no seg model)
backend: str = "anime-seg" # "anime-seg" or "birefnet"
model_name: str = "skytnt/anime-seg"
device: str = "cuda"
input_size: int = 1024
sharpen: float = 0.0 # unsharp-mask amount on the mask (0 = off)
sharpen_radius: int = 4
@dataclass(frozen=True)
@@ -128,6 +101,9 @@ class PipelineSettings:
alpha_post: AlphaPostSettings = AlphaPostSettings()
foreground: ForegroundSettings = ForegroundSettings()
despill: DespillSettings = DespillSettings()
recolor: RecolorSettings = RecolorSettings()
model: ModelSettings = ModelSettings()
segmentation: SegmentationSettings = SegmentationSettings()
# Background-colour prior. None = auto-detect a green screen (current default,
# unchanged). A hex string like "#CFEFFF" keys chroma + despill off that colour
# instead, for arbitrary (e.g. pastel) flat backgrounds.
screen_color: str | None = None