跨時間的稠密預測:前沿
歡迎來到階梯的頂端。回頭看看這趟攀登。在第 1 篇,我們察覺到「有東西在動」——一個粗略的二元訊號。在第 2 篇,我們用光流稠密地測量了運動,為每個像素都標上一支速度箭頭。在第 3 篇,我們不再只看像素,而開始追蹤整個「物件」,讓它的身分跨影格存活。在第 4 篇,我們一路拉遠鏡頭,對整段影片問出一個問題:「這裡發生了什麼事?」每一階都用一點空間細節,換取多一點語意上的企圖心。
這最後一篇拒絕那種取捨。我們現在同時要求兩者:對「每一影格中的每一個位置」都給出標籤或數值,而且——最大膽地——還要產生原始片段中根本不存在的影格。我們將研究稠密影片的兩大旗艦任務。第一個是 影片物件分割(VOS):產生乾淨的逐像素物件遮罩——不是一個框,而是精確的輪廓——並讓這個遮罩一影格接一影格都保持正確。第二個是 補幀:合成全新的中間影格,字面上發明攝影機從未拍下的時間瞬間。
我們從一開始就坦白,因為這正是研究的前沿。這些系統既真的令人驚艷,也真的脆弱。它們大量倚賴你已經學過的運動機制——光流提供了強大的先驗——但在遮擋、快速運動以及長時間的情況下,它們會以可預測、也很有啟發性的方式失敗。本篇的目的不是要賣給你一個已解決的問題;而是要給你一張概念地圖:理解這些任務「為何」困難、最好的方法「如何」攻克它們、以及它們仍在「哪裡」崩潰。
影片物件分割:能延續的遮罩
你已經懂得影像分割:網路讀入一張圖,為每個像素貼上標籤——這個像素是「狗」,那個是「背景」。影片物件分割就是同樣的逐像素標註,但它必須沿著第四個軸——時間——保持一致。在每一影格中產生遮罩的骨幹,正是你先前認識的影像分割機制;而「新」的問題在於:要讓橫跨數百影格的遮罩都指向同一個物件,而且邊緣是平滑移動,而不是爬行、閃爍。
一張示意圖,顯示輸入影像與輸出,輸出中每個像素都依其類別標籤上色。
這裡有兩種任務設定,差別在於你在第 0 影格得到多少協助。在半監督 VOS 中,系統會交給你第一影格的真值遮罩(由人類、或另一個模型把目標描出一次),你的任務是把那個遮罩傳播到之後的每一影格。在非監督 VOS 中,你完全得不到提示:系統必須自己「發現」那個顯著的、主要在動的物件,並全程把它分割出來。半監督是「跟著這個確切的東西」;非監督是「自己搞清楚什麼重要,然後跟著它」。它們聽起來相似,但後者難得多,因為你還得解決「是哪一個物件」的問題。
那為什麼不乾脆對每一影格各自跑一個很厲害的影像分割器就好?因為每一影格都是孤立決定的,結果會閃爍:網路在不同影格把同一條邊緣畫得略有不同,或某個像素只在某一影格翻轉了類別,看起來就像視覺雜訊。更糟的是,當有多個相似物件時(三隻一模一樣的斑馬),逐影格模型完全沒有「身分」的概念——它可能在影格之間把哪一隻斑馬是「物件 1」給調換了。這正是 物件追蹤 被發明出來要解決的失敗。所以最誠實的一句話定義是:VOS = 像素級精確的分割 + 追蹤。我們需要分割的空間精度,「也」需要追蹤的時間身分,兩者融合。
一張對比圖,顯示語意分割把同類別所有物件塗成相同顏色,相對於實例分割給每個獨立物件各自的顏色。
目前主導半監督 VOS 的範式是記憶網路(想想 STM 及其高效後裔 XMem)。這個想法簡潔得漂亮:當你處理影片時,維護一個記憶庫——一個不斷增長的集合,裝著過去的影格以及你為它們產生的遮罩。要分割新影格時,你把它的像素當成「查詢(query)」,把儲存的影格當成「鍵(key)與值(value)」:對當前影格的每個位置,你問「哪些儲存的區域看起來最像我?」並把它們的遮罩資訊拉過來。這就是跨時間的注意力——正是第 4 篇影片模型裡的那套機制,只是現在被用來檢索外觀,而不是做分類。因為記憶庫裡裝著真正的第一影格遮罩(值得信賴的真值)外加許多近期影格,模型即使在物件一時看起來怪異之後,仍能重新錨定到真實的物件上。
真實影片裡有好幾個物件,而且它們會互相遮擋。多物件 VOS 的處理方式是為每個目標保留一個獨立的遮罩通道,並在每個像素上用類似 softmax 的「這個像素屬於哪個物件?」的決策來化解競爭——這樣兩個遮罩就不能同時宣稱擁有同一個像素。遮擋是最殘酷的情況:當物件 A 走到柱子後面,它可能消失五十個影格。純逐影格的方法會弄丟它;記憶式方法卻能存活,因為當 A 重新出現時,它先前儲存在記憶庫中的外觀仍然匹配,遮罩於是被恢復。這種對「消失再回來」的穩健性,正是把玩具分割器和可用分割器區分開來的關鍵——也正是為什麼定義現代做法的是「分割 + 追蹤 + 記憶」,而不是單純的分割。
用光流傳播,以及一致性的概念
在記憶網路之前,讓遮罩延續最自然的方式,是讓光流把它帶過去。回想第 2 篇:稠密光流為每個像素給出一支箭頭,告訴你場景中那一小塊在影格之間去了哪裡。如果某個像素在第 t−1 影格被標為「物件」,而我們知道它在第 t 影格變成了哪個像素,我們就能直接「把標籤沿著箭頭搬過去」。光流是影格之間的黏著劑:它告訴我們對應關係,而標籤只是搭著運動向量、換到新座位上的乘客。
影像上的一片小箭頭,每支箭頭顯示一個像素移動到下一影格的方向與速度。
這個操作有個名字——形變(warping)——也有一條精確的公式。把它讀成「這一影格的預測遮罩,就是上一影格的遮罩,在『這個像素是從哪裡來的』那個位置取樣得到的」:
基於光流的形變:把上一影格的標籤沿著運動箭頭拖過來。
讓我們定義每個符號。x 是「當前」第 t 影格中的一個像素位置——比方說點 (100, 50)。M_{t-1} 是一個已知量:我們已經擁有的上一影格遮罩(物件處為 1,其餘為 0)。f_{t-1\to t}(x) 是該位置的光流向量——這支箭頭告訴我們那裡的場景從 t−1 「到」 t 移動了多少。\hat{M}_t 上的帽子標記它是一個「預測」,是我們對當前遮罩的猜測。關鍵動作是那個減法:為了現在填上像素 x,我們「往回看」到來源位置 x - f。具體來說:如果在 x=(100,50) 處光流為 f=(4,0)——物件向右漂移了四個像素——那麼 \hat{M}_t(100,50) = M_{t-1}(96,50)。我們把標籤從這東西「原本所在」的位置複製過來。對每個像素都這麼做,上一影格的整個遮罩就會整體滑動到它的新位置。那一個減法就是全部的直覺:把昨天的標籤,沿著今天的運動箭頭拖過來。
import numpy as np
def warp_mask(mask_prev, flow):
"""mask_prev: (H, W) labels at frame t-1.
flow: (H, W, 2) vectors f_{t-1->t} for each pixel.
returns: (H, W) predicted mask for frame t."""
H, W = mask_prev.shape
ys, xs = np.mgrid[0:H, 0:W] # grid of current-frame coords
# 'look back' to the source location each pixel came from
src_x = xs - flow[..., 0]
src_y = ys - flow[..., 1]
# x - f can land off-grid -> round/interpolate; clip to stay in bounds
src_x = np.clip(np.round(src_x).astype(int), 0, W - 1)
src_y = np.clip(np.round(src_y).astype(int), 0, H - 1)
return mask_prev[src_y, src_x] # sample the source labels現在是誠實的大師級提醒——因為形變「並非」魔法。第一,x - f 很少剛好落在整數像素上,所以我們必須在鄰居之間內插(上面的程式只是四捨五入;更好的系統用雙線性取樣)。第二,更嚴重的,是遮擋與去遮擋。當某物被遮住時,兩個當前像素可能回溯到同一個來源,遮罩於是摺疊到自己身上。當某個區域是新被「揭露」出來時(去遮擋——例如走動的人身後露出的地板),根本「沒有來源像素」可複製:公式指向的那個位置,對這個物件而言,上一影格根本不存在。那些洞必須被「發明」出來。第三,誤差會累積:形變 1→2、再 2→3、再 3→4,每一個小小的光流錯誤都會疊加,所以純粹一連串的形變必然會從物件上漂移開來。
這正是「為什麼」上一節的記憶/注意力方法會勝過純光流傳播:注意力可以從一個值得信賴的儲存影格重新匹配真實物件,治癒形變無法處理的漂移。但光流遠未過時。現代的觀點是一種合作關係:光流提供強大的運動先驗——對所有東西去了哪裡的、有把握的第一猜測——而一個學習過的網路修正它的錯誤,填補去遮擋的洞、剔除壞的對應。所以 光流,尤其是第 2 篇的 稠密光流,並不是遺物;它是讓 影片物件分割 變得可解的運動引擎。這就是第 2 篇的稠密光流付出它最後一筆紅利之處。
影片補幀:發明時間
到目前為止的每個任務都在「分析」影片。現在我們要「生成」它。影片補幀取兩張真實的、連續的影格,並在它們「之間」合成出一張或多張全新的影格——攝影機從未記錄過的瞬間。何必這麼做?為了平滑的慢動作(在每一對影格之間插入七張發明出來的影格,把每秒 30 張真實影格變成絲滑的 240 張)、影格率提升(讓 24 fps 的電影在 120 Hz 螢幕上流暢播放),以及更平滑的播放或影片壓縮。這是整條軌道上最具生成性——也可說最神奇——的任務。
主導的做法,再一次,建立在運動之上。直覺是:如果一個像素在兩張真實影格之間以大致直線、大致等速行進,那麼它在某個中間瞬間的位置,就只是沿著那條直線路徑走到一半的某個點——一種沿光流向量的線性內插。所以我們估計兩影格之間的光流,把它縮放到目標瞬間,把「兩個」鄰居都向那個瞬間形變,再把兩張形變後的視圖混合起來。
- 在兩張真實影格之間雙向估計光流:前向 f(0→1) 與後向 f(1→0)。
- 選一個目標時間 t,落在 (0,1) 之間——例如 t=0.5 代表正中間的影格。
- 把每條光流按 t(或 1−t)縮放,並把每張真實影格形變到那個中間瞬間。
- 用「偏向較近的原始影格」的權重,把兩張形變後的影格混合起來。
- 精修:一個網路預測柔性的可見度/遮擋遮罩,修補洞與鬼影。
兩影格之間的一片運動箭頭,每支箭頭中途標出一個中點,代表內插後的位置。
中間時間 t(落在 0 與 1 之間)的線性光流內插。
逐個符號來看。I_0 與 I_1 是兩張「真實」影格(起點與終點)。t \in (0,1) 是我們想要的分數瞬間——t=0.5 就是正中間。f_{0\to 1} 是前向光流(像素如何從 I_0 移到 I_1),f_{1\to 0} 是後向光流。\hat{I}_t 是合成出來的中間影格。看第一項:x - t\,f_{0\to 1}(x) 只取了完整運動的「一小部分」t,所以它把 I_0 「形變到一半」、朝中間移動——一個原本要走 10 像素到影格 1 的像素,在 t=0.5 時只移動 5 像素。第二項則從 I_1 做鏡像,往回移動 (1-t) 的路程。最後權重 (1-t) 與 t 把兩張形變視圖混合起來,偏向「在時間上較近」的那張真實影格:在 t=0.5 時兩者各半平均;在 t=0.1 時結果是 90% 的起始影格,這是對的,因為我們才剛剛越過它一點點。
現在是那些讓這仍是活躍研究問題的困難部分——注意每一個都是上述某個假設的失效。遮擋:如果某區域只在兩張影格之一中可見(在 I_0 中被移動的車擋住、卻在 I_1 中露出來的牆),天真地平均兩個形變會把「它根本不存在」的那張影格的垃圾混進來——所以在那裡混合「不該」是各半,而應該只取「真正看得到該像素」的那張影格。非線性/快速運動:直線等速的假設對彈跳的球或快速轉身會失效,於是線性形變把像素落到了錯誤的位置。混合瑕疵:即使光流不錯,平均兩張略為錯位的形變也會產生鬼影——淡淡的雙重影像。這就是為什麼現代深度補幀器不止步於公式:它們預測「任務專屬」的光流、輸出柔性的可見度(遮擋)遮罩讓每個像素都取自真正看得到它的那張影格、並跑一個合成網路來精修混合、在兩個形變都不可信之處幻想出合理的內容。整座大廈以 運動估計 為引擎,正如 補幀 所要求的——但在 光流 單獨失效之處,是學習在收拾善後。
評估、瑕疵與誠實的極限
大師不只會打造這些系統——他們還懂得如何「評判」它們,而一個數字是會說謊的。先從 VOS 開始。我們評分兩件互補的事。區域指標 \mathcal{J} 是預測遮罩相對於真值遮罩的交集除以聯集(IoU):重疊面積除以聯集面積,完美匹配為 1、毫無重疊為 0——它問的是「你整體上有沒有蓋對像素?」邊界指標 \mathcal{F} 則評分輪廓精度:你的遮罩「邊緣」與真實邊緣對得有多齊,這能抓出那種面積重疊還行、但邊界鬆垮、毛糙的團塊遮罩。標準的頭條數字是兩者的平均,\mathcal{J}\&\mathcal{F},這樣一個方法就無法只靠擅長其中一項取勝。
對補幀,我們把合成的影格與被保留下來的真實影格比較。經典的像素保真度指標是 PSNR 與 SSIM。PSNR(如下定義)量測原始誤差;SSIM(結構相似性)則聰明一些,比較的是局部的亮度、對比與結構,而非赤裸裸的像素差。以下是每位影片研究者都認識的公式:
峰值訊噪比,以分貝(dB)計——越高越好。
拆解它。\mathrm{MSE}——均方誤差——是對全部 N 個像素取平均、預測像素 I_{\text{pred},i} 與真實像素 I_{\text{gt},i} 之差的平方;當重建偏離很多時它很大,完美複製時為零。\mathrm{MAX} 是像素可能的最大值(8 位元影像為 255),所以 \mathrm{MAX}^2 是影像「能」承載的最強訊號。於是比值 \mathrm{MAX}^2/\mathrm{MSE} 就是「真實訊號強度相對於重建誤差」,而 10\log_{10} 把那個比值轉成分貝——一個壓縮過的刻度,越大越好。算個快速的數字:8 位元影像上微小的平均誤差 \mathrm{MSE}=1 給出 10\log_{10}(255^2/1) \approx 48 dB(極佳);較大的 \mathrm{MSE}=100 給出 \approx 28 dB(明顯劣化)。直覺:PSNR 問的是「真實訊號相較於你引入的誤差有多強?」
把特徵性的瑕疵列成清單,並把每一個都追溯回我們已經點過名的根本原因。VOS 中的閃爍——一張在影格間微微抖動的遮罩——來自缺乏時間綁定的逐影格決策(第 2 節)。補幀中的鬼影——淡淡的雙重影像——來自混合兩張錯位的形變(第 4 節)。快速運動下的模糊來自運動大或非線性時錯誤的光流,再加上前述 MSE 偏好模糊的誘因。遮擋/去遮擋處的洞來自形變對新揭露的區域沒有有效的來源像素(第 3 節)。注意這個規律:每一種瑕疵,都是某個被打破的特定假設留下的、看得見的指紋。
最後,兩個誠實的現實。算力:稠密影片意味著要對數千個影格做逐像素的工作,而記憶網路必須儲存並注意越來越長的歷史——所以決定一個方法能否落地的,不只是準確度,還有記憶體與延遲(這正是為什麼 XMem 高效的記憶體很重要)。未解的失敗模式:這些系統仍會弄丟細小結構(頭髮、腳踏車輻條)、在長時間遮擋後混淆幾乎一模一樣的物件、在很長的影片上漂移、並在被要求對「快到或非剛性到光流追不上」的運動補幀時產生幻覺。把這些直說出來。前沿是真的,進展是真的,懸崖也是真的。
貫穿的主線:運動就是訊號
退一步,把整條軌道一次看完,會發現有一個理念貫穿了每一篇:運動估計是影片理解的連結組織。它在第 1 篇偵測變化。它在第 2 篇成為 光流 那顯式而稠密的場。在第 3 篇它驅動了卡爾曼濾波器的「預測」步,並推動把偵測串成軌跡的資料關聯。在第 4 篇它餵入時間串流——那「運動視角」——讓網路得以辨識一個動作,而不只是一個姿勢。而在本篇,它既「傳播」標籤(在 VOS 中把遮罩向前形變),也「合成」像素(在補幀中把影格向一個發明出來的瞬間形變)。同一支箭頭,在每一階都被重複使用。
誠實地向前看。這個領域正從任務專屬的流水線,走向影片基礎模型——在海量影片上預訓練、可調適到偵測、分割、影片分類 等多種任務的大型網路。時空 transformer 把你在 VOS 記憶與動作辨識中看到的注意力,擴展成一個能同時對「何處」與「何時」進行推理的單一架構。而基於擴散的影片生成把合成推得遠遠超過補幀,能從文字或單張影像生成整段片段——與補幀同樣的生成企圖心,只是規模放大到極致。
但請守住你辛苦掙來的懷疑精神。那些未解的挑戰,正是本篇教你去看見的:長程時間連貫性(在數百影格、而非僅僅幾張之間保持一致)、算力(影片極其龐大,而對時空做二次方的注意力是殘酷的),以及評估(我們仍缺乏能可靠匹配人類對影片判斷的指標——PSNR 的模糊問題,放大到整段生成的片段)。這些不是註腳;它們是活生生的研究問題。
這就是運動與影片的大師層級。你現在能在每一種粒度上推理一段影片——一閃而過的變化、一片稠密的箭頭場、一個被追蹤的身分、一個被辨識的動作、一個能延續的逐像素遮罩、以及一張被發明出來的影格——而且同樣重要的是,你能精確說出每個方法「為何」有效、「在哪裡」崩潰。你已握有概念地圖與誠實的提醒。前沿是開放的;去看它如何運動吧。