Achtergronden in bulk verwijderen met Python: rembg of een API

Twee complete scripts om de achtergrond te verwijderen van een hele map afbeeldingen: een met de open-source bibliotheek rembg op je eigen hardware, en een met een HTTP-API die retries, rate limits en dubbele kosten correct afhandelt.

Bijgewerkt op 23 oktober 2026 · 9 min lezen

Sla de stappen over: verwijder de achtergrond met één klik. Afbeelding uploaden

Je hebt een map met een paar honderd productfoto's, portretfoto's of advertentieafbeeldingen, en van elke afbeelding moet je de achtergrond verwijderen. Dat met de hand doen in een editor is een dag klikken. In Python is het een lus, en de echte keuze is wat er in die lus draait: een open-source model op je eigen machine, of een HTTP-API.

Deze gids bouwt allebei, met complete scripts die je vandaag nog op een map kunt draaien, en behandelt de onderdelen die een batchjob betrouwbaar maken: modelkeuze, CPU of GPU, retries, rate limits, idempotentie en hervatten na een crash.

Optie A of optie B?

Lokaal model (rembg)HTTP-API
KostenGratis software; je betaalt in hardware en tijdPer afbeelding
InstallatiePython 3.11 tot 3.13, modellen downloaden, optioneel CUDAEen API-sleutel en requests
SnelheidHangt af van je CPU of GPU en het modelHangt af van de dienst, parallelle verzoeken
KwaliteitHangt af van het model dat je kiestHangt af van de dienst
PrivacyAfbeeldingen verlaten je machine nooitAfbeeldingen worden geüpload naar een derde partij
OnderhoudJij beheert modellen, drivers en licentiesDe aanbieder doet het
Geschikt voorOffline werk, gevoelige afbeeldingen, experimentenProductiepipelines, servers zonder GPU

Veel teams gebruiken allebei: een lokaal model voor experimenten en interne tools, een API voor productie, waar voorspelbare kwaliteit en geen GPU-servers belangrijker zijn.

Optie A: rembg, de open-source route

rembg is een Python-bibliotheek en CLI onder MIT-licentie die verschillende modellen voor achtergrondverwijdering bundelt en ze draait met ONNX Runtime. Op het moment van schrijven is de huidige release 2.0.86, en die vereist Python 3.11 of later en ouder dan 3.14.

Installeren

Kies de backend die bij je hardware past. De CPU-build werkt overal:

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

Voor NVIDIA-GPU's installeer je in plaats daarvan rembg[gpu]. Het project meldt dat dit onnxruntime-gpu nodig heeft plus een passende CUDA en cuDNN, en stelt voor terug te vallen op de CPU-build als je die niet kunt installeren. AMD-GPU's op Linux gebruiken rembg[rocm] met onnxruntime-rocm.

rembg kiest automatisch de execution provider CUDA (of ROCm) als ONNX Runtime die als beschikbaar meldt, en waarschuwt als het terugvalt op de CPU. Zo controleer je wat ONNX Runtime ziet:

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

Kies een model

Modellen worden bij het eerste gebruik gedownload naar ~/.rembg/models/ (stel REMBG_HOME in om dat te wijzigen). Een paar handige uit de lijst van het project:

ModelOpmerkingen
bria-rmbgDe standaard. RMBG-2.0 van BRIA, ongeveer 1,02 GB, draait op 1024 x 1024. De licentie vereist een betaalde overeenkomst voor commercieel gebruik.
birefnet-generalBiRefNet-model voor algemeen gebruik; goede zachte randen
birefnet-general-liteLichtere BiRefNet voor algemeen gebruik
birefnet-portraitAfgestemd op mensen
isnet-general-useIS-Net-model voor algemeen gebruik
u2netHet klassieke algemene model; sneller, hoekigere randen
u2netpLichte u2net; snelle tests op trage machines
siluetaHetzelfde als u2net, verkleind tot 43 MB

Twee punten zijn belangrijker dan ze lijken. Ten eerste: het standaardmodel is niet gratis voor commercieel gebruik. Modelgewichten hebben hun eigen licenties, los van de MIT-licentie van rembg, dus controleer de bron van elk model dat je meelevert. Ten tweede geven de nieuwere modellen (bria-rmbg, birefnet-*, isnet-general-use) zachte, goed gevormde randen, terwijl de oudere u2net-familie meestal hardere, hoekigere randen geeft.

Opties voor randkwaliteit

rembg biedt drie manieren om zachte randpixels zoals haar en vacht te behandelen:

  • Standaard: prima voor onderwerpen met harde randen, zoals producten, auto's en logo's.
  • decontaminate=True (-dc in de CLI): haalt de gekleurde rand weg die de oude achtergrond in randpixels achterlaat. Het verandert alleen de kleur, nooit de vorm van het masker, kost heel weinig en kan veilig aan blijven voor een hele batch.
  • alpha_matting=True (-a): berekent de rand zelf opnieuw als de vorm van het masker niet klopt, bijvoorbeeld als lokken zijn afgeknipt. Het is veel trager en kan niet convergeren, waarna rembg terugvalt op een gedecontamineerde uitsnede.

Voor batches zonder toezicht raadt het project -dc aan boven -a, omdat het sneller is en niet kan mislukken. De gids over het alfakanaal legt uit waarom gekleurde randen ontstaan.

De snelle manier: de CLI

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

rembg p verwerkt een hele map; voeg -w toe om de map in de gaten te houden en nieuwe bestanden te verwerken zodra ze verschijnen.

Het Python-script

Deze versie laadt het model één keer, slaat bestanden over die al klaar zijn zodat je kunt stoppen en opnieuw starten, en gaat door als één bestand mislukt:

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:])

Start het met python batch_rembg.py photos cutouts birefnet-general. De sessie één keer aanmaken is belangrijk: een model laden duurt veel langer dan het draaien, en de README van rembg raadt hergebruik van de sessie aan voor batchprestaties. De uitvoer is altijd PNG, omdat het resultaat een alfakanaal heeft.

Waar rembg tekortschiet

  • De eerste run is traag terwijl het model wordt gedownload, en grote modellen hebben veel RAM of VRAM nodig.
  • De doorvoer op de CPU is bescheiden met de modellen van hoge kwaliteit. Test met 20 representatieve afbeeldingen voordat je aan duizend begint.
  • Licenties en drivers zijn jouw taak. Zet je versies van rembg en het model vast, zodat een latere upgrade de resultaten niet ongemerkt verandert.

Optie B: een HTTP-API

Met een API gebeurt het zware werk op de GPU's van iemand anders, en draait je script vooral om het netjes afhandelen van het netwerk. Het voorbeeld gebruikt de API van removebackground.dev, omdat het contract als OpenAPI 3.1 is gepubliceerd op /openapi.json; dezelfde patronen gelden voor elke vergelijkbare dienst.

Het contract

  • POST https://removebackground.dev/v1/remove met je sleutel in de header x-api-key.
  • Stuur de afbeelding als multipartveld image_file (JPG, PNG of WebP, tot 25 MB), of als JSON met image_url.
  • Optionele velden zijn onder meer format (png, jpg of webp), bg_color, size (preview, medium, hd, full) en crop.
  • 200 betekent dat de body de afgewerkte afbeelding is. 202 betekent dat hij na ongeveer 55 seconden nog wordt verwerkt: de body is JSON met een job-id, en je pollt GET /v1/jobs/{id}/result, dat met 409 not_ready en Retry-After antwoordt tot de afbeelding klaar is. Bewaar een 202-body nooit als afbeelding.
  • Maximaal 60 verwerkingsverzoeken per minuut per account; daarboven krijg je 429 rate_limited met Retry-After.
  • Een header Idempotency-Key maakt retries veilig: dezelfde sleutel met hetzelfde verzoek geeft de oorspronkelijke job terug in plaats van opnieuw kosten te rekenen. Dezelfde sleutel met een ander verzoek geeft 409 idempotency_key_reused.
  • Resultaten worden na 48 uur verwijderd, dus bewaar ze zodra je ze krijgt.

Retryregels waardoor je niet twee keer betaalt

ResponseWat je doet
200Bewaar de afbeelding
202Poll het resultaat van de job; upload niet opnieuw
400, 401, 402, 413, 422Corrigeer het verzoek, de sleutel, de credits of de afbeelding; niet in een lus herhalen
429Wacht Retry-After seconden, stuur opnieuw met dezelfde sleutel
500, 503, time-out, verbroken verbindingWacht steeds langer en stuur opnieuw met dezelfde sleutel
502Die afbeelding is mislukt en niet in rekening gebracht; log het

De truc is om de idempotentiesleutel deterministisch te maken: een hash van de bytes van de afbeelding plus de opties. Crasht het script en start je het opnieuw, dan draagt elke herhaling dezelfde sleutel, zodat niets twee keer wordt verwerkt of in rekening gebracht.

Het Python-script

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])

Start het met REMOVEBG_API_KEY=... python batch_api.py photos cutouts. Vier workers is een bescheiden begin; duwen snelle antwoorden je over de limiet per minuut, dan wacht de afhandeling van 429 en stuurt ze opnieuw met dezelfde sleutel. Wil je URL's sturen in plaats van bestanden, post dan 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),
)

Voor lange lijsten met URL's heeft dezelfde API POST /v1/batch, dat tot 100 afbeeldings-URL's in de wachtrij zet als één job die je pollt.

Elke batchjob robuust maken

  • Sla afgewerkte uitvoer over, zodat een nieuwe run hervat in plaats van opnieuw te beginnen. Beide scripts doen dit.
  • Let op naamconflicten. shoe.jpg en shoe.png in dezelfde map worden allebei shoe.png; hernoem de invoer of neem de oorspronkelijke extensie op in de naam van de uitvoer.
  • Log mislukkingen in een bestand en draai alleen die opnieuw.
  • Controleer steekproefsgewijs op 100% zoom op donkere en lichte achtergronden voordat je alles verwerkt.
  • Laat originelen ongemoeid. Schrijf naar een aparte uitvoermap.
  • Kies het uitvoerformaat voor de bestemming. PNG of WebP voor transparantie, JPG op wit voor marktplaatsen. Zie PNG vs JPEG vs WebP.

Vragen

Is rembg gratis voor commercieel gebruik?

rembg zelf valt onder de MIT-licentie, maar elk model heeft een eigen licentie. Het standaardmodel, RMBG-2.0 van BRIA (bria-rmbg), vereist een betaalde overeenkomst voor commercieel gebruik, dus controleer de licentie van het model dat je gebruikt of kies een ander.

Gebruikt rembg automatisch mijn GPU?

Ja, als je rembg[gpu] installeert en ONNX Runtime de CUDA-provider kan laden (waarvoor passende versies van CUDA en cuDNN nodig zijn). rembg gebruikt CUDA of ROCm als die beschikbaar zijn en waarschuwt als het terugvalt op de CPU. Controleer onnxruntime.get_available_providers().

Welk rembg-model geeft de beste kwaliteit?

De nieuwere modellen, bria-rmbg (de standaard), de birefnet-familie en isnet-general-use, geven zachtere en beter gevormde randen dan de oudere u2net-familie. birefnet-portrait is een goed begin voor mensen. Test een steekproef van je eigen afbeeldingen, want de resultaten verschillen per onderwerp.

Hoe voorkom ik dat ik twee keer betaal als ik API-calls opnieuw probeer?

Stuur een header Idempotency-Key mee en gebruik die opnieuw bij elke herhaling voor dezelfde afbeelding. Leid je hem af van een hash van de bytes van de afbeelding en de opties, dan hergebruikt zelfs een nieuwe run van het hele script dezelfde sleutels.

Wat moet mijn script doen met een 202-response?

Behandel het als nog in verwerking, niet als een afbeelding. Lees het job-id uit de JSON-body en poll GET /v1/jobs/{id}/result, en wacht Retry-After af zolang het antwoord 409 not_ready is. Upload de afbeelding niet opnieuw.

Hoe snel kan ik verzoeken sturen naar de API van removebackground.dev?

Tot 60 verwerkingsverzoeken per minuut per account. Daarboven antwoordt de API met 429 rate_limited en een header Retry-After. Voor veel URL's accepteert POST /v1/batch er tot 100 per verzoek.

Probeer het met je eigen afbeelding

Nieuwe accounts krijgen 50 gratis credits. Eén credit per afbeelding, geen abonnement, en credits verlopen nooit.

Achtergrond verwijderen

Bronnen