在影像之間匹配描述子
這是收成的一課。前四課你學會了找出可重複偵測的關鍵點(角點、跨尺度的斑點),並把每個關鍵點周圍的鄰域濃縮成一個描述子向量——SIFT、HOG、SURF、BRIEF、ORB。但一個孤零零待在單張影像裡的描述子什麼也做不了。魔法要等到你有兩張拍攝同一場景的照片,並開始問:這邊的某個關鍵點,和那邊的哪個關鍵點是同一個實際物理點?回答這個問題就叫做特徵匹配,它是從「描述單張影像」走向「推理兩張影像之間關係」的橋樑。
核心想法美在簡單:每個描述子都是高維空間裡的一個點,所以「同一個物理點」即使從略微不同的角度看,也應該落在那個空間中幾乎相同的位置。要為影像 1 的描述子 f 找匹配,我們就在影像 2 的所有描述子裡找它的最近鄰——距離最小的那一個。我們直接沿用第 4 課的距離度量:對於實數值描述子如 SIFT 描述子(以及 SURF)使用歐氏(L2)距離;對於二元描述子如 ORB 描述子(以及 BRIEF)使用漢明距離——也就是不同位元的數目,CPU 用一次 XOR 再做 popcount 就算出來。度量不是事後補上的細節,而是由描述子當初怎麼建構所決定的。
左右兩張同一棟建築的影像,許多細線把彼此對應的角點與斑點連接起來。
我們實際上要怎麼搜尋最近鄰?最誠實的基準是暴力匹配:對影像 1 的每個描述子,計算它到影像 2 每個描述子的距離,保留最小的。若兩張影像各有 m 與 n 個關鍵點,那就是 m × n 次距離計算——幾千個特徵還好,但它隨規模呈平方成長,量大時就成了瓶頸。所以實務上我們常改用近似最近鄰結構:KD-tree(沿著座標軸遞迴切分描述子空間,很適合像 SIFT 這種低到中維度的浮點向量),或是 LSH 局部敏感雜湊(把相似的二元描述子雜湊進同一個桶裡——這是漢明空間下 ORB 的自然選擇)。「近似」意味著我們以極小的「漏掉真正最近鄰」機率,換取巨大的加速——通常非常划算。
# Brute-force matching with the RIGHT metric per descriptor type.
# (conceptual; OpenCV's cv2.BFMatcher wraps this efficiently)
def match_brute_force(desc1, desc2, metric):
matches = []
for i, f in enumerate(desc1): # each descriptor in image 1
best_j, best_d = None, float('inf')
second_d = float('inf')
for j, g in enumerate(desc2): # scan all of image 2
d = metric(f, g) # L2 for SIFT/SURF, Hamming for ORB
if d < best_d:
second_d = best_d # demote old best to 2nd best
best_d, best_j = d, j
elif d < second_d:
second_d = d
# keep BOTH neighbours -- we need them for Lowe's ratio test (next section)
matches.append((i, best_j, best_d, second_d))
return matches好匹配與壞匹配:Lowe 比值測試
這裡有個令人不安的事實:最近鄰匹配很吵雜。影像 1 裡的每個描述子,保證都能在影像 2 中找到某個最近鄰——即使它真正的夥伴根本不在影像 2 裡(被遮擋了,或落在畫面之外)。更糟的是,真實場景充滿重複紋理:磚塊、窗戶、樹葉、欄杆柱、字母。一塊磚上的描述子,和它隔壁那塊磚上的描述子幾乎一模一樣,於是匹配器可能很有自信地配錯。我們需要一種方法,不只問「最接近的匹配是哪個?」,還要問「這個最接近的匹配是不是與眾不同地接近?」
Lowe 比值測試:唯有當最佳鄰居明顯優於次佳時才保留該匹配。
我們一個符號一個符號拆開。f 是來自影像 1 的查詢描述子;NN₁ 是它在影像 2 中最接近的描述子,NN₂ 是第二接近的(一個不同的關鍵點);d(·,·) 是先前同樣的描述子距離(L2 或漢明)。這個測試算出最佳距離與次佳距離的比值,唯有比值低於門檻 ρ(通常 0.7–0.8)時才接受匹配。直覺正是重點所在:若 f 真正的夥伴在影像 2 裡,那個夥伴應該脫穎而出——NN₁ 應該比 NN₂ 近得多,給出很小的比值(例如 0.3)。但若 f 落在重複紋理上,NN₁ 與 NN₂ 都是同樣優秀的冒名頂替者,比值就會逼近 1.0——這匹配模稜兩可,我們把它丟掉。具體例子:SIFT 距離 d(f,NN₁)=120、d(f,NN₂)=150,比值 0.80,處於邊界,在 ρ=0.7 下大概會被拒;但 d(f,NN₁)=120 而 d(f,NN₂)=400,比值 0.30——這是乾淨俐落、值得信賴的匹配。Lowe 發現這單一門檻能消除約 90% 的錯誤匹配,卻只丟掉約 5% 的正確匹配。
一個互補的篩選器是對稱(交叉檢查)匹配:影像 1 的關鍵點 a 與影像 2 的關鍵點 b 之間的匹配,唯有當 a 的最近鄰是 b,而且 b 回到影像 1 的最近鄰是 a 時才保留。它要求兩張影像彼此達成共識,而不是相信單向的投票。比值測試與交叉檢查從不同角度切入問題——比值測試衡量「與眾不同的程度」,交叉檢查衡量「相互一致」——兩者常一起使用,以得到更乾淨的匹配集合。
單應性:連結平面的兩個視角
幾何給了我們一個強大的篩選器,但前提是場景願意配合。有兩種常見情形下,單一、精確的映射就能連結兩張影像:(1) 場景是一個平坦的平面(一面牆、一張海報、一張桌面、無人機俯瞰的地面),或 (2) 相機不移動、只在原地旋轉(經典的全景,你以原地轉動掃過地平線)。這兩種情形下,影像 1 中某點與它在影像 2 中位置之間的關係,都能由單一個 3×3 矩陣捕捉,這個矩陣叫做單應性,記為 H。一旦知道 H,我們就能精準預測影像 1 的任一點該出現在影像 2 的哪裡——而與這個預測不一致的匹配就被揭穿為離群值。
要乾淨地寫出 H,我們需要齊次座標——這是你第一次遇到它,所以慢慢來。訣竅是把一個 2D 點 (x, y) 後面補一個 1,變成三元組 (x, y, 1)。何必如此?因為投影(3D 世界壓扁到 2D 感測器的方式)牽涉除法,而除法用矩陣乘法寫出來很彆扭。齊次座標把那個除法吸收進一條規則:兩個只差一個整體縮放因子的三元組,代表同一個 2D 點。所以 (2, 4, 2)、(1, 2, 1) 與 (3, 6, 3) 全都是同一個點 (1, 2)。可以把它想成「影子」:一個點與穿過它的光線,無論你沿光線滑多遠,投在影像平面上的影子都一樣——尺度無關緊要,只有方向重要。
單應性把齊次的影像 1 點映到齊次的影像 2 點,差一個未知尺度 s。
解讀這個方程式:(x, y) 是影像 1 中的點,齊次寫法為 (x, y, 1)。乘上 3×3 矩陣 H 得到一個三維向量;這個向量就是 (x', y')——該點在影像 2 中的位置——但被一個未知數 s 縮放了。我們用第三個分量去除就能還原真正的像素座標:若 H·(x,y,1)ᵀ = (u, v, w)ᵀ,則 x' = u/w、y' = v/w。這個除法正是 s 所代表的「被移除的尺度」,也正是它讓單應性不只是一個普通的線性映射。現在數一數未知數:H 有 9 個元素,但因為整個方程式只在「差一個尺度」的意義下成立,我們可以固定其中一個元素(常令 h₃₃ = 1),剩下 8 個自由度。每組點對應給我們兩條方程式(一條給 x'、一條給 y'),所以 4 組對應提供 8 條方程式——剛好夠解出 8 個未知數。這個神奇數字 4,正是下一節每次抽 4 組匹配的原因。
兩個相機中心觀看一個 3D 點的示意圖,包含投影射線,以及限制某點在另一張影像中可出現位置的幾何線條。
RANSAC:在離群值中找出真相
我們現在手上有一組匹配,多數是好的、但夾雜著倖存的離群值,還有一個它們應當服從的幾何模型(單應性)。問題在於一般的最小平方擬合是個膽小鬼:若你把所有匹配都餵給它、要它求最佳的 H,即使只有少數幾個離譜的離群值,也能把答案拉離真相老遠,因為平方誤差會懲罰擬合器「忽略它們」。我們需要一種方法,能找出獲得多數支持的模型,同時乾脆地無視那些說謊者。這個方法就是 RANSAC——RANdom SAmple Consensus(隨機抽樣共識)。打個比方:想像你向一群吵雜的人問一個是非題,多數人誠實、少數人說謊。你不會把所有人平均;你會找出最大、彼此意見全一致的那一群,然後相信他們。
- 抽樣:隨機挑出擬合模型所需的最小匹配集合——對單應性而言剛好是 4 組對應(來自第 3 節的計數)。
- 擬合:僅用這 4 組匹配解出一個候選單應性 H。
- 評分:把 H 套用到其他每一組匹配的影像 1 點上,量測重投影誤差(H 預測該點落點與其匹配點實際位置之間的距離),數出有多少匹配落在一個小門檻內。這些一致的匹配就是內群值;它們的數目就是共識分數。
- 保留最佳:記住目前為止贏得最多內群值的那個 H。
- 重複:把步驟 1–4 跑很多次,好讓一次「全是好點」的隨機抽樣幾乎必然至少出現一次。
- 重新擬合:取出獲勝模型的內群值,用最小平方從它們全部重新估計 H——此時安全了,因為離群值都已被排除。
RANSAC 要以機率 p 成功,所需的隨機抽樣次數。
這條公式告訴你要跑多少次迭代 N,一旦拆開就無比直觀。w 是內群值比例——你的匹配中真正正確的比例(例如 0.5 表示一半是好的)。s 是每一輪抽取的樣本大小(單應性時 s = 4)。p 是 RANSAC 成功的目標機率,亦即 N 次抽樣中至少有一次是純內群值(常取 p = 0.99)。一步步建構:隨機抽一組匹配是內群值的機率是 w,所以這 s 組全是內群值——一次全好的抽樣——的機率是 wˢ(各次抽取近似獨立)。因此一次抽樣被汙染(至少含一個離群值)的機率是 1 − wˢ,而全部 N 次都被汙染的機率是 (1 − wˢ)ᴺ。我們希望這個失敗機率至多為 1 − p,令 (1 − wˢ)ᴺ = 1 − p,取對數,解出 N——就得到上面的公式。代入數字:w = 0.5、s = 4、p = 0.99,得 wˢ = 0.0625,所以 N = log(0.01)/log(0.9375) ≈ 71 次迭代。把內群值比例降到 w = 0.3,N 就跳到約 567——鮮明地呈現離群值增加時成本如何爆炸。深層的啟示是:RANSAC 的工作量取決於資料有多髒,而非你有多少組匹配。
影像拼接:打造全景圖
現在來到把整個學程串在一起的「啊哈」時刻。當你舉著手機掃過一片風景,它把多張照片融成一張寬闊的全景圖,這五課裡的每一個概念都在默默運作。拼接是典範式的端到端應用:它需要可重複的關鍵點、與眾不同的描述子、謹慎的匹配、穩健的幾何,以及影像扭曲——正是你一路爬上來的階梯。讓我們把完整的流水線走一遍,並為每一階標上教過它的那一課。
- 在兩張照片中偵測關鍵點——能在尺度與視角變化下倖存的角點與斑點(第 1–2 課)。
- 用一個不變的描述子描述每個關鍵點——SIFT/HOG,或快速的二元 SURF/BRIEF/ORB(第 3–4 課)。
- 以最近鄰在兩張影像之間匹配描述子,再用 Lowe 比值測試與交叉檢查清理集合(本課第 1–2 節)。
- 用 RANSAC 估計兩個視角之間的單應性 H,同時丟棄倖存的離群值(本課第 3–4 節)。
- 把 H 套用到每個像素,將一張影像扭曲進另一張的座標框架,使重疊內容精準對齊。
- 融合重疊區以隱藏接縫、抹平曝光差異,產生一張無縫的寬幅影像。
第 5 步,扭曲,正是單應性發揮價值之處。概念上我們選一張影像當畫布,對每個輸出像素,用 H(或其反矩陣)去查出哪個來源像素映到該處,並在鄰近像素間內插以達到次像素精度。因為 H 只用內群值擬合,平面/旋轉的幾何成立,第二張影像便乾淨地嵌進第一張——直線保持筆直,牆面保持平整。這正是那個抽象的 3×3 矩陣化為「肉眼可見的正確疊合」的時刻。
但即使幾何扭曲完美,仍會露出醜陋的接縫,因為兩張照片的曝光與白平衡略有不同——一側較亮,接合處呈現一道生硬的邊。融合解決了這點。最簡單的是羽化:在重疊區,對兩張影像取加權平均,權重從一邊平滑地過渡到另一邊,讓轉換漸進而非突兀。更精緻的是多頻段融合,它在較寬的範圍上融合低空間頻率(大範圍亮度),在較窄的範圍上融合高頻率(細緻紋理與邊緣)——既隱藏曝光差異,又不抹糊細節。成果就是你的手機一秒鐘交還給你的那張無縫全景。
視覺詞袋:從匹配到辨識
幾何是描述子的一大用途,但還有第二個同等重要的用途:辨識與檢索——回答「這張影像裡有什麼?」或「幫我找出所有像這張的影像」,而且要橫跨數百萬張的資料庫。成對匹配對此遠遠太慢;要把一張查詢影像和一百萬張影像逐一匹配每個描述子,會耗到天荒地老。訣竅直接借自文字搜尋,就是視覺詞袋(BoVW)。在文字裡,一篇文件的詞袋就是各個詞彙出現次數的計數,忽略順序——「貓 坐下了」和「坐下了 貓」得到相同的直方圖。BoVW 對影像做一模一樣的事,由描述子扮演「詞」的角色。
首先我們建立一個視覺詞彙表。我們從許多訓練影像中收集一大堆描述子(例如 SIFT 描述子),用 k-means 把它們分成 k 群——也許 k = 1,000 或 100,000。每個群心成為一個「視覺詞」:一種具代表性的局部外觀,像是「某個朝向的角點」或「亮底上的暗斑」。關鍵在於,略有不同但視覺相似的描述子(同一個角點在略微不同光照下)會被歸整到同一個詞,這正是我們想要的穩健性。詞彙表只在離線時建立一次。
向量量化:把每個描述子吸附到它最近的詞彙詞。
這條小小的方程式就是向量量化——把一個連續的描述子轉成離散的詞標籤。解讀它:d 是從影像中抽出的一個描述子;c_k 是第 k 群的群心(第 k 個視覺詞);‖d − c_k‖ 是描述子到那個群心的歐氏距離;對 k 取 argmin 則挑出使距離最小的索引 k。白話說:把每個描述子指派給它最像的那個視覺詞。對一張影像的所有描述子都這麼做,你就能把它們累加成一個直方圖 h,一個長度為 k 的向量,其中 h[k] 計數有多少描述子落入詞 k(再做正規化,使影像大小不影響)。這個直方圖就是該影像的整圖簽章——一個固定長度的向量,可以餵給分類器,或用簡單的向量距離與其他影像比較。文字的類比完全吻合:就像你用「提到『經濟』12 次、『進球』0 次」來描述一篇文件,你用「視覺詞 47 出現 9 次、詞 88 出現 3 次」來描述一張影像。
示意圖顯示一張輸入影像,其偵測到的局部特徵被量化進視覺詞的格子裡,形成長條圖直方圖,餵入一個輸出類別標籤的分類器。
深度學習時代的經典流水線
讓我們以一張清晰的地圖為這個學程收尾,標出你所學的一切今日身處何處。經典流水線是一條乾淨的五階段生產線:偵測關鍵點 →描述它們 →匹配描述子 →用 RANSAC 幾何驗證 →用視覺詞袋辨識或檢索。你現在理解每一個方塊與每一條箭頭。公允的問題是:在深度網路如此主宰視覺的時代,這套還重要嗎?答案是有自信的「是」——但你應該確切知道在哪裡、為什麼。
經典的、手工設計的特徵在好幾個領域仍然居於主導。在視覺 SLAM(機器人與 AR 頭戴裝置即時追蹤自身位姿)與運動恢復結構(從照片集合重建 3D 場景)中,以幾何為先的「關鍵點 + 匹配 + RANSAC」流水線是主力——它快速、被充分理解,且面對新環境不需要訓練資料。在手機與無人機上,ORB 描述子的裝置端效率(二元、可在微秒內以漢明比對)很難被超越。而在資料稀少或對可解釋性至關重要的場域——醫療、科學或安全領域,你必須解釋為何兩張影像匹配——一個透明的描述子距離,勝過一個你無從稽核的不透明神經嵌入。
那麼學習式方法如今在哪裡領先?當你擁有充足資料、負擔得起訓練時,神經偵測器與描述子在純粹的匹配準確度上勝出,尤其是在艱難的外觀變化下——日夜、季節、巨大的視角擺動。像 SuperPoint(一個聯合學習關鍵點與描述子的 CNN)與 SuperGlue(一個用圖神經網路、藉由聯合推理所有候選來匹配的方法,遠比各自獨立的最近鄰+比值測試聰明)這類系統,在艱難的基準上創下頂尖成績。它們並沒有拋棄經典結構——它們學出同樣那些方塊的更好版本。SuperPoint 仍是偵測器加描述子;SuperGlue 仍是匹配器;兩者下游仍然餵給 RANSAC 與單應性。