feat: harden pipeline UX — preprocess, rembg alpha, remix/lightbox
Downscale to 2000px JPEG before rembg, random blend opacity, timeout retries, per-variant remix prefill, lightbox, and longer SFTP idle. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
# Background-removal comparison
|
||||
|
||||
Inbox originals from `live.f12.rocks` vs. backends.
|
||||
|
||||
## Layout
|
||||
|
||||
- `inputs/` — originals from boka inbox
|
||||
- `outputs/` — results, naming:
|
||||
|
||||
```
|
||||
<stem>__rembg-u2net__no-alpha.png
|
||||
<stem>__rembg-u2net__alpha.png
|
||||
<stem>__rembg-default__no-alpha.png
|
||||
<stem>__rembg-default__alpha.png
|
||||
<stem>__rembg-birefnet-general__no-alpha.png
|
||||
<stem>__rembg-birefnet-general__alpha.png
|
||||
<stem>__withoutbg-open-weights.png
|
||||
```
|
||||
|
||||
- `rembg-default` = `rembg.remove()` without session (library default = u2net)
|
||||
- `rembg-u2net` = explicit `new_session("u2net")` — expect identical to default
|
||||
- `withoutbg` open-weights includes matting in-graph (~2GB RAM claimed)
|
||||
|
||||
## Re-run
|
||||
|
||||
```bash
|
||||
./run.sh
|
||||
```
|
||||
|
||||
Skips existing non-empty outputs. Models cached in `model-cache/`.
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# Run rembg/withoutbg comparison in Python 3.11 (host is 3.14-only).
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "$0")" && pwd)"
|
||||
IMG="livef12-bg-compare:py311"
|
||||
LOG="$ROOT/compare.log"
|
||||
|
||||
docker build -t "$IMG" - <<'EOF'
|
||||
FROM python:3.11-slim-bookworm
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends libgl1 libglib2.0-0 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN pip install --no-cache-dir 'rembg>=2.0.59,<2.1' 'pillow>=10.4,<11' \
|
||||
'onnxruntime>=1.19,<1.20' withoutbg
|
||||
WORKDIR /work
|
||||
EOF
|
||||
|
||||
echo "Starting comparison; log: $LOG"
|
||||
docker run --rm \
|
||||
--name livef12-bg-compare \
|
||||
-e TQDM_DISABLE=1 \
|
||||
-e COMPARE_INPUT=/data/inputs \
|
||||
-e COMPARE_OUTPUT=/data/outputs \
|
||||
-e HOME=/cache \
|
||||
-v "$ROOT/inputs:/data/inputs:ro" \
|
||||
-v "$ROOT/outputs:/data/outputs" \
|
||||
-v "$ROOT/run_compare.py:/work/run_compare.py:ro" \
|
||||
-v "$ROOT/model-cache:/cache" \
|
||||
"$IMG" python -u /work/run_compare.py 2>&1 | tee "$LOG"
|
||||
@@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compare background-removal backends on the live.f12.rocks inbox uploads.
|
||||
|
||||
Outputs land next to each other with explicit names so Fränky can pick a winner:
|
||||
|
||||
<stem>__rembg-u2net__no-alpha.png
|
||||
<stem>__rembg-u2net__alpha.png
|
||||
<stem>__rembg-default__no-alpha.png
|
||||
<stem>__rembg-default__alpha.png
|
||||
<stem>__rembg-birefnet-general__no-alpha.png
|
||||
<stem>__rembg-birefnet-general__alpha.png
|
||||
<stem>__withoutbg-open-weights.png
|
||||
|
||||
`rembg-default` = rembg.remove() with no session (library default model).
|
||||
`rembg-u2net` = rembg.new_session("u2net") explicitly.
|
||||
withoutbg embeds matting in its open-weights graph — one output only.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
from pathlib import Path
|
||||
|
||||
os.environ.setdefault("TQDM_DISABLE", "1")
|
||||
|
||||
INPUT_DIR = Path(os.environ.get("COMPARE_INPUT", "/data/inputs"))
|
||||
OUTPUT_DIR = Path(os.environ.get("COMPARE_OUTPUT", "/data/outputs"))
|
||||
EXTS = {".jpg", ".jpeg", ".png", ".webp"}
|
||||
|
||||
|
||||
def log(msg: str) -> None:
|
||||
print(msg, flush=True)
|
||||
|
||||
|
||||
def run_rembg_named(data: bytes, label: str, model: str | None, alpha: bool, out: Path) -> None:
|
||||
from rembg import new_session, remove
|
||||
|
||||
t0 = time.monotonic()
|
||||
kwargs: dict = {"alpha_matting": alpha}
|
||||
if model is None:
|
||||
result = remove(data, **kwargs)
|
||||
model_note = "default"
|
||||
else:
|
||||
session = new_session(model)
|
||||
result = remove(data, session=session, **kwargs)
|
||||
model_note = model
|
||||
out.write_bytes(result)
|
||||
log(f" OK {label} model={model_note} alpha={alpha} {time.monotonic()-t0:.1f}s -> {out.name} ({out.stat().st_size} bytes)")
|
||||
|
||||
|
||||
def run_withoutbg(path: Path, out: Path) -> None:
|
||||
from withoutbg import WithoutBG
|
||||
|
||||
t0 = time.monotonic()
|
||||
model = WithoutBG.open_weights()
|
||||
result = model.remove_background(str(path))
|
||||
result.save(out)
|
||||
log(f" OK withoutbg-open-weights {time.monotonic()-t0:.1f}s -> {out.name} ({out.stat().st_size} bytes)")
|
||||
|
||||
|
||||
def process_one(path: Path) -> None:
|
||||
stem = path.stem
|
||||
log(f"=== {path.name} ===")
|
||||
data = path.read_bytes()
|
||||
jobs = [
|
||||
("rembg-u2net", "u2net", False),
|
||||
("rembg-u2net", "u2net", True),
|
||||
("rembg-default", None, False),
|
||||
("rembg-default", None, True),
|
||||
("rembg-birefnet-general", "birefnet-general", False),
|
||||
("rembg-birefnet-general", "birefnet-general", True),
|
||||
]
|
||||
for label, model, alpha in jobs:
|
||||
tag = "alpha" if alpha else "no-alpha"
|
||||
out = OUTPUT_DIR / f"{stem}__{label}__{tag}.png"
|
||||
if out.exists() and out.stat().st_size > 0:
|
||||
log(f" SKIP {out.name} (exists)")
|
||||
continue
|
||||
try:
|
||||
run_rembg_named(data, label, model, alpha, out)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
err = OUTPUT_DIR / f"{stem}__{label}__{tag}.ERROR.txt"
|
||||
err.write_text(f"{type(exc).__name__}: {exc}\n\n{traceback.format_exc()}", encoding="utf-8")
|
||||
log(f" FAIL {label} alpha={alpha}: {type(exc).__name__}: {exc}")
|
||||
|
||||
out_w = OUTPUT_DIR / f"{stem}__withoutbg-open-weights.png"
|
||||
if out_w.exists() and out_w.stat().st_size > 0:
|
||||
log(f" SKIP {out_w.name} (exists)")
|
||||
else:
|
||||
try:
|
||||
run_withoutbg(path, out_w)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
err = OUTPUT_DIR / f"{stem}__withoutbg-open-weights.ERROR.txt"
|
||||
err.write_text(f"{type(exc).__name__}: {exc}\n\n{traceback.format_exc()}", encoding="utf-8")
|
||||
log(f" FAIL withoutbg: {type(exc).__name__}: {exc}")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
inputs = sorted(p for p in INPUT_DIR.iterdir() if p.is_file() and p.suffix.lower() in EXTS)
|
||||
if not inputs:
|
||||
log(f"No images in {INPUT_DIR}")
|
||||
return 1
|
||||
log(f"Comparing {len(inputs)} image(s); outputs -> {OUTPUT_DIR}")
|
||||
for path in inputs:
|
||||
process_one(path)
|
||||
log("DONE")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user