圖形學筆記(三)—— Harris角點檢測器

圖形學筆記(三)—— Harris角點檢測器

前言

CSDN不支持我的公式,大家可以到我的博客:wang-sy.github.io去看
從現在開始學習的是書中的第二章:局部圖像描述子。這裏主要是尋找圖像間的對應點和對應區域。

Harris角點檢測器

參考資料

  • Jan Erik Solem. Python計算機視覺編程 (圖靈程序設計叢書) (p. 33). 人民郵電出版社. Kindle 版本.
  • 人工智能:harris角點檢測_bilibili
  • harris corner detection(角點檢測)_bilibili
  • 【機器學習】【線性代數 for PCA】矩陣與對角陣相似、 一般矩陣的相似對角化、實對稱矩陣的相似對角化_csdn
  • 線代022|矩陣的相似對角化 看過有收穫,真心分享給大家_bilibili

什麼是角點?

說白了就是物體邊緣的拐點

image-20200412133125796

上面圖像中的紅點處就是角點,有了角點之後,就可以幹很多的事情了。

Harris檢測角點的核心思路

Harris的核心思路是滑動窗口檢測,這裏我們將圖像中的點分三類進行討論:

  • 一、平坦點:在圖像的某個像素點周圍是平坦的,如圖所示:

    image-20200412133537098

    平坦點的周圍的像素值沒有太大的變化。

  • 二、邊緣點:像素點位於一條邊上

    image-20200412133612077

    邊緣點的周圍的像素向某個方向變化較爲劇烈。

  • 三、角點:像素點位於一個角上

    image-20200412133958279

    角點的周圍各個方向上像素的變化都比較劇烈。

Moravec

知道了這三類情況後,我們來看公式:
E(u,v)=Σx,yw(x,y)[I(x+u,y+v)I(x,y)]2 E(u,v) = \Sigma_{x,y}w(x,y) [I(x+u,y+v) - I(x,y)]^2
其中,E(u,v)E(u,v)表示豎直和水平方向的偏移,w(x,y)w(x,y)表示窗口的中心,I(x+u,y+v)I(x+u,y+v)表示中心增加偏移後的灰度值,I(x,y)I(x,y)表示中心的灰度值。

這個公式就代表着某個點附近的變化的劇烈程度,w(x,y)w(x,y)是一個濾波(常用高斯)。那麼結合上面的理論,這個點的E(u,v)E(u,v)越大,那麼它就越有可能是個角點。但是值得注意的是,這種方法沒有旋轉不變性。

Harris

泰勒展開

Moravec的缺點是,他只考慮了四種方向,所以他沒有旋轉不變性。而Harris將方向進行了細化。於是Harris使用全微分的方法將方向進行了細化,具體來說,是用了泰勒展開。

先複習一下什麼是泰勒展開:
f(x+u,y+v)f(x,y)+ufx(x,y)+vfy(x,y) f(x+u,y+v) \approx f(x,y) +uf_x(x,y)+vf_y(x,y)
上面的式子是二變量函數的一階展開,細心如你能發現上面的式子裏表示中心增加偏移後的灰度值的式子可以使用泰勒展開!經過一番計算之後可以得到這樣一個式子。

計算過程也是非常簡單的:
$$\begin{align*}
E(u,v) &= \Sigma_{x,y}w(x,y) [I(x+u,y+v) - I(x,y)]^2\
&=w(x,y)\Sigma_{x,y}[I(x,y)+uI_x(x,y)+vI_y(x,y) - I(x,y)]^2\
&=w(x,y)\Sigma_{x,y}[uI_x(x,y)+vI_y(x,y)]^2\
&=w(x,y)\Sigma_{x,y}[u2I_x(x,y)2+v2I_y(x,y)2+2uvI_x(x,y)I_y(x,y)]\

\end{align*}: 將最終的結果簡寫一下,就可以得到:
E(u,v) = \Sigma_{x,y}w(x,y) (u2I_x2+v2I_y2+2uvI_xI_y)
$$

矩陣表示

這裏Ix,IyI_x,I_y代表圖像在水平、垂直方向上的梯度,我們將這個式子用矩陣來表示(從現在開始就是書上寫的了):
E(u,v)=[u,v](Σw(x,y)[Ix2IxIyIxIyIy2])[uv] E(u,v) = [u,v] \left( \Sigma w(x,y) \left[ \begin{matrix} I_x^2&I_xI_y\\ I_xI_y&I_y^2 \end{matrix} \right] \right) \left[ \begin{matrix} u\\ v \end{matrix} \right]
最後再來個特殊表示:
E(u,v)=[uv]MI[uv] E(u,v) = \left[ \begin{matrix} u & v \end{matrix} \right] M_I \left[ \begin{matrix} u\\ v \end{matrix} \right]
那麼問題來了,我們做了這麼多東西,不就是用泰勒展開了一下Moravec的式子,然後用矩陣表示了一下麼?我們到底是怎麼消除其旋轉不變性的呢?

下面來到了這個算法的關鍵步驟:

相似對角化

我們看到上面的式子E(u,v)=[uv]MI[uv]E(u,v) =\left[\begin{matrix} u & v \end{matrix}\right]M_I\left[\begin{matrix} u\\v\end{matrix}\right]我們使用矩陣相似對角化就可以得到下面的式子:
E(u,v)=[uv]P[λ100λ2]P1[uv] E(u,v) = \left[ \begin{matrix} u & v \end{matrix}\right]P\left[ \begin{matrix} \lambda_1 & 0\\ 0 & \lambda_2\\ \end{matrix}\right]P^{-1}\left[ \begin{matrix} u\\ v \end{matrix}\right]

什麼是相似對角化?

那麼在此之前,我們來複習一下到底什麼是矩陣相似對角化呢?(我也忘了)

  • 定義一:設A、B都是n階矩陣,若存在可逆矩陣PP,使得:
    KaTeX parse error: No such environment: align* at position 8: \begin{̲a̲l̲i̲g̲n̲*̲}̲ P^{-1} AP = B …
    則稱爲矩陣A和矩陣B相似

  • 定義二:如果方陣A與對角陣相似,則一定存在一個可逆矩陣P,按照下面公式求出方陣A的相似對角矩陣:
    KaTeX parse error: No such environment: align* at position 8: \begin{̲a̲l̲i̲g̲n̲*̲}̲ \Lambda = PAP…

相似對角化就是求出來這個對角矩陣Λ\Lambda(好久沒碰線性代數了,如果理解有誤歡迎指出)

那麼怎麼算這個Λ\Lambda呢?在此之前我們需要知道什麼樣的矩陣能夠被相似對角化。
KaTeX parse error: No such environment: align* at position 8: \begin{̲a̲l̲i̲g̲n̲*̲}̲ 一個矩陣可以被特徵化 \L…
又有一條件:屬於不同特徵值的特徵向量一定線性無關(這裏不能再深挖了,在深挖沒完了,有興趣的自己深挖)

那麼根據上面的兩個條件,我們可以知道:如果n階方陣A有n個不相等的特徵值,那麼說明A可以被相似對角化。而如果A有重特徵值,那麼檢查重特徵值對應的線性無關的特徵向量的個數若等於特徵值的重數,那麼就代表A可對角化。另外還有特殊情況:

  • 若A爲n階對稱陣,那麼A可對角化
進行相似對角化

那麼根據上面的理論,我們的這個:
MI=[Ix2IxIyIxIyIy2] M_I = \left[ \begin{matrix} I_x^2&I_xI_y\\ I_xI_y&I_y^2 \end{matrix} \right]
他顯然是一個對稱矩陣,所以根據上面的定理他是可以相似對角化的,我們對它使用相似對角化就可以得到上面的式子中的一部分:
MI=P[λ100λ2]P1 M_I =P\left[ \begin{matrix} \lambda_1 & 0\\ 0 & \lambda_2\\ \end{matrix}\right]P^{-1}
將這部分帶入即可得到我們的式子:
E(u,v)=[uv]P[λ100λ2]P1[uv] E(u,v) = \left[ \begin{matrix} u & v \end{matrix}\right]P\left[ \begin{matrix} \lambda_1 & 0\\ 0 & \lambda_2\\ \end{matrix}\right]P^{-1}\left[ \begin{matrix} u\\ v \end{matrix}\right]
我們可以先計算[u v]P[u\ v]P以及P1[uv]P^{-1}\left[ \begin{matrix} u\\ v \end{matrix}\right],假設得到的結果是[u v][u'\ v']以及[u v]T[u'\ v']^T,那麼我們上面的式子就可以改寫爲:
$$
\begin{align}
\begin{array}

E E(u,v) &= \left[
\begin{matrix}
u’ & v’
\end{matrix}
\right]

\left[
\begin{matrix}
\lambda_1 & 0\
0 & \lambda_2\
\end{matrix}
\right]

\left[
\begin{matrix}
u’\
v’
\end{matrix}
\right]
\
&= \lambda_1(u’)2+\lambda_2(v’)2\
&= \frac{u’2}{\frac{1}{\lambda_1}}+\frac{v’2}{\frac{1}{\lambda_21}}
\end{array}
\end{align}
$$
好的,到了這裏大家打眼一看就知道了,這是橢圓公式啊!那麼我們對比一下原來橢圓的公式:x2a2+y2b2\frac{x^2}{a^2}+\frac{y^2}{b^2}在這裏我們可以寫爲:a2=1λ1a^2 = \frac{1}{\lambda_1}那麼我們就可以知道a=λ112a = \lambda_1^{-\frac{1}{2}},同理可得b=λ212b = \lambda_2^{-\frac{1}{2}}

image-20200412152559928

回到問題本身

我們看一些圖

image-20200412142519387

如果我們取點附近窗口的梯度的話,我們可以看到:

image-20200412142725820

上面的三個圖像分別代表:

  • 左-平坦點附近窗口內的梯度:離原點普遍較近
  • 中-邊緣點附近的窗口的梯度:分佈在某個座標軸周圍
  • 右-角點附近的窗口的梯度:分佈在兩個座標軸周圍

我們的目標是:讓這個橢圓的兩個半軸儘量的長,用數學語言來表示就是:讓λ1,λ2\lambda_1,\lambda_2儘量的大,有一張圖非常的形象:

image-20200412152745271

再把我們的目標明確一下:我們希望λ1,λ2\lambda_1,\lambda_2同時很大但又差不多大於是我們構建出了這樣的式子:
R=det(Mλ)ktrace(Mλ)2 R = det({M_\lambda}) - k trace({M_\lambda})^2
相信很多同學和我一樣,已經忘記了這些符號,或者根本沒有學過這些符號,那麼我來解釋一下:

  • det:det:矩陣的行列式,如果你不知道什麼是行列式,那你只能自己百度了
  • trace:trace:矩陣的跡主對角線上各個元素的總和

反應在這個式子裏:

  • Mλ=[λ100λ2]M_\lambda = \left[\begin{matrix}\lambda_1&0\\0&\lambda_2\end{matrix}\right]
  • det(Mλ)=λ1λ2det({M_\lambda}) = \lambda_1\lambda_2
  • trace(Mλ)=λ1+λ2trace({M_\lambda})=\lambda_1+\lambda_2

所以我們的式子也可以寫成:
R=λ1λ2+t(λ1+λ2)2 R = \lambda_1\lambda_2 + t(\lambda_1+\lambda_2)^2
如果這個R很大的話,就說明這裏是角點,如果這個R很小的話,那麼就不是。值得注意的是,這個R評分並不是一個準確的描述問題的機制,拓展一下,這類問題屬於病態的問題,也就是說我們無法找到一種準確的方式來描述這個問題,只能通過近似的方式來描述它。放到這個例子裏就是我們人眼很容易觀測出一個點是不是角點,也可以說出來很多抽象的方法來判斷它是否是角點。但之所以我們能夠這麼輕易的去判斷,是因爲我們有足夠的抽象能力,什麼是角點?角點是在角上的點。我們之所以能夠如此輕易的判斷,是因爲我們將角這個概念進行了抽象和封裝,而計算機不會。所以我們只能使用像是上面R這種方法來近似地描述問題。(不廢話了,繼續講)

實際計算

但是上面說的東西怎麼量化的來算呢?我們先計算每個像素點的M矩陣
M=Σw(x,y)[Ix2IxIyIxIyIy2] M = \Sigma w(x,y) \left[ \begin{matrix} I_x^2&I_xI_y\\ I_xI_y&I_y^2 \end{matrix} \right]
這裏的Ix,IyI_x,I_y也就是圖像在水平、垂直方向的導數,可以用可以用Sobel核去做一個卷積就可以得到。然後這個window函數可以使用高斯核等。該卷積的目的是得到MIM_I在周圍像素上的局部平均。

可以看到計算的方法很簡單,也沒啥好講的,理論懂了就萬事大吉了,最終我們使用這兩項的比值來衡量這個問題:
finalScore=det(M)trace(M)2 finalScore = \frac{det({M})}{trace({M})^2}

代碼

from PIL import Image
from numpy import *
from pylab import *
from scipy. ndimage import filters


# 計算比值得分的函數,即計算finalScore
def computeHarrisResponse(im, sigma = 3):
    """
        在一幅灰度圖像中,對每個像素計算角點器響應函數
        輸入:
            im:表示需要求R的圖像(需要是灰度圖)
            sigma:考慮半徑
        返回:
            Wdet / Wtr : lambda1*lambda2 與 (lambda1+lambda2)^2的比
    """
    
    # 計算導數
    # I_x
    imx = zeros(im.shape)
    filters.gaussian_filter(im, (sigma, sigma), (0, 1) , imx)
    # I_y
    imy = zeros(im.shape)
    filters.gaussian_filter(im, (sigma, sigma), (1, 0) , imy)
    
    # 計算Harris矩陣的分量
    Wxx = filters.gaussian_filter(imx * imx, sigma)
    Wxy = filters.gaussian_filter(imx * imy, sigma)
    Wyy = filters.gaussian_filter(imy * imy, sigma)
    
    # 計算特徵值和跡
    Wdet = Wxx * Wyy - Wxy ** 2
    Wtr = Wxx + Wyy
    
    return Wdet / Wtr


# 從每個像素計算角點器響應函數到圖像中的所有角點
def getHarrisPoints(harrisim, minDist = 10, threshold = 0.1):
    """
        從一幅Harris響應圖像中返回角點。
        輸入:
            minDist:分割角點和圖像邊界的最少像素數目
        輸出:
            角點們
    """
    
    # 尋找高於閾值的候選角點
    # 角點閾值等於得分矩陣中最大的*0.1
    cornerThreshold = harrisim.max() * threshold
    #harrisim_t爲1的位置就是可能是角點的
    harrisimT = (harrisim > cornerThreshold) * 1
    
    # 得到候選點的座標
    coords = array(harrisimT.nonzero()).T
    
    # 候選點的Harris 響應值
    candidateValues = [harrisim[c[0], c[1]] for c in coords]
    
    # 對候選點按照Harris 響應值進行排序
    index = argsort(candidateValues)
    
    # 將可行點的位置保存到數組中
    allowedLocations = zeros(harrisim.shape) 
    allowedLocations[minDist : -minDist, minDist : -minDist] = 1

    # 按照minDistance 原則,選擇最佳Harris點
    filteredCoords = []
    for i in index:
        if(allowedLocations[coords[i, 0], coords[i, 1]] == 1):
            filteredCoords.append(coords[i])
            allowedLocations[(coords[i, 0] - minDist) : (coords[i, 0] + minDist),
                             (coords[i, 1] - minDist) : (coords[i, 1] + minDist)] = 0
    
    return filteredCoords

# 顯示角點
def plotHarrisPoints(img, filteredCoords):
    """
        繪製圖像中檢測到的角點
    """
    figure()
    #灰度圖
    gray()
    #顯示圖
    imshow(img)
    # 顯示點
    plot([p[1] for p in filteredCoords], [p[0] for p in filteredCoords], "*")
    # 關閉座標
    axis('off')
    show()
    
if __name__ == "__main__":
    im = array(Image.open(r'C:\Users\wangsy\Desktop\learning\ch3\timg.jpg').convert('L'))
    harrisim = computeHarrisResponse(im)
    filteredCoords = getHarrisPoints(harrisim, 6)
    plotHarrisPoints(im, filteredCoords)

我們使用第一講裏面用到的埃菲爾鐵塔試試,原圖如下:

timg

使用上述代碼提取角點:

image-20200412165450824

可以看到,效果是可以的,但是艾菲爾鐵塔的角點太多了,有點難看出來,所以我們換張圖來看看。

這是一個象棋棋盤:

timg2

提取效果如下

image-20200412165729281

可以看出提取效果還是非常好的,雖然提取的有點多。其中有的地方提取效果不佳,這有可能是由於後面的紋理導致的。同時我們可以調整閾值進行查看。將閾值調整到0.3,得到的結果如下:

image-20200412170009129

好了,今天就到這裏了,這是第二章的第一小節的前半部分,如果只想囫圇吞棗的話,很快就可以水完,但是如果想要抓住事情的來龍去脈,把事情耨清楚,就需要一定的努力了,之前的博客半章4000字就能搞定了,現在一個問題就需要4000字了。

學這個問題也讓我理解到,好的參考讀物有多麼的重要,讀那些垃圾博客再多也不如多讀一些經典的東西。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章