JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

教網路看影片:影片分類與動作辨識

從追蹤像素邁向理解事件——深度網路如何透過運動流、3D 卷積與 I3D 技巧,學會辨識一段影片中正在發生的事。

從影像 CNN 到影片理解

在本系列至今,你一直在看像素移動:你把靜止影像變成連續串流、用光流量測每個像素的運動、再用追蹤器跨時間跟住物體。這些回答的都是「東西去了哪裡」。現在我們要問一個更難、更貼近人類的問題:「正在發生什麼事?」有人揮拍、一道浪打下來、一扇門被打開——這些都是事件,而辨認它們正是影片理解的工作。好消息是:你手上已經握有大半工具,也就是影像 CNN。唯一新加進來的材料,是「時間」。

本篇圍繞兩個關係密切的任務展開。影片分類是把整段短片指派到單一類別——「烹飪」「籃球」「一場雷暴」。動作辨識則是其中極常見的特例:類別本身就是一個人類動作——「跳躍」「切菜」「握手」。實務上兩者通常是同一套機器,只是對準不同的標籤集合:一個吃進一段短片、輸出各類別機率的網路。因此我們替其中一個打造的東西,幾乎都能用在另一個上。當例子是「某人在做某事」時我們說「動作辨識」,泛指情況時則說「影片分類」。

現在把唯一真正新增的困難講清楚:影片模型必須在一個序列上推理,而且幀的順序帶有意義。一張手靠近門的照片是含糊的;只有幀的推進過程才能告訴你門是在打開還是關上。這條線索貫穿整篇。我們會遇到的每一種架構,本質上都是對同一個問題的不同回答:我們要怎麼讓「順序」變得重要?

我們用一口氣把影像 CNN 複習一遍,因為下面所有東西都是它的延伸。CNN 把小小的、可學習的濾波器(kernel)在影像上滑動;每個濾波器對一小塊局部像素做加權和——這就是卷積——結果是一張特徵圖,會在濾波器所偵測的模式出現之處亮起(邊緣、角點,之後是車輪或眼睛)。接著池化把每張特徵圖縮小(例如對 2×2 區塊取最大值),讓表示更小、也更能容忍微小位移。把許多這種「卷積加池化」層疊起來,特徵就從邊緣長成紋理、再到物件部件、再到整個物件;最後一個分類器把頂層特徵圖轉成各類別分數。記住這幅圖:卷積 → 特徵圖 → 池化 → 重複 → 分類。

我們已熟悉的影像 CNN 流程:疊起來的卷積與池化把特徵從邊緣一路建到物件,最後分類器把它讀出來。影片模型把這幅圖延伸進時間。

一張由左到右的流程圖:輸入影像進入一疊卷積與池化層,產生愈來愈小的特徵圖,最後接到一個全連接分類器輸出各類別分數。

我們會逐步建立三個主要的架構構想,每一個都用不同方式把時間加進來。第一,把外觀與運動分開:跑兩個網路,一個看單幀的內容,一個看預先算好的運動(雙流構想,這正是你先前學光流的原因)。第二,在時空中做卷積:把 2D 濾波器延伸成也會沿時間滑動的 3D 濾波器,讓單一網路端到端地學會運動(3D 卷積)。第三,從 2D 啟動 3D:巧妙重用強大的、在 ImageNet 上預訓練好的 2D 網路來初始化 3D 網路(I3D 技巧)。讀完之後,你會有一棵小小的決策樹,知道何時該動用哪一招。

天真的基準法,以及它們為何不足

在動用聰明機器之前,謹慎的工程師會先試最笨、但可能行得通的方法——這樣花俏的方法才必須憑本事證明自己值得那份複雜。對一段短片,最笨而合理的基準是:拿你已經訓練好的影像 CNN,對每一幀各自獨立地跑一次,再把各幀的類別機率平均成一個預測。這就是逐幀平均,是一種晚期融合(我們在最後才融合,也就是每幀都先各自被完整分類之後)。

現在來看那個致命缺陷,值得你打從心底感受一下。平均是對順序不變的:一組數字的平均,不會因為你把這組數字洗牌而改變。所以逐幀平均的模型,字面上就分不出「開門」和「關門」、分不出「坐下」和「站起」、分不出「一杯水在裝滿」和「一杯水在倒空」。這些成對的動作共用一模一樣的幀——差別只在順序——而順序正是平均所丟掉的資訊。把短片倒著播,預測一點都不會變。

這恰好點出了缺的是什麼:時序建模——表示外觀如何演變的能力,而不只是表示出現了什麼。逐幀平均的網路有豐富的空間理解,卻有零時間理解。(平心而論,晚期融合並非毫無用處:對那些「單一關鍵幀就足以洩漏類別」的短片——「這是海灘嗎?」——它既便宜又強。它只在真正由運動定義的動作上崩潰。)從這裡開始,本篇的每一種架構都是對同一個問題的不同回答:我們要怎麼把時間順序注入網路?

雙流網路:外觀加上運動

第一個夠強的架構,把你學過的光流通通變現了。它的構想是兩個獨立 CNN 之間乾淨俐落的分工。空間流看單一張 RGB 幀,回答「是什麼」:有哪些物件、什麼場景、看起來像什麼?(一張人在網子旁騰空的靜照,已經暗示了「排球」。)時間流看運動,回答「怎麼動」:這個動作特有的運動模式。每一條流都是一個普通的影像 CNN;兩者合起來就構成一個雙流網路

關鍵的訣竅在於時間流的輸入。你餵給它的不是原始幀,而是一疊預先算好的光流,橫跨好幾個連續幀。回想第 2 篇:一個光流場在每個像素上給出一個小小的 (dx, dy) 向量,說明那個像素如何移動到下一幀。把比方說 10 對幀的光流疊起來,你就得到一個小張量,它的通道全都是運動。所以時間流字面上是把運動當成它的輸入影像來讀

一個光流場:在每個像素上,一個箭頭顯示它在兩幀之間如何移動。時間流把許多這樣的場疊起來,把這份運動當作它的輸入影像——把乾淨的運動直接交給網路,而不是逼它從原始像素重新發現運動。

一張影像疊上密密的小箭頭網格,每個箭頭指向某像素在相鄰幀之間移動的方向,運動愈快處箭頭愈長。

為什麼餵光流這麼有用?因為它把乾淨的運動資訊直接端到網路面前,而不是逼一個 CNN 從原始 RGB 像素去發現「運動」這個概念本身。計算光流是一個被充分研究過、近乎物理的問題(亮度恆定、孔徑問題——都來自第 2 篇);把它當成前處理步驟,等於替學習器卸下了一大塊重擔。網路接著就能把容量花在「哪些運動模式代表哪個動作」上,而不是花在重新發明運動估計上。經驗上,在以運動為主的動作上,光時間流單獨往往就贏過空間流——這強烈證明訊號就住在運動裡。

最後是融合:怎麼把兩條流合起來。最簡單的是晚期融合——讓每條流各自產生類別分數,再平均(或加權)它們,很像基準法,只是現在有一條流懂運動了。更豐富的選項是中期融合——在某個中間層把兩條流接合,讓網路能學到「外觀加運動」的聯合特徵(例如「這個物件這種方式移動」)。中期融合通常更勝一籌,因為它讓兩條流在做決定前先互動,但訓練更重。無論哪種,雙流設計都是本系列透過光流時序建模的回報,它也曾長期高踞動作辨識排行榜頂端。

3D 卷積:學習時空濾波器

第二個大構想,靠著教網路自己找運動來免掉那筆預計算稅。一旦看懂,這一步幾乎自然到令人不好意思。一個 2D 濾波器在兩個軸上滑動——高與寬——盯著一小塊方形區塊(比方 3×3 像素)。一個 3D 濾波器在三個軸上滑動——高、寬,外加時間——所以它盯著一個小小的時空立方體(比方 3×3×3:三像素高、三像素寬、三幀深)。這樣一個立方體可以直接裝下一個運動事件,例如「在第 t 幀偏左一格、在第 t+1 幀偏右一格的邊緣」——也就是一條向右移動的邊緣。濾波器端到端地直接從原始幀學到這個模式,不需要預先算好的光流。

y(t,i,j) = \sum_{d_t}\sum_{d_i}\sum_{d_j} w(d_t, d_i, d_j)\; x(t+d_t,\; i+d_i,\; j+d_j)

3D 卷積:一個輸出值是對一小塊時空立方體的加權和。

把它對照你已經會的 2D 卷積來讀。在 2D 裡你有 y(i,j) = Σ w(di,dj)·x(i+di, j+dj):第 i 列、第 j 行的輸出像素,是鄰近輸入像素的加權和,偏移量 di、dj 在濾波器範圍內掃過(例如 3×3 就是 −1,0,+1)。3D 版本加了一個新索引 t,也就是時間位置(哪一幀),以及一個對應的新偏移 d_t。於是:x(t+d_t, i+d_i, j+d_j) 是距離 d_t 幀、d_i 列、d_j 行的輸入像素;w(d_t, d_i, d_j) 是 3D 核中對應的可學習權重;而這個三重求和讓立方體沿著高、寬,外加時間滑動,替每個時空位置產生一個輸出值 y(t,i,j)。直覺是:每個輸出總結一小塊時空,所以濾波器能對運動有反應,而不只是對靜態紋理。

我們用一個小小的演算草圖把這個多出來的索引變具體:一個 3×3×3 濾波器掃過一疊 3 幀。要產生中心位置 (t, i, j) 的輸出,你把立方體對齊,使它的中央格落在輸入像素 (t, i, j) 上,然後對全部 27 個格子做乘加。這個立方體往回伸一幀 (t−1)、往前伸一幀 (t+1),在每個空間方向各伸一個像素。

# A single 3x3x3 filter over a 3-frame stack, one output value.
# x has shape (T, H, W); w has shape (3, 3, 3) indexed by (dt, di, dj) in {-1,0,1}.
# This computes y[t,i,j] for ONE center location -- the network slides this over all t,i,j.

def conv3d_one_output(x, w, t, i, j):
    acc = 0.0
    for dt in (-1, 0, 1):        # NEW: sweep over time (previous, current, next frame)
        for di in (-1, 0, 1):    # sweep over rows  (same as 2D)
            for dj in (-1, 0, 1):# sweep over cols  (same as 2D)
                acc += w[dt + 1][di + 1][dj + 1] * x[t + dt][i + di][j + dj]
    return acc  # one number summarizing a 3x3x3 cube of space-time

# Compared to 2D conv, the ONLY new thing is the outer `for dt` loop:
# the filter now also looks one frame back and one frame forward,
# so a weight pattern like 'bright on the left at t-1, bright on the
# right at t+1' fires for an edge moving rightward -- motion, learned directly.
相較於 2D 卷積,唯一的改變是多出一層對時間 (dt) 的迴圈。就是這一個新軸,讓濾波器看得見運動。

把許多這樣的層疊起來,就建成一個 3D 卷積網路;其原型是 C3D,它像 VGG 那樣疊 3×3×3 卷積,但在時空中進行。隨著加深,感受野現在不只在空間、也在時間上增長:早期層的一個神經元只看到幾幀,但高好幾層的神經元,透過整疊堆疊,擁有橫跨許多幀的有效視窗——長到足以看見一整個手勢,而不只是一閃。這正是你在 2D 裡熟悉的感受野增長,只是現在沿著第三個軸進行。

卷積是滑動的加權和。2D 核掃過高與寬;3D 核多加一道沿時間的掃描,因此每個輸出總結的是一小塊時空立方體,而不是一塊平面區塊。

一個小核格疊在輸入上,把重疊的數值相乘再加總成一個輸出格,接著滑到下一個位置。

感受野的增長。隨著層疊加,單一個深層神經元「看到」的輸入區域變大。在 3D 網路裡,這個視窗也在時間上擴張,讓深層單元能感知到完整的運動。

一張圖顯示深層的一個神經元如何透過數層往回連接到原始輸入的一大塊區域。

I3D:把 2D 膨脹成 3D,重用 ImageNet

上一節結束在一個尖銳的問題:3D 網路強大卻渴求資料,從頭訓練很難。這裡有個巧妙的解法。我們早就有在 ImageNet 上預訓練好的優秀 2D 網路——數百萬張標註影像教會了它們豐富的外觀特徵(邊緣、紋理、物件部件),那花了極大的計算量才學成。把它丟掉太可惜。膨脹式 3D 卷積網路(I3D)重用了它:拿一個成熟的 2D 架構連同它預訓練好的權重,把每個 2D 核沿著新的時間維度複製膨脹成一個 3D 核。結果是一個 3D 卷積網路,在第一天就已經懂得東西長什麼樣子。

w_{\text{3D}}(d_t, i, j) = \frac{w_{\text{2D}}(i, j)}{T}, \qquad d_t = 1, \dots, T

核膨脹:把 2D 濾波器複製到全部 T 個時間切片,再除以 T。

仔細拆開來看。w_2D(i, j) 是一個預訓練好的 2D 濾波器在空間偏移 (i, j) 處的一個權重。新的 3D 濾波器多了一個時間軸,有 T 個切片(T 是濾波器的時間深度,例如 T = 3 或 7)。我們把每一個時間切片 d_t = 1…T 都設成同一個 2D 權重,再除以 T。為什麼要除以 T?想像餵給膨脹後的網路一段「影片」,它其實只是同一張影像重複 T 次——一段凍結的短片。在每個空間位置上,3D 濾波器把它 T 個相同的切片對 T 個相同的幀求和:那就是 T 份 (w_2D/T)·x,加回來剛好等於 w_2D·x——原本的 2D 反應。所以一段靜態影片通過膨脹後的網路,會精準重現 2D 網路的行為:這是一個合理、忠實的初始化,而不是隨機雜訊。

這套啟動邏輯就是整件事的重點。依照建構方式,膨脹後的 3D 網路一出生就表現得像一個強大的影像分類器,所以它永遠不必從零重學外觀;接著在影片上訓練,只需要教它時間那一部分——這些特徵應該如何隨時間變化。我們把已知的 2D 知識沿時間均勻地鋪開,作為暖身起點,再在真實(會動的)影片上微調。這把一個難以處理的「從頭訓練 3D」問題,轉化成一個溫和的微調問題,也正是這個技巧最終讓深層 3D 網路變得實用。

最後的點睛之筆把前兩節合而為一。最強的 I3D 系統會把膨脹和雙流構想結合:訓練一個吃 RGB 幀的 I3D(從 ImageNet 膨脹而來),再訓練第二個吃疊起來的光流的 I3D,然後融合它們的預測——外觀與運動,現在各自都是一個深層 3D 網路。再搭配 Kinetics——一個大型、多樣、專為餵飽渴求資料的 3D 網路而建的人類動作資料集——雙流 I3D 樹立了動作辨識的現代基準。所以我們的三個構想並非互相競爭;這個領域多年來的最佳結果,字面上就是三者一起上:把 3D 卷積(第 4 節)膨脹(第 5 節),再排成雙流(第 3 節)。

卷積之外的時序建模,以及如何評量

3D 卷積並不是做時序建模的唯一辦法。一個經典替代方案:對每一幀跑一個 2D CNN 得到逐幀的特徵向量,再把這個向量序列餵進一個 RNN / LSTM。這個循環網路把一個隱藏狀態從一幀帶到下一幀——一份持續更新的記憶——所以它在第 t 幀的預測取決於之前看到的一切,這讓它在設計上就對順序敏感(它能通過洗牌測試)。這乾淨地把工作分開:CNN 處理空間,RNN 處理時間。它在 3D 卷積成熟之前很流行,至今仍是一個合理而輕量的選擇。

更現代的做法是影片 transformer 裡的時空注意力。回想 transformer 系列裡的注意力:注意力不像卷積那樣只看固定的局部鄰居,而是讓每個位置對所有其他位置計算一個加權關係,學會該關聯哪些。像 TimeSformerViViT 這類影片 transformer 把它套用到跨幀:第 t 幀的一個圖塊可以直接注意到第 t+20 幀的一個圖塊,不需要一整疊卷積去搭橋。它的口號是「讓模型自己決定要關聯哪些時刻」——網路自己學到,比方說「揮臂」和「投擲」就是定義「投球」的兩個時刻,無論它們相隔多遠。

多頭注意力。每個位置對所有其他位置形成加權關係;數個「頭」並行學習不同類型的關係。在影片中,它被套用到跨幀,讓模型能直接關聯相隔遙遠的時刻。

一張注意力示意圖:一組查詢、鍵、值向量產生一個加權和輸出,數個並行的注意力頭在最後合併。

這浮現出一個實務上的軸線:短程 vs 長程的時間推理。卷積和短片在短程線索上很出色(單一次揮擊、一個腳步)。但許多真實動作是長的——「組裝家具」「泡茶」——在好幾秒甚至好幾分鐘裡展開。長動作之所以難,有個清醒的理由:基於計算考量,我們通常只餵給網路一段短片(幾幀到幾十幀),所以它從未一次看到整個事件。這讓取樣變得極其重要:你要取連續的幀(密集、細緻的運動、極小的時間視窗),還是取分散在整段影片的幀(稀疏、粗略的運動、完整覆蓋)?這個選擇可能直接決定網路是否連回答所需的證據都拿不到。

最後,我們怎麼衡量成功?有兩種粒度很重要。短片層級準確率評分每一段短片的預測;影片層級準確率評分整段影片,通常做法是對影片取樣出數段短片各跑一次再平均——它幾乎總是更高,也是最常被引用的數字。一如影像辨識,我們在 Kinetics 這類基準上回報 top-1(單一個最佳猜測對不對?)與 top-5(真實答案在不在前五名猜測之內?)。而貫穿這一切的,是那永恆的準確度 vs 計算量取捨:3D 網路與 transformer 準確但沉重,逐幀方法便宜卻對時間視而不見。

  1. 單一幀就能判定類別嗎(一個場景、一個物件)?逐幀平均/晚期融合的 2D CNN 既便宜又強——從這裡開始。
  2. 動作是由運動定義的,而且負擔得起預先計算光流嗎?動用雙流網路(或在 RGB 旁加一條光流流)。
  3. 想要端到端學運動、不要光流,而且手上有大型資料集(或有可膨脹的 ImageNet 2D 網路)?用 3D 卷積網路,最好以 I3D 方式初始化。
  4. 需要關聯相隔遙遠的時刻、或建模長動作,而且還有餘裕的計算量?用帶時空注意力的影片 transformer。
  5. 計算量或記憶體很吃緊?退回 2D CNN 加 LSTM,或用稀疏幀取樣搭配輕量的 2D 骨幹。

那棵決策樹就是你關於影片分類動作辨識的實用地圖。你現在懂得四根槓桿——幀融合、雙流、3D 卷積與注意力——以及每一根如何注入時間順序,還有如何評量與權衡結果。接下來,在本系列的最後一篇,我們會從「替整段短片貼標籤」推進到對影片做密集推理:把每一個像素跨時間地分割與內插。