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 <noreply@anthropic.com>
This commit is contained in:
2026-07-11 12:37:02 +08:00
parent 18c6918e45
commit f22af16517
4 changed files with 41 additions and 25 deletions
+20 -14
View File
@@ -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 |
|---|---|---|---|
+1 -1
View File
@@ -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:
+13 -7
View File
@@ -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
+7 -3
View File
@@ -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