1. Canny算法概述
Canny邊緣檢測算子是John F. Canny於 1986 年開發出來的一個多級邊緣檢測算法。更爲重要的是 Canny 創立了邊緣檢測計算理論(Computational theory of edge detection)解釋這項技術如何工作。
通常情況下邊緣檢測的目的是在保留原有圖像屬性的情況下,顯著減少圖像的數據規模。有多種算法可以進行邊緣檢測,雖然Canny算法年代久遠,但可以說它是邊緣檢測的一種標準算法,而且仍在研究中廣泛使用。
2.最優邊緣準則
Canny 的目標是找到一個最優的邊緣檢測算法,最優邊緣檢測的含義是:
(1)最優檢測:算法能夠儘可能多地標識出圖像中的實際邊緣,漏檢真實邊緣的概率和誤檢非邊緣的概率都儘可能小;
(2)最優定位準則:檢測到的邊緣點的位置距離實際邊緣點的位置最近,或者是由於噪聲影響引起檢測出的邊緣偏離物體的真實邊緣的程度最小;
(3)檢測點與邊緣點一一對應:算子檢測的邊緣點與實際邊緣點應該是一一對應。
爲了滿足這些要求 Canny 使用了變分法(calculus of variations),這是一種尋找優化特定功能的函數的方法。最優檢測使用四個指數函數項表示,但是它非常近似於高斯函數的一階導數。
2. Canny算法實現步驟
第一步:灰度化
第二步:高斯濾波
任何邊緣檢測算法都不可能在未經處理的原始數據上很好地工作,所以第一步是對原始數據與高斯 mask 作卷積,得到的圖像與原始圖像相比有些輕微的模糊(blurred)。這樣,單獨的一個像素噪聲在經過高斯平滑的圖像上變得幾乎沒有影響。
首先生成二維高斯分佈矩陣:
然後與灰度圖像進行卷積實現濾波:
第三步:計算梯度值和方向
求變化率時,對於一元函數,即求導;對於二元函數,求偏導。 數字圖像處理中,用一階有限差分近似求取灰度值的梯度值(變化率)。
(即:使差商(Δf/Δx)近似取代微商(∂f/∂x)。求灰度的變化率,分別取x和y方向上相鄰像素做差,代替求取x和y 方向一階偏導) 。
其中f爲圖像灰度值,P代表X方向梯度幅值,Q代表Y方向 梯度幅值,M是該點幅值,Θ是梯度方向,也就是角度。
注:圖像梯度方向與邊緣方向互相垂直:
第四步:非極大值抑制(NMS)
這一步的目的是將模糊(blurred)的邊界變得清晰(sharp)。通俗的講,就是保留了每個像素點上梯度強度的極大值,而刪掉其他的值。對於每個像素點,進行如下操作:
沿着梯度方向,比較它前面和後面的梯度值。在沿其方向上鄰域的梯度幅值最大,則保留;否則,抑制。
圖中的數字代表了像素點的梯度強度,箭頭方向代表了梯度方向。以第二排第三個像素點爲例,由於梯度方向向上,則將這一點的強度(7)與其上下兩個像素點的強度(5和4)比較,由於這一點強度最大,則保留。處理後效果如下圖所示。
上圖中,可以想象,邊界處的梯度方向總是指向垂直於邊界的方向,即最後會保留一條邊界處最亮的一條細線。
5.雙閾值(Double Thresholding),邊緣連接
經過非極大抑制後圖像中仍然有很多噪聲點。Canny算法中應用了一種叫雙閾值的技術。即設定一個閾值上界和閾值下界(opencv中通常由人爲指定的),圖像中的像素點如果大於閾值上界則認爲必然是邊界(稱爲強邊界,strong edge),小於閾值下界則認爲必然不是邊界,兩者之間的則認爲是候選項(稱爲弱邊界,weak edge),需進行進一步處理。
選取高閾值T H 和低閾值T L ,比率爲2:1或3:1。(一般取TH =0.3 或 0.2, TL =0.1 )
取出非極大值抑制後的圖像中的最大梯度幅值,重新定義高低閾值。即:T H ×Max,T L ×Max 。(當然可以自己給定)
將小於TL 的點拋棄,賦0;將大於T H 的點立即標記(這些點就是邊緣點),賦1。
將大於TL ,小於TH 的點使用8連通區域確定(即:只有與TH 像素連接時纔會被接受,成爲邊緣點,賦1) 。
import matplotlib.pyplot as plt
import numpy as np
import math
import cv2
img = cv2.imread('./Images/lena.jpg')
img=cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
blur = cv2.GaussianBlur(img, (5, 5), 0) # 用高斯濾波處理原圖像降噪
canny = cv2.Canny(blur, 50, 150) # 50是最小閾值,150是最大閾值
sigma1 = sigma2 = 1
sum = 0
gaussian = np.zeros([5, 5])
for i in range(5):
for j in range(5):
gaussian[i,j] = math.exp(-1/2 * (np.square(i-3)/np.square(sigma1) #生成二維高斯分佈矩陣
+ (np.square(j-3)/np.square(sigma2)))) / (2*math.pi*sigma1*sigma2)
sum = sum + gaussian[i, j]
gaussian = gaussian/sum
# print(gaussian)
def rgb2gray(rgb):
return np.dot(rgb[...,:3], [0.299, 0.587, 0.114])
# step1.高斯濾波
gray = rgb2gray(img)
W, H = gray.shape
new_gray = np.zeros([W-5, H-5])
for i in range(W-5):
for j in range(H-5):
new_gray[i,j] = np.sum(gray[i:i+5,j:j+5]*gaussian) # 與高斯矩陣卷積實現濾波
# plt.imshow(new_gray, cmap="gray")
# step2.增強 通過求梯度幅值
W1, H1 = new_gray.shape
dx = np.zeros([W1-1, H1-1])
dy = np.zeros([W1-1, H1-1])
d = np.zeros([W1-1, H1-1])
for i in range(W1-1):
for j in range(H1-1):
dx[i,j] = new_gray[i, j+1] - new_gray[i, j]
dy[i,j] = new_gray[i+1, j] - new_gray[i, j]
d[i, j] = np.sqrt(np.square(dx[i,j]) + np.square(dy[i,j])) # 圖像梯度幅值作爲圖像強度值
# plt.imshow(d, cmap="gray")
# setp3.非極大值抑制 NMS
W2, H2 = d.shape
NMS = np.copy(d)
NMS[0,:] = NMS[W2-1,:] = NMS[:,0] = NMS[:, H2-1] = 0
for i in range(1, W2-1):
for j in range(1, H2-1):
if d[i, j] == 0:
NMS[i, j] = 0
else:
gradX = dx[i, j]
gradY = dy[i, j]
gradTemp = d[i, j]
# 如果Y方向幅度值較大
if np.abs(gradY) > np.abs(gradX):
weight = np.abs(gradX) / np.abs(gradY)
grad2 = d[i-1, j]
grad4 = d[i+1, j]
# 如果x,y方向梯度符號相同
if gradX * gradY > 0:
grad1 = d[i-1, j-1]
grad3 = d[i+1, j+1]
# 如果x,y方向梯度符號相反
else:
grad1 = d[i-1, j+1]
grad3 = d[i+1, j-1]
# 如果X方向幅度值較大
else:
weight = np.abs(gradY) / np.abs(gradX)
grad2 = d[i, j-1]
grad4 = d[i, j+1]
# 如果x,y方向梯度符號相同
if gradX * gradY > 0:
grad1 = d[i+1, j-1]
grad3 = d[i-1, j+1]
# 如果x,y方向梯度符號相反
else:
grad1 = d[i-1, j-1]
grad3 = d[i+1, j+1]
gradTemp1 = weight * grad1 + (1-weight) * grad2
gradTemp2 = weight * grad3 + (1-weight) * grad4
if gradTemp >= gradTemp1 and gradTemp >= gradTemp2:
NMS[i, j] = gradTemp
else:
NMS[i, j] = 0
# plt.imshow(NMS, cmap = "gray")
# step4. 雙閾值算法檢測、連接邊緣
W3, H3 = NMS.shape
DT = np.zeros([W3, H3])
# 定義高低閾值
TL = 0.2 * np.max(NMS)
TH = 0.3 * np.max(NMS)
for i in range(1, W3-1):
for j in range(1, H3-1):
if (NMS[i, j] < TL):
DT[i, j] = 0
elif (NMS[i, j] > TH):
DT[i, j] = 1
elif ((NMS[i-1, j-1:j+1] < TH).any() or (NMS[i+1, j-1:j+1]).any()
or (NMS[i, [j-1, j+1]] < TH).any()):
DT[i, j] = 1
plt.figure(1)
#第一行第一列圖形
ax1 = plt.subplot(1,3,1)
plt.sca(ax1)
plt.imshow(img)
plt.title("artwork")
#第一行第二列圖形
ax2 = plt.subplot(1,3,2)
plt.sca(ax2)
plt.imshow(canny,cmap="gray")
plt.title("opencv Canny")
ax3 = plt.subplot(1,3,3)
plt.sca(ax3)
plt.imshow(DT,cmap="gray")
plt.title("my Canny")
參考:
【1】https://www.cnblogs.com/king-lps/p/8007134.html