GIF / 動圖 透明背景摳圖
需要收集的輸入
- 原始檔案:GIF、PNG 序列還是影片。影片走 ffmpeg 抽幀(腳本已內建)
- 觀看底色:使用者是在什麼背景上看這張圖的?深色介面還是白底頁面會暴露完全不同的殘留。 不知道就預設做全套底色驗證(腳本已內建),不要跳過
- 主體是否有接近背景色的淺色部件(白手套、白靴、白邊)? 這是能力天花板,見下方「先做可解性判斷」
先做可解性判斷(跳過這步會白幹幾十分鐘)
摳圖是資訊恢復問題,不是演算法調優問題。當主體淺色部件與背景在顏色上接近時,後處理 無法把它們分開 —— 色度、平滑度、時間中位數、背景曲面擬合全會誤傷主體。
別靠肉眼估顏色,跑一下:
.venv-bg\Scripts\python scripts\cutout.py --input <源檔案> --work work --stage solvability
它用「相機靜止 + 主體會動」分離出背景板和主體,輸出三個數:
- 背景板中位色 / 主體最亮色調 / 兩者差值
- 落在背景色 ±25/255 以內的主體像素佔比 —— 這個數最可解釋
實測這份機器人素材:背景 (212,207,210),主體最亮 (250,251,250),差值 40.7,
但32.6% 的主體像素落在背景色 25 以內 → 打印 AMBIGUOUS。
看到 AMBIGUOUS 就要先告訴使用者:
這份素材的淺色部件和背景顏色非常接近,純摳圖做不到像素級乾淨,頂多做到「放大後有極細殘留」。 有原始工程的話,換高飽和純綠/品紅幕布重渲一次,我就能做到完全無殘留; 只有成品檔案的話我只能做到逼近乾淨。
然後問使用者要不要繼續,別悶頭跑 20 分鐘。
注意:這個檢查是預警訊號,不是判決。最終以跑完之後的殘留量化為準 —— 「能分」也可能因為模型邊緣不準而出殘留,「不能分」也可能實際摳得很乾淨。
Procedure
-
建工作區並裝環境
python -m venv .venv-bg && .venv-bg\Scripts\python -m pip install rembg onnxruntime pillow scipy(scipy 來自 rembg 依賴鏈,引導濾波要用它 —— 見下) -
跑管線
.venv-bg\Scripts\python scripts\cutout.py --input <源檔案> --outdir out --scale 2--input接受影片檔案、GIF/PNG 檔案,或一個已抽好幀的 PNG 序列目錄。--scale 2不是可選項:肢體與軀幹之間的縫只有 5–15px,720p 下分割模型根本看不見。 個別幀還頑固就--stage matte單獨重跑那幾幀、--scale 3。純 CPU 下 70 幀 720p 跑 2× 約 20 分鐘(約 17.6 秒/幀,onnxruntime 無 GPU)。 提前告訴使用者這個量級,別讓人以為卡死。
-
驗收(見下節),不通過就定位階段重跑,不要整體重跑。
-
交付:GIF + WebP + APNG 三份都給,並說明各自適用場景。
硬規則(違反過、代價都很大)
- 拓撲處理只用 opening,絕不用 closing。
binary_closing會填掉 ≤2px 的背景縫, 把手臂和軀幹之間的窄縫焊死,之後再多的處理也救不回來。實測這一條比其它所有問題加起來 造成的返工都多。 - 空洞填充閾值取小值(60px 量級,不是幾百)。真正的肢體縫隙有幾千像素,針孔只有幾十。
- 引導濾波半徑 ≤2,且預設不要中值濾波。3×3 中值和半徑 4 的引導濾波都會抹掉幾像素寬的縫。
- 不要對 alpha 做非線性壓縮。曾經寫過
clip((clip((a-0.04)/0.92)-0.06)/0.88)這種對映, 意圖是收緊邊緣,實際在 alpha≈0.7 處反而抬高了 alpha,等於親手把殘留補得更實。 - GIF 不要用有序抖動(Bayer)。靜幀上邊緣更柔,但那個網點鎖在螢幕座標系裡, 輪廓一動穿過去就「沸騰」閃爍 —— 動畫裡反而更糟。GIF 走清理後的硬閾值, 柔邊 alpha 留給 WebP / APNG。
- 模型選 BiRefNet。isnet / u2net 會把地面鏡面倒影判成實體摳進去;換成
birefnet-general-lite後 alpha 在靴底處直接歸零。 - 不要為了「更乾淨」去擦背景色。用背景曲面/色度判據大面積擦殘留,會連主體淺色部件一起擦掉。 殘留的正確解法是提高 matte 解析度,不是改閾值。
Output contract
out/<name>.gif—— 透明索引 255、transparency=255、disposal=2,1-bit 硬邊。 拓撲清理(去散點)只作用於 GIFout/<name>.webp—— 保留連續 alpha,不做拓撲清理,所以會留一層柔邊暖色毛邊。 疊複雜背景優先用它,這個差異是刻意的,別當成 bugout/<name>.png—— APNG,瀏覽器相容性最好- 保持原始檔案的畫布尺寸、幀數、每幀時長、循環設定不變;在回覆裡列出這幾項的實測值 (動畫 WebP 的逐幀時長 PIL 讀不出來,遇到這種情況照實說「以 GIF 實測為準」,別編)
- 用
<media>標籤交付檔案,並說明 GIF 與 WebP 各自的適用場景
Failure handling
- GIF 在瀏覽器裡顯示成灰底:先別改檔案。查 VP8X 的 ALPHA 位與 ANIM 背景色
(
verify.py會打印),兩者都正常的話是檢視器給透明區鋪了底色。 若verify.py報ANMF ... blend=0,那才是真問題 ——cutout.py匯出時會自動改成blend=1,如果這份檔案的 blend 是 0,說明它不是本腳本產的,或走了外部工具。 - 主體被啃掉:幾乎都是背景判據太寬。回退到
final/那一層重新匯出,不要在擦除邏輯上繼續調參。 - 某幾幀縫隙沒摳開:
--stage matte --scale 3只重跑那幾幀,單幀成本約 20 秒。 - rembg 模型下載慢:rembg 2.x 把模型放在
~/.rembg/models/<model>/<model>.onnx(不是老版的~/.u2net),birefnet-general-lite 約 214MB。首次執行要等下載。
驗收(這一步不能省)
必須在使用者真實觀看的那塊底色上逐幀看,否則等於沒驗:
.venv-bg\Scripts\python scripts\verify.py out\<name>.gif --backdrop grey --ref work\final
它會輸出:散點數、逐幀不透明佔比(判斷有無閃爍)、最低不透明行、GIF 相對原始 matte 的 顏色量化誤差、WebP 容器標誌,並生成四底色對比圖 + 全幀接觸表 + 指定區域放大圖。
判斷標準:
| 指標 | 通過 |
|---|---|
| 散點數 | 0 |
| 不透明佔比逐幀極差 | < 0.05 |
| 最低不透明行 | 逐幀一致(反射殘留會讓它忽高忽低或頂到畫面底) |
| 顏色量化平均誤差 | < 3/255(縮圖看不出丟色相,只能靠這個數) |
| 四底色對比 | 每塊底色都能透出來,沒有統一淺灰 |
| 接觸表 | 逐幀無殘影、無雜點、肢體縫隙通透 |
肉眼只看單幀、或只在黑底上看,都會漏掉問題 —— 暖色殘留疊在深灰底上才明顯。
Windows (win32) platform notes
- 直譯器用
py或python,python3在 Windows 上不存在;別用 PATH 上的python(可能指向無 pip 的 hermes venv),優先使用 venv 裡的.venv-bg\Scripts\python.exe - 路徑含中文時,用
Write/Read工具寫腳本檔案,不要用Set-Content拼內容 - PIL 的
ImageFilter.GaussianBlur不支援 mode"F"和 float RGB;對浮點圖做模糊用scipy.ndimage.gaussian_filter(rembg 已經裝了 scipy) Image.fromarray(float32_array, "RGB")會直接報錯,先.astype(np.float32)再按通道處理- 不要用
Remove-Item清理;cutout.py --clean已經封裝了目錄清理
Examples
輸入:「把這段 GIF 的背景摳透明」
→ cutout.py --input 動作.gif --outdir out --scale 2,再 verify.py,
→ 交付 GIF/WebP/APNG 三份,列出 720×720 / 70 幀 / 每幀 100ms / 總 7000ms。
輸入:「摳乾淨點,邊緣還有雜點,手臂和身體之間還有沒摳的地方」
→ 不要去調閾值。先跑 verify.py --zoom <框選座標> 確認缺陷位置,
→ 散點多就加 --median 3,縫隙不通用就 --stage matte --scale 3 重跑對應幀,
→ 再驗收。回覆裡說清是哪一類缺陷、用了哪條修復,別只說「已優化」。