大家好,好快哦,今天是第五講了,現在的時間是2018年5月27日,很快5月份就結束了,也就是說半年的時間就過去了,我過年的事兒還能記住呢。曾經有一個姓張的同學說過,時間隨着年齡的增長會越來越快。以1歲時候過一年的時間來說,要是20歲就只有二十分之一的長度了。速度快了20倍。這個理論很有意思。我這個快30歲的人,時間確實過的飛快啊。
先說一下上節課的問題,上節課在開閉運算的時候有一個第八個參數叫做:morphologyDefaultBorderValue() ,這個參數是判斷你是腐蝕還是膨脹,之前我們定義了超出邊緣的話,我們假定爲一個常數值。如果是膨脹的話那麼邊緣的常數值就是低亮(全0),如果是腐蝕的話就是高亮(全1)。它會返回一個64位的浮點數值(全0或者全1),之後根據我們圖像的顏色深度,我們再改回32、16或者8位。
今天我要講的內容是canny邊緣檢測。首先先說件事兒,我們的公衆平臺已經這麼多人關注了,你們只看不轉可不道義哦,你們學會了知識,在知網發表了幾篇文,拿到了獎學金,我不需要你們的感謝,你們轉發幾條到朋友圈幫我宣傳一下總可以吧?這裏也給各位抱拳了。這也是爲了更多中國人民能夠學習到知識,把知識普及。促進我國的發展做貢獻了!
廢話就不說了,說一下canny吧,先說一下什麼叫canny檢測,canny是一個人,澳大利亞的人,這個人是一個天才,它發明了一種方法,能夠把圖片中的輪廓從圖片中剝離出去。那麼canny檢測就很好理解了,檢測啥?當然是檢測圖像的輪廓邊緣啊。所以canny檢測又稱爲邊緣檢測算法。那麼這個算法是怎麼進行計算的呢?其實它分爲了5部分:
高斯濾波
首先第一部分是高斯濾波。我們在第三講中說過了,濾波分爲高通和低通濾波,低通濾波是進行去噪聲用的。而計算方式是用一張圖片與低通濾波器進行卷積。之前我們自己創造了一個濾波器:
這個是5*5的一個濾波器,所有的元素之和爲1,這樣的濾波器就是低通濾波器。然而我們一般不需要自己創造一個濾波器,而是用其它人創造好的東西,直接拿來用,這樣的效果也比自己創造的好。於是我們就引入了高斯濾波器,他也是低通濾波器的一種。
那麼高斯濾波器是什麼樣子的呢?其實就是基於高斯分佈的濾波器,高斯分佈也叫正態分佈,X、Y兩個變量的正態分佈計算公式如下:
一般來說,在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卷積核做卷積,一共有兩個卷積核,做卷積後會得出Gx,Gy兩個方向的梯度。梯度方向垂直方向就是邊緣方向。
非極大值抑制
我們計算完成梯度之後,我們要做的就是邊緣檢測了,怎麼檢測?首先第一步是要將這張圖片的邊緣離散化爲上下左右斜8個方向:
接下來求出各個方向的梯度這個梯度值的求法和上面的sobel算子差不多,比如90度的話就是用f(90)-f(0)/1,然後乘以它們之間的方向[0,1]。這樣就OK了。其它的方向也是如此,我們知道了這個方向以後,下一步是利用公式:
我們Gy,Gx已經用sobel算子求出來了,然後8個方向的梯度G也求出來了,接下來我們假定:
梯度的方向會與G45,G0交於點P1,G225,G180交與點P2,這兩個點怎麼算?首先我們要以Gy爲縱座標,Gx爲橫座標,把G45,G0畫到座標上面,然後我們把這兩個點連線,形成一個一次函數Gy=K1*Gx+b1。
然後我們把Gy,Gx的方向作爲一個正比例函數Gy=k2*Gx,然後聯立,就會求出P1點的座標。
同理,我們把G225,G180畫到座標上面,然後我們把這兩個點連線,形成一個一次函數,然後與Gy,Gx的方向的正比例函數聯立,這樣P2座標就求出來了。
接下來我們要判斷邊緣了:
我們用G(Gy,Gx)與我們就出來的P1,P2進行比較,如果G比P1且P2都大的話,那麼說明它可能是邊緣,並記錄下來。如果要是比其中一個小的話,那就不成立了,直接判斷不是邊緣!這個方法就叫做非最大值抑制,也就是比其中一個小就給它幹掉!
雙閾值檢測
在非最大值抑制過後,如果這個像素被檢測出來可能邊緣,那我們還要對它進行的是雙閾值檢測,怎麼檢測呢,我們需要手動定義兩個閾值,一個是高閾值,一個是低閾值。然後我們用G(Gy,Gx)與閾值進行比較,如果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
也可以掃描二維碼哦!