白話文講計算機視覺-第五講-canny邊緣檢測算法

大家好,好快哦,今天是第五講了,現在的時間是2018527日,很快5月份就結束了,也就是說半年的時間就過去了,我過年的事兒還能記住呢。曾經有一個姓張的同學說過,時間隨着年齡的增長會越來越快。以1歲時候過一年的時間來說,要是20歲就只有二十分之一的長度了。速度快了20倍。這個理論很有意思。我這個快30歲的人,時間確實過的飛快啊。

先說一下上節課的問題,上節課在開閉運算的時候有一個第八個參數叫做:morphologyDefaultBorderValue() ,這個參數是判斷你是腐蝕還是膨脹,之前我們定義了超出邊緣的話,我們假定爲一個常數值。如果是膨脹的話那麼邊緣的常數值就是低亮(全0),如果是腐蝕的話就是高亮(全1)。它會返回一個64位的浮點數值(全0或者全1),之後根據我們圖像的顏色深度,我們再改回3216或者8位。

今天我要講的內容是canny邊緣檢測。首先先說件事兒,我們的公衆平臺已經這麼多人關注了,你們只看不轉可不道義哦,你們學會了知識,在知網發表了幾篇文,拿到了獎學金,我不需要你們的感謝,你們轉發幾條到朋友圈幫我宣傳一下總可以吧?這裏也給各位抱拳了。這也是爲了更多中國人民能夠學習到知識,把知識普及。促進我國的發展做貢獻了!

廢話就不說了,說一下canny吧,先說一下什麼叫canny檢測,canny是一個人,澳大利亞的人,這個人是一個天才,它發明了一種方法,能夠把圖片中的輪廓從圖片中剝離出去。那麼canny檢測就很好理解了,檢測啥?當然是檢測圖像的輪廓邊緣啊。所以canny檢測又稱爲邊緣檢測算法。那麼這個算法是怎麼進行計算的呢?其實它分爲了5部分:

高斯濾波

首先第一部分是高斯濾波。我們在第三講中說過了,濾波分爲高通和低通濾波,低通濾波是進行去噪聲用的。而計算方式是用一張圖片與低通濾波器進行卷積。之前我們自己創造了一個濾波器:


這個是5*5的一個濾波器,所有的元素之和爲1,這樣的濾波器就是低通濾波器。然而我們一般不需要自己創造一個濾波器,而是用其它人創造好的東西,直接拿來用,這樣的效果也比自己創造的好。於是我們就引入了高斯濾波器,他也是低通濾波器的一種。

那麼高斯濾波器是什麼樣子的呢?其實就是基於高斯分佈的濾波器,高斯分佈也叫正態分佈,XY兩個變量的正態分佈計算公式如下:

一般來說,在canny算法中是一個3*3的濾波器(必須是奇數),所以我們假定卷積核是一個3*3的矩陣,中心點的座標是(2,2),那麼我們可以定義卷積核內每一個小矩形格的座標:

(1,1)

(1,2)

(1,3)

(2,1)

(2,2)

(2,3)

(3,1)

(3,2)

(3,3)

那麼這個矩陣裏面對應正態分佈計算公式的u1,u2,σ1,σ2怎麼計算呢,我們先說一下u1,u2,這個說白了就是中心的位置,一維的正態分佈就是中心點的座標哦!二維同樣也是的哦!那麼中心點的位置是哪裏呢?不用多說,當然是中央啦,也就是第二行第二列,所以u1=2,u2=2,如果它不是一個3*3的,比如5*5的卷積核,也就是2*2+1,2*2+1的卷積核,我們的u1=u2=2+1=3也就是第三行第三列。我們把2*2+1,2*2+1擴展一下,變成(2k+1)*(2k+1)的卷積核,那麼它的u1=k+1,u2=k+1。

說完了u1,u2,我們再說一下σ1,σ2,我們假定的是這兩個值是相等的,都等於:

σ= 0.3*((ksize-1)*0.5 - 1) + 0.8,比如我們是3*3的核,那麼σ1,σ2就都爲0.8。

這樣我們的參數都確定好了,那麼我們把卷積核內的座標都帶入進去,不就能把每一個小格子裏面的H給就出來了麼?求出的結果如下:

 

之後,我們要進行歸一化處理,怎麼歸一化?這裏和標準歸一化有點區別。這裏首先是用第一行第一列的數據的倒數除以裏面的每一個數據,得到:

 

最後,我們保留整數位,捨棄小數位得到:

 

這樣一個3*3的高斯卷積核就製作完畢了。

但是我們在canny裏面用的高斯卷積和正常的卷積核還是有一些出路,出路就在我們歸一化的時候,我們不是除以第一個數據,而是除以所有得到的數據的和的導數,然後也不捨棄小數。首先我們同理得到用正態分佈公式計算的結果:

 

然後我們把每一個數除以所有數據的和:

 

這樣就獲得了canny的卷積核了。

接下來我們導入一張想要獲取邊緣信息的圖片,然後與高斯卷積核做卷積,得到卷積後去除噪聲的圖像。(估計卷積之後也就是別人說的那樣:垃圾圖片進,垃圾圖片出)

這樣第一步完成了。

計算梯度

第二部分是計算圖像邊緣的梯度,什麼叫梯度,就是函數下降最快的方向,用數學表達式就是:

梯度的幅值和方向就是:


我們知道梯度方向就是兩個偏導數之比,但是我們怎麼求這個偏導數呢,我們於是引入了sobel算子:

 

這兩個就分別是x方向以及y方向的卷積核,也成sobel算子。

至於爲什麼卷積核的數據是這幾個數呢。這裏面篇幅比較少,我就簡單講解一下吧:

首先我們假定有一張圖片的部分3*3的圖片矩陣:

A

B

C

D

E

F

G

H

I

我們把它的座標定爲:

(x-1,y+1)

(x,y+1)

(x+1,y+1)

(x-1,y)

(x,y)

(x+1,y)

(x-1,y-1)

(x,y-1)

(x+1,y-1)

我們假設想要求E點的梯度,那麼我們不知道梯度方向是啥,因爲這TMD全是離散的數據,那麼我們怎麼辦?Sobel算子的發明人就有個一個想法(也就是下面這個學霸),我們假設它的方向有4個分別是AI方向:


這四個方向分別是(A,I) (B,H) (C,G) (F,D),然後我們把四個方向給求出來:

(A,I)就是:

[((x-1)-(x+1)),((y+1)-(y-1))]=[-2,2],約分得到[-1,1]。

(B,H)就是:

[(x-x),((y+1)-(y-1))]=[0,2],約分得到[0,1]。

C,G)就是:

[((x+1)-(x-1)),((y+1)-(y-1))]=[2,2],約分得到[1,1]。

(F,D)就是:

[((x-1)-(x+1)),y-(y)]=[2,0],約分得到[1,0]。

這四個方向哪個是梯度的方向啊?我們也不知道,可能有一個是,也有可能都不是,那麼我們怎麼辦?

我們假設圖片上面最中間的值爲f(x,y),那麼A,I兩點就分別是f(x-1,y+1),f(x+1,y-1)。這兩個點的距離是4,爲啥,數格子,最短路徑是4.也就是x兩個格子,y兩個格子(因爲是離散數據,不能走斜線)。我們用這兩個點的數據相減,然後除以4會得到:

f(x-1,y+1)-f(x+1,y-1)/4=(A-I)/4

我們稱爲AI的方向導數,也就是AI的變化率。

導數值我們求出來了,但是我們這個是一個標量,所以接下來我們要把這個數與方向相乘[-1,1],獲得AI的梯度值,至於爲什麼相乘就是梯度值,我也不大清楚,應該是作者定的吧。同理我們把(B,H) (C,G) (F,D)都給它求出來,我們得到:

(C-G)/4 * [ 1, 1]

 

(A-I)/4 * [-1, 1]

 

(B-H)/2 * [ 0, 1]

 

(F-D)/2 * [ 1, 0]

這些值我們求完之後呢,我們給這四個值加和,加和之後我們應該除以4求一個平均值作爲梯度。但是我們的卷積核是整數,我們不能除,所以我們選擇乘以4,給這個平均值擴大16倍,比如X座標方向的值:

Gx = [(c-g-a+i)/4 + (f-d)/2, (c-g+a-i)/4 + (b-h)/2]

Gx' = 4*Gx = [c-g-a+i + 2*(f-d), c-g+a-i + 2*(b-h)]

然後我們把相應個字母前面的係數帶入到對應的矩陣中,就會得到卷積核:

同理Y的卷積核也可以求出:

至於上面的推導過程,大家不懂的話沒有事兒,因爲本人也是很懵逼的,尤其是(C-G)/4 * [ 1, 1]相乘,我一直就想不明白,如果大家明白請告訴我哈!

說了這麼多,我們還是說以下怎麼計算梯度吧:

其實,我們上面已經告訴了,就是4*Gx = [c-g-a+i + 2*(f-d), c-g+a-i + 2*(b-h)] ,也就是我們用高斯處理後的圖像與sobel卷積核做卷積,一共有兩個卷積核,做卷積後會得出GxGy兩個方向的梯度。梯度方向垂直方向就是邊緣方向。

非極大值抑制

我們計算完成梯度之後,我們要做的就是邊緣檢測了,怎麼檢測?首先第一步是要將這張圖片的邊緣離散化爲上下左右斜8個方向:

接下來求出各個方向的梯度這個梯度值的求法和上面的sobel算子差不多,比如90度的話就是用f(90)-f(0)/1,然後乘以它們之間的方向[0,1]。這樣就OK了。其它的方向也是如此,我們知道了這個方向以後,下一步是利用公式:

我們GyGx已經用sobel算子求出來了,然後8個方向的梯度G也求出來了,接下來我們假定:

梯度的方向會與G45,G0交於點P1G225G180交與點P2,這兩個點怎麼算?首先我們要以Gy爲縱座標,Gx爲橫座標,把G45,G0畫到座標上面,然後我們把這兩個點連線,形成一個一次函數Gy=K1*Gx+b1

然後我們把GyGx的方向作爲一個正比例函數Gy=k2*Gx,然後聯立,就會求出P1點的座標。

同理,我們把G225G180畫到座標上面,然後我們把這兩個點連線,形成一個一次函數,然後與GyGx的方向的正比例函數聯立,這樣P2座標就求出來了。

接下來我們要判斷邊緣了:

我們用GGyGx)與我們就出來的P1,P2進行比較,如果GP1P2都大的話,那麼說明它可能是邊緣,並記錄下來。如果要是比其中一個小的話,那就不成立了,直接判斷不是邊緣!這個方法就叫做非最大值抑制,也就是比其中一個小就給它幹掉!

雙閾值檢測

在非最大值抑制過後,如果這個像素被檢測出來可能邊緣,那我們還要對它進行的是雙閾值檢測,怎麼檢測呢,我們需要手動定義兩個閾值,一個是高閾值,一個是低閾值。然後我們用GGyGx)與閾值進行比較,如果G比高閾值大,那麼它就是一個強邊緣,如果比高閾值小但是比低閾值大,那麼它就是弱邊緣。如果比低閾值都小,那麼直接判斷這個不是邊緣。然後把可能是邊緣的值都記錄下來。

孤立低閾值

我們就這樣,可以使用上面的方法檢測圖片中第一行第一列的像素。但是我們強邊緣我們認爲一定是邊緣了,但是弱邊緣一定是邊緣嗎?不一定,它還需要與其它地方相互比較。我們檢測完成第一個像素之後,下一步就是往左邊移動一格,檢測第二個位置。也是前面4個過程計算完畢。依次類推,第一行最後一格檢測完畢後。返回第一行第一列,然後向下移動一格,檢測第二行第一列,一直就這麼幹,直到所有元素都檢測完畢。我們檢測出一堆邊緣。然後我們返回第一個像素,尋找弱邊緣的元素,找到一個之後,我們查看弱邊緣像素及其8方向的像素,也就是上下左右斜,看看有沒有一個元素是強邊緣,如果有一個強邊緣,那麼它就是邊緣,如果一個也沒有,那麼認定它不是邊緣。就這樣把所有弱邊緣過一遍,並且記錄。

我們把記錄的邊緣值高亮顯示,其它的值都變爲低亮。這樣邊緣就出來了。

 

最後我們附上代碼,這個代碼是canny算法的代碼:

首先我們有一張圖(還是小熊,我最喜歡的小熊了):

接下來運用代碼,進行邊緣檢測,代碼如下:

#導入類庫

import cv2

import numpy as np

 

#讀入圖片

img = cv2.imread("D:/xiaomu/dawawazao.jpg", 0)

#顯示圖片

cv2.imshow('orgin',img)

#進行邊緣檢測,設定高低閾值分別爲300,200。後把canny邊緣圖片保存到硬盤,名字爲canny.jpe

cv2.imwrite("canny.jpg", cv2.Canny(img, 200, 300))

#顯示邊緣圖片

cv2.imshow("canny", cv2.imread("canny.jpg"))

#按任意鍵退出

cv2.waitKey()

cv2.destroyAllWindows()
 

最後得到結果:

 

邊緣檢測本人就講解完畢了,下節課講解輪廓檢測。

———————————————

如果對我的課程感興趣的話,歡迎關注小木希望學園-微信公衆號: 

mutianwei521

也可以掃描二維碼哦!


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