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:
@@ -208,9 +208,10 @@ is expected to authenticate). `/healthz` is always open. See [DEPLOY.md](DEPLOY.
|
||||
| Field | Required | Values | Meaning |
|
||||
| --- | --- | --- | --- |
|
||||
| `file` | yes | image file | Source image (field name fixed as `file`). |
|
||||
| `screen_color` | no | `#RRGGBB`, or omit/empty/`auto` | Background-colour prior; omit to auto-detect from the border. |
|
||||
| `background_mode` | no | `flat` (default) / `complex` | `flat` = solid-colour background (colour-keyed pipeline). `complex` = non-flat/scene background: no colour key, the segmenter alone drives the matte. `screen_color` is ignored in `complex`; expect flat backgrounds to stay stronger on hair detail. |
|
||||
| `screen_color` | no | `#RRGGBB`, or omit/empty/`auto` | Background-colour prior; omit to auto-detect from the border. Ignored when `background_mode=complex`. |
|
||||
| `seg_model` | no | `birefnet` (default) / `anime-seg` | Segmentation backend. |
|
||||
| `cross_check` | no | `1`/`0` (also `true`/`false`/`yes`/`no`/`on`/`off`) | Per-request override of the cross-model veto; omit/empty = service default. |
|
||||
| `cross_check` | no | `1`/`0` (also `true`/`false`/`yes`/`no`/`on`/`off`) | Per-request override of the cross-model veto; omit/empty = service default in `flat` mode, but **defaults off** in `complex` mode (set `on` to enable its hue-free gate). |
|
||||
|
||||
### Response headers (on `200`)
|
||||
|
||||
@@ -220,13 +221,14 @@ is expected to authenticate). `/healthz` is always open. See [DEPLOY.md](DEPLOY.
|
||||
| `X-BGFilter-Seg-Model` | `birefnet` | Segmenter actually used. |
|
||||
| `X-BGFilter-Screen-Color` | `#CFEFFF` / `auto` | Background colour used. |
|
||||
| `X-BGFilter-Cross-Check` | `on` / `off` | Effective cross-check state. |
|
||||
| `X-BGFilter-Background-Mode` | `flat` / `complex` | Background mode actually used. |
|
||||
|
||||
### Status codes
|
||||
|
||||
| Code | When |
|
||||
| --- | --- |
|
||||
| `200` | Success — body is the RGBA PNG. |
|
||||
| `400` | Bad input: unreadable image, invalid `screen_color`/`seg_model`/`cross_check`, or auto-detect failed (pass `screen_color`). |
|
||||
| `400` | Bad input: unreadable image, invalid `screen_color`/`seg_model`/`cross_check`/`background_mode`, or auto-detect failed (pass `screen_color`). |
|
||||
| `401` | Auth enabled and the token is missing or wrong. |
|
||||
| `413` | Image exceeds `BGFILTER_MAX_IMAGE_PIXELS` (~4 MP by default). |
|
||||
| `500` | Inference failed. |
|
||||
@@ -247,6 +249,13 @@ curl -sS \
|
||||
-F "seg_model=anime-seg" \
|
||||
-F "cross_check=off" \
|
||||
http://127.0.0.1:18083/remove-background -o output.png
|
||||
|
||||
# non-flat / scene background (no colour key); cross-check defaults off here,
|
||||
# add cross_check=on to enable the hue-free residue veto
|
||||
curl -sS \
|
||||
-F "file=@scene.jpg" \
|
||||
-F "background_mode=complex" \
|
||||
http://127.0.0.1:18083/remove-background -o output.png
|
||||
```
|
||||
|
||||
Config comes from `BGFILTER_CONFIG` (defaults to `configs/default.yaml` when present);
|
||||
|
||||
+12
-3
@@ -187,9 +187,10 @@ python -m uvicorn app:app --host 127.0.0.1 --port 18083 --workers 1
|
||||
| 字段 | 必填 | 取值 | 含义 |
|
||||
| --- | --- | --- | --- |
|
||||
| `file` | 是 | 图片文件 | 源图(字段名固定为 `file`)。 |
|
||||
| `screen_color` | 否 | `#RRGGBB`,或留空/`auto` | 背景色先验;留空则从边框自动探测。 |
|
||||
| `background_mode` | 否 | `flat`(默认)/ `complex` | `flat` = 纯色背景(走色键管线)。`complex` = 非纯色/实景背景:无色键,完全由分割器驱动抠图;此模式下 `screen_color` 被忽略,发丝细节通常弱于纯色模式。 |
|
||||
| `screen_color` | 否 | `#RRGGBB`,或留空/`auto` | 背景色先验;留空则从边框自动探测。`background_mode=complex` 时忽略。 |
|
||||
| `seg_model` | 否 | `birefnet`(默认)/ `anime-seg` | 分割后端。 |
|
||||
| `cross_check` | 否 | `1`/`0`(也接受 `true`/`false`/`yes`/`no`/`on`/`off`) | 按请求覆盖交叉否决;留空 = 服务端默认。 |
|
||||
| `cross_check` | 否 | `1`/`0`(也接受 `true`/`false`/`yes`/`no`/`on`/`off`) | 按请求覆盖交叉否决;留空在 `flat` 模式 = 服务端默认,但在 `complex` 模式**默认关闭**(传 `on` 启用其无色相门控)。 |
|
||||
|
||||
### 响应头(`200` 时)
|
||||
|
||||
@@ -199,13 +200,14 @@ python -m uvicorn app:app --host 127.0.0.1 --port 18083 --workers 1
|
||||
| `X-BGFilter-Seg-Model` | `birefnet` | 实际使用的分割器。 |
|
||||
| `X-BGFilter-Screen-Color` | `#CFEFFF` / `auto` | 实际使用的背景色。 |
|
||||
| `X-BGFilter-Cross-Check` | `on` / `off` | 本次实际的 cross-check 状态。 |
|
||||
| `X-BGFilter-Background-Mode` | `flat` / `complex` | 本次实际的背景模式。 |
|
||||
|
||||
### 状态码
|
||||
|
||||
| 码 | 何时 |
|
||||
| --- | --- |
|
||||
| `200` | 成功——响应体是 RGBA PNG。 |
|
||||
| `400` | 输入有误:图片无法解码,`screen_color`/`seg_model`/`cross_check` 非法,或自动探测失败(请传 `screen_color`)。 |
|
||||
| `400` | 输入有误:图片无法解码,`screen_color`/`seg_model`/`cross_check`/`background_mode` 非法,或自动探测失败(请传 `screen_color`)。 |
|
||||
| `401` | 鉴权已开且 token 缺失或错误。 |
|
||||
| `413` | 图片超过 `BGFILTER_MAX_IMAGE_PIXELS`(默认约 4 MP)。 |
|
||||
| `500` | 推理失败。 |
|
||||
@@ -226,6 +228,13 @@ curl -sS \
|
||||
-F "seg_model=anime-seg" \
|
||||
-F "cross_check=off" \
|
||||
http://127.0.0.1:18083/remove-background -o output.png
|
||||
|
||||
# 非纯色/实景背景(无色键);此模式 cross-check 默认关闭,
|
||||
# 需要时加 cross_check=on 启用无色相残留否决
|
||||
curl -sS \
|
||||
-F "file=@scene.jpg" \
|
||||
-F "background_mode=complex" \
|
||||
http://127.0.0.1:18083/remove-background -o output.png
|
||||
```
|
||||
|
||||
配置来自 `BGFILTER_CONFIG`(存在时默认 `configs/default.yaml`);`BGFILTER_DEVICE` 同时
|
||||
|
||||
@@ -168,6 +168,23 @@ def _normalize_cross_check(value: str | None) -> bool | None:
|
||||
raise HTTPException(status_code=400, detail="invalid cross_check")
|
||||
|
||||
|
||||
def _normalize_background_mode(value: str | None) -> str | None:
|
||||
"""None/empty -> service default (flat); else "flat" or "complex".
|
||||
|
||||
"flat" (default) is the colour-keyed pipeline; "complex" is non-flat/scene
|
||||
matting with no colour key (segmentation drives the trimap). We deliberately
|
||||
do not surface the internal "chroma" wording here.
|
||||
"""
|
||||
if value is None:
|
||||
return None
|
||||
v = value.strip().lower()
|
||||
if v == "":
|
||||
return None
|
||||
if v in ("flat", "complex"):
|
||||
return v
|
||||
raise HTTPException(status_code=400, detail="invalid background_mode")
|
||||
|
||||
|
||||
def _decode_image(data: bytes) -> np.ndarray:
|
||||
if not data:
|
||||
raise HTTPException(status_code=400, detail="empty image")
|
||||
@@ -202,6 +219,7 @@ async def remove_background(
|
||||
screen_color: str | None = Form(None),
|
||||
seg_model: str | None = Form(None),
|
||||
cross_check: str | None = Form(None),
|
||||
background_mode: str | None = Form(None),
|
||||
_auth: None = Depends(require_auth),
|
||||
):
|
||||
if _manager is None:
|
||||
@@ -210,6 +228,15 @@ async def remove_background(
|
||||
normalized_color = _normalize_screen_color(screen_color)
|
||||
normalized_seg = _normalize_seg_model(seg_model) or _manager.default_seg_model
|
||||
normalized_cc = _normalize_cross_check(cross_check)
|
||||
# None/"flat" -> flat (chroma on); "complex" -> non-flat (chroma off).
|
||||
normalized_bg = _normalize_background_mode(background_mode)
|
||||
chroma_enabled = normalized_bg != "complex"
|
||||
effective_bg = "complex" if not chroma_enabled else "flat"
|
||||
# Complex mode defaults the cross-check veto OFF (it costs the HR-matting
|
||||
# forward and only clears residue near strands); an explicit cross_check=on
|
||||
# still enables its hue-free gate.
|
||||
if not chroma_enabled and normalized_cc is None:
|
||||
normalized_cc = False
|
||||
# Honors the lane lock (a locked cpu-fast box reports/runs "off" even on cross_check=on).
|
||||
effective_cc = _manager.effective_cross_check(normalized_cc)
|
||||
|
||||
@@ -226,6 +253,7 @@ async def remove_background(
|
||||
screen_color=normalized_color,
|
||||
seg_model=normalized_seg,
|
||||
cross_check=normalized_cc,
|
||||
chroma=chroma_enabled,
|
||||
),
|
||||
)
|
||||
except RuntimeError as exc:
|
||||
@@ -248,5 +276,6 @@ async def remove_background(
|
||||
"X-BGFilter-Seg-Model": normalized_seg,
|
||||
"X-BGFilter-Screen-Color": normalized_color if normalized_color else "auto",
|
||||
"X-BGFilter-Cross-Check": "on" if effective_cc else "off",
|
||||
"X-BGFilter-Background-Mode": effective_bg,
|
||||
}
|
||||
return Response(content=png, media_type="image/png", headers=headers)
|
||||
|
||||
+13
-6
@@ -59,7 +59,7 @@ def suppress_alpha_by_chroma(
|
||||
def cross_check_alpha(
|
||||
alpha: np.ndarray,
|
||||
second_alpha: np.ndarray,
|
||||
proj: np.ndarray,
|
||||
proj: np.ndarray | None,
|
||||
lightness: np.ndarray,
|
||||
trimap: np.ndarray,
|
||||
settings: CrossCheckSettings,
|
||||
@@ -76,6 +76,13 @@ def cross_check_alpha(
|
||||
- the primary alpha is high (``gate_lo -> gate_hi`` ramp): pixels the
|
||||
pipeline already renders soft (outer wisps) are exempt by construction.
|
||||
|
||||
``proj`` is None in complex-background mode (chroma disabled): no key colour
|
||||
means no hue-defined suspect zone, so instead the veto requires the second
|
||||
opinion itself to be confidently near-empty (full strength at/below
|
||||
``second_lo``, none at/above ``second_hi``). A thin strand the downsampled
|
||||
second model merely blurs to mid-alpha is left untouched; only regions it
|
||||
decisively rejects can be cleared.
|
||||
|
||||
Inside the zone this deliberately overrides the trimap-FG clamp -- the
|
||||
residue it exists to clear is mostly trimap-FG. Sure background cannot be
|
||||
disturbed: min-fusion keeps alpha 0 at 0.
|
||||
@@ -83,16 +90,16 @@ def cross_check_alpha(
|
||||
if not settings.enabled:
|
||||
return alpha
|
||||
cv2 = require_cv2()
|
||||
zone = (
|
||||
(proj >= settings.proj_min)
|
||||
& (lightness >= settings.l_min)
|
||||
& (trimap != 0)
|
||||
)
|
||||
zone = (lightness >= settings.l_min) & (trimap != 0)
|
||||
if proj is not None:
|
||||
zone &= proj >= settings.proj_min
|
||||
weight = zone.astype(np.float32)
|
||||
if settings.feather_sigma > 0:
|
||||
blur = cv2.GaussianBlur(weight, (0, 0), settings.feather_sigma)
|
||||
weight = np.where(zone, 1.0, np.clip(blur, 0.0, 1.0)).astype(np.float32)
|
||||
gate = weight * _smoothstep(alpha, settings.gate_lo, settings.gate_hi)
|
||||
if proj is None:
|
||||
gate = gate * (1.0 - _smoothstep(second_alpha, settings.second_lo, settings.second_hi))
|
||||
out = alpha * (1.0 - gate) + np.minimum(alpha, second_alpha) * gate
|
||||
return np.clip(out, 0.0, 1.0).astype(np.float32)
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ def main(
|
||||
device: str | None = typer.Option(None, "--device"),
|
||||
precision: str | None = typer.Option(None, "--precision", help="Compute precision for all models (ViTMatte cast + BiRefNet autocast): fp32 (default) | bf16 (faster + halves matting activation memory; needs bf16-capable hardware, else falls back to fp32)"),
|
||||
screen_color: str | None = typer.Option(None, "--screen-color", help="Background colour prior as #RRGGBB (default: auto-detect the flat background colour)"),
|
||||
chroma: bool | None = typer.Option(None, "--chroma/--no-chroma", help="Flat-colour background mode (default: on). --no-chroma is non-flat/complex-background mode: no colour key, segmentation drives the trimap; needs a segmentation backend + matting_method vitmatte"),
|
||||
matting_method: str | None = typer.Option(None, "--matting-method"),
|
||||
fallback_to_chroma_alpha: bool | None = typer.Option(None, "--fallback-to-chroma-alpha/--no-fallback-to-chroma-alpha"),
|
||||
sure_bg_threshold: float | None = typer.Option(None, "--sure-bg-threshold", min=0.0, max=1.0),
|
||||
@@ -43,6 +44,7 @@ def main(
|
||||
device=device,
|
||||
precision=precision,
|
||||
screen_color=screen_color,
|
||||
chroma=chroma,
|
||||
matting_method=matting_method,
|
||||
fallback_to_chroma_alpha=fallback_to_chroma_alpha,
|
||||
sure_bg_threshold=sure_bg_threshold,
|
||||
|
||||
+4
-1
@@ -99,6 +99,9 @@ def override_settings(settings: PipelineSettings, **overrides: Any) -> PipelineS
|
||||
}
|
||||
if overrides.get("trimap_mode") is not None:
|
||||
trimap_updates["mode"] = overrides["trimap_mode"]
|
||||
chroma_updates: dict[str, Any] = {}
|
||||
if overrides.get("chroma") is not None:
|
||||
chroma_updates["enabled"] = overrides["chroma"]
|
||||
despill_updates: dict[str, Any] = {}
|
||||
if overrides.get("despill") is not None:
|
||||
despill_updates["enabled"] = overrides["despill"]
|
||||
@@ -130,7 +133,7 @@ def override_settings(settings: PipelineSettings, **overrides: Any) -> PipelineS
|
||||
else settings.screen_color
|
||||
)
|
||||
return PipelineSettings(
|
||||
chroma=chroma,
|
||||
chroma=_update_dataclass(chroma, chroma_updates),
|
||||
trimap=_update_dataclass(trimap, trimap_updates),
|
||||
alpha_post=alpha_post,
|
||||
cross_check=_update_dataclass(settings.cross_check, cross_check_updates),
|
||||
|
||||
+3
-2
@@ -10,7 +10,7 @@ from .settings import DespillSettings
|
||||
def despill(
|
||||
rgb: np.ndarray,
|
||||
alpha: np.ndarray,
|
||||
model: BackgroundModel,
|
||||
model: BackgroundModel | None,
|
||||
settings: DespillSettings,
|
||||
) -> tuple[np.ndarray, np.ndarray]:
|
||||
"""Remove background-colour spill from the foreground.
|
||||
@@ -24,7 +24,8 @@ def despill(
|
||||
|
||||
Returns the corrected RGB (uint8) and the per-pixel despill weight (float32).
|
||||
"""
|
||||
if not settings.enabled:
|
||||
# model is None with chroma disabled: no key colour means no spill direction.
|
||||
if not settings.enabled or model is None:
|
||||
return rgb.copy(), np.zeros(alpha.shape, dtype=np.float32)
|
||||
|
||||
bg_lab = np.asarray(model.lab_center, dtype=np.float32)
|
||||
|
||||
@@ -15,7 +15,7 @@ def estimate_foreground_rgb(
|
||||
rgb: np.ndarray,
|
||||
alpha: np.ndarray,
|
||||
bg_confidence: np.ndarray,
|
||||
model: BackgroundModel,
|
||||
model: BackgroundModel | None,
|
||||
settings: ForegroundSettings,
|
||||
) -> np.ndarray:
|
||||
"""Estimated foreground colour F (uint8 HxWx3) for compositing over alpha."""
|
||||
@@ -27,10 +27,15 @@ def estimate_foreground_rgb(
|
||||
try:
|
||||
return _estimate_ml(rgb, alpha, settings)
|
||||
except RuntimeError:
|
||||
if not settings.fallback_to_unmix:
|
||||
if not settings.fallback_to_unmix or model is None:
|
||||
raise
|
||||
method = "unmix"
|
||||
if method == "unmix":
|
||||
if model is None:
|
||||
raise RuntimeError(
|
||||
"Foreground method 'unmix' needs the chroma background model, which "
|
||||
"does not exist with chroma disabled; use method 'ml'."
|
||||
)
|
||||
return _estimate_unmix(rgb, alpha, bg_confidence, model, settings)
|
||||
raise RuntimeError(
|
||||
f"Unsupported foreground method '{settings.method}'. Use 'ml' or 'unmix'."
|
||||
|
||||
+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),
|
||||
}
|
||||
|
||||
+7
-3
@@ -25,9 +25,10 @@ class PipelineManager:
|
||||
ViTMatte is the matting model shared by both segmentation backends, so it is
|
||||
loaded once and reused. Each segmentation backend is lazily loaded once on
|
||||
first use and cached by name. Per-request parameters (``screen_color``,
|
||||
``seg_model``, ``cross_check``) never trigger a model reload -- ``screen_color``
|
||||
only affects this request's chroma/despill, and ``seg_model``/``cross_check``
|
||||
select an already-cached (or first-time-loaded, then cached) model.
|
||||
``seg_model``, ``cross_check``, ``chroma``) never trigger a model reload --
|
||||
they only re-parametrise the pipeline over already-cached (or first-time-
|
||||
loaded, then cached) models. ``chroma=False`` selects non-flat/complex
|
||||
background mode (no colour key); ``screen_color`` is then unused.
|
||||
"""
|
||||
|
||||
def __init__(self, base_settings: PipelineSettings, preload: bool = True):
|
||||
@@ -142,6 +143,7 @@ class PipelineManager:
|
||||
screen_color: str | None,
|
||||
seg_model: str,
|
||||
cross_check: bool | None = None,
|
||||
chroma: bool | None = None,
|
||||
) -> MattingResult:
|
||||
if seg_model not in _SEG_MODELS:
|
||||
raise ValueError(f"invalid seg_model '{seg_model}'")
|
||||
@@ -149,6 +151,8 @@ class PipelineManager:
|
||||
self._base,
|
||||
screen_color=screen_color,
|
||||
seg_backend=seg_model,
|
||||
# None leaves the base default; False selects non-flat/complex mode.
|
||||
chroma=chroma,
|
||||
# Honor the lane lock: a locked lane ignores the per-request override
|
||||
# (a cpu-fast box never runs cross-check, even on cross_check=on).
|
||||
cross_check=self.effective_cross_check(cross_check),
|
||||
|
||||
@@ -5,6 +5,14 @@ from dataclasses import dataclass
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ChromaSettings:
|
||||
# False = non-flat-background mode (--no-chroma): no colour key at all.
|
||||
# Segmentation alone drives the trimap (mode forced to "seg"), and the
|
||||
# colour-keyed stages are bypassed: background auto-detect, the directional
|
||||
# hue split, chroma alpha suppression, despill, and the unmix foreground
|
||||
# fallback. The cross-check veto still runs (if enabled) with a hue-free
|
||||
# gate -- see CrossCheckSettings.second_lo/hi. Requires
|
||||
# segmentation.enabled and matting_method 'vitmatte'.
|
||||
enabled: bool = True
|
||||
border_ratio: float = 0.04
|
||||
min_samples: int = 2048
|
||||
lab_sigma_min: float = 10.0
|
||||
@@ -104,6 +112,13 @@ class CrossCheckSettings:
|
||||
feather_sigma: float = 2.0 # Gaussian feather of the zone boundary, in px
|
||||
gate_lo: float = 0.70 # primary alpha below this -> fully exempt
|
||||
gate_hi: float = 0.95 # primary alpha above this -> fully vetoable
|
||||
# Complex-background mode only (chroma disabled -> no hue-defined suspect
|
||||
# zone): the veto instead requires the second opinion itself to be
|
||||
# confidently near-empty -- full strength at/below second_lo, none at/above
|
||||
# second_hi. Thin structures the downsampled second model merely blurs to
|
||||
# mid-alpha stay untouched; only decisively-rejected regions can be cleared.
|
||||
second_lo: float = 0.15
|
||||
second_hi: float = 0.40
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
|
||||
@@ -3,6 +3,15 @@
|
||||
# like "#CFEFFF" sets it explicitly. Also overridable via --screen-color.
|
||||
screen_color: null
|
||||
|
||||
chroma:
|
||||
# true = flat-colour background (default). false = non-flat/complex background
|
||||
# mode (--no-chroma): no colour key; segmentation alone drives the trimap (mode
|
||||
# forced to "seg") and the colour-keyed stages are bypassed (auto-detect, hue
|
||||
# split, chroma suppression, despill). The cross-check veto still runs if
|
||||
# enabled, using its hue-free gate (cross_check.second_lo/hi). Needs
|
||||
# segmentation.enabled + matting_method vitmatte.
|
||||
enabled: true
|
||||
|
||||
model:
|
||||
model_name: hustvl/vitmatte-base-composition-1k
|
||||
device: cpu
|
||||
@@ -91,6 +100,11 @@ cross_check:
|
||||
feather_sigma: 2.0 # zone-boundary feather, px
|
||||
gate_lo: 0.70 # primary alpha below this -> fully exempt
|
||||
gate_hi: 0.95 # primary alpha above this -> fully vetoable
|
||||
# Complex-background (no-chroma) mode only: no hue-defined suspect zone, so the
|
||||
# veto requires the second opinion itself to be confidently near-empty -- full
|
||||
# strength at/below second_lo, fading to none at/above second_hi.
|
||||
second_lo: 0.15
|
||||
second_hi: 0.40
|
||||
|
||||
foreground:
|
||||
enabled: true
|
||||
|
||||
@@ -4,6 +4,15 @@
|
||||
# off (it false-positives on subjects sharing the background hue).
|
||||
screen_color: null
|
||||
|
||||
chroma:
|
||||
# true = flat-colour background (default). false = non-flat/complex background
|
||||
# mode: no colour key; segmentation alone drives the trimap (mode forced to
|
||||
# "seg") and the colour-keyed stages are bypassed (auto-detect, hue split,
|
||||
# chroma suppression, despill). The cross-check veto still runs if enabled,
|
||||
# using its hue-free gate (cross_check.second_lo/hi). The service selects this
|
||||
# per request via background_mode=flat|complex; this is only the base default.
|
||||
enabled: true
|
||||
|
||||
model:
|
||||
model_name: hustvl/vitmatte-base-composition-1k
|
||||
device: cuda
|
||||
@@ -51,6 +60,11 @@ cross_check:
|
||||
feather_sigma: 2.0
|
||||
gate_lo: 0.70
|
||||
gate_hi: 0.95
|
||||
# Complex-background (non-flat) mode only: no hue-defined suspect zone, so the
|
||||
# veto requires the second opinion itself to be confidently near-empty -- full
|
||||
# strength at/below second_lo, fading to none at/above second_hi.
|
||||
second_lo: 0.15
|
||||
second_hi: 0.40
|
||||
|
||||
foreground:
|
||||
enabled: true
|
||||
|
||||
Reference in New Issue
Block a user