คุณมีโฟลเดอร์รูปสินค้า รูปโปรไฟล์ หรือรูปประกาศขายอยู่หลายร้อยรูป และทุกรูปต้องลบพื้นหลัง ถ้าทำด้วยมือในโปรแกรมแต่งรูปก็ต้องคลิกกันทั้งวัน แต่ใน Python มันคือลูปเดียว สิ่งที่ต้องตัดสินใจจริงๆ คือจะให้อะไรทำงานอยู่ในลูปนั้น: โมเดลโอเพนซอร์สบนเครื่องของคุณเอง หรือ HTTP API
คู่มือนี้สร้างทั้งสองแบบ พร้อมสคริปต์ครบชุดที่นำไปรันกับโฟลเดอร์ได้ทันที และอธิบายส่วนที่ทำให้งานแบบแบตช์เชื่อถือได้: การเลือกโมเดล, CPU กับ GPU, การลองใหม่, ขีดจำกัดอัตราการเรียก, idempotency และการทำงานต่อหลังโปรแกรมล่ม
เลือกแบบ A หรือแบบ B
| โมเดลบนเครื่อง (rembg) | HTTP API | |
|---|---|---|
| ค่าใช้จ่าย | ซอฟต์แวร์ฟรี แต่จ่ายเป็นค่าฮาร์ดแวร์และเวลา | คิดต่อรูป |
| การติดตั้ง | Python 3.11 ถึง 3.13, ดาวน์โหลดโมเดล, CUDA (ไม่บังคับ) | API key และ requests |
| ความเร็ว | ขึ้นอยู่กับ CPU หรือ GPU และโมเดลที่ใช้ | ขึ้นอยู่กับบริการ และการส่งคำขอพร้อมกัน |
| คุณภาพ | ขึ้นอยู่กับโมเดลที่เลือก | ขึ้นอยู่กับบริการ |
| ความเป็นส่วนตัว | รูปไม่ออกจากเครื่องของคุณเลย | รูปถูกอัปโหลดไปยังบุคคลที่สาม |
| การดูแลรักษา | คุณต้องจัดการโมเดล ไดรเวอร์ และไลเซนส์เอง | ผู้ให้บริการจัดการให้ |
| เหมาะกับ | งานออฟไลน์ รูปที่เป็นความลับ การทดลอง | ระบบโปรดักชัน เซิร์ฟเวอร์ที่ไม่มี GPU |
หลายทีมใช้ทั้งสองแบบ: ใช้โมเดลบนเครื่องสำหรับการทดลองและเครื่องมือภายใน และใช้ API ในโปรดักชัน ซึ่งคุณภาพที่คาดเดาได้และการไม่ต้องดูแลเซิร์ฟเวอร์ GPU สำคัญกว่า
แบบ A: rembg ทางเลือกโอเพนซอร์ส
rembg เป็นไลบรารี Python และ CLI ภายใต้ไลเซนส์ MIT ที่รวมโมเดลลบพื้นหลังหลายตัวไว้ และรันด้วย 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สำหรับ GPU ของ NVIDIA ให้ติดตั้ง rembg[gpu] แทน โปรเจกต์ระบุว่าต้องใช้ onnxruntime-gpu พร้อม CUDA และ cuDNN เวอร์ชันที่ตรงกัน และแนะนำให้กลับไปใช้บิลด์ CPU ถ้าติดตั้งสิ่งเหล่านี้ไม่ได้ ส่วน GPU ของ AMD บน Linux ใช้ rembg[rocm] กับ onnxruntime-rocm
rembg จะเลือก execution provider ของ CUDA (หรือ ROCm) ให้อัตโนมัติเมื่อ ONNX Runtime แจ้งว่าใช้งานได้ และจะเตือนถ้าต้องกลับไปใช้ CPU วิธีเช็กว่า ONNX Runtime มองเห็นอะไรบ้าง:
import onnxruntime
print(onnxruntime.get_available_providers()) # look for CUDAExecutionProviderเลือกโมเดล
โมเดลจะถูกดาวน์โหลดตอนใช้งานครั้งแรกไปที่ ~/.rembg/models/ (ตั้งค่า REMBG_HOME เพื่อเปลี่ยนตำแหน่ง) ตัวที่น่าสนใจจากรายการของโปรเจกต์:
| โมเดล | หมายเหตุ |
|---|---|
bria-rmbg | ค่าเริ่มต้น คือ RMBG-2.0 ของ BRIA ขนาดประมาณ 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 |
มีสองเรื่องที่สำคัญกว่าที่เห็น เรื่องแรก โมเดลเริ่มต้นไม่ได้ฟรีสำหรับการใช้เชิงพาณิชย์: น้ำหนักของโมเดลมีไลเซนส์ของตัวเอง แยกจากไลเซนส์ MIT ของ rembg ดังนั้นให้ตรวจสอบแหล่งที่มาของโมเดลที่คุณจะนำไปใช้จริง เรื่องที่สอง โมเดลรุ่นใหม่ (bria-rmbg, birefnet-*, isnet-general-use) ให้ขอบที่นุ่มและได้รูปทรงดี ส่วนตระกูล u2net รุ่นเก่ามักให้ขอบที่แข็งและหยาบกว่า
ตัวเลือกคุณภาพขอบ
rembg มีวิธีจัดการพิกเซลขอบที่นุ่ม เช่น เส้นผมและขนสัตว์ อยู่ 3 แบบ:
- ค่าเริ่มต้น: เพียงพอสำหรับวัตถุที่ขอบคม เช่น สินค้า รถยนต์ และโลโก้
decontaminate=True(-dcใน CLI): ลบขอบสีที่พื้นหลังเดิมทิ้งไว้ในพิกเซลขอบ เปลี่ยนแค่สี ไม่เปลี่ยนรูปทรงของมาสก์ ใช้ทรัพยากรน้อยมาก และเปิดไว้ทั้งแบตช์ได้อย่างปลอดภัยalpha_matting=True(-a): คำนวณขอบใหม่เมื่อรูปทรงของมาสก์ผิด เช่น เส้นผมขาดหาย ช้ากว่ามากและอาจคำนวณไม่ลู่เข้า ซึ่งในกรณีนั้น rembg จะกลับไปใช้ผลแบบ decontaminate แทน
สำหรับแบตช์ที่ปล่อยให้รันเองโดยไม่มีคนดู โปรเจกต์แนะนำ -dc มากกว่า -a เพราะเร็วกว่าและไม่มีทางล้มเหลว คู่มือ alpha channel อธิบายว่าทำไมจึงเกิดขอบสี
วิธีเร็ว: ใช้ CLI
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 การสร้าง session ครั้งเดียวสำคัญมาก เพราะการโหลดโมเดลใช้เวลานานกว่าการรันมาก และ README ของ rembg ก็แนะนำให้ใช้ session ซ้ำเพื่อประสิทธิภาพของงานแบตช์ ไฟล์ผลลัพธ์เป็น PNG เสมอ เพราะผลลัพธ์มี alpha channel
ข้อจำกัดของ rembg
- รันครั้งแรกช้า ระหว่างดาวน์โหลดโมเดล และโมเดลขนาดใหญ่ต้องใช้ RAM หรือ VRAM มาก
- ความเร็วบน CPU ค่อนข้างจำกัด เมื่อใช้โมเดลคุณภาพสูง ให้ทดสอบกับรูปตัวอย่าง 20 รูปก่อนลงมือทำหนึ่งพันรูป
- ไลเซนส์และไดรเวอร์เป็นหน้าที่ของคุณ ล็อกเวอร์ชันของ rembg และโมเดลไว้ เพื่อไม่ให้การอัปเกรดในภายหลังเปลี่ยนผลลัพธ์โดยไม่รู้ตัว
แบบ B: HTTP API
เมื่อใช้ API งานหนักจะเกิดขึ้นบน GPU ของคนอื่น และสคริปต์ของคุณจะเน้นไปที่การจัดการเครือข่ายให้ดี ตัวอย่างนี้ใช้ API ของ removebackground.dev เพราะสัญญาการใช้งานเผยแพร่เป็น OpenAPI 3.1 ที่ /openapi.json ส่วนรูปแบบเดียวกันนี้ใช้ได้กับบริการที่คล้ายกันทุกเจ้า
สัญญาการใช้งาน
POST https://removebackground.dev/v1/removeพร้อม key ในเฮดเดอร์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 วินาที: เนื้อหาเป็น JSON ที่มี
idของงาน และคุณต้อง pollGET /v1/jobs/{id}/resultซึ่งจะตอบ 409not_readyพร้อมRetry-Afterจนกว่ารูปจะพร้อม ห้ามบันทึกเนื้อหาของคำตอบ 202 เป็นรูปภาพเด็ดขาด - ส่งคำขอประมวลผลได้สูงสุด 60 ครั้งต่อนาที ต่อบัญชี ถ้าเกินจะได้ 429
rate_limitedพร้อมRetry-After - เฮดเดอร์
Idempotency-Keyทำให้การลองใหม่ปลอดภัย: key เดิมกับคำขอเดิมจะคืนงานเดิมแทนการคิดเงินอีกครั้ง ส่วน key เดิมกับคำขอที่ต่างออกไปจะได้ 409idempotency_key_reused - ผลลัพธ์จะถูกลบหลัง 48 ชั่วโมง ดังนั้นให้บันทึกทันทีที่ได้รับ
กฎการลองใหม่ที่ช่วยไม่ให้จ่ายซ้ำ
| คำตอบ | สิ่งที่ต้องทำ |
|---|---|
| 200 | บันทึกรูป |
| 202 | poll ผลลัพธ์ของงาน ห้ามอัปโหลดซ้ำ |
| 400, 401, 402, 413, 422 | แก้ไขคำขอ key เครดิต หรือรูป อย่าวนลูปส่งซ้ำ |
| 429 | รอ Retry-After วินาที แล้วส่งใหม่ด้วย key เดิม |
| 500, 503, หมดเวลา, การเชื่อมต่อหลุด | รอแบบ backoff แล้วส่งใหม่ด้วย key เดิม |
| 502 | รูปนั้นล้มเหลวและไม่ถูกคิดเงิน ให้บันทึกลง log |
เคล็ดลับคือทำให้ idempotency key กำหนดได้แน่นอน (deterministic): ใช้แฮชของไบต์รูปภาพรวมกับตัวเลือก ถ้าสคริปต์ล่มแล้วคุณรันใหม่ การส่งซ้ำทุกครั้งจะใช้ key เดิม จึงไม่มีอะไรถูกประมวลผลหรือคิดเงินซ้ำ
สคริปต์ 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 worker ถือว่าพอประมาณ ถ้าคำตอบกลับมาเร็วจนเกินขีดจำกัดต่อนาที ส่วนที่จัดการ 429 จะรอแล้วส่งใหม่ด้วย key เดิม ถ้าต้องการส่ง URL แทนไฟล์ ให้ POST เป็น 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 ซึ่งรับ URL รูปได้สูงสุด 100 รายการเป็นงานเดียวที่คุณ poll ผลได้
ทำให้งานแบตช์ทุกแบบทนทาน
- ข้ามไฟล์ที่ทำเสร็จแล้ว เพื่อให้การรันใหม่ทำงานต่อจากเดิมแทนที่จะเริ่มใหม่ทั้งหมด สคริปต์ทั้งสองทำแบบนี้อยู่แล้ว
- ระวังชื่อไฟล์ชนกัน
shoe.jpgกับshoe.pngในโฟลเดอร์เดียวกันจะกลายเป็นshoe.pngทั้งคู่ ให้เปลี่ยนชื่อไฟล์ต้นฉบับ หรือใส่นามสกุลเดิมไว้ในชื่อไฟล์ผลลัพธ์ - บันทึกไฟล์ที่ล้มเหลวลงไฟล์ log แล้วรันใหม่เฉพาะไฟล์เหล่านั้น
- สุ่มตรวจตัวอย่าง ที่ซูม 100% บนพื้นหลังสีเข้มและสีอ่อน ก่อนประมวลผลทั้งหมด
- อย่าแตะต้นฉบับ ให้เขียนผลลัพธ์ลงโฟลเดอร์แยก
- เลือกฟอร์แมตผลลัพธ์ให้ตรงกับปลายทาง PNG หรือ WebP สำหรับพื้นหลังโปร่งใส และ JPG บนพื้นขาวสำหรับมาร์เก็ตเพลส ดู PNG vs JPEG vs WebP
คำถามที่พบบ่อย
rembg ใช้เชิงพาณิชย์ได้ฟรีไหม
ตัว rembg ใช้ไลเซนส์ MIT แต่แต่ละโมเดลมีไลเซนส์ของตัวเอง โมเดลเริ่มต้นคือ RMBG-2.0 ของ BRIA (bria-rmbg) ต้องทำข้อตกลงแบบเสียเงินหากใช้เชิงพาณิชย์ จึงควรตรวจสอบไลเซนส์ของโมเดลที่ใช้ หรือเลือกโมเดลอื่น
rembg ใช้ GPU ให้อัตโนมัติไหม
ใช่ ถ้าคุณติดตั้ง rembg[gpu] และ ONNX Runtime โหลด CUDA provider ได้ (ต้องมี CUDA และ cuDNN เวอร์ชันที่ตรงกัน) rembg จะใช้ CUDA หรือ ROCm เมื่อพร้อมใช้งาน และจะเตือนเมื่อต้องกลับไปใช้ CPU เช็กได้ด้วย onnxruntime.get_available_providers()
โมเดลไหนของ rembg ให้คุณภาพดีที่สุด
โมเดลรุ่นใหม่ ได้แก่ bria-rmbg (ค่าเริ่มต้น) ตระกูล birefnet และ isnet-general-use ให้ขอบที่นุ่มและได้รูปทรงดีกว่าตระกูล u2net รุ่นเก่า สำหรับรูปคน birefnet-portrait เป็นจุดเริ่มต้นที่ดี ควรทดสอบกับรูปตัวอย่างของคุณเอง เพราะผลลัพธ์ต่างกันไปตามวัตถุ
จะป้องกันการถูกคิดเงินซ้ำเมื่อลองเรียก API ใหม่ได้อย่างไร
ส่งเฮดเดอร์ Idempotency-Key และใช้ค่าเดิมทุกครั้งที่ลองส่งรูปเดิมใหม่ ถ้าสร้าง key จากแฮชของไบต์รูปภาพและตัวเลือก แม้จะรันสคริปต์ใหม่ทั้งหมดก็จะใช้ key เดิม
สคริปต์ควรทำอย่างไรเมื่อได้คำตอบ 202
ให้ถือว่ายังประมวลผลอยู่ ไม่ใช่รูปภาพ อ่าน id ของงานจากเนื้อหา JSON แล้ว poll GET /v1/jobs/{id}/result โดยรอตาม Retry-After ทุกครั้งที่ได้คำตอบ 409 not_ready และห้ามอัปโหลดรูปซ้ำ
ส่งคำขอไปยัง API ของ removebackground.dev ได้เร็วแค่ไหน
ได้สูงสุด 60 คำขอประมวลผลต่อนาทีต่อบัญชี ถ้าเกิน API จะตอบ 429 rate_limited พร้อมเฮดเดอร์ Retry-After ถ้ามี URL จำนวนมาก POST /v1/batch รับได้สูงสุด 100 รายการต่อคำขอ
ลองกับรูปของคุณเอง
บัญชีใหม่รับฟรี 50 เครดิต หนึ่งเครดิตต่อรูป ไม่ต้องสมัครสมาชิกรายเดือน และเครดิตไม่มีวันหมดอายุ
ลบพื้นหลังแหล่งที่มา
- 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)