Pythonで画像の背景を一括削除する:rembgとAPIの比較

フォルダ内のすべての画像の背景を削除する、2つの完全なスクリプトを紹介します。1つは自分のハードウェアでオープンソースのrembgライブラリを使うもの、もう1つはリトライ、レート制限、二重課金を正しく扱うHTTP APIを使うものです。

2026年10月23日 更新 · 6分で読めます

商品写真、顔写真、物件写真が数百枚入ったフォルダがあり、すべての背景を削除する必要があるとします。編集ソフトで手作業すると、1日中クリックし続けることになります。Pythonならループ1つで済み、本当に決めるべきなのはループの中で何を動かすかです。自分のマシンで動くオープンソースのモデルか、HTTP APIか。

このガイドでは両方を作ります。今日すぐフォルダに対して実行できる完全なスクリプトに加え、一括処理を確実にするための要素、つまりモデルの選び方、CPUとGPU、リトライ、レート制限、冪等性、クラッシュ後の再開も解説します。

方法Aか方法Bか

ローカルのモデル(rembg)HTTP API
コストソフトウェアは無料。ハードウェアと時間で払う画像ごと
セットアップPython 3.11から3.13、モデルのダウンロード、必要ならCUDAAPIキーと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 command

NVIDIAの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。遅いマシンでの簡単なテスト向け
siluetau2netと同じで、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をポーリングします。画像ができるまでは409 not_readyとRetry-Afterが返ります。202の本文を画像として保存してはいけません。
  • アカウントあたり1分間に60件の処理リクエストまで。超えるとRetry-After付きの429 rate_limitedが返ります。
  • Idempotency-Keyヘッダーでリトライを安全にできます。同じキーで同じリクエストを送ると、再び課金せずに元のジョブが返ります。同じキーで違うリクエストを送ると409 idempotency_key_reusedが返ります。
  • 結果は48時間後に削除されるので、受け取ったら保存しましょう。

二重に支払わないためのリトライのルール

レスポンス対応
200画像を保存する
202ジョブの結果をポーリングする。再アップロードはしない
400、401、402、413、422リクエスト、キー、クレジット、画像を直す。ループしない
429Retry-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クレジット、サブスクなし、クレジットは期限切れになりません。

背景を削除する

出典