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

人臉的形狀與外觀:特徵臉、Fisher 臉與可變形模型

把一張人臉當成廣大空間中的一個點,學出真正重要的少數方向 — 從特徵臉、Fisher 臉,到能以學到的方式變形的形狀與紋理。

一張人臉是百萬維空間中的一個點

先做一個觀念上的翻轉,整篇指南就會豁然開朗。拿一張小小的灰階照片,比方說 100 × 100 像素,那就是 100 × 100 = 10,000 個數字,每個數字是 0(黑)到 255(白)之間的亮度值。現在別再把這 10,000 個數字想成一格一格、用眼睛看的小網格,而是把它們想成一條長長的清單——一根有 10,000 個項目的直行向量。一條有 10,000 個數字的清單,正好就是 10,000 維空間裡的一個點,就像兩個數字 (x, y) 是紙上的一個點、三個數字是房間裡的一個點一樣。把某一個像素調亮一點,你就沿著這 10,000 條座標軸的其中一條走了一小步。一張真正的百萬像素人臉,字面上就住在一個百萬維的空間裡。

這個空間大到無法想像,而關鍵事實是:它裡面幾乎沒有任何地方看起來像個東西。隨機挑 10,000 個數字,得到的「影像」就是一片雪花——沒調好的類比電視那種視覺雜訊。人臉並不是散落在整個空間各處,而是擠在其中薄得驚人、而且平滑的一小塊區域裡。往某些方向移動一點點,臉還是臉(臉頰圓潤一些、嘴角多了一絲笑意);但往幾乎其他所有方向移動同樣的距離,臉立刻就溶解成雪花。這塊真實人臉所居住的、薄薄而彎曲的區域,就叫做人臉子空間(face subspace),或人臉流形(face manifold)。

這個重新框定的觀點,正是後面一切的動機。如果人臉真的只沿著少數幾個方向變化,那我們應該能把它從 10,000 個數字壓縮到大約 50 個,而且幾乎看不出損失——而在那個整潔的小空間裡做辨識,會比在原始像素的叢林裡容易得多。這就是我們接下來要建立的子空間方法的核心想法:特徵臉以及它的近親。它也乾淨地嵌進你已經學過的古典辨識流程裡:我們只不過是在把東西交給分類器之前,選了一個更聰明的特徵表示(一段短短的編碼),而不是直接比對原始像素。

特徵臉:用主成分分析看人臉

特徵臉用一個經典的統計工具——主成分分析(PCA)——回答這個問題:「人臉變化最大的少數幾個方向是哪些?」要記在腦子裡的畫面是一份食譜。先從一張平均臉開始——把一大疊訓練照片一個像素一個像素地平均起來,得到的那張模糊、通用的臉。接著保留一小組「材料臉」,要重建出某個特定的人,就在平均臉之上把每種材料各加上一份加權的量:平均臉 + 3.1 ×(材料₁)− 1.7 ×(材料₂)+ 0.4 ×(材料₃)+ … 。這些權重——不過幾十個數字——就是那個人完整的臉部編碼。而那些材料臉本身就是特徵臉,它們出了名地看起來像蒼白、鬼魅般、半成形的臉。

# Eigenfaces = PCA on a stack of face images
# X: shape (N, D) -- N training faces, each flattened to D = H*W pixels
mean_face = X.mean(axis=0)        # the "average face", a length-D vector
A = X - mean_face                 # centre every face: subtract the mean

# When D >> N (e.g. 10000 pixels, 300 faces), do NOT build the D x D
# covariance. Build the small N x N Gram matrix instead -- same eigenfaces,
# vastly cheaper. (See the 'Gram trick' note below.)
G = (A @ A.T) / N                 # shape (N, N)
eigvals, V = eig(G)               # sort columns of V by eigvals, descending

# Map the small eigenvectors back to image space -> the eigenfaces
U = A.T @ V                       # shape (D, N): each column is an eigenface
U = U / norm(U, axis=0)           # normalise each eigenface to unit length

# Keep only the top-k directions (largest eigenvalues)
Uk = U[:, :k]                     # shape (D, k)

def encode(face):                 # turn a 10000-pixel face into a k-number code
    return Uk.T @ (face - mean_face)   # y: the compact "face code"

def reconstruct(y):               # rebuild an approximate face from its code
    return mean_face + Uk @ y
整個特徵臉方法就在十幾行裡:置中、找出主要方向、投影、(必要時)重建。
C = \frac{1}{N}\sum_{n=1}^{N}(x_n-\bar{x})(x_n-\bar{x})^{\top}, \qquad C\,u_k = \lambda_k\,u_k

置中後人臉的共變異數矩陣(左),以及它的特徵分解(右)。

我們把左邊的式子逐個符號拆開。每個 x_n 是第 n 張訓練人臉,寫成一根有 D 個像素的高直行向量;N 是訓練人臉的張數;x̄(讀作「x bar」)是平均臉,也就是逐像素的平均。(x_n − x̄) 這一項是某張臉減掉平均後的結果,所以它只攜帶「這張臉和普通臉有何不同」的資訊。把一根長度為 D 的直行向量乘上它自己的轉置,(x_n − x̄)(x_n − x̄)ᵀ,會得到一張 D×D 的表,其中第 (i, j) 格說的是:「當像素 i 高於平均時,像素 j 是不是也傾向高於平均?」把這張表對全部 N 張臉取平均,就得到共變異數矩陣 C:像素如何一起變化的完整摘要。右邊的式子 C·u_k = λ_k·u_k 是特徵分解:它在尋找一些特別的方向 u_k,C 對它們只做拉伸、從不旋轉。這些方向 u_k 就是特徵臉,而特徵值 λ_k 正是人臉沿著 u_k 變化的量(也就是該方向的變異數)。λ 大代表「人臉在這個方向散得很開——資訊量大」;λ 很小代表「人臉在這裡幾乎不動——是雜訊」。具體來說,若 λ₁ = 50,000、λ₅₀ = 12、λ₅₁ = 0.3,把排序後的 λ 值(特徵值頻譜)畫出來會看到陡降,你只要保留到曲線變平為止——通常 30 到 150 個成分就能解釋約 95% 的總變異。

y = U_k^{\top}\,(x-\bar{x})

把一張臉投影到前 k 個特徵臉上,得到它精簡的編碼 y。

最後這一行就是我們壓縮人臉的方法。U_k 是一個 D×k 的矩陣,它的各行就是前 k 個特徵臉(資訊量最大的方向,按 λ 由大到小排列)。我們先把新臉減掉 x̄ 置中,接著 U_kᵀ·(x − x̄) 量出這張臉含有多少份每一個特徵臉——也就是把這張臉和每種材料做內積。結果 y 只是 k 個數字(比方 50 個),正是我們食譜裡的那些「權重」。我們之所以保留 λ 大的方向,正因為它們攜帶最多的變化,所以這段短編碼幾乎保住了所有能區分人臉的資訊,同時丟掉了雜訊。要往回走,reconstruct ≈ x̄ + U_k·y 就能從編碼重建出一張近似的臉;保留越多成分,重建就越清晰。現在辨識變得便宜得驚人:把每個已知人物的編碼 y 存起來,將新照片編碼,找出最接近的那筆編碼即可。這正是第 2 篇指南裡的k 近鄰影像分類器——只不過它跑在乾淨的 50 維人臉空間裡,而不是 10,000 個原始像素上,所以更快、也穩健得多。上面的程式碼裡藏著一個實務上的警告:當 D = 10,000 時建構 D×D 的共變異數矩陣 C,是個有一億個項目的怪物,但若只有 N ≈ 300 張臉,它其實大半是多餘的。Gram 矩陣技巧改為計算那個小小的 N×N 矩陣(形式上像 (1/N)·AᵀA),再把它的特徵向量映射回影像空間,能用一小部分的成本算出一模一樣的特徵臉。

Fisher 臉:區分『人』而不是『光線』

特徵臉有個尖銳的缺陷,把它點出來,正好帶出下一個想法。PCA 找的是總變異最大的方向——但它不知道、也不在乎那個變異代表什麼意思。在真實的人臉資料集裡,像素變化最大的單一來源通常不是「這個人是誰」,而是光照(從左邊打光和從右邊打光的同一張臉,逐像素比起來差很多)與表情。於是 PCA 樂得把它最前面、威力最強的幾個特徵臉拿去描述光照,反而可能把細微的「這是誰」訊號,埋進它當成「很小」而丟掉的那些成分裡。令人不安的後果是:一個特徵臉系統可能僅僅因為燈移動了,就判定兩張照片相距很遠,即便那是同一個人。

Fisher 臉的解法,是把目標從「捕捉最大的變化」改成「捕捉能把不同人分開的變化」。用的工具是線性判別分析(LDA),這是 Fisher 的經典想法。想像紙上有兩團點,一個人一團(每個點是那個人的一張照片)。PCA 會投影到「把所有點混在一起後散得最開」的那條軸上——這條軸很可能直接穿過兩團點、把它們抹成一片。LDA 反而去尋找這樣一條軸:把點的影子投上去之後,兩團點落成兩個緊湊、彼此分得很開的小堆。它的做法是:最大化各團「團心之間」的散布,同時最小化「每團內部」的散布。由此得到的方向,就是 Fisher 臉,它對光照穩健得多,因為光照變化屬於同類別內部的晃動(同一個人在不同燈光下),而這正是 LDA 主動想壓掉的東西。

兩團點(兩個人的照片)。PCA 投影到整體散布最大的軸上;LDA 則挑出能把兩團推開、又把每團縮小的那條軸,於是出現一條乾淨的分隔。

二維散點圖中有兩個不同顏色的點群與兩條候選投影軸;一條軸(PCA)把它們混在一起,另一條(LDA)把它們分開。

J(w) = \frac{w^{\top} S_B\, w}{w^{\top} S_W\, w}, \qquad S_B\, w = \lambda\, S_W\, w

Fisher 準則(左),以及讓它最大化的廣義特徵值問題(右)。

把 J(w) 讀成某個候選投影方向 w 的「分離分數」(把 w 想成我們拿來投影影子的一根箭頭)。分數由兩個材料構成。S_B 是類別間散布矩陣:它量的是每個人的團心離全域平均臉有多遠——S_B 大代表各人分得很開。S_W 是類別內散布矩陣:它量的是每個人自己的照片,繞著自己的團心散得多開——S_W 大代表各團糊成一片、亂七八糟。式子裡 wᵀS_B w 是沿方向 w 看到的類別間散布,wᵀS_W w 是沿 w 的類別內散布。我們要分子大(人和人離得遠)、分母小(每個人都很緊湊),所以最大化它們的比值 J(w)。小例子:若沿某個 w,各類別的團心投影到 0 與 10(間距 10,故 S_B 項 ≈ 100),而每個人的照片只散布 ±0.5(S_W 項 ≈ 0.25),則 J ≈ 400——很棒。沿著光照軸,間距也許是 1,但人內部的散布是 8,得到 J ≈ 0.125——糟透了,正是我們想避開的那條軸。最大化這個比值,結果就是右邊的廣義特徵值問題 S_B·w = λ·S_W·w,它最前面的幾個特徵向量就是 Fisher 臉。

以學到的方式變形:主動形狀模型

特徵臉與 Fisher 臉模擬的是人臉的外觀——也就是它的像素亮度。但人臉也有幾何結構:各部位的排列方式。主動形狀模型(ASM)抓的就是這個。我們在每張訓練人臉上標出一組固定的地標點(landmarks)——比方說約定好的 68 個點,落在眼角、沿著嘴、繞著鼻子、描著下顎輪廓。每個地標都有一個 (x, y) 位置,所以 68 個地標疊成一條有 136 個數字的「形狀向量」。就像人臉曾是像素空間裡的一個點,形狀現在也是這個 136 維形狀空間裡的一個點,而真人臉的地標排列只住在其中薄薄的一塊區域裡——大多數隨機形狀看起來像一團亂線,而不是一張臉。

得先處理一個小麻煩。同一張臉的兩張照片,可能僅僅因為其中一張裡頭比較大、位移了、或歪了一點,就給出不同的形狀向量——這些位置、尺度、旋轉上的差異,和臉真正的形狀毫無關係。所以在學習之前,我們先把所有訓練形狀對齊到一個共同的座標框,把每一個平移、縮放、旋轉到盡量吻合。這個對齊步驟叫做 Procrustes 分析(取名自希臘神話裡那個把客人拉長或砍短以塞進他床的人)。形狀對齊之後,我們就套用特徵臉那一節同樣的 PCA 機器——只不過作用在形狀向量上,而非像素向量上。輸出是一個點分布模型(Point Distribution Model):一個平均形狀,加上少數幾個變化模態(modes of variation)。每個模態都是形狀空間裡的一個方向,而且令人安心地對應到從資料學到的某種有意義的東西——模態 1 可能是把嘴張開/閉合,模態 2 可能是把頭左右轉,模態 3 可能是把下顎變寬。

x = \bar{x} + P\,b, \qquad |b_i| \le 3\sqrt{\lambda_i}

點分布模型(左),以及每個形狀參數的合理性限制(右)。

左邊的式子是一個人臉形狀產生器。x 是它產生的形狀向量——某張臉全部 136 個地標座標。x̄ 是平均形狀,也就是平均的地標排列。P 是各行為形狀特徵向量的矩陣,也就是變化模態(比方 PCA 找到的前 10 個方向)。b 則是那串短短的形狀參數——每個模態一個數字,說明要加入多少份該模態。令 b = 0 就得到完全的平均臉;令 b = (3, 0, 0, …),而模態 1 若是「嘴張開」,你就得到一張嘴大張的平均臉。所以一整張臉的形狀,被 b 裡大約 10 個數字摘要了。右邊的不等式則是讓這個產生器規規矩矩的關鍵。回想 λ_i 是模態 i 在訓練集上的變異數,所以 √λ_i 是它的標準差。限制 |b_i| ≤ 3·√λ_i 把每個參數封頂在 ±3 個標準差——也就是訓練人臉實際探索過的範圍內。具體來說,若模態 1 的 √λ₁ = 4,那麼 b₁ 可以在 [−12, +12] 間遊走,但不能再遠。這正是 ASM 的核心:模型可以彎,但只能往它在訓練時看過真臉彎的方向、彎它看過的量。把 b 推過界限,形狀就會變形成沒有任何真人臉做得出來的樣子;夾住 b,就能讓每一個產生出來的形狀都合理。

  1. 初始化:把平均形狀 x̄ 大致擺到影像中臉的位置上(例如用第 3 篇指南的人臉偵測器框出來的框)。
  2. 局部搜尋:在每個地標處,沿著與形狀邊界垂直(法線)的方向往外看一小段距離,找出附近最強的邊緣——也就是該點應該落在哪裡的更佳猜測。
  3. 提案:把每個地標移到它最佳的新邊緣位置,組成一個原始的提案形狀(此時看起來可能有點不像人)。
  4. 投影回子空間:用模型 x = x̄ + P·b 去擬合這個提案,並把每個 |b_i| 夾在 ≤ 3·√λ_i,把形狀拉回到最接近的合理人臉。
  5. 重複步驟 2–4,直到地標不再移動為止(收斂)。

加入紋理:主動外觀模型

主動形狀模型知道人臉各部位在哪裡,卻不知道它們長什麼樣子——它追蹤的是輪廓,而不是皮膚、眼睛顏色或陰影。主動外觀模型(AAM)把這幅圖補完,連外觀也一起建模,而讓它能運作的訣竅,是把形狀和紋理乾淨地分開。對每張訓練人臉,AAM 都把它的像素做扭曲變形,使它的地標正好落到平均形狀上——在幾何上像「橡皮布」一樣把每張臉都拉進同一個標準框裡。如今每張扭曲後的臉都具有相同的形狀,所以剩下的任何像素差異就是純粹的紋理(膚色、鬍子、臉頰上的光照),幾何已被抽離。我們把一張臉乾淨地拆成兩個彼此獨立的描述:它的形狀(點在哪裡)與它的紋理(標準框內的顏色)。

從這裡開始,AAM 做的是現在已經熟悉的那一招:對形狀跑 PCA(得到形狀參數,就像 ASM),再分別對去除形狀後的紋理跑 PCA(得到紋理參數),然後把兩者合併成單一組外觀參數。結果是一個關於整張臉的生成模型:餵給它一段短短的參數向量,它就合成出一張完整、擬真的人臉影像——既有正確的輪廓,也有填進去的正確像素。特徵臉只能把臉糊成一團;AAM 卻能從少少幾個數字渲染出一張清晰、具體的臉,因為它把幾何與紋理分開建模,再重新組合起來。

\min_{p}\; \big\| r(p) \big\|^2, \qquad r(p) = I_{\text{image}}\big(\text{warp}(x;\,p)\big) - A_{\text{model}}(p)

擬合 AAM:最小化「扭曲後的真實影像」與「模型合成的臉」之間的像素平方差。

擬合就是找出能讓合成臉吻合真實照片的那組參數。這裡 p 是參數的完整集合(形狀+紋理+姿態)。A_model(p) 是模型用這些參數渲染出來的臉——它對外觀的最佳猜測。warp(x; p) 拿模型預測的形狀,把真實輸入影像扭曲到標準框上,而 I_image(warp(x; p)) 就是在那裡取樣到的真實像素。兩者之差,殘差 r(p),不過就是「模型的臉減去真實的臉」,逐像素相減——當它處處接近零,模型就鎖定了這張臉。我們把殘差平方後加總(那個 ‖·‖²),並搜尋讓它最小的 p:這是一個迭代式的最小平方最小化,輕推參數、重新渲染、量測新的誤差、再重複。AAM 經典的加速訣竅,是事先計算一次「給定的像素誤差樣式應該換算成怎樣的參數更新」(一個離線學好、固定的線性更新/Jacobian),於是每次迭代只是一次便宜的矩陣乘法,而不是昂貴的重新最佳化——這正是讓 AAM 快到足以做即時人臉追蹤的原因。

從單一物體到多個部件:可變形模型預告

退一步,注意貫穿整篇指南的那個共同假設。特徵臉、Fisher 臉、ASM、AAM 全都很強大,但它們每一個都假設了一個單一、而且大致對齊的物體類別:人臉,早已被找到並框好,全都朝著前方、落在差不多同一個框裡。它們建模的是「一個已知的東西如何變化」。對於「在這條雜亂的街景裡某處,把每一個行人和每一輛車都找出來」,它們沒有好的答案。

一般的物體偵測需要這些模型所缺的兩樣東西。第一,模型必須在許多位置與尺度上掃過整個場景,就像第 3 篇指南裡的人臉偵測器把一個視窗刷過整張影像那樣——而不是從一塊整齊、預先對齊好的裁切開始。第二,也是新加的材料,它的各部件必須能彼此相對移動:一個走路的人,手臂、腿和頭並不像正面臉的雙眼那樣處在固定位置。剛性樣板會崩;我們需要一個模型,它的各部件能在有彈性的連結上挪動,同時仍能被認得出是同一個物體。

那正是可變形部件模型(DPM),第 5 篇指南的主題,而理解它的最好方式,是把它看成這整個學習軌道的綜合體。它取用一個涵蓋整個物體的根樣板(root template),加上數個較小的部件樣板,這些部件以「彈簧」連到根上,懲罰部件偏離它預期的位置——於是物體可以變形,但只能合理地變形,呼應的正是本篇指南裡那個「學到的變形」想法。這些樣板由第 2 篇指南的梯度方向特徵(HOG)構成,整套東西以第 3 篇指南的偵測風格刷過影像、為各視窗評分,而它是用一個潛在 SVM(latent SVM)訓練的,這個 SVM 在沒有人告訴它的情況下自行推斷部件該落在哪裡。特徵、偵測、與可變形幾何,終於合併成單一個偵測器——這就是我們接下來要去的地方。