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ût | Logiciel gratuit ; vous payez en matériel et en temps | À l'image |
| Mise en place | Python 3.11 à 3.13, téléchargement des modèles, CUDA en option | Une clé d'API et requests |
| Vitesse | Dépend de votre CPU ou GPU et du modèle | Dépend du service, requêtes en parallèle |
| Qualité | Dépend du modèle choisi | Dépend du service |
| Confidentialité | Les images ne quittent jamais votre machine | Les images sont envoyées à un tiers |
| Maintenance | Vous gérez modèles, pilotes et licences | Le fournisseur s'en charge |
| Idéal pour | Travail hors ligne, images sensibles, expérimentations | Pipelines 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 commandPour 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 CUDAExecutionProviderChoisir 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èle | Remarques |
|---|---|
bria-rmbg | Le 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-general | Modèle BiRefNet généraliste ; bons contours doux |
birefnet-general-lite | BiRefNet plus léger pour un usage général |
birefnet-portrait | Optimisé pour les personnes |
isnet-general-use | Modèle IS-Net généraliste |
u2net | Le modèle généraliste classique ; plus rapide, contours plus grossiers |
u2netp | u2net allégé ; tests rapides sur des machines lentes |
silueta | Identique à 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(-dcen 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/removeavec votre clé dans l'en-têtex-api-key.- Envoyez l'image dans un champ multipart
image_file(JPG, PNG ou WebP, jusqu'à 25 MB), ou en JSON avecimage_url. - Les champs facultatifs incluent
format(png,jpgouwebp),bg_color,size(preview,medium,hd,full) etcrop. - 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
idde tâche, et vous interrogezGET /v1/jobs/{id}/result, qui répond 409not_readyavecRetry-Afterjusqu'à 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_limitedavecRetry-After. - Un en-tête
Idempotency-Keyrend 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 409idempotency_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éponse | Que faire |
|---|---|
| 200 | Enregistrer l'image |
| 202 | Interroger le résultat de la tâche ; ne pas renvoyer l'image |
| 400, 401, 402, 413, 422 | Corriger la requête, la clé, les crédits ou l'image ; ne pas boucler |
| 429 | Attendre Retry-After secondes, renvoyer avec la même clé |
| 500, 503, délai dépassé, connexion coupée | Patienter de plus en plus longtemps et renvoyer avec la même clé |
| 502 | Cette 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.jpgetshoe.pngdans le même dossier donnent tous deuxshoe.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-planSources
- rembg on GitHub (README: installation, models, edge modes)
- rembg on PyPI (version and Python requirement)
- BRIA RMBG-2.0 model card and license
- ONNX Runtime: Get started and installation matrix
- removebackground.dev API reference and guides
- removebackground.dev OpenAPI 3.1 schema
- Requests: Quickstart (multipart file uploads, timeouts)