Add complex-background matting mode to the service via background_mode
Backports master's non-flat matting (chroma.enabled: false + the hue-free cross-check gate) into server-edition, and exposes it over HTTP without surfacing the internal "chroma" wording: /remove-background gains a background_mode form field (flat, default | complex). complex maps to chroma disabled -- no colour key, segmentation alone drives the trimap and every colour-keyed stage (auto-detect, hue split, chroma suppression, despill) is bypassed. The cross-check veto still works in complex mode via its second-opinion-confidence gate (cross_check.second_lo/hi) but defaults OFF there (it costs the HR-matting forward); an explicit cross_check=on re-enables it. No new model weights: complex mode reuses the already-provisioned BiRefNet seg + ViTMatte (+ optional HR-matting cross-check). Flat mode is unchanged (bit-identical), and server-edition's own extras (cross_check.lock, foreground.use_gpu CuPy path) are preserved -- the port is surgical, not a copy of master's files. - settings: ChromaSettings.enabled, CrossCheckSettings.second_lo/hi - config: override_settings chroma passthrough - despill/foreground: model=None safe guards (foreground keeps GPU path) - alpha_post: cross_check_alpha hue-free gate when proj is None - pipeline: _process_rgb complex branch (seg-only trimap, skip colour stages) - service/app: process(chroma=), background_mode field, complex-defaults-off cross-check, X-BGFilter-Background-Mode header - cli: --chroma/--no-chroma - configs/docs: gpu.yaml + default.yaml + README/README_ZH Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+34
-13
@@ -132,10 +132,26 @@ def run_image(
|
||||
|
||||
def _process_rgb(rgb: np.ndarray, pipeline: MattingPipeline) -> MattingResult:
|
||||
settings = pipeline.settings
|
||||
screen_color = parse_hex_color(settings.screen_color) if settings.screen_color else None
|
||||
bg_confidence, model = compute_bg_confidence(
|
||||
rgb, settings=settings.chroma, screen_color=screen_color
|
||||
)
|
||||
if settings.chroma.enabled:
|
||||
screen_color = parse_hex_color(settings.screen_color) if settings.screen_color else None
|
||||
bg_confidence, model = compute_bg_confidence(
|
||||
rgb, settings=settings.chroma, screen_color=screen_color
|
||||
)
|
||||
else:
|
||||
if not settings.segmentation.enabled:
|
||||
raise RuntimeError(
|
||||
"chroma.enabled: false (non-flat background mode) needs the segmentation "
|
||||
"pipeline; enable segmentation or re-enable chroma."
|
||||
)
|
||||
if settings.model.matting_method == "chroma":
|
||||
raise RuntimeError(
|
||||
"matting_method 'chroma' needs the chroma key; use 'vitmatte' or re-enable chroma."
|
||||
)
|
||||
# Non-flat background: no colour key exists. A zero background-confidence
|
||||
# map makes every chroma-fused formula degrade to its seg-only form; the
|
||||
# colour-keyed stages (hue split, suppression, veto, despill) are skipped.
|
||||
bg_confidence = np.zeros(rgb.shape[:2], dtype=np.float32)
|
||||
model = None
|
||||
second_alpha = None
|
||||
if settings.segmentation.enabled:
|
||||
reuse = (
|
||||
@@ -154,7 +170,9 @@ def _process_rgb(rgb: np.ndarray, pipeline: MattingPipeline) -> MattingResult:
|
||||
seg_mask = second_alpha
|
||||
else:
|
||||
seg_mask = pipeline._segment(rgb)
|
||||
mode = settings.trimap.mode
|
||||
# The directional modes are colour tests against the key colour; without
|
||||
# one, the seg-topology trimap is the only meaningful choice.
|
||||
mode = settings.trimap.mode if settings.chroma.enabled else "seg"
|
||||
if mode == "seg":
|
||||
trimap, trimap_stats = fuse_trimap(seg_mask, bg_confidence, settings.trimap)
|
||||
elif mode in ("directional", "directional-hard-bg"):
|
||||
@@ -173,12 +191,13 @@ def _process_rgb(rgb: np.ndarray, pipeline: MattingPipeline) -> MattingResult:
|
||||
raw_alpha, alpha_source = pipeline._predict_alpha(rgb, trimap, bg_confidence)
|
||||
|
||||
alpha = enforce_trimap(raw_alpha, trimap)
|
||||
alpha = suppress_alpha_by_chroma(
|
||||
alpha, bg_confidence, trimap, settings.alpha_post,
|
||||
# The matte-confidence gate only makes sense for a real matting prediction;
|
||||
# a chroma-seeded alpha is itself colour evidence, so no gate there.
|
||||
raw_alpha=raw_alpha if alpha_source == "vitmatte" else None,
|
||||
)
|
||||
if settings.chroma.enabled:
|
||||
alpha = suppress_alpha_by_chroma(
|
||||
alpha, bg_confidence, trimap, settings.alpha_post,
|
||||
# The matte-confidence gate only makes sense for a real matting prediction;
|
||||
# a chroma-seeded alpha is itself colour evidence, so no gate there.
|
||||
raw_alpha=raw_alpha if alpha_source == "vitmatte" else None,
|
||||
)
|
||||
alpha = clean_alpha(alpha, trimap, settings.alpha_post)
|
||||
if settings.cross_check.enabled:
|
||||
lab = convert_color_spaces(rgb)[1]
|
||||
@@ -187,7 +206,9 @@ def _process_rgb(rgb: np.ndarray, pipeline: MattingPipeline) -> MattingResult:
|
||||
alpha = cross_check_alpha(
|
||||
alpha,
|
||||
second_alpha,
|
||||
bg_hue_projection(lab, model.lab_center),
|
||||
# No key colour in complex-background mode: proj=None switches the
|
||||
# veto to its second-opinion-confidence gate (see cross_check_alpha).
|
||||
bg_hue_projection(lab, model.lab_center) if model is not None else None,
|
||||
lab[..., 0],
|
||||
trimap,
|
||||
settings.cross_check,
|
||||
@@ -199,7 +220,7 @@ def _process_rgb(rgb: np.ndarray, pipeline: MattingPipeline) -> MattingResult:
|
||||
|
||||
metadata = {
|
||||
"alpha_source": alpha_source,
|
||||
"background_model": model.to_dict(),
|
||||
"background_model": model.to_dict() if model is not None else None,
|
||||
"trimap": trimap_stats,
|
||||
"settings": asdict(settings),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user