Tienes una carpeta con unos cuantos cientos de fotos de producto, retratos o imágenes de anuncios, y a todas hay que quitarles el fondo. Hacerlo a mano en un editor es un día entero de clics. En Python es un bucle, y la decisión de verdad es qué se ejecuta dentro del bucle: un modelo de código abierto en tu propia máquina o una API HTTP.
Esta guía construye las dos opciones, con scripts completos que puedes ejecutar hoy sobre una carpeta, y cubre lo que hace fiable un proceso por lotes: elección del modelo, CPU frente a GPU, reintentos, límites de frecuencia, idempotencia y reanudar tras un fallo.
¿Opción A u opción B?
| Modelo local (rembg) | API HTTP | |
|---|---|---|
| Coste | Software gratuito; pagas en hardware y en tiempo | Por imagen |
| Instalación | Python 3.11 a 3.13, descarga de modelos, CUDA opcional | Una clave de API y requests |
| Velocidad | Depende de tu CPU o GPU y del modelo | Depende del servicio, peticiones en paralelo |
| Calidad | Depende del modelo que elijas | Depende del servicio |
| Privacidad | Las imágenes nunca salen de tu máquina | Las imágenes se suben a un tercero |
| Mantenimiento | Tú gestionas modelos, drivers y licencias | Lo hace el proveedor |
| Ideal para | Trabajo sin conexión, imágenes sensibles, experimentos | Pipelines de producción, servidores sin GPU |
Muchos equipos usan las dos: un modelo local para experimentos y herramientas internas, y una API en producción, donde importan más una calidad predecible y no tener servidores con GPU.
Opción A: rembg, la vía de código abierto
rembg es una biblioteca y CLI de Python con licencia MIT que integra varios modelos para quitar el fondo y los ejecuta con ONNX Runtime. En el momento de escribir esto, la versión actual es la 2.0.86, y requiere Python 3.11 o posterior y anterior a 3.14.
Instalación
Elige el backend que corresponda a tu hardware. La versión para CPU funciona en todas partes:
python -m venv .venv && source .venv/bin/activate
pip install "rembg[cpu]" # library only
pip install "rembg[cpu,cli]" # library plus the rembg commandPara GPU NVIDIA, instala rembg[gpu] en su lugar. El proyecto indica que esto necesita onnxruntime-gpu más una versión compatible de CUDA y cuDNN, y sugiere volver a la versión para CPU si no puedes instalarlos. Las GPU AMD en Linux usan rembg[rocm] con onnxruntime-rocm.
rembg elige automáticamente el proveedor de ejecución CUDA (o ROCm) cuando ONNX Runtime lo indica como disponible, y avisa si recurre a la CPU. Para ver qué detecta ONNX Runtime:
import onnxruntime
print(onnxruntime.get_available_providers()) # look for CUDAExecutionProviderElige un modelo
Los modelos se descargan la primera vez que se usan en ~/.rembg/models/ (define REMBG_HOME para cambiarlo). Algunos útiles de la lista del proyecto:
| Modelo | Notas |
|---|---|
bria-rmbg | El predeterminado. RMBG-2.0 de BRIA, unos 1,02 GB, trabaja a 1024 x 1024. Su licencia exige un acuerdo de pago para uso comercial. |
birefnet-general | Modelo BiRefNet de uso general; buenos bordes suaves |
birefnet-general-lite | BiRefNet más ligero para uso general |
birefnet-portrait | Ajustado para personas |
isnet-general-use | Modelo IS-Net de uso general |
u2net | El modelo general clásico; más rápido, bordes más toscos |
u2netp | u2net ligero; pruebas rápidas en máquinas lentas |
silueta | Igual que u2net, reducido a 43 MB |
Hay dos puntos que importan más de lo que parece. Primero, el modelo predeterminado no es gratuito para uso comercial: los pesos de los modelos tienen sus propias licencias, distintas de la licencia MIT de rembg, así que revisa el origen del modelo que vayas a usar en producción. Segundo, los modelos más nuevos (bria-rmbg, birefnet-*, isnet-general-use) dan bordes suaves y bien formados, mientras que la familia u2net, más antigua, tiende a dar bordes más duros y toscos.
Opciones de calidad de bordes
rembg ofrece tres formas de tratar los píxeles de borde suaves, como el pelo humano o el de los animales:
- Predeterminado: suficiente para sujetos de bordes nítidos, como productos, coches y logotipos.
decontaminate=True(-dcen la CLI): elimina el halo de color que el fondo antiguo deja en los píxeles del borde. Solo cambia el color, nunca la forma de la máscara, apenas cuesta nada y se puede dejar activado sin riesgo en todo un lote.alpha_matting=True(-a): vuelve a calcular el propio borde cuando la forma de la máscara es incorrecta, por ejemplo con mechones cortados. Es mucho más lento y puede no converger, en cuyo caso rembg recurre a un recorte descontaminado.
Para lotes desatendidos, el proyecto recomienda -dc en lugar de -a porque es más rápido y no puede fallar. La guía sobre el canal alfa explica por qué aparecen los halos.
La vía rápida: la CLI
rembg p -m birefnet-general -dc photos/ cutouts/rembg p procesa una carpeta entera; añade -w para vigilar la carpeta y procesar los archivos nuevos según aparecen.
El script de Python
Esta versión carga el modelo una sola vez, se salta los archivos ya hechos para que puedas pararlo y reiniciarlo, y sigue adelante cuando falla un archivo:
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:])Ejecútalo con python batch_rembg.py photos cutouts birefnet-general. Crear la sesión una sola vez es importante: cargar un modelo tarda mucho más que ejecutarlo, y el README de rembg recomienda reutilizar la sesión para el rendimiento en lotes. Las salidas siempre son PNG porque el resultado tiene canal alfa.
Dónde se queda corto rembg
- La primera ejecución es lenta mientras se descarga el modelo, y los modelos grandes necesitan mucha RAM o VRAM.
- El rendimiento en CPU es modesto con los modelos de alta calidad. Prueba con 20 imágenes representativas antes de lanzarte con mil.
- Las licencias y los drivers son cosa tuya. Fija las versiones de rembg y del modelo para que una actualización posterior no cambie los resultados sin que te des cuenta.
Opción B: una API HTTP
Con una API, el trabajo pesado se hace en las GPU de otro, y tu script se dedica sobre todo a gestionar bien la red. El ejemplo usa la API de removebackground.dev porque su contrato está publicado como OpenAPI 3.1 en /openapi.json; los mismos patrones sirven para cualquier servicio parecido.
El contrato
POST https://removebackground.dev/v1/removecon tu clave en la cabecerax-api-key.- Envía la imagen como campo multipart
image_file(JPG, PNG o WebP, hasta 25 MB), o como JSON conimage_url. - Los campos opcionales incluyen
format(png,jpgowebp),bg_color,size(preview,medium,hd,full) ycrop. - 200 significa que el cuerpo es la imagen terminada. 202 significa que sigue procesándose tras unos 55 segundos: el cuerpo es un JSON con un
idde trabajo, y consultasGET /v1/jobs/{id}/result, que responde 409not_readyconRetry-Afterhasta que la imagen está lista. Nunca guardes el cuerpo de un 202 como imagen. - Hasta 60 peticiones de procesado por minuto por cuenta; por encima recibes 429
rate_limitedconRetry-After. - Una cabecera
Idempotency-Keyhace seguros los reintentos: la misma clave con la misma petición devuelve el trabajo original en vez de cobrar otra vez. La misma clave con una petición distinta responde 409idempotency_key_reused. - Los resultados se borran a las 48 horas, así que guárdalos cuando los recibas.
Reglas de reintento para no pagar dos veces
| Respuesta | Qué hacer |
|---|---|
| 200 | Guarda la imagen |
| 202 | Consulta el resultado del trabajo; no vuelvas a subirla |
| 400, 401, 402, 413, 422 | Corrige la petición, la clave, los créditos o la imagen; no repitas en bucle |
| 429 | Espera Retry-After segundos y reenvía con la misma clave |
| 500, 503, timeout, conexión cortada | Espera un poco más cada vez y reenvía con la misma clave |
| 502 | Esa imagen falló y no se cobró; regístralo |
El truco es que la clave de idempotencia sea determinista: un hash de los bytes de la imagen más las opciones. Si el script se cae y lo vuelves a ejecutar, cada reenvío lleva la misma clave, así que nada se procesa ni se cobra dos veces.
El script de 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])Ejecútalo con REMOVEBG_API_KEY=... python batch_api.py photos cutouts. Cuatro workers es un comienzo prudente; si las respuestas rápidas te hacen superar el límite por minuto, la gestión del 429 espera y reenvía con la misma clave. Para enviar URL en lugar de archivos, envía 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),
)Para listas largas de URL, la misma API tiene POST /v1/batch, que pone en cola hasta 100 URL de imágenes como un solo trabajo que luego consultas.
Cómo hacer robusto cualquier proceso por lotes
- Sáltate las salidas ya terminadas para que una nueva ejecución continúe en lugar de empezar de cero. Los dos scripts lo hacen.
- Cuidado con los nombres repetidos.
shoe.jpgyshoe.pngen la misma carpeta acaban los dos enshoe.png; renombra las entradas o incluye la extensión original en el nombre de salida. - Registra los fallos en un archivo y vuelve a ejecutar solo esos.
- Revisa una muestra al 100% de zoom sobre fondos oscuros y claros antes de procesarlo todo.
- No toques los originales. Escribe en una carpeta de salida aparte.
- Elige el formato de salida según el destino. PNG o WebP para transparencia, JPG sobre blanco para marketplaces. Consulta PNG vs JPEG vs WebP.
Preguntas
¿rembg es gratuito para uso comercial?
rembg en sí tiene licencia MIT, pero cada modelo tiene su propia licencia. El modelo predeterminado, RMBG-2.0 de BRIA (bria-rmbg), exige un acuerdo de pago para uso comercial, así que revisa la licencia del modelo que uses o elige otro.
¿rembg usa mi GPU automáticamente?
Sí, si instalas rembg[gpu] y ONNX Runtime puede cargar el proveedor CUDA (lo que requiere versiones compatibles de CUDA y cuDNN). rembg usa CUDA o ROCm cuando están disponibles y avisa cuando recurre a la CPU. Compruébalo con onnxruntime.get_available_providers().
¿Qué modelo de rembg da la mejor calidad?
Los modelos más nuevos, bria-rmbg (el predeterminado), la familia birefnet e isnet-general-use, dan bordes más suaves y mejor formados que la familia u2net, más antigua. birefnet-portrait es un buen punto de partida para personas. Prueba con una muestra de tus propias imágenes, porque los resultados cambian según el sujeto.
¿Cómo evito que me cobren dos veces al reintentar llamadas a la API?
Envía una cabecera Idempotency-Key y reutilízala en cada reintento de la misma imagen. Si la generas a partir de un hash de los bytes de la imagen y de las opciones, incluso volver a ejecutar todo el script reutiliza las mismas claves.
¿Qué debe hacer mi script con una respuesta 202?
Tratarla como un trabajo que sigue en proceso, no como una imagen. Lee el id del trabajo en el cuerpo JSON y consulta GET /v1/jobs/{id}/result, esperando lo que indique Retry-After cada vez que responda 409 not_ready. No vuelvas a subir la imagen.
¿A qué ritmo puedo enviar peticiones a la API de removebackground.dev?
Hasta 60 peticiones de procesado por minuto por cuenta. Por encima de eso, la API responde 429 rate_limited con una cabecera Retry-After. Para muchas URL, POST /v1/batch acepta hasta 100 por petición.
Pruébalo con tu propia imagen
Las cuentas nuevas reciben 50 créditos gratis. Un crédito por imagen, sin suscripción, y los créditos no caducan.
Quitar un fondoFuentes
- 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)