Rimuovere lo sfondo da immagini in blocco con Python: rembg o un'API

Due script completi per rimuovere lo sfondo da un'intera cartella di immagini: uno con la libreria open source rembg sul tuo hardware, l'altro con un'API HTTP che gestisce correttamente nuovi tentativi, limiti di frequenza e doppi addebiti.

Aggiornato il 23 ottobre 2026 · 9 min di lettura

Hai una cartella con qualche centinaio di foto prodotto, ritratti o immagini per inserzioni, e a ognuna va tolto lo sfondo. Farlo a mano in un editor è una giornata di clic. In Python è un ciclo, e la vera decisione è cosa gira dentro il ciclo: un modello open source sulla tua macchina o un'API HTTP.

Questa guida li costruisce entrambi, con script completi da lanciare oggi su una cartella, e copre gli aspetti che rendono affidabile un'elaborazione in blocco: scelta del modello, CPU o GPU, nuovi tentativi, limiti di frequenza, idempotenza e ripresa dopo un crash.

Opzione A o opzione B?

Modello locale (rembg)API HTTP
CostoSoftware gratuito; paghi in hardware e tempoA immagine
ConfigurazionePython da 3.11 a 3.13, download dei modelli, CUDA facoltativoUna chiave API e requests
VelocitàDipende da CPU o GPU e dal modelloDipende dal servizio, richieste in parallelo
QualitàDipende dal modello sceltoDipende dal servizio
PrivacyLe immagini non lasciano mai la tua macchinaLe immagini vengono caricate presso terzi
ManutenzioneGestisci tu modelli, driver e licenzeSe ne occupa il fornitore
Ideale perLavoro offline, immagini sensibili, esperimentiPipeline di produzione, server senza GPU

Molti team usano entrambe: un modello locale per esperimenti e strumenti interni, un'API per la produzione, dove contano di più una qualità prevedibile e non dover gestire server con GPU.

Opzione A: rembg, la strada open source

rembg è una libreria Python e una CLI con licenza MIT che racchiude diversi modelli di rimozione dello sfondo e li esegue con ONNX Runtime. Al momento in cui scriviamo la versione attuale è la 2.0.86, e richiede Python 3.11 o successivo e precedente al 3.14.

Installazione

Scegli il backend adatto al tuo hardware. La versione per CPU funziona ovunque:

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

Per le GPU NVIDIA installa invece rembg[gpu]. Il progetto segnala che servono onnxruntime-gpu più versioni compatibili di CUDA e cuDNN, e suggerisce di tornare alla versione CPU se non riesci a installarle. Le GPU AMD su Linux usano rembg[rocm] con onnxruntime-rocm.

rembg sceglie automaticamente il provider di esecuzione CUDA (o ROCm) quando ONNX Runtime lo segnala come disponibile, e avvisa se ripiega sulla CPU. Per vedere cosa rileva ONNX Runtime:

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

Scegliere un modello

I modelli si scaricano al primo utilizzo in ~/.rembg/models/ (imposta REMBG_HOME per cambiare percorso). Alcuni utili dall'elenco del progetto:

ModelloNote
bria-rmbgIl predefinito. RMBG-2.0 di BRIA, circa 1,02 GB, lavora a 1024 x 1024. La sua licenza richiede un accordo a pagamento per l'uso commerciale.
birefnet-generalModello BiRefNet generico; buoni bordi morbidi
birefnet-general-liteBiRefNet più leggero per uso generico
birefnet-portraitOttimizzato per le persone
isnet-general-useModello IS-Net generico
u2netIl classico modello generico; più veloce, bordi più squadrati
u2netpu2net alleggerito; test rapidi su macchine lente
siluetaCome u2net, ridotto a 43 MB

Due punti contano più di quanto sembri. Primo, il modello predefinito non è gratuito per l'uso commerciale: i pesi dei modelli hanno licenze proprie, separate dalla licenza MIT di rembg, quindi controlla la fonte del modello che distribuisci. Secondo, i modelli più recenti (bria-rmbg, birefnet-*, isnet-general-use) producono bordi morbidi e ben definiti, mentre la vecchia famiglia u2net tende a dare bordi più rigidi e squadrati.

Opzioni per la qualità dei bordi

rembg offre tre modi per trattare i pixel morbidi del bordo come capelli e pelo:

  • Predefinito: va bene per soggetti dai bordi netti come prodotti, auto e loghi.
  • decontaminate=True (-dc da CLI): elimina l'alone colorato che il vecchio sfondo lascia nei pixel del bordo. Cambia solo il colore, mai la forma della maschera, costa pochissimo e si può lasciare attivo per un intero lotto.
  • alpha_matting=True (-a): ristima il bordo stesso quando la forma della maschera è sbagliata, ad esempio con ciocche tagliate. È molto più lento e può non convergere, nel qual caso rembg ripiega su un ritaglio decontaminato.

Per i lotti senza supervisione il progetto consiglia -dc invece di -a, perché è più veloce e non può fallire. La guida al canale alfa spiega perché si formano gli aloni.

Il modo rapido: la CLI

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

rembg p elabora un'intera cartella; aggiungi -w per monitorarla ed elaborare i nuovi file appena compaiono.

Lo script Python

Questa versione carica il modello una sola volta, salta i file già elaborati così puoi fermarlo e riavviarlo, e va avanti quando un file fallisce:

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

Avvialo con python batch_rembg.py photos cutouts birefnet-general. Creare la sessione una sola volta è importante: caricare un modello richiede molto più tempo che eseguirlo, e il README di rembg consiglia di riutilizzare la sessione per le prestazioni in blocco. L'output è sempre PNG perché il risultato ha un canale alfa.

Dove rembg non basta

  • Il primo avvio è lento mentre il modello si scarica, e i modelli grandi richiedono molta RAM o VRAM.
  • Su CPU la velocità è modesta con i modelli di alta qualità. Prova con 20 immagini rappresentative prima di impegnarti su mille.
  • Licenze e driver sono affar tuo. Fissa le versioni di rembg e dei modelli, così un aggiornamento non cambia i risultati senza che te ne accorga.

Opzione B: un'API HTTP

Con un'API il lavoro pesante avviene sulle GPU di qualcun altro, e il tuo script si occupa soprattutto di gestire bene la rete. L'esempio usa l'API di removebackground.dev perché il suo contratto è pubblicato in OpenAPI 3.1 su /openapi.json; gli stessi schemi valgono per qualsiasi servizio simile.

Il contratto

  • POST https://removebackground.dev/v1/remove con la chiave nell'intestazione x-api-key.
  • Invia l'immagine come campo multipart image_file (JPG, PNG o WebP, fino a 25 MB), oppure come JSON con image_url.
  • I campi facoltativi includono format (png, jpg o webp), bg_color, size (preview, medium, hd, full) e crop.
  • 200 significa che il corpo è l'immagine finita. 202 significa che l'elaborazione è ancora in corso dopo circa 55 secondi: il corpo è un JSON con un id di job, e interroghi GET /v1/jobs/{id}/result, che risponde 409 not_ready con Retry-After finché l'immagine non è pronta. Non salvare mai un corpo 202 come immagine.
  • Fino a 60 richieste di elaborazione al minuto per account; oltre ricevi 429 rate_limited con Retry-After.
  • Un'intestazione Idempotency-Key rende sicuri i nuovi tentativi: la stessa chiave con la stessa richiesta restituisce il job originale invece di addebitare di nuovo. La stessa chiave con una richiesta diversa risponde 409 idempotency_key_reused.
  • I risultati vengono eliminati dopo 48 ore, quindi salvali appena li ricevi.

Regole per i nuovi tentativi che evitano di pagare due volte

RispostaCosa fare
200Salva l'immagine
202Interroga il risultato del job; non ricaricare l'immagine
400, 401, 402, 413, 422Correggi richiesta, chiave, crediti o immagine; non ripetere in ciclo
429Attendi Retry-After secondi, reinvia con la stessa chiave
500, 503, timeout, connessione interrottaAttendi sempre più a lungo e reinvia con la stessa chiave
502Quell'immagine non è riuscita e non è stata addebitata; registralo

Il trucco è rendere la chiave di idempotenza deterministica: un hash dei byte dell'immagine più le opzioni. Se lo script va in crash e lo rilanci, ogni reinvio porta la stessa chiave, quindi niente viene elaborato o addebitato due volte.

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

Avvialo con REMOVEBG_API_KEY=... python batch_api.py photos cutouts. Quattro worker sono un inizio prudente; se risposte rapide ti fanno superare il limite al minuto, la gestione del 429 attende e reinvia con la stessa chiave. Per inviare URL invece di file, invia un 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),
)

Per lunghi elenchi di URL, la stessa API ha POST /v1/batch, che mette in coda fino a 100 URL di immagini come un unico job da interrogare.

Rendere robusta qualsiasi elaborazione in blocco

  • Salta gli output già prodotti così un nuovo avvio riprende invece di ricominciare. Entrambi gli script lo fanno.
  • Attenzione ai nomi in conflitto. shoe.jpg e shoe.png nella stessa cartella diventano entrambi shoe.png; rinomina i file di input o includi l'estensione originale nel nome di output.
  • Registra gli errori in un file e rilancia solo quelli.
  • Controlla un campione allo zoom al 100% su sfondi scuri e chiari prima di elaborare tutto.
  • Non toccare gli originali. Scrivi in una cartella di output separata.
  • Scegli il formato di output in base alla destinazione. PNG o WebP per la trasparenza, JPG su bianco per i marketplace. Vedi PNG, JPEG o WebP.

Domande

rembg è gratuito per l'uso commerciale?

rembg in sé ha licenza MIT, ma ogni modello ha una licenza propria. Il modello predefinito, RMBG-2.0 di BRIA (bria-rmbg), richiede un accordo a pagamento per l'uso commerciale, quindi controlla la licenza del modello che usi o scegline un altro.

rembg usa automaticamente la mia GPU?

Sì, se installi rembg[gpu] e ONNX Runtime riesce a caricare il provider CUDA (che richiede versioni compatibili di CUDA e cuDNN). rembg usa CUDA o ROCm quando disponibili e avvisa quando ripiega sulla CPU. Controlla con onnxruntime.get_available_providers().

Quale modello di rembg dà la qualità migliore?

I modelli più recenti, bria-rmbg (il predefinito), la famiglia birefnet e isnet-general-use, producono bordi più morbidi e meglio definiti della vecchia famiglia u2net. birefnet-portrait è un buon punto di partenza per le persone. Prova su un campione delle tue immagini, perché i risultati variano in base al soggetto.

Come evito il doppio addebito quando ritento le chiamate all'API?

Invia un'intestazione Idempotency-Key e riutilizzala a ogni nuovo tentativo per la stessa immagine. Ricavandola da un hash dei byte dell'immagine e delle opzioni, anche un nuovo avvio completo dello script riutilizza le stesse chiavi.

Cosa deve fare il mio script con una risposta 202?

Considerarla come elaborazione ancora in corso, non come un'immagine. Leggi l'id del job dal corpo JSON e interroga GET /v1/jobs/{id}/result, attendendo Retry-After ogni volta che risponde 409 not_ready. Non ricaricare l'immagine.

Con che frequenza posso inviare richieste all'API di removebackground.dev?

Fino a 60 richieste di elaborazione al minuto per account. Oltre, l'API risponde 429 rate_limited con un'intestazione Retry-After. Per molti URL, POST /v1/batch ne accetta fino a 100 per richiesta.

Provalo con la tua immagine

I nuovi account ricevono 50 crediti gratuiti. Un credito per immagine, nessun abbonamento, e i crediti non scadono mai.

Rimuovi uno sfondo

Fonti