Usuwanie tła ze zdjęć hurtowo w Pythonie: rembg czy API

Dwa kompletne skrypty do usuwania tła z całego folderu zdjęć: jeden z otwartoźródłową biblioteką rembg na Twoim sprzęcie, drugi z API HTTP, który poprawnie obsługuje ponowienia, limity zapytań i podwójne opłaty.

Zaktualizowano 23 października 2026 · 8 min czytania

Pomiń kroki: usuń tło jednym kliknięciem. Prześlij zdjęcie

Masz folder z kilkuset zdjęciami produktów, portretami albo zdjęciami do ofert i z każdego trzeba usunąć tło. Ręcznie w edytorze to dzień klikania. W Pythonie to jedna pętla, a prawdziwa decyzja dotyczy tego, co działa w jej środku: otwartoźródłowy model na Twoim komputerze czy API HTTP.

W tym poradniku budujemy oba warianty, z kompletnymi skryptami, które możesz uruchomić na folderze jeszcze dziś. Omawiamy też to, co sprawia, że hurtowe usuwanie tła działa niezawodnie: wybór modelu, CPU czy GPU, ponowienia, limity zapytań, idempotencję i wznawianie po awarii.

Opcja A czy opcja B?

Model lokalny (rembg)API HTTP
KosztDarmowe oprogramowanie; płacisz sprzętem i czasemZa zdjęcie
KonfiguracjaPython od 3.11 do 3.13, pobieranie modeli, opcjonalnie CUDAKlucz API i requests
SzybkośćZależy od CPU lub GPU i modeluZależy od usługi, zapytania równoległe
JakośćZależy od wybranego modeluZależy od usługi
PrywatnośćZdjęcia nigdy nie opuszczają Twojego komputeraZdjęcia trafiają do zewnętrznej firmy
UtrzymanieSam zarządzasz modelami, sterownikami i licencjamiZajmuje się tym dostawca
Dobre doPracy offline, wrażliwych zdjęć, eksperymentówProcesów produkcyjnych, serwerów bez GPU

Wiele zespołów korzysta z obu: z lokalnego modelu do eksperymentów i narzędzi wewnętrznych, a z API na produkcji, gdzie przewidywalna jakość i brak serwerów z GPU są ważniejsze.

Opcja A: rembg, droga open source

rembg to biblioteka Pythona i narzędzie wiersza poleceń na licencji MIT, które obsługuje kilka modeli do usuwania tła i uruchamia je przez ONNX Runtime. W chwili pisania aktualne wydanie to 2.0.86 i wymaga Pythona w wersji co najmniej 3.11, ale niższej niż 3.14.

Instalacja

Wybierz backend pasujący do Twojego sprzętu. Wersja na CPU działa wszędzie:

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

Dla kart NVIDIA zainstaluj zamiast tego rembg[gpu]. Projekt zaznacza, że wymaga to onnxruntime-gpu oraz pasujących wersji CUDA i cuDNN, i radzi wrócić do wersji na CPU, jeśli nie możesz ich zainstalować. Karty AMD w Linuksie używają rembg[rocm] z onnxruntime-rocm.

rembg sam wybiera dostawcę wykonawczego CUDA (lub ROCm), gdy ONNX Runtime zgłasza go jako dostępnego, i ostrzega, jeśli przełącza się na CPU. Aby sprawdzić, co widzi ONNX Runtime:

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

Wybór modelu

Modele pobierają się przy pierwszym użyciu do ~/.rembg/models/ (zmienna REMBG_HOME zmienia tę lokalizację). Kilka przydatnych z listy projektu:

ModelUwagi
bria-rmbgDomyślny. RMBG-2.0 od BRIA, około 1,02 GB, działa w rozdzielczości 1024 x 1024. Licencja wymaga płatnej umowy przy użyciu komercyjnym.
birefnet-generalUniwersalny model BiRefNet; dobre miękkie krawędzie
birefnet-general-liteLżejszy BiRefNet do ogólnych zastosowań
birefnet-portraitDostrojony do zdjęć ludzi
isnet-general-useUniwersalny model IS-Net
u2netKlasyczny model ogólny; szybszy, bardziej kanciaste krawędzie
u2netpLekki u2net; szybkie testy na wolnych komputerach
siluetaTo samo co u2net, zmniejszone do 43 MB

Dwie rzeczy są ważniejsze, niż się wydaje. Po pierwsze, domyślny model nie jest darmowy do użytku komercyjnego: wagi modeli mają własne licencje, niezależne od licencji MIT rembg, więc sprawdź źródło modelu, którego używasz w produkcie. Po drugie, nowsze modele (bria-rmbg, birefnet-*, isnet-general-use) dają miękkie, dobrze ukształtowane krawędzie, a starsza rodzina u2net zwykle daje twardsze, bardziej kanciaste.

Opcje jakości krawędzi

rembg oferuje trzy sposoby traktowania miękkich pikseli na krawędziach, takich jak włosy i sierść:

  • Domyślny: wystarcza przy obiektach o twardych krawędziach, takich jak produkty, samochody i logo.
  • decontaminate=True (-dc w wierszu poleceń): usuwa kolorową poświatę, którą stare tło zostawia w pikselach krawędzi. Zmienia tylko kolor, nigdy kształtu maski, prawie nic nie kosztuje i można go bezpiecznie włączyć dla całej partii.
  • alpha_matting=True (-a): ponownie wyznacza samą krawędź, gdy kształt maski jest błędny, na przykład gdy ucięło kosmyki. Jest dużo wolniejszy i może się nie zbiec, a wtedy rembg zwraca wycinek z samą dekontaminacją.

Przy partiach przetwarzanych bez nadzoru projekt zaleca -dc zamiast -a, bo jest szybsze i nie może zawieść. Poradnik o kanale alfa wyjaśnia, skąd biorą się poświaty.

Szybki sposób: wiersz poleceń

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

rembg p przetwarza cały folder; dodaj -w, aby obserwować folder i przetwarzać nowe pliki, gdy się pojawią.

Skrypt w Pythonie

Ta wersja ładuje model raz, pomija pliki już przetworzone, więc możesz ją zatrzymać i uruchomić ponownie, i działa dalej, gdy jeden plik się nie powiedzie:

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

Uruchom go poleceniem python batch_rembg.py photos cutouts birefnet-general. Jednorazowe utworzenie sesji ma znaczenie: ładowanie modelu trwa znacznie dłużej niż jego działanie, a README rembg zaleca ponowne użycie sesji dla wydajności przy partiach. Wyniki są zawsze w PNG, bo mają kanał alfa.

Gdzie rembg nie wystarcza

  • Pierwsze uruchomienie jest wolne, bo trwa pobieranie modelu, a duże modele potrzebują dużo RAM lub VRAM.
  • Wydajność na CPU jest umiarkowana przy modelach wysokiej jakości. Przetestuj 20 reprezentatywnych zdjęć, zanim weźmiesz się za tysiąc.
  • Licencje i sterowniki są na Twojej głowie. Przypnij wersje rembg i modeli, żeby późniejsza aktualizacja nie zmieniła po cichu wyników.

Opcja B: API HTTP

Przy API ciężką pracę wykonują cudze GPU, a Twój skrypt zajmuje się głównie dobrą obsługą sieci. Przykład używa API removebackground.dev, bo jego kontrakt jest opublikowany jako OpenAPI 3.1 pod /openapi.json; te same wzorce sprawdzą się w każdej podobnej usłudze.

Kontrakt

  • POST https://removebackground.dev/v1/remove z kluczem w nagłówku x-api-key.
  • Wyślij zdjęcie jako pole multipart image_file (JPG, PNG lub WebP, do 25 MB) albo jako JSON z image_url.
  • Opcjonalne pola to między innymi format (png, jpg lub webp), bg_color, size (preview, medium, hd, full) i crop.
  • 200 oznacza, że treść odpowiedzi to gotowy obraz. 202 oznacza, że zdjęcie wciąż jest przetwarzane po około 55 sekundach: treść to JSON z id zadania, a Ty odpytujesz GET /v1/jobs/{id}/result, który odpowiada 409 not_ready z Retry-After, dopóki obraz nie będzie gotowy. Nigdy nie zapisuj treści odpowiedzi 202 jako obrazu.
  • Do 60 zapytań przetwarzających na minutę na konto; powyżej dostajesz 429 rate_limited z Retry-After.
  • Nagłówek Idempotency-Key sprawia, że ponowienia są bezpieczne: ten sam klucz z tym samym zapytaniem zwraca pierwotne zadanie zamiast pobierać opłatę drugi raz. Ten sam klucz z innym zapytaniem daje odpowiedź 409 idempotency_key_reused.
  • Wyniki są usuwane po 48 godzinach, więc zapisuj je od razu.

Zasady ponowień, dzięki którym nie zapłacisz dwa razy

OdpowiedźCo zrobić
200Zapisz obraz
202Odpytuj wynik zadania; nie wysyłaj zdjęcia ponownie
400, 401, 402, 413, 422Popraw zapytanie, klucz, kredyty lub zdjęcie; nie ponawiaj w pętli
429Odczekaj Retry-After sekund, wyślij ponownie z tym samym kluczem
500, 503, przekroczenie czasu, zerwane połączenieOdczekaj i wyślij ponownie z tym samym kluczem
502To zdjęcie się nie udało i nie zostało policzone; zapisz to w logu

Cała sztuka polega na tym, żeby klucz idempotencji był deterministyczny: hash bajtów zdjęcia plus opcji. Jeśli skrypt się wysypie i uruchomisz go ponownie, każde ponowne wysłanie ma ten sam klucz, więc nic nie zostanie przetworzone ani policzone dwa razy.

Skrypt w Pythonie

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

Uruchom go poleceniem REMOVEBG_API_KEY=... python batch_api.py photos cutouts. Cztery wątki to skromny początek; jeśli szybkie odpowiedzi wypchną Cię ponad limit na minutę, obsługa 429 odczeka i wyśle zapytanie ponownie z tym samym kluczem. Aby wysyłać adresy URL zamiast plików, wyślij 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),
)

Przy długich listach adresów URL to samo API ma POST /v1/batch, które kolejkuje do 100 adresów zdjęć jako jedno zadanie do odpytywania.

Jak zrobić każde przetwarzanie hurtowe odpornym na błędy

  • Pomijaj gotowe pliki wynikowe, żeby ponowne uruchomienie wznawiało pracę zamiast zaczynać od zera. Oba skrypty to robią.
  • Uważaj na kolizje nazw. shoe.jpg i shoe.png w tym samym folderze dają ten sam shoe.png; zmień nazwy plików wejściowych albo dodaj pierwotne rozszerzenie do nazwy pliku wynikowego.
  • Zapisuj błędy do pliku i uruchamiaj ponownie tylko te pliki.
  • Sprawdź próbkę w powiększeniu 100% na ciemnym i jasnym tle, zanim przetworzysz wszystko.
  • Nie ruszaj oryginałów. Zapisuj wyniki do osobnego folderu.
  • Dobierz format pliku do miejsca docelowego. PNG lub WebP, gdy potrzebujesz przezroczystości, JPG na białym tle dla marketplace'ów. Zobacz PNG, JPEG czy WebP.

Pytania

Czy rembg można używać komercyjnie za darmo?

Sam rembg ma licencję MIT, ale każdy model ma własną licencję. Domyślny model, RMBG-2.0 od BRIA (bria-rmbg), wymaga płatnej umowy przy użyciu komercyjnym, więc sprawdź licencję modelu, którego używasz, albo wybierz inny.

Czy rembg automatycznie korzysta z mojej karty graficznej?

Tak, jeśli zainstalujesz rembg[gpu], a ONNX Runtime może załadować dostawcę CUDA (co wymaga pasujących wersji CUDA i cuDNN). rembg używa CUDA lub ROCm, gdy są dostępne, i ostrzega, gdy przełącza się na CPU. Sprawdź onnxruntime.get_available_providers().

Który model rembg daje najlepszą jakość?

Nowsze modele, bria-rmbg (domyślny), rodzina birefnet i isnet-general-use, dają miększe i lepiej ukształtowane krawędzie niż starsza rodzina u2net. Do zdjęć ludzi dobrym punktem wyjścia jest birefnet-portrait. Przetestuj próbkę własnych zdjęć, bo wyniki zależą od tematu.

Jak uniknąć podwójnej opłaty przy ponawianiu wywołań API?

Wysyłaj nagłówek Idempotency-Key i używaj tego samego klucza przy każdym ponowieniu dla tego samego zdjęcia. Jeśli wyliczasz go z hasha bajtów zdjęcia i opcji, nawet ponowne uruchomienie całego skryptu użyje tych samych kluczy.

Co skrypt powinien zrobić z odpowiedzią 202?

Potraktuj ją jako zadanie wciąż w toku, a nie jako obraz. Odczytaj id zadania z treści JSON i odpytuj GET /v1/jobs/{id}/result, czekając tyle, ile podaje Retry-After, za każdym razem, gdy odpowiedź to 409 not_ready. Nie wysyłaj zdjęcia ponownie.

Jak szybko mogę wysyłać zapytania do API removebackground.dev?

Do 60 zapytań przetwarzających na minutę na konto. Powyżej tego limitu API odpowiada 429 rate_limited z nagłówkiem Retry-After. Przy wielu adresach URL POST /v1/batch przyjmuje do 100 w jednym zapytaniu.

Wypróbuj na własnym zdjęciu

Nowe konta dostają 50 darmowych kredytów. Jeden kredyt za zdjęcie, bez subskrypcji, a kredyty nigdy nie wygasają.

Usuń tło

Źródła