客戶陸續要收到毛孩的桌曆和掛卡樣品,阿詩得把上百張居家拍攝的貓狗照片換成乾淨的白底或漸層底
對電腦來說,一張照片其實是一格一格排好的像素網格。去背(背景移除)讓照片背景消失、變透明,只留下你要的主角這件事,說穿了就是AI要幫「每一個像素」打一個分數:這格是主角就給它高不透明度(opacity)一個像素「有多不透明」的數值,最高代表完全看得見、最低代表完全透明,是背景就給它趨近於零,卡在邊界上的像素則給一個中間值,讓它呈現半透明。
要打好這個分數,AI其實得身兼兩種能力。第一種是把前景/背景前景是你要留下的主角,背景是準備被移除的部分分開——如果背景很單純,靠顏色差異就能猜個大概;但遇到雜亂背景、低對比、或主角顏色跟背景很像(保護色)的情況,就得靠形狀、材質、上下文才能認出誰是主角。第二種能力更難,是把主角的邊界描準——這件事有個專門名字叫影像摳圖(image matting)不是簡單判斷「是」或「不是」前景,而是精算邊界像素該有幾成不透明,讓毛髮、髮絲能呈現自然的半透明漸層,而不是死板的鋸齒邊。長毛、細線、殘影這些狀況下,邊界早就不是一條清楚的線了。
過去常見的做法,是把這兩種能力交給不同的訓練資料分開練——結果很容易顧此失彼:一批資料練出來的模型很會找主角形狀,卻描不好邊;另一批很會描邊,卻常常整塊主角漏掉。FeyNoBg的作者觀察到,這其實是同一個問題的兩面,得在同一個模型裡讓兩種能力互相搭配,而不是分開單練。
於是FeyNoBg挑了BiRefNetFeyNoBg採用的基礎架構名稱,本身就把「找主角」和「描邊界」設計成兩個分工模組當骨架:圖片先經過一個分四階段層層萃取的模組,把畫面整理成一份份特徵圖(feature map)AI看過照片之後在腦中整理出的「摘要筆記」,後面的模組看這份筆記做判斷,不用重新翻閱整張原始照片;接著定位模組專門負責回答「主角在哪裡」的那部分模型和重建模組專門負責回答「主角的邊界確切在哪裡」的那部分模型,也是處理毛髮、細線這類難題的關鍵,共用同一批特徵圖分頭合作。FeyNoBg目前已經公開釋出,並在八項業界常用評測中的四項拿下最佳的S-measure業界常用來衡量去背/分割結果跟標準答案有多相似的一種評分方式,分數越高代表做得越像,其餘四項也跟第一名差距在2%以內。
這段是簡化過的「把照片丟進FeyNoBg、拿到去背結果」核心流程,對應下面下載專案裡的remove_bg.py
model = AutoModelForImageSegmentation.from_pretrained(MODEL_ID, trust_remote_code=True)preprocess = transforms.Compose([transforms.Resize(size), transforms.ToTensor(), transforms.Normalize(...)])prediction = model(input_tensor)[-1].sigmoid()opacity_map = transforms.ToPILImage()(prediction.squeeze().cpu())result.putalpha(opacity_map)這是一個真實小專案:下載(或複製)檔案,照步驟在你電腦上跑起來。
torch
torchvision
transformers
pillow
from pathlib import Path
import torch
from PIL import Image
from torchvision import transforms
from transformers import AutoModelForImageSegmentation
MODEL_ID = "usefeyn/FeyNoBg" # 依 Hugging Face 上 FeyNoBg 頁面公布的模型代號為準
INPUT_PATH = Path("cat_photo.jpg")
OUTPUT_PATH = Path("cat_photo_transparent.png")
def load_model():
model = AutoModelForImageSegmentation.from_pretrained(
MODEL_ID, trust_remote_code=True
)
model.eval()
return model
def make_opacity_map(model, image):
size = (1024, 1024)
preprocess = transforms.Compose([
transforms.Resize(size),
transforms.ToTensor(),
transforms.Normalize([0.5, 0.5, 0.5], [1.0, 1.0, 1.0]),
])
input_tensor = preprocess(image).unsqueeze(0)
with torch.no_grad():
prediction = model(input_tensor)[-1].sigmoid()
opacity = transforms.ToPILImage()(prediction.squeeze().cpu())
return opacity.resize(image.size)
def cut_out(image_path, output_path):
model = load_model()
original = Image.open(image_path).convert("RGB")
opacity_map = make_opacity_map(model, original)
result = original.copy()
result.putalpha(opacity_map)
result.save(output_path)
print(f"已輸出去背圖片:{output_path}")
if __name__ == "__main__":
cut_out(INPUT_PATH, OUTPUT_PATH)