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

角點、斑點與尺度:尋找可重複的關鍵點

光有邊緣還不夠——看看角點與斑點如何給我們穩定的關鍵點,以及尺度空間如何讓我們在任何縮放下都找得到它們。

什麼才是好的關鍵點?

在上一篇指南中,我們學會了用梯度尋找邊緣——也就是影像亮度劇烈變化的地方。邊緣是很好的起點,但光靠它還不足以把一張照片對應到另一張。想像你站在稍微不同的位置,對同一棟建築拍了兩張照片,現在要讓電腦判斷:照片 A 裡的某個點,對應到照片 B 裡的哪個同一個實際位置?沿著屋簷的一條邊緣,在這條線上的每個位置看起來都一模一樣,電腦根本分不清自己落在邊緣的哪一段。我們需要更能精準定位的東西:關鍵點

一個好的影像關鍵點要用四項設計目標來衡量。可重複性(repeatability):當同一個場景再次被拍攝——換個角度、不同光線、不同縮放——偵測器應該能重新找到完全相同的那些實際位置。獨特性(distinctiveness):關鍵點周圍那一小塊區域在局部上應該長得很不尋常,這樣之後才能和其他區塊區分開來。局部性(locality):關鍵點應該只依賴影像中的一小塊區域,這樣即使場景有一部分被遮住(遮蔽),其他地方的大多數關鍵點仍然安然無恙。不變性(invariance):關鍵點本身與它被測得的位置,在旋轉、亮度變化與尺度變化下都應該保持穩定。請牢牢記住這四個詞——本篇指南裡的每一個偵測器,都是在試圖滿足它們。

回想上一篇講過的孔徑問題(aperture problem):透過一個小窗口盯著一條邊緣看,你能判斷它橫越邊緣移動了多少,卻無法判斷它沿著邊緣滑動了多少——畫面看起來毫無變化。這正是為什麼邊緣在 2D 中定位很差:它只能釘住一個方向。角點(corner)——兩條邊緣相交之處——就不一樣了:把窗口往任何方向輕推,區塊都會改變。因為亮度在每個方向上都有變化,角點在 x 與 y 兩個方向上都被牢牢釘住。這個單一的觀察,就是我們接下來要打造的Harris 角點偵測器的種子。

放大看像素:沿著一條直邊滑動時,每個窗口看起來都一樣;但在角點處,局部的圖樣是獨一無二的。

一格像素網格,顯示平坦區域、直邊與角點;箭頭標示出唯有在角點處,窗口圖樣才會出現獨特變化。

Harris 角點偵測器:直覺

我們先用一個問題來打造角點偵測器,暫時不用任何方程式。在影像上放一個小方形窗口。現在把這個窗口往某個方向輕輕滑動一點點,然後問:窗口內的亮度圖樣改變了多少? 如果區塊幾乎沒變,表示窗口正坐落在沒有特徵的地方;如果改變很大,表示窗口坐落在有結構的地方。Harris 的訣竅,就是同時對每一個滑動方向都問這個問題,並尋找那些不論你往哪個方向走都會大幅改變的位置。

  1. 平坦區域(例如一片乾淨的天空):往任何方向滑動窗口,區塊幾乎都不變——各個方向的響應都很小。不是關鍵點。
  2. 邊緣(例如屋頂與天空交界處):沿著邊緣滑動時,區塊幾乎不變;但橫越邊緣滑動時卻變化很大。一個方向變化大、另一個方向變化極小。沿著邊緣方向仍然是模稜兩可的。
  3. 角點(例如兩條屋頂邊緣交會的尖端):往每一個方向滑動窗口,區塊都會大幅改變。所有方向都變化很大——這正是我們要的關鍵點。
三種典型情況——平坦、邊緣、角點——差別完全在於窗口滑動時改變的方式。

三個像素窗口:平坦區塊在所有方向都不變、邊緣區塊只在橫越邊緣時改變、角點區塊在所有方向都改變。

為了把這個思想實驗變成一個數字,我們測量當窗口位移一個小量 (u, v) 時,區塊內的總平方變化量。取平方讓每一個變化都算成正值(變暗的像素和變亮的像素都會貢獻),而對整個窗口求和,則把整塊區域聚合成一個分數。我們把這個分數稱為 E(u, v):位移 (u, v) 所造成之變化的能量。

E(u,v)=\sum_{x,y} w(x,y)\,\bigl[\,I(x+u,\;y+v)-I(x,y)\,\bigr]^2

我們一項一項來讀。求和 ∑ 跑遍窗口內的每一個像素 (x, y)。I(x, y) 是像素 (x, y) 在原始位置的亮度;I(x+u, y+v) 則是同一個區塊像素在窗口位移 (u, v) 之後的亮度。兩者之差,就是這個像素的亮度在位移下改變了多少;取平方把它變成一個正的「變化量」。w(x, y) 是窗口權重——通常是一個高斯隆起,讓窗口中心的權重高於邊緣,這樣單一個有雜訊的邊緣像素就無法獨大。所以 E(u, v) 就是某一個候選位移所造成的加權總變化。角點,就是 E(u, v) 對每一個 (u, v) 方向都很大的位置;平坦區域則處處 E ≈ 0;邊緣則在橫越方向的位移下 E 很大、沿邊緣方向 E ≈ 0。對所有位移逐一計算 E 太昂貴了,因此接下來我們用一階泰勒近似(Taylor approximation)——I(x+u, y+v) ≈ I(x, y) + u·I_x + v·I_y——把位移後的影像,換成我們上一篇已經學會計算的梯度。這一步代換,就把 E 變成一個精巧的矩陣,給了我們Harris 角點偵測器

結構張量與角點響應

把泰勒近似代入 E。括號 [I(x+u, y+v) − I(x, y)] 就單純變成 (u·I_x + v·I_y),也就是局部梯度所預測的變化。把它平方得到 u²I_x² + 2uv·I_x I_y + v²I_y²。每一項都是梯度的乘積再乘上 u 或 v 的某個次方,所以當我們對窗口求和時,可以把 (u, v) 的部分提出來,並把所有梯度的總和收進一個 2×2 的矩陣 M。結果出奇地整潔:E(u, v) ≈ [u v] M [u v]ᵀ。關於區塊在每個方向如何變化的全部資訊,現在都住在 M 裡面——它叫做二階矩矩陣(second-moment matrix),或結構張量(structure tensor)

M=\sum_{x,y} w(x,y)\begin{bmatrix} I_x^2 & I_x I_y \\ I_x I_y & I_y^2 \end{bmatrix}

這裡的 I_xI_y 就是上一篇學過的水平與垂直影像梯度(往右走、往下走時亮度上升的快慢)。對每個像素,我們組出三個乘積——I_x²(水平方向的邊緣強度)、I_y²(垂直方向的邊緣強度)、以及 I_x I_y(兩者如何相關)——再以 w(x, y) 為權重對窗口求和。M 是對稱矩陣,因此有兩個實特徵值(eigenvalue) λ₁ 與 λ₂,對應互相垂直的特徵向量。特徵向量指向區塊的兩個主方向,而每個特徵值衡量的是區塊亮度沿著該方向變化得多強。這就是 Harris 的幾何核心:λ₁ 與 λ₂ 分別是「方向一的變化」與「方向二的變化」。兩個特徵值都大,代表區塊在兩個獨立方向上都大幅變化——這是角點。一大一小,代表只在一個方向上變化——這是邊緣。兩個都小,則是平坦區域。

R=\det(M)-k\,(\operatorname{trace} M)^2=\lambda_1\lambda_2-k(\lambda_1+\lambda_2)^2

在每個像素都計算特徵值很慢,所以 Harris 用了一個巧妙的捷徑,只需要我們手上已有的那些總和。線性代數有兩個事實:det(M) = λ₁λ₂ 以及 trace(M) = λ₁ + λ₂——而且關鍵在於,det 與 trace 可以直接從 M 的元素讀出(det = I_x²·I_y² − (I_x I_y)²、trace = I_x² + I_y²),完全不必去解特徵值。角點響應(corner response) R 把它們組合起來,其中 k 是一個經驗常數(通常 0.04–0.06),用來調整對邊緣懲罰的嚴厲程度。把 R 當溫度計來讀:R 大且為正 ⇒ 角點R 大且為負 ⇒ 邊緣|R| 小 ⇒ 平坦。我們用 k = 0.05 來分類三個區塊。平坦:λ₁ = 0.01、λ₂ = 0.02 → R = 0.0002 − 0.05·(0.03)² ≈ +0.00016,極小 → 平坦。邊緣:λ₁ = 5、λ₂ = 0.01 → R = 0.05 − 0.05·(5.01)² ≈ −1.2,大的負值 → 邊緣。角點:λ₁ = 5、λ₂ = 4 → R = 20 − 0.05·(9)² = 20 − 4.05 = 15.95,大的正值 → 角點。最後,我們只保留那些 R 超過門檻而且是 R 的局部最大值的像素(非極大值抑制),這樣每個角點就只產生一個清晰的點,而不是一團胖斑。這整條流程,就是Harris 角點偵測器

影像上各處的 Harris 響應 R:明亮的正峰落在角點上、暗色的低谷落在邊緣上、近乎零則在平坦區域。

影像上角點響應的熱力圖,明亮的尖點標示出偵測到的角點。

# Harris corner response (no eigenvalues needed)
import numpy as np
from scipy.ndimage import gaussian_filter, sobel

def harris_response(img, k=0.05, sigma=1.0):
    Ix = sobel(img, axis=1)              # horizontal gradient I_x
    Iy = sobel(img, axis=0)              # vertical gradient I_y
    # Window-weighted sums of gradient products (the Gaussian plays the role of w)
    Sxx = gaussian_filter(Ix * Ix, sigma)
    Syy = gaussian_filter(Iy * Iy, sigma)
    Sxy = gaussian_filter(Ix * Iy, sigma)
    det_M   = Sxx * Syy - Sxy * Sxy      # det(M) = lambda1 * lambda2
    trace_M = Sxx + Syy                  # trace(M) = lambda1 + lambda2
    return det_M - k * trace_M**2        # R: large + at corners, large - at edges
用梯度乘積與一個高斯窗口算出的 Harris 角點響應——完全不必直接去解特徵值。

斑點:有大小的特徵

角點很棒,但這個世界充滿了不是角點的特徵。一個印刷的圓點、夜裡遠方的一盞路燈、顯微鏡下的一顆細胞、一塊咖啡漬——這些都是斑點(blob):大致呈圓形、比周圍均勻地更亮(或更暗)的區域。斑點偵測要尋找的,正是這些緊湊的亮度隆起。與角點不同,斑點沒有邊緣的銳利交會;它的決定性特徵是一個從背景中突顯出來的區域

有兩個理由讓斑點不可或缺。第一,許多自然與人造結構本身就是斑點狀的,角點偵測器會錯過它們,或只在它們的邊緣上微弱反應。第二——這也是支撐本篇指南其餘部分的關鍵想法——斑點具有內在的大小(size)。角點是一個零維的點,沒有自然的寬度;但斑點有小有大,而這個大小是該特徵真實、可測量的性質。因此偵測一個斑點,同時也告訴你它有多大,這正是通往「尺度」概念的橋樑。

我們要怎麼做出一個會在「特定大小」的斑點上發亮的濾波器?答案是高斯拉普拉斯算子(Laplacian of Gaussian, LoG)。先用高斯把影像稍微模糊一下(這決定了濾波器在意的大小),再套用拉普拉斯算子——一種二階導數運算子,反應的是一個像素與其鄰居平均值差了多少。組合起來的濾波器有一個「墨西哥帽」的形狀:中央為正、外圍一圈為負。把它在影像上滑動,它就像一個中央—周邊(center-surround)偵測器——只要一個明亮的中心被較暗的周邊環抱(也就是斑點),它就產生強烈的響應。而且當帽子的寬度與斑點的寬度吻合時,它的「鳴響」最大聲。

\sigma^2\,\nabla^2 G * I,\qquad \nabla^2 G=\frac{\partial^2 G}{\partial x^2}+\frac{\partial^2 G}{\partial y^2}

逐項來讀:G 是寬度為 σ(sigma)的高斯模糊,這個旋鈕設定了濾波器的大小;∇²G 是它的拉普拉斯算子——x 與 y 方向二階導數的總和——也就是那個墨西哥帽形狀;\* 是卷積,意思是我們把那頂帽子在整張影像 I 上滑動,並在每個像素記下它的響應。最前面的 σ² 是至關重要的尺度正規化。少了它,拉普拉斯的響應會隨著模糊得越多而自然縮小,於是大斑點看起來總會比小斑點弱;乘上 σ² 把響應重新縮放,使不同大小之間可以互相比較。回報是:當你用許多不同大小 σ 的偵測器去試時,一個真實斑點中心處的響應,會在與該斑點實際半徑吻合的那個 σ 上達到峰值。所以 LoG 不只找到斑點——它還找到斑點的大小。(具體來說,一個半徑為 r 的亮圓盤,會在大約 σ ≈ r/√2 時,給出最強的尺度正規化響應。)

尺度正規化的 LoG 響應:每個斑點都在與自身半徑吻合的濾波器大小上,發出最強的光。

一張有數個不同大小圓點的影像,以及一張響應圖,每個圓點都在與其吻合的濾波器尺度上發亮。

尺度空間:在每個縮放層級都看得見

我們剛剛看到,斑點的響應取決於濾波器的大小 σ。但還有一個更深層的問題:同一個物體在一張照片裡可能填滿整個畫面,在另一張裡卻只是一個小點,端看相機離得多遠。一個只在單一大小下運作的偵測器,會在近處找到一張臉,卻錯過房間另一頭那張一模一樣的臉。解法是尺度空間:我們不在單一解析度下分析影像,而是在一整段連續的模糊層級下分析它,讓資料本身告訴我們每個特徵活在哪個尺度。

要建立尺度空間,就把影像用寬度 σ 逐漸增大的高斯去模糊,產生一疊越來越柔和的副本——這就是高斯金字塔(Gaussian pyramid)。增大 σ 就像從畫面往後退一步:細微的細節融化消失,只剩較大的結構存活下來。小的 σ 保留住每一塊磚的紋理;大的 σ 只保留整面牆的輪廓。把許多 σ 值疊起來,我們就得到一個 3D 體積 L(x, y, σ):寬、高,以及現在作為第三軸的尺度。在影像中很小的特徵會出現在小 σ 處;很大的特徵則出現在大 σ 處。

\begin{aligned} L(x,y,\sigma) &= G(x,y,\sigma) * I(x,y) \\ D(x,y,\sigma) &= L(x,y,k\sigma) - L(x,y,\sigma) \end{aligned}

第一行定義了尺度空間:L(x, y, σ) 是影像 I 與寬度為 σ 的高斯 G 卷積(\*,滑動相乘再求和)——也就是被模糊了 σ 那麼多的影像。第二行則是省錢的關鍵。在許多尺度上計算尺度正規化的 LoG 很昂貴,但事實證明,LoG 可以用單純相減兩個相鄰模糊層級來緊密近似:這就是高斯差(Difference of Gaussians, DoG),D(x, y, σ) = L(x, y, kσ) − L(x, y, σ),其中 k 是相鄰尺度之間的固定乘法步進(例如 k ≈ 1.26,所以每一層都比上一層模糊約 26%)。相減幾乎不花成本,而兩個高斯之差有著與 LoG 相同的墨西哥帽形狀,因此 D 是一個便宜又精確的斑點偵測器,而且早已內建了 σ² 正規化。一個強的 DoG 值就代表:『這裡住著一個這個大小的斑點。』

攀爬金字塔:隨著模糊 σ 增大,每一層都對越來越大的結構有反應——就像從越來越遠的地方觀看場景。

一疊越來越模糊的影像副本,越往頂端,存活下來的結構越大。

跨尺度尋找關鍵點

現在我們把所有東西組裝成一個偵測器。我們有 DoG 體積 D(x, y, σ):一疊差分影像,每個尺度一張,落在 (x, y, 尺度) 的 3D 空間裡。當某個體素(voxel)是 D 在空間與尺度同時局部極值——極大或極小——時,我們就在那裡宣告一個關鍵點。具體來說,把每個候選像素拿來和同一尺度平面內的 8 個鄰居、加上相鄰尺度中正上方的 9 個與正下方的 9 個像素比較:8 + 9 + 9 = 26 個鄰居。如果中心值比這 26 個都大(或都小),它就是一個在位置與大小上都最佳定位的斑點。這就是斑點偵測尺度空間如何結合,把一個特徵在三個座標上都釘住。

# A keypoint is a local extremum of the DoG in space AND scale
def is_keypoint(D, x, y, s, contrast_thresh=0.03):
    # D is the DoG stack indexed [scale, row, col]
    block = D[s-1:s+2, y-1:y+2, x-1:x+2]   # 3 x 3 x 3 = 27 values
    center = D[s, y, x]
    # Compare the center against all 26 neighbours
    if center < block.max() and center > block.min():
        return False                        # neither a max nor a min -> reject
    if abs(center) < contrast_thresh:
        return False                        # too faint -> unstable -> reject
    return True                             # accept candidate (x, y, scale = s)
一個 DoG 關鍵點,是比它在空間與尺度上全部 26 個鄰居都更大或更小、而且不會太微弱的值。

原始的極值是不錯的候選,但大師級的偵測器會用三個樸實的步驟去精煉它們。次像素/次尺度定位:在極值附近對 D 擬合一個小小的二次曲面(類似拋物面),然後跳到它真正的峰頂,這樣關鍵點就會落在像素之間、尺度之間,而不是硬卡在格點上。低對比剔除:丟掉那些精煉後 DoG 值太小的點——它們很微弱、容易被雜訊翻轉,會傷害可重複性。邊緣剔除:DoG 在邊緣上也會反應,而邊緣定位很差(還記得孔徑問題嗎),所以我們用局部 Hessian(二階導數的 2×2 矩陣)兩個特徵值的比值,把它們剔除。當一個特徵值遠遠壓過另一個,就表示我們在邊緣上、而不是斑點上——這正是 Harris 用來區分角點與邊緣的同一個特徵值比值的想法。存活下來的點,就構成一組穩定的關鍵點

這一切的輸出,恰恰是下一階段所需要的:每個關鍵點是一個三元組 (x, y, 尺度)——一個位置以及一個特徵大小。尺度極為重要:它告訴描述子,該觀察點周圍多大的一塊區域,這樣一來,近處測得的特徵與遠處測得的同一特徵,最終會在成比例大小的區塊上被描述,因而能夠互相匹配。在下一篇指南中,我們會把每個 (x, y, 尺度) 關鍵點餵給 SIFT 描述子,它還會指派一個方向,然後把局部梯度摘要成一個向量,讓你能拿去和另一張影像的關鍵點比對。