以前去背,你得先拍一張沒有人的背景照:RobustVideoMatting 讓影片自己記得上一幀
要把一個人從影片裡摳出來換到別的背景,五年前的標準流程是租一間有綠幕的棚,把光打勻,然後在後製軟體裡下色鍵。
綠幕之後出現的第一代替代方案叫 Background Matting,到 BGMv2 已經相當堪用。它不要棚了,但要你先拍一張「沒有人的純背景照」當參考。這一步聽起來很小,實際上它把用途鎖死了:攝影機不能動,光線不能變,你也不能在人已經站在畫面裡的時候才想到要去背。
同一時期的另一條路是 trimap。你逐幀標出「這裡一定是人、這裡一定是背景、這一圈我不確定」,模型負責把不確定的那一圈算清楚。品質很好,代價是有人要坐在那裡標。
再往後,一般的 neural matting 終於不需要任何額外輸入了。丟一張圖進去就吐一張 alpha。問題換了個地方冒出來:它把影片當成一疊照片,每一幀各算各的。人一動,邊緣就逐幀跳動,看起來在閃;被東西擋住再出現,前後也接不起來。
RobustVideoMatting(下稱 RVM)是 ByteDance 在 WACV 2022 發表的模型,論文題目直接把答案寫在裡面:Robust High-Resolution Video Matting with Temporal Guidance。它只吃影片本身,不要棚、不要背景照、不要 trimap,而且處理第 n+1 幀的時候,手上還有第 n 幀留下來的東西。
底下這幾段就是同一件事的「以前怎麼做」跟「現在怎麼做」對照,一項一項來。
模型怎麼看影片:從重新認識,到一路盯著
以前每幀重新算一次,現在把上一幀的狀態接回來。
RVM 的 decoder 裡放了 4 層 ConvGRU,也就是把 RNN 那套 hidden state 換成卷積版。第 n 幀算完會吐出 4 個 recurrent state,第 n+1 幀把它們接回去當輸入,模型因此記得上一幀的人在哪、邊緣長什麼樣子。
用個比方:一般模型每一幀都在重新認識這個人,RVM 是一路盯著這個人在動。
這句不只是說法。論文用 dtSSD 衡量時間連貫性,RVM 是 1.01,MODNet 是 1.57,數字越低越穩。這一項差距最直接對應到肉眼看到的「不閃」。
效能上還有個小聰明,我第一次讀到的時候愣了一下:ConvGRU 只吃一半的 channel。
1 | self.gru = ConvGRU(channels // 2) |
一半的特徵直接穿過去,另一半才付 recurrent 的計算成本。四層 GRU 分別掛在 1/16、1/8、1/4、1/2 四個尺度上,這就是 recurrent state 剛好是 4 個的原因。
解析度怎麼扛:從按解析度付錢,到 4K 跟 HD 同價
以前想跑 4K 就得付 4K 的算力。現在不用。
論文 Table 4 那組數字是整個設計最漂亮的地方:
| 解析度 | downsample_ratio | FPS(GTX 1080 Ti) | GMACs |
|---|---|---|---|
| 512×288 | 1 | 131.9 | 4.57 |
| 1920×1080 | 0.25 | 104.2 | 4.15 |
| 3840×2160 | 0.125 | 76.5 | 4.15 |
HD 跟 4K 的計算量一模一樣。原因是 downsample_ratio 把送進主網路的畫面壓到差不多大:HD 乘 0.25 是 480×270,4K 乘 0.125 也是 480×270。主網路根本不知道原圖多大。
那細節怎麼補回來?靠 Deep Guided Filter。整條資料流長這樣:
1 | src(原始高解析度影格) |
DGF 學的是「怎麼把低解析度的粗略答案,照著高解析度原圖的紋理線索重新插值回去」。所以髮絲邊緣會沿著原圖的真實邊界走,而不是把糊掉的 alpha 硬放大。整個管線裡只有這一步在原解析度上跑。
前景怎麼算:從畫出來,到只說差多少
一般做法是讓網路直接預測前景的 RGB。RVM 預測的是殘差:
1 | fgr = fgr_residual + src |
網路只要學「這一格的前景跟原圖差多少」。絕大多數像素上前景就等於原圖,輸出接近 0 就好,學習負擔小很多,也比較不會把人臉顏色畫歪。
這種「只學差值」的招式在 ML 裡到處都是,但放在 matting 上特別合理:畫面裡真正需要動的像素只有人的邊緣那一圈。
模型該多大:從越大越好,到官方叫你用小的
RVM 只有兩個版本,model.py 裡就一行 assert,合法值只有這兩個:
| variant | Backbone | 參數量 | 權重大小 | HD FPS |
|---|---|---|---|---|
mobilenetv3 |
MobileNetV3-Large | 3.749 M | 14.5 MB | 104.2 |
resnet50 |
ResNet50 | 26.890 M | 102.9 MB | 71.1 |
README 對 ResNet50 的原話是只有「small performance improvements」。大了 7 倍,品質只小贏,所以官方自己叫你用 MobileNetV3,除非你在做離線的高品質輸出。實際下載量也是這樣:MobileNetV3 權重 14 萬次,ResNet50 只有 3.6 萬次。
一個 14.5 MB、375 萬參數的模型,塞得進消費級筆電的 GPU,這才是它到現在還被一堆下游專案當模組用的理由。
有幾件事,五年來一點都沒變
前面講的都是進步。接下來這幾件是這個 repo 到今天還在絆人的地方,而且大多不寫在 README 開頭。
先講最容易誤判的:README 那張測速表不是你跑 inference.py 會看到的速度。 README 自己的 Note 3 就寫了,那些數字量的是 tensor throughput,附的轉檔腳本「預期會慢很多」,因為沒用硬體影片編解碼,也沒把 tensor 傳輸放到平行執行緒。
issue #206 是活生生的案例:使用者跑 inference.py 只有 4.2 it/s、RTX 2060 使用率在 0 到 13% 之間跳;同一台機器跑 inference_speed_test.py 有 33.2 it/s。差了約 8 倍,瓶頸在編解碼和資料搬運,不在模型。要做真正的即時管線,官方建議自己接 PyNvCodec。
環境相依也是個坑。requirements_inference.txt 全文五行,版本全部釘死在 2021 年:
1 | av==8.0.3 |
torch 1.9.0 的預編譯版不含 40 系列顯卡需要的 compute capability,所以 issue #221 和 #232 都是 RTX 4090 報錯。實務上多數人直接忽略釘死的版本裝新版 torch。
授權那條更麻煩。程式碼是 GPL-3.0,強 copyleft,包進閉源商業 App 是明確衝突。而權重的授權呢?issue #191 在 2022 年 8 月開出來直接問這件事,多位開發者附議並指出這對商用和上架 App Store 影響重大,作者一次都沒回覆,issue 至今仍是 open。保守做法是假設權重也受 GPL-3.0 約束,要商用請找法務評估。
還有一件該知道的事:這個 repo 的核心模型自 2021 年 9 月釋出 v1.0.0 之後就沒再動過,master 最後一筆 commit 停在 2023 年 3 月 13 日。9,463 顆星,但它現在的角色是一個穩定堪用的現成元件,不是還在演進的專案。
最容易寫錯的那三行
如果你只從這篇帶走一段程式碼,帶這段。RVM 的精髓在 recurrent state 怎麼傳,而它也是最容易寫錯的地方:
1 | model = MattingNetwork('mobilenetv3').eval().cuda() |
關鍵在那個星號:
1 | fgr, pha, *rec = model(src, *rec, downsample_ratio) |
下一圈迴圈就帶著上一幀的記憶。官方文件特地用 Correct / Wrong Way 對照,因為漏掉這件事的後果很陰險:
1 | # 錯誤:沒有循環傳遞,等於把影片當一疊獨立照片處理 |
這樣寫不會噴錯,它會跑,只是整個模型退化成逐幀影像去背,這篇前面講的所有好處全部消失。你要靠肉眼看邊緣有沒有在閃才會發現。
順便講幾個同一類的地雷。downsample_ratio 的判準只有一句:讓縮小後的解析度落在 256 到 512 之間,而且官方明寫 higher is not always better。全身入鏡的影片要用比特寫更高的值(1080p 特寫 0.25,全身 0.4),傳 None 走自動模式算出來是 0.267,偏向特寫那一欄,所以拍全身不要只靠自動值。另外 output_type='video' 給你的不是透明背景,是綠幕合成的 mp4,因為一般影片格式沒有 alpha 通道;要真透明得用 png_sequence。轉場的時候記得把 rec 重設回 [None] * 4,不然 GRU 會帶著轉場前的記憶進到完全不同的畫面。
這五年真正改變的東西
回頭看綠幕、背景照、trimap 這三種舊做法,它們有個共同點:都在想辦法從外部補一個資訊給模型。棚補的是「背景是純色的」,背景照補的是「背景長這樣」,trimap 補的是「這一圈才要算」。
RVM 沒有補任何外部資訊。它做的是把一個一直都在、卻被丟掉的東西撿回來:影片的相鄰兩幀之間本來就有大量重複,上一幀算出來的東西,這一幀有九成還能用。舊做法的模型每一幀都把它扔了,然後回頭要你用綠幕來補。
認對這件事之後,剩下的設計決策幾乎都是同一句話的變形:能不算的就不要算,能沿用的就沿用。ConvGRU 只吃一半 channel、只有 DGF 在原解析度跑、只預測前景殘差不預測整張前景,三個決策一個形狀。
它也還有沒解掉的部分,而且是設計上的邊界不是 bug:pha 只有 1 個 channel,畫面有多人時是「所有人 vs 背景」,沒辦法分出第一個人和第二個人;影子去不掉,issue #22 有人問,作者沒給解法;髮絲級的細節後來被 SGHM、SAM2Matting 這類方法拿來當對照組並宣稱勝出。iPhone 12 跑 1080p 每幀要 180 到 200 ms,換算約 5 FPS,離手機上即時還很遠。
補一句誠實的:這篇是照論文、README、官方推論文件和一輪 issue 讀出來的,我沒有在自己的機器上跑過完整的轉檔管線,上面所有速度數字都來自官方或 issue 回報者。如果你要拿它上線,inference_speed_test.py 和 inference.py 兩邊都跑一次,差距會告訴你瓶頸在哪。
來源
- GitHub:PeterL1n/RobustVideoMatting
- 論文:arXiv:2108.11515,Shanchuan Lin、Linjie Yang、Imran Saleemi、Soumyadip Sengupta,WACV 2022
- 官方推論文件:documentation/inference.md
- 瀏覽器即時 Demo(可以看到 recurrent state 長什麼樣):線上試玩










