Files
vnassets/vnassets/background.py
Michele Rossi 8a11325b2f vnassets/background: add bg removal via rembg + isnet-anime
rembg with ONNX Runtime on CPU was chosen over BiRefNet: the latter
requires deform_conv2d which crashes on ROCm bfloat16 and runs at 40s
in float32. rembg delivers 0.23s per 1024x1024 image, no GPU deps,
and isnet-anime is trained specifically on anime images — exactly the
target domain.

CLI and session API both support single-image and batch (plural-first)
modes, reusing one model session across files.
2026-07-08 11:20:36 +02:00

100 lines
3.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Background removal for character sprites using rembg + isnet-anime.
Uses onnxruntime on CPU — the model is ~176 MB and runs in ~0.25s per
1024×1024 image on Strix Halo. No GPU dependency keeps things simple.
"""
import time
from pathlib import Path
from typing import Sequence
from PIL import Image
from rembg import new_session, remove
def _load_session(model: str = "isnet-anime"):
"""Load a rembg session for the given model. Cached in the caller."""
return new_session(model)
def remove_background(
input_path: str,
output_path: str,
model: str = "isnet-anime",
session=None,
) -> None:
"""Remove background from a single image. Output is RGBA PNG.
Args:
input_path: Path to the input RGB image.
output_path: Where to save the RGBA PNG.
model: rembg model name. Default "isnet-anime" is trained on anime
images. Other options: "u2net", "u2netp", "u2net_human_seg",
"isnet-general-use", "sam".
session: A rembg session (from ``new_session()``). If None, one
is created and immediately discarded. For batch use, create a
session once and pass it to avoid reloading the model.
Raises:
FileNotFoundError: If input_path doesn't exist.
"""
input_file = Path(input_path)
if not input_file.exists():
raise FileNotFoundError(f"Input image not found: {input_path}")
output_file = Path(output_path)
output_file.parent.mkdir(parents=True, exist_ok=True)
_session = session
close_session = session is None
if _session is None:
_session = _load_session(model)
img = Image.open(input_file).convert("RGB")
t0 = time.perf_counter()
result = remove(img, session=_session)
t_elapsed = round(time.perf_counter() - t0, 3)
result.save(output_file)
print(f"Saved {output_file} (bg removed, {t_elapsed}s)")
if close_session:
del _session
def remove_backgrounds(
input_paths: Sequence[str],
output_dir: str,
model: str = "isnet-anime",
) -> None:
"""Remove backgrounds from multiple images, reusing one model session.
Output files are named ``{stem}_nobg.png`` in ``output_dir``.
Args:
input_paths: List of input image paths.
output_dir: Directory for output RGBA PNGs.
model: rembg model name. Default "isnet-anime".
"""
out_dir = Path(output_dir)
out_dir.mkdir(parents=True, exist_ok=True)
session = _load_session(model)
try:
total = 0.0
for input_path in input_paths:
stem = Path(input_path).stem
output_path = out_dir / f"{stem}_nobg.png"
img = Image.open(input_path).convert("RGB")
t0 = time.perf_counter()
result = remove(img, session=session)
t_elapsed = time.perf_counter() - t0
total += t_elapsed
result.save(output_path)
print(f"Saved {output_path} ({t_elapsed:.3f}s)")
print(f"Done: {len(input_paths)} images, {total:.2f}s total")
finally:
del session