你有一個資料夾,裡面是幾百張產品照片、證件照或房源圖片,每一張都要去除背景。在編輯器裡手動處理,要點上一整天。用 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,在圖片就緒之前它會返回 409not_ready和Retry-After。千萬不要把 202 的響應體當作圖片儲存。 - 每個帳戶每分鐘最多 60 個處理請求;超過後會收到 429
rate_limited和Retry-After。 Idempotency-Key請求頭讓重試變得安全:相同的鍵加相同的請求會返回原來的任務,而不會再次扣費。相同的鍵加不同的請求會返回 409idempotency_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 個額度,無需訂閱,額度永不過期。
去除背景資料來源
- 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)