1. Canny介紹
Canny算子與Marr(LoG)邊緣檢測方法類似,也屬於是先平滑后求導數的方法。John Canny研究了最優邊緣檢測方法所需的特性,給出了評價邊緣檢測性能優劣的三個指標:
- 1 好的信噪比,即將非邊緣點判定為邊緣點的概率要低,將邊緣點判為非邊緣點的概率要低;
- 2 高的定位性能,即檢測出的邊緣點要盡可能在實際邊緣的中心;
- 3 對單一邊緣僅有唯一響應,即單個邊緣產生多個響應的概率要低,並且虛假響應邊緣應該得到最大抑制。
2. Canny檢測實現過程
第一步:灰度化
第二步:高斯濾波
首先生成二維高斯分布矩陣:
然后與灰度圖像進行卷積實現濾波:
第三步:計算梯度值和方向
求變化率時,對於一元函數,即求導;對於二元函數,求偏導。 數字圖像處理中,用一階有限差分近似求取灰度值的梯度值(變化率)。
(即:使差商(Δf/Δx)近似取代微商(∂f/∂x)。求灰度的變化率,分別取x和y方向上相鄰像素做差,代替求取x和y 方向一階偏導) 。
其中f為圖像灰度值,P代表X方向梯度幅值,Q代表Y方向 梯度幅值,M是該點幅值,Θ是梯度方向,也就是角度。
注:圖像梯度方向與邊緣方向互相垂直:
第四步:非極大值抑制(NMS)
通俗意義上是指尋找像素點局部最大值。沿着梯度方向,比較它前面和后面的梯度值。在沿其方向上鄰域的梯度幅值最大,則保留;否則,抑制。
具體參考此文:canny 非極大值抑制 NMS
可以進行插值來提高結果。
第五步:雙閾值的選取、邊緣連接
- 選取高閾值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) 。
3. Canny檢測Python實現
具體實現略有不同,例如:
高斯矩陣的實現過程、梯度幅值的實現過程、非極大值抑制的角度選取(可以選0,45,90,135)、邊緣檢測的實現過程。
# -*- coding: utf-8 -*- """ Created on Thu Dec 7 21:12:41 2017 @author: L.P.S """ import matplotlib.pyplot as plt import numpy as np import math img = plt.imread('G:\\360downloads\\lps.png') 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.imshow(DT, cmap = "gray")
4. 實驗結果
原圖 雙閾值:0.1*max, 0.3*max 雙閾值:0.2*max, 0.3*max
參考:
Canny算子中的非極大值抑制(Non-Maximum Suppression)分析