商品写真、顔写真、物件写真が数百枚入ったフォルダがあり、すべての背景を削除する必要があるとします。編集ソフトで手作業すると、1日中クリックし続けることになります。Pythonならループ1つで済み、本当に決めるべきなのはループの中で何を動かすかです。自分のマシンで動くオープンソースのモデルか、HTTP APIか。
このガイドでは両方を作ります。今日すぐフォルダに対して実行できる完全なスクリプトに加え、一括処理を確実にするための要素、つまりモデルの選び方、CPUとGPU、リトライ、レート制限、冪等性、クラッシュ後の再開も解説します。
方法Aか方法Bか
| ローカルのモデル(rembg) | HTTP API | |
|---|---|---|
| コスト | ソフトウェアは無料。ハードウェアと時間で払う | 画像ごと |
| セットアップ | Python 3.11から3.13、モデルのダウンロード、必要ならCUDA | APIキーとrequests |
| 速度 | CPUやGPUとモデル次第 | サービス次第、並列リクエスト |
| 品質 | 選ぶモデル次第 | サービス次第 |
| プライバシー | 画像はマシンから出ない | 画像を第三者にアップロードする |
| 保守 | モデル、ドライバー、ライセンスを自分で管理 | 提供元が管理 |
| 向いている用途 | オフラインの作業、機密性の高い画像、実験 | 本番のパイプライン、GPUのないサーバー |
両方を使うチームも多くあります。実験や社内ツールにはローカルのモデルを、品質が安定していてGPUサーバーが不要なことが重要な本番にはAPIを使うという形です。
方法A:rembg、オープンソースの方法
rembgはMITライセンスのPythonライブラリとCLIで、複数の背景削除モデルをまとめ、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 commandNVIDIAのGPUなら、代わりにrembg[gpu]をインストールします。プロジェクトによると、これにはonnxruntime-gpuと、それに合ったCUDAとcuDNNが必要で、インストールできない場合はCPU版に戻すよう勧めています。Linux上のAMDのGPUでは、rembg[rocm]とonnxruntime-rocmを使います。
rembgは、ONNX Runtimeが利用可能と報告すればCUDA(またはROCm)の実行プロバイダーを自動で選び、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に縮小したもの |
見た目以上に重要な点が2つあります。1つ目に、既定のモデルは商用利用が無料ではありません。モデルの重みにはrembgのMITライセンスとは別のライセンスがあるので、組み込むモデルの提供元を確認しましょう。2つ目に、新しいモデル(bria-rmbg、birefnet-*、isnet-general-use)は柔らかく形の整った輪郭を出しますが、古いu2net系は硬くブロック状の輪郭になりがちです。
輪郭の品質のオプション
rembgには、髪や毛のような柔らかい輪郭のピクセルを扱う方法が3つあります。
- 既定: 商品、車、ロゴなど輪郭の硬い被写体には十分です。
decontaminate=True(CLIでは-dc):元の背景が輪郭のピクセルに残す色のふちを取り除きます。変わるのは色だけでマスクの形は変わらず、負荷もごくわずかなので、一括処理全体でオンのままにしても安全です。alpha_matting=True(-a):毛が切れているなどマスクの形がおかしいときに、輪郭そのものを推定し直します。ずっと遅く、収束しないこともあり、その場合rembgはdecontaminateした切り抜きに戻します。
無人で実行する一括処理には、速くて失敗しないため、プロジェクトは-aより-dcを勧めています。ふちが出る理由はアルファチャンネルのガイドで説明しています。
手早い方法:CLI
rembg p -m birefnet-general -dc photos/ cutouts/rembg pはフォルダ全体を処理します。-wを付けるとフォルダを監視し、新しいファイルが現れるたびに処理します。
Pythonスクリプト
このバージョンはモデルを一度だけ読み込み、処理済みのファイルを飛ばすので途中で止めて再開でき、1つのファイルが失敗しても処理を続けます。
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です。
rembgの弱点
- モデルをダウンロードする初回の実行が遅く、大きいモデルには多くのRAMやVRAMが必要です。
- 高品質なモデルではCPUでの処理速度は控えめです。1000枚に取りかかる前に、代表的な20枚でテストしましょう。
- ライセンスとドライバーは自分の責任です。 あとのアップグレードで結果が知らないうちに変わらないよう、rembgとモデルのバージョンを固定しましょう。
方法B:HTTP API
APIなら重い処理はほかの誰かのGPUで行われ、スクリプトの仕事はほぼネットワークをうまく扱うことになります。例ではremovebackground.devのAPIを使います。仕様がOpenAPI 3.1として/openapi.jsonで公開されているからです。同じパターンは似たサービスならどれにも当てはまります。
仕様
POST https://removebackground.dev/v1/removeに、x-api-keyヘッダーでキーを付けて送ります。- 画像はマルチパートのフィールド
image_file(JPG、PNG、WebP、25 MBまで)か、image_urlを含むJSONで送ります。 - 省略可能なフィールドには
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の本文を画像として保存してはいけません。 - アカウントあたり1分間に60件の処理リクエストまで。超えると
Retry-After付きの429rate_limitedが返ります。 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で実行します。ワーカー4つは控えめな出発点です。レスポンスが速くて1分あたりの上限を超えた場合は、429の処理が待ってから同じキーで再送します。ファイルではなくURLを送るには、JSONをPOSTします。
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を1つのジョブとしてキューに入れ、それをポーリングします。
どんな一括処理も堅牢にするには
- 完成した出力は飛ばし、再実行で最初からではなく続きから再開できるようにします。どちらのスクリプトもそうしています。
- 名前の衝突に注意します。 同じフォルダの
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にはどのくらいの速さでリクエストを送れますか?
アカウントあたり1分間に60件の処理リクエストまでです。超えると、APIはRetry-Afterヘッダー付きの429 rate_limitedを返します。URLが多い場合は、POST /v1/batchで1リクエストあたり最大100件を送れます。
自分の画像で試す
新規アカウントには 50 クレジットを無料で進呈。1枚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)