Bildhintergründe im Stapel mit Python entfernen: rembg oder eine API

Zwei vollständige Skripte, um die Hintergründe eines ganzen Bilderordners zu entfernen: eines mit der Open-Source-Bibliothek rembg auf deiner eigenen Hardware, eines mit einer HTTP-API, das Wiederholungen, Rate Limits und doppelte Abrechnung richtig behandelt.

Aktualisiert am 23. Oktober 2026 · 8 Min. Lesezeit

Du hast einen Ordner mit ein paar Hundert Produktfotos, Porträts oder Angebotsbildern, und bei jedem muss der Hintergrund weg. Von Hand in einem Editor ist das ein Tag voller Klicks. In Python ist es eine Schleife, und die eigentliche Entscheidung ist, was in der Schleife läuft: ein Open-Source-Modell auf deinem eigenen Rechner oder eine HTTP-API.

Dieser Ratgeber baut beides, mit vollständigen Skripten, die du noch heute auf einen Ordner loslassen kannst. Dazu kommen die Teile, die einen Stapeljob zuverlässig machen: Modellwahl, CPU oder GPU, Wiederholungsversuche, Rate Limits, Idempotenz und Fortsetzen nach einem Absturz.

Option A oder Option B?

Lokales Modell (rembg)HTTP-API
KostenKostenlose Software; du zahlst mit Hardware und ZeitPro Bild
EinrichtungPython 3.11 bis 3.13, Modell-Downloads, optional CUDAEin API-Schlüssel und requests
GeschwindigkeitHängt von CPU oder GPU und dem Modell abHängt vom Dienst ab, parallele Anfragen
QualitätHängt vom gewählten Modell abHängt vom Dienst ab
DatenschutzBilder verlassen deinen Rechner nieBilder werden zu einem Drittanbieter hochgeladen
WartungDu kümmerst dich um Modelle, Treiber und LizenzenDer Anbieter erledigt das
Gut fürOffline-Arbeit, sensible Bilder, ExperimenteProduktions-Pipelines, Server ohne GPU

Viele Teams nutzen beides: ein lokales Modell für Experimente und interne Tools, eine API für die Produktion, wo berechenbare Qualität und keine GPU-Server wichtiger sind.

Option A: rembg, der Open-Source-Weg

rembg ist eine MIT-lizenzierte Python-Bibliothek mit CLI, die mehrere Modelle zur Hintergrundentfernung bündelt und sie mit ONNX Runtime ausführt. Zum Zeitpunkt des Schreibens ist die aktuelle Version 2.0.86, und sie braucht Python 3.11 oder neuer und älter als 3.14.

Installation

Wähle das Backend passend zu deiner Hardware. Die CPU-Variante läuft überall:

python -m venv .venv && source .venv/bin/activate
pip install "rembg[cpu]"        # library only
pip install "rembg[cpu,cli]"    # library plus the rembg command

Für NVIDIA-GPUs installierst du stattdessen rembg[gpu]. Laut Projekt braucht das onnxruntime-gpu sowie passendes CUDA und cuDNN, und wenn du die nicht installieren kannst, solltest du auf die CPU-Variante ausweichen. AMD-GPUs unter Linux nutzen rembg[rocm] mit onnxruntime-rocm.

rembg wählt automatisch den CUDA- (oder ROCm-) Execution Provider, wenn ONNX Runtime ihn als verfügbar meldet, und warnt, wenn es auf die CPU zurückfällt. So prüfst du, was ONNX Runtime sieht:

import onnxruntime
print(onnxruntime.get_available_providers())  # look for CUDAExecutionProvider

Ein Modell wählen

Modelle werden beim ersten Einsatz nach ~/.rembg/models/ heruntergeladen (mit REMBG_HOME änderst du den Ort). Einige nützliche aus der Liste des Projekts:

ModellHinweise
bria-rmbgDer Standard. BRIAs RMBG-2.0, etwa 1,02 GB, läuft mit 1024 x 1024. Seine Lizenz verlangt für kommerzielle Nutzung eine kostenpflichtige Vereinbarung.
birefnet-generalBiRefNet-Modell für allgemeine Zwecke; gute weiche Kanten
birefnet-general-liteLeichteres BiRefNet für allgemeine Zwecke
birefnet-portraitAuf Personen abgestimmt
isnet-general-useIS-Net-Modell für allgemeine Zwecke
u2netDas klassische Allzweckmodell; schneller, gröbere Kanten
u2netpLeichtes u2net; schnelle Tests auf langsamen Rechnern
siluetaWie u2net, verkleinert auf 43 MB

Zwei Punkte sind wichtiger, als sie aussehen. Erstens: Das Standardmodell ist für kommerzielle Nutzung nicht kostenlos. Modellgewichte haben eigene Lizenzen, getrennt von der MIT-Lizenz von rembg, prüfe also die Quelle jedes Modells, das du einsetzt. Zweitens liefern die neueren Modelle (bria-rmbg, birefnet-*, isnet-general-use) weiche, gut geformte Kanten, während die ältere u2net-Familie eher festere, gröbere Kanten erzeugt.

Optionen für die Kantenqualität

rembg bietet drei Arten, weiche Kantenpixel wie Haare und Fell zu behandeln:

  • Standard: gut für Motive mit harten Kanten wie Produkte, Autos und Logos.
  • decontaminate=True (-dc in der CLI): entfernt den farbigen Saum, den der alte Hintergrund in den Kantenpixeln hinterlässt. Es ändert nur die Farbe, nie die Form der Maske, kostet sehr wenig und kann für einen ganzen Stapel eingeschaltet bleiben.
  • alpha_matting=True (-a): schätzt die Kante selbst neu, wenn die Maskenform falsch ist, etwa bei abgeschnittenen Strähnen. Das ist viel langsamer und kann fehlschlagen; dann fällt rembg auf einen dekontaminierten Freisteller zurück.

Für unbeaufsichtigte Stapel empfiehlt das Projekt -dc statt -a, weil es schneller ist und nicht fehlschlagen kann. Der Ratgeber zum Alphakanal erklärt, warum Säume entstehen.

Der schnelle Weg: die CLI

rembg p -m birefnet-general -dc photos/ cutouts/

rembg p verarbeitet einen ganzen Ordner; mit -w überwacht es den Ordner und verarbeitet neue Dateien, sobald sie erscheinen.

Das Python-Skript

Diese Version lädt das Modell einmal, überspringt bereits fertige Dateien, damit du es anhalten und neu starten kannst, und macht weiter, wenn eine Datei fehlschlägt:

import sys
from pathlib import Path

from rembg import new_session, remove

EXTS = {".jpg", ".jpeg", ".png", ".webp"}


def main(in_dir: str, out_dir: str, model: str = "birefnet-general") -> None:
    src_dir, dst_dir = Path(in_dir), Path(out_dir)
    dst_dir.mkdir(parents=True, exist_ok=True)
    session = new_session(model)  # load the model once, reuse it for every image

    files = sorted(p for p in src_dir.iterdir() if p.suffix.lower() in EXTS)
    for i, src in enumerate(files, 1):
        dst = dst_dir / f"{src.stem}.png"
        if dst.exists():
            continue  # resume where a previous run stopped
        try:
            out = remove(src.read_bytes(), session=session, decontaminate=True)
            dst.write_bytes(out)
            print(f"[{i}/{len(files)}] ok      {src.name}")
        except Exception as e:  # one bad file shouldn't stop the batch
            print(f"[{i}/{len(files)}] failed  {src.name}: {e}")


if __name__ == "__main__":
    main(*sys.argv[1:])

Starte es mit python batch_rembg.py photos cutouts birefnet-general. Die Session einmal zu erstellen ist wichtig: Ein Modell zu laden dauert viel länger, als es auszuführen, und die README von rembg empfiehlt die Wiederverwendung der Session für die Leistung bei Stapeln. Die Ausgaben sind immer PNG, weil das Ergebnis einen Alphakanal hat.

Wo rembg an Grenzen stößt

  • Der erste Lauf ist langsam, während das Modell herunterlädt, und große Modelle brauchen viel RAM oder VRAM.
  • Der Durchsatz auf der CPU ist bescheiden mit den hochwertigen Modellen. Teste mit 20 repräsentativen Bildern, bevor du dich auf tausend festlegst.
  • Lizenzen und Treiber sind deine Sache. Fixiere die Versionen von rembg und den Modellen, damit ein späteres Upgrade die Ergebnisse nicht unbemerkt verändert.

Option B: eine HTTP-API

Mit einer API erledigen die GPUs von jemand anderem die schwere Arbeit, und dein Skript kümmert sich vor allem darum, das Netzwerk gut zu behandeln. Das Beispiel nutzt die API von removebackground.dev, weil ihr Vertrag als OpenAPI 3.1 unter /openapi.json veröffentlicht ist; dieselben Muster gelten für jeden ähnlichen Dienst.

Der Vertrag

  • POST https://removebackground.dev/v1/remove mit deinem Schlüssel im Header x-api-key.
  • Schick das Bild als Multipart-Feld image_file (JPG, PNG oder WebP, bis 25 MB) oder als JSON mit image_url.
  • Optionale Felder sind unter anderem format (png, jpg oder webp), bg_color, size (preview, medium, hd, full) und crop.
  • 200 heißt, der Body ist das fertige Bild. 202 heißt, nach etwa 55 Sekunden wird noch verarbeitet: Der Body ist JSON mit einer Job-id, und du fragst GET /v1/jobs/{id}/result ab, das mit 409 not_ready und Retry-After antwortet, bis das Bild fertig ist. Speichere einen 202-Body nie als Bild.
  • Bis zu 60 Verarbeitungsanfragen pro Minute pro Konto; darüber bekommst du 429 rate_limited mit Retry-After.
  • Ein Header Idempotency-Key macht Wiederholungen sicher: Derselbe Schlüssel mit derselben Anfrage liefert den ursprünglichen Job zurück, statt erneut abzurechnen. Derselbe Schlüssel mit einer anderen Anfrage antwortet mit 409 idempotency_key_reused.
  • Ergebnisse werden nach 48 Stunden gelöscht, speichere sie also, sobald du sie bekommst.

Wiederholungsregeln, damit du nicht doppelt zahlst

AntwortWas zu tun ist
200Bild speichern
202Job-Ergebnis abfragen; nicht erneut hochladen
400, 401, 402, 413, 422Anfrage, Schlüssel, Credits oder Bild korrigieren; keine Schleife
429Retry-After Sekunden warten, mit demselben Schlüssel erneut senden
500, 503, Timeout, abgebrochene VerbindungWarten und mit demselben Schlüssel erneut senden
502Dieses Bild ist fehlgeschlagen und wurde nicht abgerechnet; protokollieren

Der Trick ist, den Idempotenzschlüssel deterministisch zu machen: ein Hash aus den Bildbytes plus den Optionen. Stürzt das Skript ab und du startest es neu, trägt jedes erneute Senden denselben Schlüssel, und nichts wird doppelt verarbeitet oder abgerechnet.

Das Python-Skript

import hashlib
import mimetypes
import os
import sys
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path

import requests

API = os.environ.get("REMOVEBG_API", "https://removebackground.dev")
KEY = os.environ["REMOVEBG_API_KEY"]  # keep the key out of your code
OPTIONS = {"format": "png", "size": "full"}  # or {"format": "jpg", "bg_color": "ffffff"}
WORKERS = 4  # the API allows 60 processing requests a minute
EXTS = {".jpg", ".jpeg", ".png", ".webp"}
RETRYABLE = {429, 500, 503}


def idempotency_key(data: bytes) -> str:
    # Same image + same options = same key, so a rerun never pays twice.
    h = hashlib.sha256(data)
    h.update(repr(sorted(OPTIONS.items())).encode())
    return h.hexdigest()


def wait(res, default=2.0):
    try:
        return float(res.headers.get("Retry-After", default))
    except ValueError:
        return default


def save(res, dst: Path) -> Path:
    if res.status_code != 200 or not res.headers.get("Content-Type", "").startswith("image/"):
        raise RuntimeError(f"HTTP {res.status_code}: {res.text[:300]}")
    dst.write_bytes(res.content)
    return dst


def process(src: Path, dst: Path) -> Path:
    data = src.read_bytes()
    mime = mimetypes.guess_type(src.name)[0] or "application/octet-stream"
    headers = {"x-api-key": KEY, "Idempotency-Key": idempotency_key(data)}

    for attempt in range(6):
        try:
            res = requests.post(
                f"{API}/v1/remove",
                files={"image_file": (src.name, data, mime)},
                data=OPTIONS,
                headers=headers,
                timeout=(10, 120),
            )
        except (requests.ConnectionError, requests.Timeout):
            time.sleep(2 ** attempt)  # same key: safe to resend
            continue
        if res.status_code in RETRYABLE:
            time.sleep(wait(res, 2 ** attempt))
            continue
        break
    else:
        raise RuntimeError("gave up after retries")

    if res.status_code != 202:
        return save(res, dst)  # 200 is the image; 4xx and 502 raise

    # 202: still processing. Poll the same job; never upload the image again.
    job_id = res.json()["id"]
    deadline = time.monotonic() + 600
    while time.monotonic() < deadline:
        time.sleep(wait(res))
        try:
            res = requests.get(f"{API}/v1/jobs/{job_id}/result", headers=headers, timeout=30)
        except (requests.ConnectionError, requests.Timeout):
            continue  # reading a job is free and safe to repeat
        if res.status_code == 409 and res.json()["error"]["code"] == "not_ready":
            continue
        if res.status_code >= 500:
            continue
        return save(res, dst)
    raise TimeoutError(f"job {job_id} still running; fetch it later")


def main(in_dir: str, out_dir: str) -> None:
    src_dir, dst_dir = Path(in_dir), Path(out_dir)
    dst_dir.mkdir(parents=True, exist_ok=True)
    ext = ".jpg" if OPTIONS.get("format") == "jpg" else "." + OPTIONS.get("format", "png")
    todo = [
        (p, dst_dir / (p.stem + ext))
        for p in sorted(src_dir.iterdir())
        if p.suffix.lower() in EXTS and not (dst_dir / (p.stem + ext)).exists()
    ]
    print(f"{len(todo)} images to process")

    failed = []
    with ThreadPoolExecutor(max_workers=WORKERS) as pool:
        jobs = {pool.submit(process, s, d): s for s, d in todo}
        for fut in as_completed(jobs):
            src = jobs[fut]
            try:
                print("ok    ", src.name, "->", fut.result().name)
            except Exception as e:
                failed.append(src.name)
                print("failed", src.name, e)
    print(f"done: {len(todo) - len(failed)} ok, {len(failed)} failed")


if __name__ == "__main__":
    main(sys.argv[1], sys.argv[2])

Starte es mit REMOVEBG_API_KEY=... python batch_api.py photos cutouts. Vier Worker sind ein bescheidener Anfang; bringen dich schnelle Antworten über das Minutenlimit, wartet die 429-Behandlung und sendet mit demselben Schlüssel erneut. Um URLs statt Dateien zu schicken, sende JSON:

res = requests.post(
    f"{API}/v1/remove",
    json={"image_url": "https://example.com/shoe.jpg", "format": "png"},
    headers={"x-api-key": KEY, "Idempotency-Key": "shoe-front-v1"},
    timeout=(10, 120),
)

Für lange URL-Listen hat dieselbe API POST /v1/batch, das bis zu 100 Bild-URLs als einen Job einreiht, den du abfragst.

Jeden Stapeljob robust machen

  • Fertige Ausgaben überspringen, damit ein neuer Lauf fortsetzt, statt von vorn zu beginnen. Beide Skripte tun das.
  • Auf Namenskollisionen achten. shoe.jpg und shoe.png im selben Ordner werden beide zu shoe.png; benenne Eingaben um oder nimm die ursprüngliche Endung in den Ausgabenamen auf.
  • Fehler in eine Datei protokollieren und nur diese erneut verarbeiten.
  • Eine Stichprobe prüfen bei 100 % Zoom auf dunklem und hellem Hintergrund, bevor du alles verarbeitest.
  • Originale unangetastet lassen. Schreib in einen separaten Ausgabeordner.
  • Das Ausgabeformat nach dem Ziel wählen. PNG oder WebP für Transparenz, JPG auf Weiß für Marktplätze. Siehe PNG, JPEG oder WebP.

Fragen

Ist rembg für kommerzielle Nutzung kostenlos?

rembg selbst steht unter MIT-Lizenz, aber jedes Modell hat eine eigene Lizenz. Das Standardmodell, BRIAs RMBG-2.0 (bria-rmbg), verlangt für kommerzielle Nutzung eine kostenpflichtige Vereinbarung. Prüfe also die Lizenz des Modells, das du nutzt, oder wähle ein anderes.

Nutzt rembg automatisch meine GPU?

Ja, wenn du rembg[gpu] installierst und ONNX Runtime den CUDA-Provider laden kann (was passendes CUDA und cuDNN braucht). rembg nutzt CUDA oder ROCm, wenn verfügbar, und warnt, wenn es auf die CPU zurückfällt. Prüfe onnxruntime.get_available_providers().

Welches rembg-Modell liefert die beste Qualität?

Die neueren Modelle, bria-rmbg (der Standard), die birefnet-Familie und isnet-general-use, erzeugen weichere und besser geformte Kanten als die ältere u2net-Familie. birefnet-portrait ist ein guter Start für Personen. Teste eine Stichprobe deiner eigenen Bilder, denn die Ergebnisse hängen vom Motiv ab.

Wie vermeide ich doppelte Abrechnung, wenn ich API-Aufrufe wiederhole?

Sende einen Header Idempotency-Key und verwende ihn bei jeder Wiederholung desselben Bildes erneut. Leitest du ihn aus einem Hash der Bildbytes und Optionen ab, nutzt selbst ein kompletter Neustart des Skripts dieselben Schlüssel.

Was sollte mein Skript mit einer 202-Antwort tun?

Sie als noch in Bearbeitung behandeln, nicht als Bild. Lies die Job-id aus dem JSON-Body und frage GET /v1/jobs/{id}/result ab, wobei du jedes Mal Retry-After abwartest, wenn die Antwort 409 not_ready lautet. Lade das Bild nicht erneut hoch.

Wie schnell darf ich Anfragen an die API von removebackground.dev senden?

Bis zu 60 Verarbeitungsanfragen pro Minute pro Konto. Darüber antwortet die API mit 429 rate_limited und einem Header Retry-After. Für viele URLs nimmt POST /v1/batch bis zu 100 pro Anfrage an.

Probier es mit deinem eigenen Bild

Neue Konten bekommen 50 kostenlose Credits. Ein Credit pro Bild, kein Abo, und Credits verfallen nie.

Hintergrund entfernen

Quellen