Supprimer l'arrière-plan d'images par lot avec Python : rembg ou une API

Deux scripts complets pour supprimer l'arrière-plan de tout un dossier d'images : l'un avec la bibliothèque open source rembg sur votre propre matériel, l'autre avec une API HTTP qui gère correctement les nouvelles tentatives, les limites de débit et les doubles facturations.

Mis à jour le 23 octobre 2026 · 9 min de lecture

Vous avez un dossier de quelques centaines de photos produit, de portraits ou d'images d'annonces, et chacune doit perdre son arrière-plan. Le faire à la main dans un éditeur, c'est une journée de clics. En Python, c'est une boucle, et la vraie décision porte sur ce qui tourne dans la boucle : un modèle open source sur votre propre machine, ou une API HTTP.

Ce guide construit les deux, avec des scripts complets à lancer dès aujourd'hui sur un dossier, et couvre ce qui rend un traitement par lot fiable : choix du modèle, CPU ou GPU, nouvelles tentatives, limites de débit, idempotence et reprise après un plantage.

Option A ou option B ?

Modèle local (rembg)API HTTP
CoûtLogiciel gratuit ; vous payez en matériel et en tempsÀ l'image
Mise en placePython 3.11 à 3.13, téléchargement des modèles, CUDA en optionUne clé d'API et requests
VitesseDépend de votre CPU ou GPU et du modèleDépend du service, requêtes en parallèle
QualitéDépend du modèle choisiDépend du service
ConfidentialitéLes images ne quittent jamais votre machineLes images sont envoyées à un tiers
MaintenanceVous gérez modèles, pilotes et licencesLe fournisseur s'en charge
Idéal pourTravail hors ligne, images sensibles, expérimentationsPipelines de production, serveurs sans GPU

Beaucoup d'équipes utilisent les deux : un modèle local pour les essais et les outils internes, une API pour la production, où une qualité prévisible et l'absence de serveurs GPU comptent davantage.

Option A : rembg, la voie open source

rembg est une bibliothèque Python et un outil en ligne de commande sous licence MIT, qui encapsule plusieurs modèles de suppression d'arrière-plan et les exécute avec ONNX Runtime. Au moment de la rédaction, la version actuelle est la 2.0.86, et elle exige Python 3.11 ou ultérieur et antérieur à 3.14.

Installation

Choisissez le backend adapté à votre matériel. La version CPU fonctionne partout :

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

Pour les GPU NVIDIA, installez plutôt rembg[gpu]. Le projet précise qu'il faut alors onnxruntime-gpu ainsi qu'une version de CUDA et de cuDNN compatible, et suggère de revenir à la version CPU si vous ne pouvez pas les installer. Les GPU AMD sous Linux utilisent rembg[rocm] avec onnxruntime-rocm.

rembg choisit automatiquement le fournisseur d'exécution CUDA (ou ROCm) quand ONNX Runtime le signale comme disponible, et avertit s'il se rabat sur le CPU. Pour voir ce qu'ONNX Runtime détecte :

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

Choisir un modèle

Les modèles se téléchargent à la première utilisation dans ~/.rembg/models/ (définissez REMBG_HOME pour changer l'emplacement). Quelques modèles utiles de la liste du projet :

ModèleRemarques
bria-rmbgLe modèle par défaut. RMBG-2.0 de BRIA, environ 1,02 GB, fonctionne en 1024 x 1024. Sa licence exige un accord payant pour un usage commercial.
birefnet-generalModèle BiRefNet généraliste ; bons contours doux
birefnet-general-liteBiRefNet plus léger pour un usage général
birefnet-portraitOptimisé pour les personnes
isnet-general-useModèle IS-Net généraliste
u2netLe modèle généraliste classique ; plus rapide, contours plus grossiers
u2netpu2net allégé ; tests rapides sur des machines lentes
siluetaIdentique à u2net, réduit à 43 MB

Deux points comptent plus qu'il n'y paraît. D'abord, le modèle par défaut n'est pas gratuit pour un usage commercial : les poids des modèles ont leurs propres licences, distinctes de la licence MIT de rembg, donc vérifiez la source du modèle que vous livrez. Ensuite, les modèles récents (bria-rmbg, birefnet-*, isnet-general-use) produisent des contours doux et bien formés, alors que l'ancienne famille u2net donne plutôt des bords fermes et grossiers.

Options de qualité des contours

rembg propose trois façons de traiter les pixels de contour doux comme les cheveux et la fourrure :

  • Par défaut : convient aux sujets aux bords nets comme les produits, les voitures et les logos.
  • decontaminate=True (-dc en ligne de commande) : supprime le halo coloré que l'ancien fond laisse dans les pixels du contour. Il ne modifie que la couleur, jamais la forme du masque, coûte très peu et peut rester activé pour tout un lot.
  • alpha_matting=True (-a) : réestime le contour lui-même quand la forme du masque est fausse, par exemple des mèches coupées. C'est beaucoup plus lent et peut ne pas converger, auquel cas rembg se rabat sur un détourage décontaminé.

Pour les lots sans surveillance, le projet recommande -dc plutôt que -a, car il est plus rapide et ne peut pas échouer. Le guide sur la couche alpha explique l'origine des halos.

La méthode rapide : la ligne de commande

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

rembg p traite un dossier entier ; ajoutez -w pour surveiller le dossier et traiter les nouveaux fichiers dès leur arrivée.

Le script Python

Cette version charge le modèle une seule fois, saute les fichiers déjà traités pour que vous puissiez l'arrêter et le relancer, et continue quand un fichier échoue :

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

Lancez-le avec python batch_rembg.py photos cutouts birefnet-general. Créer la session une seule fois est important : charger un modèle prend bien plus de temps que l'exécuter, et le README de rembg recommande de réutiliser la session pour les performances en lot. Les sorties sont toujours en PNG, car le résultat a une couche alpha.

Les limites de rembg

  • Le premier lancement est lent pendant le téléchargement du modèle, et les gros modèles demandent beaucoup de RAM ou de VRAM.
  • Le débit sur CPU est modeste avec les modèles de haute qualité. Testez sur 20 images représentatives avant de vous engager sur un millier.
  • Licences et pilotes sont à votre charge. Figez vos versions de rembg et des modèles pour qu'une mise à jour ne change pas les résultats à votre insu.

Option B : une API HTTP

Avec une API, le gros du travail se fait sur les GPU de quelqu'un d'autre, et votre script consiste surtout à bien gérer le réseau. L'exemple utilise l'API de removebackground.dev, car son contrat est publié en OpenAPI 3.1 à /openapi.json ; les mêmes principes s'appliquent à tout service similaire.

Le contrat

  • POST https://removebackground.dev/v1/remove avec votre clé dans l'en-tête x-api-key.
  • Envoyez l'image dans un champ multipart image_file (JPG, PNG ou WebP, jusqu'à 25 MB), ou en JSON avec image_url.
  • Les champs facultatifs incluent format (png, jpg ou webp), bg_color, size (preview, medium, hd, full) et crop.
  • 200 signifie que le corps est l'image finale. 202 signifie que le traitement est toujours en cours après environ 55 secondes : le corps est un JSON avec un id de tâche, et vous interrogez GET /v1/jobs/{id}/result, qui répond 409 not_ready avec Retry-After jusqu'à ce que l'image soit prête. N'enregistrez jamais un corps 202 comme une image.
  • Jusqu'à 60 requêtes de traitement par minute et par compte ; au-delà, vous recevez 429 rate_limited avec Retry-After.
  • Un en-tête Idempotency-Key rend les nouvelles tentatives sûres : la même clé avec la même requête renvoie la tâche d'origine au lieu de facturer à nouveau. La même clé avec une requête différente répond 409 idempotency_key_reused.
  • Les résultats sont supprimés après 48 heures, donc enregistrez-les dès réception.

Des règles de nouvelle tentative pour ne jamais payer deux fois

RéponseQue faire
200Enregistrer l'image
202Interroger le résultat de la tâche ; ne pas renvoyer l'image
400, 401, 402, 413, 422Corriger la requête, la clé, les crédits ou l'image ; ne pas boucler
429Attendre Retry-After secondes, renvoyer avec la même clé
500, 503, délai dépassé, connexion coupéePatienter de plus en plus longtemps et renvoyer avec la même clé
502Cette image a échoué et n'a pas été facturée ; la consigner

L'astuce consiste à rendre la clé d'idempotence déterministe : un hachage des octets de l'image et des options. Si le script plante et que vous le relancez, chaque nouvel envoi porte la même clé, donc rien n'est traité ni facturé deux fois.

Le script Python

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

Lancez-le avec REMOVEBG_API_KEY=... python batch_api.py photos cutouts. Quatre workers, c'est un départ modeste ; si des réponses rapides vous font dépasser la limite par minute, la gestion du 429 attend et renvoie avec la même clé. Pour envoyer des URL plutôt que des fichiers, postez du 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),
)

Pour de longues listes d'URL, la même API propose POST /v1/batch, qui met en file jusqu'à 100 URL d'images dans une seule tâche que vous interrogez.

Rendre n'importe quel traitement par lot robuste

  • Sautez les sorties déjà produites pour qu'une relance reprenne au lieu de tout recommencer. Les deux scripts le font.
  • Attention aux collisions de noms. shoe.jpg et shoe.png dans le même dossier donnent tous deux shoe.png ; renommez les fichiers d'entrée ou incluez l'extension d'origine dans le nom de sortie.
  • Consignez les échecs dans un fichier et relancez seulement ceux-là.
  • Contrôlez un échantillon à 100 % de zoom sur des fonds sombres et clairs avant de tout traiter.
  • Ne touchez pas aux originaux. Écrivez dans un dossier de sortie séparé.
  • Choisissez le format de sortie selon la destination. PNG ou WebP pour la transparence, JPG sur blanc pour les marketplaces. Voir PNG, JPEG ou WebP.

Questions

rembg est-il gratuit pour un usage commercial ?

rembg lui-même est sous licence MIT, mais chaque modèle a sa propre licence. Le modèle par défaut, RMBG-2.0 de BRIA (bria-rmbg), exige un accord payant pour un usage commercial : vérifiez donc la licence du modèle que vous utilisez ou choisissez-en un autre.

rembg utilise-t-il automatiquement mon GPU ?

Oui, si vous installez rembg[gpu] et qu'ONNX Runtime peut charger le fournisseur CUDA (ce qui exige des versions de CUDA et cuDNN compatibles). rembg utilise CUDA ou ROCm quand ils sont disponibles et avertit quand il se rabat sur le CPU. Vérifiez avec onnxruntime.get_available_providers().

Quel modèle rembg donne la meilleure qualité ?

Les modèles récents, bria-rmbg (par défaut), la famille birefnet et isnet-general-use, produisent des contours plus doux et mieux formés que l'ancienne famille u2net. birefnet-portrait est un bon point de départ pour les personnes. Testez sur un échantillon de vos propres images, car les résultats varient selon le sujet.

Comment éviter d'être facturé deux fois lors des nouvelles tentatives d'appel à l'API ?

Envoyez un en-tête Idempotency-Key et réutilisez-le à chaque nouvelle tentative pour la même image. En le dérivant d'un hachage des octets de l'image et des options, même une relance complète du script réutilise les mêmes clés.

Que doit faire mon script avec une réponse 202 ?

La traiter comme un traitement en cours, pas comme une image. Lisez l'id de la tâche dans le corps JSON et interrogez GET /v1/jobs/{id}/result, en attendant Retry-After chaque fois qu'il répond 409 not_ready. Ne renvoyez pas l'image.

À quel rythme puis-je envoyer des requêtes à l'API removebackground.dev ?

Jusqu'à 60 requêtes de traitement par minute et par compte. Au-delà, l'API répond 429 rate_limited avec un en-tête Retry-After. Pour de nombreuses URL, POST /v1/batch en accepte jusqu'à 100 par requête.

Essayez avec votre propre image

Les nouveaux comptes reçoivent 50 crédits gratuits. Un crédit par image, sans abonnement, et les crédits n'expirent jamais.

Supprimer un arrière-plan

Sources