From f22af16517e58a0a3cb15f73a37184b0dd9355ee Mon Sep 17 00:00:00 2001 From: lingh Date: Sat, 11 Jul 2026 12:37:02 +0800 Subject: [PATCH] Align defaults with server-edition: reuse-as-seg and despill both OFF Adopt server-edition's settings.py and default.yaml verbatim (zero diff on these files between branches now): - reuse_as_seg: false. The reuse equivalence holds for solid figures but breaks on thin pale strokes over a low-contrast background: HR-matting (trained on P3M-10k portraits + AM-2k animals) reads them as semi-transparent wisps, and as the trimap topology source it destroys white calligraphy (TestImage4: stroke alpha 0.92 -> 0.51, 48% of stroke pixels below 0.5) that the DIS5K-trained segmenter keeps solid (seg 0.97 vs 0.40). As the gated cross-check veto the same signal stays harmless (hue gate excludes neutral strokes; confidence gate exempts soft edges). Opt in with --cross-check-as-seg for character batches. - despill.enabled: false (mirrors server ba5d667): no positional/semantic guard, desaturates and hue-shifts subjects sharing the background's hue. README updated accordingly; the performance table notes its numbers were measured with reuse on. Co-Authored-By: Claude Opus 4.8 --- README.md | 34 ++++++++++++++++++++-------------- bgfilter/cli.py | 2 +- bgfilter/settings.py | 20 +++++++++++++------- configs/default.yaml | 10 +++++++--- 4 files changed, 41 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index dd9416f..f6e067d 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,12 @@ Two pipelines, selected by `segmentation.enabled` in the config: `trust_remote_code`) or `anime-seg` (ONNX, tuned for anime characters). Switch at runtime with `--seg-backend anime-seg` (it also selects the matching weights). -Both share pymatting foreground estimation and a colour de-spill. There is **no** -green-contamination rescue / recolour layer — with clean source images it is -unnecessary, so it was removed. +Both share pymatting foreground estimation and an optional colour de-spill +(**off by default**: de-spill has no positional/semantic guard, so a subject +sharing the background's hue — a blue suit on a blue backdrop — gets +desaturated and hue-shifted; enable with `--despill` when edge spill genuinely +matters). There is **no** green-contamination rescue / recolour layer — with +clean source images it is unnecessary, so it was removed. Both also run a **cross-model veto** by default: a second, trimap-free matting model (`ZhengPeng7/BiRefNet_HR-matting`) may only *lower* alpha, only on @@ -38,15 +41,17 @@ download (~0.9 GB) and one inference pass per image; disable with `--no-cross-check` (see the `cross_check` config section, and `docs/hair_gap_artifacts.md` for the analysis behind it). -When the cross-check is on, that same HR-matting forward is **reused as the -segmentation mask by default**, skipping the primary seg model entirely (one -less model to load, ~20 s faster per image on CPU). Pilot-validated -(TestImage3 / FixImage1): trimap 99.8% identical, no structural change to -fingers, hair wisps or thin lines. Disable with `--no-cross-check-as-seg` to -run the dedicated seg model instead. The reuse is a same-family swap, so it -applies to the `birefnet` backend only: with `--seg-backend anime-seg` the -anime segmenter keeps its own forward and the cross-check veto still runs -independently on top. +When the cross-check is on, `--cross-check-as-seg` reuses that same HR-matting +forward as the segmentation mask, skipping the primary seg model entirely (one +less model to load, ~20 s faster per image on CPU). **Off by default** — use it +only when the subjects are characters or similar solid figures. HR-matting is +trained on portrait/animal matting (P3M-10k, AM-2k) and reads thin pale strokes +on a low-contrast background as semi-transparent wisps: white calligraphy on +the pastel backdrop (TestImage4) loses half its strokes under reuse, while the +dedicated DIS5K-trained segmenter keeps them solid. The reuse is a same-family +swap, so it applies to the `birefnet` backend only: with `--seg-backend +anime-seg` the anime segmenter keeps its own forward and the cross-check veto +still runs independently on top. The segmentation trimap defaults to `directional` mode (chroma + seg + a hue-direction split: it keeps a background-coloured garment such as a white shirt while dropping a @@ -119,8 +124,9 @@ D:\MiniConda\envs\lightML\python.exe -m bgfilter.cli ` ## Performance (CPU reference numbers) -Measured on a Ryzen 9700X (Zen 5, native bf16), 32 GB RAM, default settings -(cross-check on, reused as seg; chunked global attention on): +Measured on a Ryzen 9700X (Zen 5, native bf16), 32 GB RAM, with cross-check on +and `--cross-check-as-seg` (reuse); chunked global attention on. Without reuse +(the default) add ~9 s fp32 / ~6 s bf16 for the dedicated seg forward: | input | precision | warm / image | peak memory | |---|---|---|---| diff --git a/bgfilter/cli.py b/bgfilter/cli.py index bcf6ee9..ad4d299 100644 --- a/bgfilter/cli.py +++ b/bgfilter/cli.py @@ -31,7 +31,7 @@ def main( fg_safe_radius_ratio: float | None = typer.Option(None, "--fg-safe-radius-ratio", min=0.0), despill: bool | None = typer.Option(None, "--despill/--no-despill"), cross_check: bool | None = typer.Option(None, "--cross-check/--no-cross-check", help="Second-opinion veto of background-hued residue between hair strands (default: on; costs one extra model inference)"), - cross_check_as_seg: bool | None = typer.Option(None, "--cross-check-as-seg/--no-cross-check-as-seg", help="Reuse the cross-check forward as the segmentation mask, skipping the primary seg model (default: on; saves ~20s on CPU; needs cross-check on)"), + cross_check_as_seg: bool | None = typer.Option(None, "--cross-check-as-seg/--no-cross-check-as-seg", help="Reuse the cross-check forward as the segmentation mask, skipping the primary seg model (default: off; saves ~20s on CPU; needs cross-check on)"), trimap_mode: str | None = typer.Option(None, "--trimap-mode", help="Trimap mode (segmentation pipeline): directional | seg | directional-hard-bg"), seg_backend: str | None = typer.Option(None, "--seg-backend", help="Segmentation backend: birefnet (default) | anime-seg"), ) -> None: diff --git a/bgfilter/settings.py b/bgfilter/settings.py index a360a83..fe95a47 100644 --- a/bgfilter/settings.py +++ b/bgfilter/settings.py @@ -84,12 +84,13 @@ class CrossCheckSettings: input_size: int = 2048 # Reuse the cross-check forward as the segmentation mask: one HR-matting # inference serves both trimap topology and the second opinion, skipping the - # primary seg model entirely (saves its load + forward). Pilot-validated on - # TestImage3/FixImage1: trimap 99.8% identical, no structural change. Only - # applies to the birefnet segmentation backend (a same-family swap); with - # anime-seg the chosen segmenter keeps its own forward and the cross-check - # runs independently. Needs cross_check and segmentation both enabled. - reuse_as_seg: bool = True + # primary seg model entirely (saves its load + forward; measured ~20 s/image + # on CPU, ~0.9-1.3 GB VRAM on GPU). Pilot-validated on TestImage3/FixImage1: + # trimap 99.8% identical. Default OFF on server-edition: the dedicated + # segmenter keeps its own forward and the veto stays an independent signal; + # opt in with --cross-check-as-seg. Only applies to the birefnet backend + # (a same-family swap); needs cross_check and segmentation both enabled. + reuse_as_seg: bool = False # Compute precision of the cross-check forward: "fp32" or "bf16" (autocast; # needs bf16-capable hardware, else falls back to fp32 with a warning). precision: str = "fp32" @@ -121,7 +122,12 @@ class ForegroundSettings: @dataclass(frozen=True) class DespillSettings: - enabled: bool = True + # Off by default: despill pulls foreground chroma along the background-hue axis + # with no positional or semantic guard, so a subject sharing the background's hue + # (e.g. a blue suit on a blue backdrop) is desaturated/hue-shifted (~ΔE 35, blue + # -> purple). This is the root default the cpu-fast lane inherits. Re-enable + # per-config or with --despill only when edge spill genuinely matters. + enabled: bool = False edge_low: float = 0.005 strength: float = 0.92 edge_expand_radius: int = 2 diff --git a/configs/default.yaml b/configs/default.yaml index c584ac8..b9b5192 100644 --- a/configs/default.yaml +++ b/configs/default.yaml @@ -80,8 +80,9 @@ cross_check: # trimap 99.8% identical, no structural change (fingers/wisps/bowstring OK). # Only applies to the birefnet backend (same-family swap); anime-seg keeps # its own forward, with the cross-check running independently on top. - # Disable with --no-cross-check-as-seg. No effect when cross_check is off. - reuse_as_seg: true + # Default OFF (server-edition): the dedicated segmenter keeps its own forward + # and the veto stays independent; enable with --cross-check-as-seg. + reuse_as_seg: false # Cross-check forward precision, same semantics as segmentation.precision # (bf16 via autocast: 2048 pass ~35s -> ~26s on a native-bf16 CPU). precision: fp32 @@ -109,7 +110,10 @@ foreground: bg_confidence_weight: 0.70 despill: - enabled: true + # Off: despill has no positional/semantic guard, so a subject sharing the + # background's hue (blue suit on a blue backdrop) gets desaturated/hue-shifted + # (~ΔE 35, blue -> purple). Re-enable per-job only when edge spill matters. + enabled: false edge_low: 0.005 strength: 0.92 edge_expand_radius: 2