יש לכם תיקייה עם כמה מאות תמונות מוצר, תמונות פרופיל או תמונות למודעות, ובכל אחת צריך להסיר את הרקע. לעשות את זה ידנית בעורך תמונות זה יום שלם של קליקים. ב-Python זו לולאה, וההחלטה האמיתית היא מה רץ בתוכה: מודל קוד פתוח על המחשב שלכם, או HTTP API.
המדריך הזה בונה את שתי האפשרויות, עם סקריפטים מלאים שאפשר להריץ על תיקייה כבר היום, ומכסה את מה שהופך עיבוד אצווה לאמין: בחירת מודל, CPU מול GPU, ניסיונות חוזרים, מגבלות קצב, idempotency והמשך אחרי קריסה.
אפשרות A או אפשרות B?
| מודל מקומי (rembg) | HTTP API | |
|---|---|---|
| עלות | תוכנה חינמית; משלמים בחומרה ובזמן | לפי תמונה |
| התקנה | Python 3.11 עד 3.13, הורדת מודלים, CUDA אופציונלי | מפתח API ו-requests |
| מהירות | תלויה ב-CPU או ב-GPU שלכם ובמודל | תלויה בשירות ובבקשות מקבילות |
| איכות | תלויה במודל שבוחרים | תלויה בשירות |
| פרטיות | התמונות לא יוצאות מהמחשב | התמונות מועלות לצד שלישי |
| תחזוקה | אתם מנהלים מודלים, דרייברים ורישיונות | הספק מטפל בזה |
| מתאים ל | עבודה בלי חיבור, תמונות רגישות, ניסויים | תהליכי פרודקשן, שרתים בלי GPU |
צוותים רבים משתמשים בשתיהן: מודל מקומי לניסויים ולכלים פנימיים, ו-API לפרודקשן, שבו איכות צפויה וויתור על שרתי GPU חשובים יותר.
אפשרות A: rembg, הדרך של הקוד הפתוח
rembg היא ספריית Python וכלי שורת פקודה ברישיון MIT, שעוטפת כמה מודלים להסרת רקע ומריצה אותם עם ONNX Runtime. בזמן כתיבת המדריך הגרסה הנוכחית היא 2.0.86, והיא דורשת Python 3.11 ומעלה ומתחת ל-3.14.
התקנה
בחרו את ה-backend שמתאים לחומרה שלכם. גרסת ה-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, התקינו במקום זאת את rembg[gpu]. הפרויקט מציין שזה דורש את onnxruntime-gpu יחד עם גרסאות תואמות של CUDA ו-cuDNN, וממליץ לחזור לגרסת ה-CPU אם אי אפשר להתקין אותן. כרטיסי AMD ב-Linux משתמשים ב-rembg[rocm] עם onnxruntime-rocm.
rembg בוחר אוטומטית את ספק ההרצה 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 מציע שלוש דרכים לטפל בפיקסלים רכים בקצוות, כמו שיער ופרווה:
- ברירת מחדל: מתאימה לאובייקטים עם קצוות חדים, כמו מוצרים, מכוניות ולוגואים.
decontaminate=True(-dcבשורת הפקודה): מסיר את ההילה הצבעונית שהרקע הישן משאיר בפיקסלים של הקצה. משנה רק צבע, אף פעם לא את צורת המסכה, עולה מעט מאוד, ובטוח להשאיר אותו פעיל לאצווה שלמה.alpha_matting=True(-a): מעריך מחדש את הקצה עצמו כשצורת המסכה שגויה, למשל כשקווצות שיער נחתכות. הרבה יותר איטי ויכול לא להתכנס, ואז rembg חוזר לגזירה עם ניקוי צבע.
לאצוות שרצות בלי השגחה, הפרויקט ממליץ על -dc ולא על -a, כי הוא מהיר יותר ולא יכול להיכשל. המדריך לערוץ אלפא מסביר למה נוצרות הילות.
הדרך המהירה: שורת הפקודה
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 כי לתוצאה יש ערוץ אלפא.
איפה 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עם המפתח בכותרת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של משימה, ואתם שואלים את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, timeout, חיבור שנפל | להמתין ולשלוח שוב עם אותו מפתח |
| 502 | התמונה הזו נכשלה ולא חויבה; לרשום ביומן |
הטריק הוא להפוך את מפתח ה-idempotency לדטרמיניסטי: hash של הבייטים של התמונה יחד עם האפשרויות. אם הסקריפט קורס ומריצים אותו שוב, כל שליחה חוזרת נושאת את אותו מפתח, ושום דבר לא מעובד או מחויב פעמיים.
סקריפט ה-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. ארבעה workers זו התחלה צנועה; אם תשובות מהירות מעבירות אתכם את המגבלה לדקה, הטיפול ב-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),
)לרשימות ארוכות של כתובות, לאותו API יש POST /v1/batch, שמכניס לתור עד 100 כתובות תמונה כמשימה אחת שאותה שואלים.
איך הופכים כל עיבוד אצווה לחסין
- דלגו על פלטים שכבר הסתיימו כדי שהרצה חוזרת תמשיך במקום להתחיל מהתחלה. שני הסקריפטים עושים זאת.
- שימו לב להתנגשויות שמות.
shoe.jpgו-shoe.pngבאותה תיקייה ממופים שניהם ל-shoe.png; שנו את שמות הקבצים או כללו את הסיומת המקורית בשם הפלט. - רשמו כשלים לקובץ והריצו מחדש רק אותם.
- בדקו מדגם בזום 100% על רקע כהה ובהיר לפני שמעבדים הכול.
- אל תגעו במקור. כתבו לתיקיית פלט נפרדת.
- בחרו את פורמט הפלט לפי היעד. PNG או WebP לשקיפות, JPG על לבן לזירות מסחר. ראו PNG מול JPEG מול WebP.
שאלות
האם rembg חינמי לשימוש מסחרי?
rembg עצמה ברישיון MIT, אבל לכל מודל יש רישיון משלו. מודל ברירת המחדל, RMBG-2.0 של BRIA (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 והשתמשו בה שוב בכל ניסיון חוזר של אותה תמונה. אם גוזרים אותה מ-hash של הבייטים של התמונה ושל האפשרויות, גם הרצה חוזרת של הסקריפט כולו משתמשת באותם מפתחות.
מה הסקריפט צריך לעשות עם תשובת 202?
להתייחס אליה כעיבוד שעדיין רץ, לא כתמונה. קראו את ה-id של המשימה מגוף ה-JSON ושאלו את GET /v1/jobs/{id}/result, וחכו לפי Retry-After בכל פעם שהוא עונה 409 not_ready. אל תעלו את התמונה שוב.
באיזה קצב אפשר לשלוח בקשות ל-API של removebackground.dev?
עד 60 בקשות עיבוד בדקה לכל חשבון. מעבר לזה ה-API עונה 429 rate_limited עם כותרת Retry-After. לכתובות רבות, 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)