Remover fundo de imagens em lote com Python: rembg ou uma API

Dois scripts completos para remover o fundo de uma pasta inteira de imagens: um com a biblioteca de código aberto rembg no seu próprio hardware e outro com uma API HTTP que lida corretamente com novas tentativas, limites de taxa e cobranças duplicadas.

Atualizado em 23 de outubro de 2026 · 9 min de leitura

Você tem uma pasta com algumas centenas de fotos de produtos, fotos de rosto ou imagens de anúncios, e todas precisam ter o fundo removido. Fazer isso à mão num editor é um dia inteiro de cliques. Em Python é um loop, e a decisão real é o que roda dentro do loop: um modelo de código aberto na sua própria máquina ou uma API HTTP.

Este guia monta as duas opções, com scripts completos que você pode rodar hoje numa pasta, e cobre o que torna um processamento em lote confiável: escolha do modelo, CPU ou GPU, novas tentativas, limites de taxa, idempotência e retomada depois de uma falha.

Opção A ou opção B?

Modelo local (rembg)API HTTP
CustoSoftware gratuito; você paga em hardware e tempoPor imagem
ConfiguraçãoPython 3.11 a 3.13, download de modelos, CUDA opcionalUma chave de API e requests
VelocidadeDepende da sua CPU ou GPU e do modeloDepende do serviço, requisições em paralelo
QualidadeDepende do modelo escolhidoDepende do serviço
PrivacidadeAs imagens nunca saem da sua máquinaAs imagens são enviadas a terceiros
ManutençãoVocê cuida de modelos, drivers e licençasO fornecedor cuida
Bom paraTrabalho offline, imagens sensíveis, experimentosPipelines de produção, servidores sem GPU

Muitas equipes usam as duas: um modelo local para experimentos e ferramentas internas, e uma API em produção, onde qualidade previsível e não ter servidores com GPU pesam mais.

Opção A: rembg, o caminho de código aberto

O rembg é uma biblioteca e CLI Python com licença MIT que reúne vários modelos de remoção de fundo e os executa com o ONNX Runtime. No momento em que este texto foi escrito, a versão atual é a 2.0.86, que exige Python 3.11 ou posterior e anterior ao 3.14.

Instalação

Escolha o backend que combina com o seu hardware. A versão para CPU funciona em qualquer lugar:

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

Para GPUs NVIDIA, instale rembg[gpu] no lugar. O projeto avisa que isso exige onnxruntime-gpu mais versões compatíveis de CUDA e cuDNN, e sugere voltar para a versão de CPU se você não conseguir instalá-los. GPUs AMD no Linux usam rembg[rocm] com onnxruntime-rocm.

O rembg escolhe automaticamente o provedor de execução CUDA (ou ROCm) quando o ONNX Runtime informa que ele está disponível, e avisa se voltar para a CPU. Para ver o que o ONNX Runtime enxerga:

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

Escolha um modelo

Os modelos são baixados no primeiro uso para ~/.rembg/models/ (defina REMBG_HOME para mudar o local). Alguns úteis da lista do projeto:

ModeloObservações
bria-rmbgO padrão. RMBG-2.0 da BRIA, cerca de 1,02 GB, roda em 1024 x 1024. A licença exige um acordo pago para uso comercial.
birefnet-generalModelo BiRefNet de uso geral; boas bordas suaves
birefnet-general-liteBiRefNet mais leve para uso geral
birefnet-portraitAjustado para pessoas
isnet-general-useModelo IS-Net de uso geral
u2netO modelo geral clássico; mais rápido, bordas mais grosseiras
u2netpu2net leve; testes rápidos em máquinas lentas
siluetaIgual ao u2net, reduzido para 43 MB

Dois pontos importam mais do que parecem. Primeiro, o modelo padrão não é gratuito para uso comercial: os pesos dos modelos têm licenças próprias, separadas da licença MIT do rembg, então confira a origem do modelo que você for usar em produção. Segundo, os modelos mais novos (bria-rmbg, birefnet-*, isnet-general-use) geram bordas suaves e bem definidas, enquanto a família u2net, mais antiga, tende a dar bordas mais duras e grosseiras.

Opções de qualidade de borda

O rembg oferece três formas de tratar pixels de borda suaves, como cabelo e pelo:

  • Padrão: bom para objetos de borda nítida, como produtos, carros e logotipos.
  • decontaminate=True (-dc na CLI): remove o halo colorido que o fundo antigo deixa nos pixels da borda. Muda só a cor, nunca o formato da máscara, custa muito pouco e pode ficar ligado sem risco num lote inteiro.
  • alpha_matting=True (-a): recalcula a própria borda quando o formato da máscara está errado, por exemplo com mechas cortadas. É bem mais lento e pode não convergir; nesse caso, o rembg volta para um recorte descontaminado.

Para lotes sem supervisão, o projeto recomenda -dc em vez de -a, porque é mais rápido e não falha. O guia sobre canal alfa explica por que os halos aparecem.

O jeito rápido: a CLI

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

rembg p processa uma pasta inteira; adicione -w para monitorar a pasta e processar arquivos novos conforme aparecem.

O script Python

Esta versão carrega o modelo uma vez, pula arquivos já processados para você poder parar e reiniciar, e continua quando um arquivo falha:

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

Rode com python batch_rembg.py photos cutouts birefnet-general. Criar a sessão uma vez só faz diferença: carregar um modelo demora muito mais do que executá-lo, e o README do rembg recomenda reutilizar a sessão para ter desempenho em lote. As saídas são sempre PNG porque o resultado tem canal alfa.

Onde o rembg deixa a desejar

  • A primeira execução é lenta enquanto o modelo é baixado, e modelos grandes precisam de bastante RAM ou VRAM.
  • O desempenho em CPU é modesto com os modelos de alta qualidade. Teste com 20 imagens representativas antes de partir para mil.
  • Licenças e drivers são por sua conta. Fixe as versões do rembg e do modelo para que uma atualização futura não mude os resultados sem você perceber.

Opção B: uma API HTTP

Com uma API, o trabalho pesado acontece nas GPUs de outra pessoa, e o seu script cuida principalmente de lidar bem com a rede. O exemplo usa a API do removebackground.dev porque o contrato dela está publicado em OpenAPI 3.1 em /openapi.json; os mesmos padrões valem para qualquer serviço parecido.

O contrato

  • POST https://removebackground.dev/v1/remove com sua chave no cabeçalho x-api-key.
  • Envie a imagem como campo multipart image_file (JPG, PNG ou WebP, até 25 MB), ou como JSON com image_url.
  • Os campos opcionais incluem format (png, jpg ou webp), bg_color, size (preview, medium, hd, full) e crop.
  • 200 significa que o corpo é a imagem pronta. 202 significa que ainda está processando depois de cerca de 55 segundos: o corpo é um JSON com o id do job, e você consulta GET /v1/jobs/{id}/result, que responde 409 not_ready com Retry-After até a imagem ficar pronta. Nunca salve o corpo de um 202 como imagem.
  • Até 60 requisições de processamento por minuto por conta; acima disso você recebe 429 rate_limited com Retry-After.
  • Um cabeçalho Idempotency-Key torna as novas tentativas seguras: a mesma chave com a mesma requisição devolve o job original em vez de cobrar de novo. A mesma chave com uma requisição diferente responde 409 idempotency_key_reused.
  • Os resultados são apagados após 48 horas, então salve assim que receber.

Regras de nova tentativa para não pagar duas vezes

RespostaO que fazer
200Salve a imagem
202Consulte o resultado do job; não envie de novo
400, 401, 402, 413, 422Corrija a requisição, a chave, os créditos ou a imagem; não fique em loop
429Espere Retry-After segundos e reenvie com a mesma chave
500, 503, timeout, conexão caídaEspere cada vez mais e reenvie com a mesma chave
502Essa imagem falhou e não foi cobrada; registre no log

O truque é deixar a chave de idempotência determinística: um hash dos bytes da imagem mais as opções. Se o script cair e você rodar de novo, todo reenvio leva a mesma chave, então nada é processado nem cobrado duas vezes.

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

Rode com REMOVEBG_API_KEY=... python batch_api.py photos cutouts. Quatro workers é um começo modesto; se respostas rápidas fizerem você passar do limite por minuto, o tratamento do 429 espera e reenvia com a mesma chave. Para enviar URLs em vez de arquivos, envie 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 longas de URLs, a mesma API tem POST /v1/batch, que coloca na fila até 100 URLs de imagens como um único job que você consulta.

Como deixar qualquer processamento em lote robusto

  • Pule as saídas já prontas para que uma nova execução continue de onde parou em vez de recomeçar. Os dois scripts fazem isso.
  • Cuidado com nomes repetidos. shoe.jpg e shoe.png na mesma pasta viram os dois shoe.png; renomeie as entradas ou inclua a extensão original no nome de saída.
  • Registre as falhas num arquivo e rode de novo só essas.
  • Confira uma amostra com zoom de 100% sobre fundos escuros e claros antes de processar tudo.
  • Não mexa nos originais. Grave numa pasta de saída separada.
  • Escolha o formato de saída pelo destino. PNG ou WebP para transparência, JPG no branco para marketplaces. Veja PNG vs JPEG vs WebP.

Perguntas

O rembg é gratuito para uso comercial?

O rembg em si tem licença MIT, mas cada modelo tem sua própria licença. O modelo padrão, RMBG-2.0 da BRIA (bria-rmbg), exige um acordo pago para uso comercial, então confira a licença do modelo que você usa ou escolha outro.

O rembg usa minha GPU automaticamente?

Sim, se você instalar rembg[gpu] e o ONNX Runtime conseguir carregar o provedor CUDA (o que exige versões compatíveis de CUDA e cuDNN). O rembg usa CUDA ou ROCm quando disponíveis e avisa quando volta para a CPU. Confira com onnxruntime.get_available_providers().

Qual modelo do rembg dá a melhor qualidade?

Os modelos mais novos, bria-rmbg (o padrão), a família birefnet e o isnet-general-use, geram bordas mais suaves e mais bem definidas que a família u2net, mais antiga. O birefnet-portrait é um bom começo para pessoas. Teste com uma amostra das suas próprias imagens, porque os resultados variam conforme o objeto.

Como evito ser cobrado duas vezes ao repetir chamadas à API?

Envie um cabeçalho Idempotency-Key e reutilize a mesma chave em toda nova tentativa da mesma imagem. Gerando a chave a partir de um hash dos bytes da imagem e das opções, até uma nova execução do script inteiro reutiliza as mesmas chaves.

O que meu script deve fazer com uma resposta 202?

Tratar como algo ainda em processamento, não como uma imagem. Leia o id do job no corpo JSON e consulte GET /v1/jobs/{id}/result, esperando o tempo de Retry-After sempre que a resposta for 409 not_ready. Não envie a imagem de novo.

Com que frequência posso enviar requisições à API do removebackground.dev?

Até 60 requisições de processamento por minuto por conta. Acima disso, a API responde 429 rate_limited com um cabeçalho Retry-After. Para muitas URLs, POST /v1/batch aceita até 100 por requisição.

Experimente com a sua imagem

Contas novas ganham 50 créditos grátis. Um crédito por imagem, sem assinatura, e os créditos nunca expiram.

Remover um fundo

Fontes