modelbeast/server/operators/bg_remove_local/run.py
MODELBEAST 1cc0b80d89 bg_remove_local: local RMBG-2.0 background removal on MPS
New operator: briaai/RMBG-2.0 image->transparent cutout, fully local on Apple
Silicon (weights unlocked by owner HF license). Free local alternative to the
cloud fal_bg_remove; the recommended pre-pass before SF3D / image->3D. Verified:
clean 1024px cutout of the FLUX astrolabe (alpha 0-255, 86.6% removed, thin rings
preserved). Same OpenMP guardrails as sf3d. scripts/install_rmbg.sh; venvs/rmbg.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 01:32:38 +10:00

69 lines
2.2 KiB
Python

import argparse
import json
import os
import sys
from pathlib import Path
# Same libomp collision guardrails as sf3d (kornia/timm/torch each may link one).
os.environ.setdefault("PYTORCH_ENABLE_MPS_FALLBACK", "1")
os.environ.setdefault("KMP_DUPLICATE_LIB_OK", "TRUE")
os.environ.setdefault("OMP_NUM_THREADS", "1")
os.environ.setdefault("MKL_NUM_THREADS", "1")
import torch # noqa: E402
from PIL import Image # noqa: E402
from torchvision import transforms # noqa: E402
from transformers import AutoModelForImageSegmentation # noqa: E402
ap = argparse.ArgumentParser()
ap.add_argument("--input", action="append", default=[])
ap.add_argument("--outdir", required=True)
ap.add_argument("--params", default="{}")
a = ap.parse_args()
p = json.loads(a.params)
if not a.input:
print("ERROR: no input image")
sys.exit(1)
res = int(p.get("resolution", 1024))
bg = p.get("background", "transparent")
device = "mps" if torch.backends.mps.is_available() else "cpu"
print(f"loading briaai/RMBG-2.0 on {device} (first run downloads weights) ...", flush=True)
model = AutoModelForImageSegmentation.from_pretrained("briaai/RMBG-2.0", trust_remote_code=True)
try:
torch.set_float32_matmul_precision("high")
except Exception:
pass
model.eval().to(device)
transform = transforms.Compose([
transforms.Resize((res, res)),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
])
src = Path(a.input[0])
image = Image.open(src).convert("RGB")
inp = transform(image).unsqueeze(0).to(device)
print("running segmentation ...", flush=True)
with torch.no_grad():
preds = model(inp)[-1].sigmoid().cpu()
mask = transforms.ToPILImage()(preds[0].squeeze()).resize(image.size)
outdir = Path(a.outdir)
name = f"{src.stem}_cutout.png"
if bg == "transparent":
out = image.copy()
out.putalpha(mask)
else:
fill = (255, 255, 255) if bg == "white" else (0, 0, 0)
canvas = Image.new("RGB", image.size, fill)
canvas.paste(image, (0, 0), mask)
out = canvas
out.save(outdir / name)
print(f"done: {name}", flush=True)
(outdir / "result.json").write_text(json.dumps(
{"outputs": [{"path": name, "meta": {"tool": "rmbg-2.0", "resolution": res, "background": bg}}]}))