用 Python 批次去除圖片背景:rembg 與 API 對比

兩個完整指令碼,幫你去除整個資料夾圖片的背景:一個在你自己的硬體上使用開源 rembg 庫,另一個使用 HTTP API,並正確處理重試、速率限制和重複扣費。

更新於 2026年10月23日 · 閱讀約 4 分鐘

跳過這些步驟:一鍵去除背景。 上傳圖片

你有一個資料夾,裡面是幾百張產品照片、證件照或房源圖片,每一張都要去除背景。在編輯器裡手動處理,要點上一整天。用 Python 寫,只是一個迴圈,真正要決定的是迴圈裡跑什麼:自己機器上的開源模型,還是 HTTP API。

本指南兩種方案都會搭建,給出今天就能在資料夾上執行的完整指令碼,並講清楚讓批處理任務可靠的關鍵環節:模型選擇、CPU 與 GPU、重試、速率限制、冪等性,以及崩潰後如何續跑。

方案 A 還是方案 B?

本地模型(rembg)HTTP API
成本軟體免費;代價是硬體和時間按張計費
配置Python 3.11 到 3.13,下載模型,可選 CUDA一個 API 金鑰和 requests
速度取決於你的 CPU 或 GPU 以及模型取決於服務,可並行請求
品質取決於你選的模型取決於服務
隱私圖片永遠不離開你的機器圖片會上傳給第三方
維護模型、驅動和許可證都由你管理由服務商負責
適合離線工作、敏感圖片、實驗生產流水線、沒有 GPU 的伺服器

很多團隊兩種都用:本地模型用於實驗和內部工具,API 用於生產環境,因為在生產中,穩定的品質和不用維護 GPU 伺服器更重要。

方案 A:rembg,開源路線

rembg 是一個採用 MIT 許可證的 Python 庫和命令列工具,封裝了多個去背景模型,並用 ONNX Runtime 執行。撰寫本文時,最新版本是 2.0.86,要求 Python 3.11 或更高、低於 3.14。

安裝

選擇與你的硬體匹配的後端。CPU 版本在任何地方都能用:

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

如果是 NVIDIA GPU,改為安裝 rembg[gpu]。專案說明指出,這需要 onnxruntime-gpu 以及匹配的 CUDA 和 cuDNN,如果裝不上,建議退回 CPU 版本。Linux 上的 AMD GPU 使用 rembg[rocm] 和 onnxruntime-rocm。

當 ONNX Runtime 報告 CUDA(或 ROCm)執行提供程式可用時,rembg 會自動選用它,回退到 CPU 時會給出警告。要檢視 ONNX Runtime 能檢測到什麼:

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

選擇模型

模型在首次使用時下載到 ~/.rembg/models/(設定 REMBG_HOME 可以修改位置)。以下是專案列表中比較實用的幾個:

模型說明
bria-rmbg預設模型。BRIA 的 RMBG-2.0,約 1.02 GB,以 1024 x 1024 執行。其許可證要求商業使用須簽訂付費協議。
birefnet-general通用 BiRefNet 模型;柔和邊緣效果好
birefnet-general-lite更輕量的通用 BiRefNet
birefnet-portrait針對人像最佳化
isnet-general-use通用 IS-Net 模型
u2net經典通用模型;更快,邊緣更生硬
u2netp輕量版 u2net;適合在慢機器上快速測試
silueta與 u2net 相同,縮小到 43 MB

有兩點比看上去更重要。第一,預設模型不能免費用於商業用途:模型權重有自己的許可證,與 rembg 的 MIT 許可證是分開的,所以無論你最終用哪個模型,都要查清它的來源。第二,較新的模型(bria-rmbg、birefnet-*、isnet-general-use)邊緣柔和、形狀準確,而較老的 u2net 系列邊緣往往更硬、更生硬。

邊緣品質選項

對於頭髮、毛髮這類柔和的邊緣畫素,rembg 提供三種處理方式:

  • 預設: 適合產品、汽車和 logo 這類硬邊主體。
  • decontaminate=True(命令列用 -dc):去除舊背景在邊緣畫素中留下的彩色光暈。它只改變顏色,從不改變蒙版形狀,開銷很小,整批開著也沒問題。
  • alpha_matting=True(-a):在蒙版形狀不對時(例如髮絲被切掉)重新估算邊緣本身。它慢得多,而且可能無法收斂,這時 rembg 會退回去色邊後的去背結果。

對於無人值守的批處理,專案推薦用 -dc 而不是 -a,因為它更快,也不會失敗。alpha 通道指南 解釋了光暈產生的原因。

快捷方式:命令列

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

rembg p 處理整個資料夾;加上 -w 可以監視資料夾,在新檔案出現時自動處理。

Python 指令碼

這個版本只加載一次模型,跳過已經處理過的檔案(所以可以隨時停下再重啟),並且在某個檔案失敗時繼續執行:

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

用 python batch_rembg.py photos cutouts birefnet-general 執行。只建立一次會話很關鍵:載入模型的時間遠遠超過執行模型的時間,rembg 的 README 也建議批處理時複用會話以提升效能。輸出始終是 PNG,因為結果帶有 alpha 通道。

rembg 的不足

  • 首次執行很慢,因為要下載模型,大模型還需要大量記憶體或視訊記憶體。
  • 用高品質模型時 CPU 吞吐量一般。 在投入處理一千張之前,先用 20 張有代表性的圖片測試。
  • 許可證和驅動都得你自己負責。 固定 rembg 和模型的版本,以免日後升級時悄悄改變結果。

方案 B:HTTP API

使用 API 時,重活在別人的 GPU 上完成,你的指令碼主要負責把網路處理好。示例使用 removebackground.dev API,因為它的介面規範以 OpenAPI 3.1 釋出在 /openapi.json;同樣的模式也適用於任何類似的服務。

介面規範

  • POST https://removebackground.dev/v1/remove,把金鑰放在 x-api-key 請求頭中。
  • 以 multipart 欄位 image_file 傳送圖片(JPG、PNG 或 WebP,最大 25 MB),或以 JSON 形式傳送 image_url。
  • 可選欄位包括 format(png、jpg 或 webp)、bg_color、size(preview、medium、hd、full)和 crop。
  • 200 表示響應體就是處理好的圖片。202 表示約 55 秒後仍在處理:響應體是帶有任務 id 的 JSON,你需要輪詢 GET /v1/jobs/{id}/result,在圖片就緒之前它會返回 409 not_ready 和 Retry-After。千萬不要把 202 的響應體當作圖片儲存。
  • 每個帳戶每分鐘最多 60 個處理請求;超過後會收到 429 rate_limited 和 Retry-After。
  • Idempotency-Key 請求頭讓重試變得安全:相同的鍵加相同的請求會返回原來的任務,而不會再次扣費。相同的鍵加不同的請求會返回 409 idempotency_key_reused。
  • 結果會在 48 小時後刪除,所以拿到後就要儲存。

避免重複付費的重試規則

響應怎麼做
200儲存圖片
202輪詢任務結果;不要重新上傳
400, 401, 402, 413, 422修正請求、金鑰、積分或圖片;不要迴圈重試
429等待 Retry-After 秒,用相同的鍵重發
500, 503, 超時, 連線中斷退避後用相同的鍵重發
502這張圖片處理失敗且未扣費;記錄下來

訣竅是讓冪等鍵確定:由圖片位元組加選項算出雜湊。如果指令碼崩潰後重新執行,每次重發都帶著相同的鍵,所以不會有任何圖片被處理兩次或扣費兩次。

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

用 REMOVEBG_API_KEY=... python batch_api.py photos cutouts 執行。四個工作執行緒是比較保守的起點;如果響應很快、超出了每分鐘限制,429 處理邏輯會等待後用相同的鍵重發。如果要傳送 URL 而不是檔案,就以 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),
)

對於很長的 URL 列表,同一個 API 還提供 POST /v1/batch,可以把最多 100 個圖片 URL 作為一個任務排隊,然後輪詢結果。

讓任何批處理任務更可靠

  • 跳過已完成的輸出,這樣重新執行時會接著處理,而不是從頭開始。兩個指令碼都這樣做了。
  • 注意檔名衝突。 同一資料夾裡的 shoe.jpg 和 shoe.png 都會對應到 shoe.png;請重新命名輸入檔案,或在輸出檔名中保留原副檔名。
  • 把失敗記錄到檔案中,之後只重跑失敗的那些。
  • 抽查樣本:在處理全部圖片之前,以 100% 縮放在深色和淺色背景上檢查。
  • 不要動原圖。 寫入單獨的輸出資料夾。
  • 按目的地選擇輸出格式。 需要透明用 PNG 或 WebP,電商平臺用白底 JPG。參見 PNG、JPEG 與 WebP 對比。

常見問題

rembg 可以免費商用嗎?

rembg 本身採用 MIT 許可證,但每個模型都有自己的許可證。預設模型 BRIA 的 RMBG-2.0(bria-rmbg)商業使用需要簽訂付費協議,所以請檢視你所用模型的許可證,或換一個模型。

rembg 會自動使用我的 GPU 嗎?

會,前提是你安裝了 rembg[gpu],並且 ONNX Runtime 能載入 CUDA 提供程式(這需要匹配的 CUDA 和 cuDNN)。rembg 在可用時使用 CUDA 或 ROCm,回退到 CPU 時會發出警告。可以用 onnxruntime.get_available_providers() 檢查。

哪個 rembg 模型品質最好?

較新的模型,即 bria-rmbg(預設)、birefnet 系列和 isnet-general-use,比較老的 u2net 系列邊緣更柔和、形狀更準確。人像可以先試 birefnet-portrait。用你自己的一批圖片測試,因為效果因主體而異。

重試 API 呼叫時怎樣避免被重複扣費?

傳送 Idempotency-Key 請求頭,並在同一張圖片的每次重試中複用它。用圖片位元組和選項的雜湊來生成這個鍵,即使重新執行整個指令碼,也會複用相同的鍵。

指令碼收到 202 響應時該怎麼處理?

把它當作仍在處理中,而不是圖片。從 JSON 響應體中讀取任務 id,輪詢 GET /v1/jobs/{id}/result,每當它返回 409 not_ready 時按 Retry-After 等待。不要重新上傳圖片。

向 removebackground.dev API 發請求最快能多快?

每個帳戶每分鐘最多 60 個處理請求。超過後,API 會返回 429 rate_limited 和 Retry-After 請求頭。如果有很多 URL,POST /v1/batch 每次請求最多接受 100 個。

用你自己的圖片試試

新帳戶可獲得 50 個免費額度。每張圖片 1 個額度,無需訂閱,額度永不過期。

去除背景

資料來源