Canny边缘检测算法(python 实现)
文章目錄
- 最優邊緣準則
 - 算法實現步驟
 - 1. 應用高斯濾波來平滑(模糊)圖像,目的是去除噪聲
 - 2. 計算梯度強度和方向
 - 3. 應用非最大抑制技術NMS來消除邊誤檢
 - 4. 應用雙閾值的方法來決定可能的(潛在的)邊界
 - 5. 利用滯后技術來跟蹤邊界
 
- opencv實現Canny邊緣檢測
 - 手寫代碼
 - 參考文章
 
最優邊緣準則
? ? Canny 的目標是找到一個最優的邊緣檢測算法,最優邊緣檢測的含義是:
 ? ? (1)最優檢測:算法能夠盡可能多地標識出圖像中的實際邊緣,漏檢真實邊緣的概率和誤檢非邊緣的概率都盡可能小;
 ? ? (2)最優定位準則:檢測到的邊緣點的位置距離實際邊緣點的位置最近,或者是由于噪聲影響引起檢測出的邊緣偏離物體的真實邊緣的程度最小;
 ? ? (3)檢測點與邊緣點一一對應:算子檢測的邊緣點與實際邊緣點應該是一 一對應。
算法實現步驟
? ? Canny邊緣檢測算法可以分為以下5個步驟:
1. 應用高斯濾波來平滑(模糊)圖像,目的是去除噪聲
? ? 高斯濾波器是將高斯函數離散化,將濾波器中對應的橫縱坐標索引代入到高斯函數,從而得到對應的值。
? ? 二維的高斯函數如下:其中 (x , y)為坐標, σ 為標準差
 H(x,y)=12πσ2e?x2+y22σ2(1)H(x,y) = \frac{1}{2\pi σ^2} e^{- \frac{x^2 + y^2}{2σ^2}} \tag1 H(x,y)=2πσ21?e?2σ2x2+y2?(1)
 ? ? 不同尺寸的濾波器,得到的值也不同,下面是 (2k+1)x(2k+1) 濾波器的計算公式 :
 H[i,j]=12πσ2e?(i?k?1)2+(j?k?1)22σ2(2)H[i,j] = \frac{1}{2\pi σ^2} e^{- \frac{(i-k-1)^2 + (j-k-1)^2}{2σ^2}} \tag2 H[i,j]=2πσ21?e?2σ2(i?k?1)2+(j?k?1)2?(2)
 ? ? 常見的高斯濾波器大小為 5*5, σ = 1.4 ,其近似值為:
2. 計算梯度強度和方向
? ? 接下來,我們要尋找邊緣,即灰度強度變化最強的位置,(一道黑邊一道白邊中間就是邊緣,它的灰度值變化是最大的)。在圖像中,用梯度來表示灰度值的變化程度和方向。
 ? ? 常見方法采用Sobel濾波器【水平x和垂直y方向】在計算梯度和方向
 水平方向的Sobel算子Gx:用來檢測 y 方向的邊緣
| -2 | 0 | 2 | 
| -1 | 0 | 1 | 
? ? 垂直方向的Sobel算子Gy:用來檢測 x 方向的邊緣( 邊緣方向和梯度方向垂直)
| 0 | 0 | 0 | 
| -1 | -2 | -1 | 
? ? 采用下列公式計算梯度和方向:
 G=(Gx2+Gy2)(3)G = \sqrt{(G_x^2 + G_y^2)} \tag3 G=(Gx2?+Gy2?)?(3)
 θ=arctanGyGx(4)\theta = arctan{\frac{G_y}{G_x}} \tag4 θ=arctanGx?Gy??(4)
3. 應用非最大抑制技術NMS來消除邊誤檢
原理:遍歷梯度矩陣上的所有點,并保留邊緣方向上具有極大值的像素
? ? 這一步的目的是將模糊(blurred)的邊界變得清晰(sharp)。通俗的講,就是保留了每個像素點上梯度強度的極大值,而刪掉其他的值。對于每個像素點,進行如下操作:
 ? ? a) 將其梯度方向近似為以下值中的一個(0,45,90,135,180,225,270,315)(即上下左右和45度方向)
? ? b) 比較該像素點,和其梯度方向正負方向的像素點的梯度強度
? ? c) 如果該像素點梯度強度最大則保留,否則抑制(刪除,即置為0)
 MT(m,n)={M(m,n),if?M(m,n)?>?T0,otherwiseM_T(m,n) = \begin{cases} M(m,n), & \text {if M(m,n) > T}\\ 0, & \text {otherwise} \end{cases} MT?(m,n)={M(m,n),0,?if?M(m,n)?>?Totherwise?
 例如:【該例子來自 Python - Opencv 之 Canny 邊緣檢測 】
? ? 點 A 位于圖像邊緣垂直方向. 梯度方向 垂直于邊緣. 點 B 和點 C 位于梯度方向. 因此,檢查點 A 和點 B,點 C,確定點A是否是局部最大值. 如果點 A 是局部最大值,則繼續下一個階段;如果點 A 不是局部最大值,則其被抑制設為0。
? ? 最后會保留一條邊界處最亮的一條細線
4. 應用雙閾值的方法來決定可能的(潛在的)邊界
? ? 這個階段決定哪些邊緣是真正的邊緣,哪些邊緣不是真正的邊緣
? ? 經過非極大抑制后圖像中仍然有很多噪聲點。Canny算法中應用了一種叫雙閾值的技術。即設定一個閾值上界maxVal和閾值下界minVal,圖像中的像素點如果大于閾值上界則認為必然是邊界(稱為強邊界,strong edge),小于閾值下界則認為必然不是邊界,兩者之間的則認為是候選項(稱為弱邊界,weak edge),需進行進一步處理——如果與確定為邊緣的像素點鄰接,則判定為邊緣;否則為非邊緣。
5. 利用滯后技術來跟蹤邊界
? ? 這個階段是進一步處理弱邊界
? ? 大體思想是,和強邊界相連的弱邊界認為是邊界,其他的弱邊界則被抑制。
 ? ? 由真實邊緣引起的弱邊緣像素將連接到強邊緣像素,而噪聲響應未連接。為了跟蹤邊緣連接,通過查看弱邊緣像素及其8個鄰域像素,只要其中一個為強邊緣像素,則該弱邊緣點就可以保留為真實的邊緣。
opencv實現Canny邊緣檢測
OpenCV 提供了 cv2.canny 函數.
edge = cv2.Canny(image, threshold1, threshold2[, edges[, apertureSize[, L2gradient ]]])參數 image - 輸入圖片,必須為單通道的灰度圖
 參數 threshold1 和 threshold2 - 分別對應于閾值 minVal 和 maxVal
 參數 apertureSize - 用于計算圖片提取的 Sobel kernel 尺寸. 默認為 3.
 參數 L2gradient - 指定計算梯度的等式. 當參數為 True 時,采用 梯度計算公式(3)(4),其精度更高;否則采用的梯度計算公式為:G=∣Gx∣+∣Gy∣G = |G_x| + |G_y|G=∣Gx?∣+∣Gy?∣. 該參數默認為 False.
e.g.
import numpy as np import cv2 as cv from matplotlib import pyplot as pltimg = cv.imread('test.jpg',0) edges = cv.Canny(img, 100, 200)plt.subplot(121),plt.imshow(img,cmap = 'gray') plt.title('Original Image'), plt.xticks([]), plt.yticks([]) plt.subplot(122),plt.imshow(edges,cmap = 'gray') plt.title('Edge Image'), plt.xticks([]), plt.yticks([])plt.show()結果如下圖:
 
手寫代碼
聲明:此部分代碼【from Python實現Canny算子邊緣檢測】
import numpy as np import cv2 as cv from matplotlib import pyplot as pltdef smooth(image, sigma = 1.4, length = 5):""" Smooth the imageCompute a gaussian filter with sigma = sigma and kernal_length = length.Each element in the kernal can be computed as below:G[i, j] = (1/(2*pi*sigma**2))*exp(-((i-k-1)**2 + (j-k-1)**2)/2*sigma**2)Then, use the gaussian filter to smooth the input image.Args:image: array of grey imagesigma: the sigma of gaussian filter, default to be 1.4length: the kernal length, default to be 5Returns:the smoothed image"""# Compute gaussian filterk = length // 2gaussian = np.zeros([length, length])for i in range(length):for j in range(length):gaussian[i, j] = np.exp(-((i-k) ** 2 + (j-k) ** 2) / (2 * sigma ** 2))gaussian /= 2 * np.pi * sigma ** 2# Batch Normalizationgaussian = gaussian / np.sum(gaussian)# Use Gaussian FilterW, H = image.shapenew_image = np.zeros([W - k * 2, H - k * 2])for i in range(W - 2 * k):for j in range(H - 2 * k):# 卷積運算new_image[i, j] = np.sum(image[i:i+length, j:j+length] * gaussian)new_image = np.uint8(new_image)return new_imagedef get_gradient_and_direction(image):""" Compute gradients and its directionUse Sobel filter to compute gradients and direction.-1 0 1 -1 -2 -1Gx = -2 0 2 Gy = 0 0 0-1 0 1 1 2 1Args:image: array of grey imageReturns:gradients: the gradients of each pixeldirection: the direction of the gradients of each pixel"""Gx = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]])Gy = np.array([[-1, -2, -1], [0, 0, 0], [1, 2, 1]])W, H = image.shapegradients = np.zeros([W - 2, H - 2])direction = np.zeros([W - 2, H - 2])for i in range(W - 2):for j in range(H - 2):dx = np.sum(image[i:i+3, j:j+3] * Gx)dy = np.sum(image[i:i+3, j:j+3] * Gy)gradients[i, j] = np.sqrt(dx ** 2 + dy ** 2)if dx == 0:direction[i, j] = np.pi / 2else:direction[i, j] = np.arctan(dy / dx)gradients = np.uint8(gradients)return gradients, directiondef NMS(gradients, direction):""" Non-maxima suppressionArgs:gradients: the gradients of each pixeldirection: the direction of the gradients of each pixelReturns:the output image"""W, H = gradients.shapenms = np.copy(gradients[1:-1, 1:-1])for i in range(1, W - 1):for j in range(1, H - 1):theta = direction[i, j]weight = np.tan(theta)if theta > np.pi / 4:d1 = [0, 1]d2 = [1, 1]weight = 1 / weightelif theta >= 0:d1 = [1, 0]d2 = [1, 1]elif theta >= - np.pi / 4:d1 = [1, 0]d2 = [1, -1]weight *= -1else:d1 = [0, -1]d2 = [1, -1]weight = -1 / weightg1 = gradients[i + d1[0], j + d1[1]]g2 = gradients[i + d2[0], j + d2[1]]g3 = gradients[i - d1[0], j - d1[1]]g4 = gradients[i - d2[0], j - d2[1]]grade_count1 = g1 * weight + g2 * (1 - weight)grade_count2 = g3 * weight + g4 * (1 - weight)if grade_count1 > gradients[i, j] or grade_count2 > gradients[i, j]:nms[i - 1, j - 1] = 0return nmsdef double_threshold(nms, threshold1, threshold2):""" Double ThresholdUse two thresholds to compute the edge.Args:nms: the input imagethreshold1: the low thresholdthreshold2: the high thresholdReturns:The binary image."""visited = np.zeros_like(nms)output_image = nms.copy()W, H = output_image.shapedef dfs(i, j):if i >= W or i < 0 or j >= H or j < 0 or visited[i, j] == 1:returnvisited[i, j] = 1if output_image[i, j] > threshold1:output_image[i, j] = 255dfs(i-1, j-1)dfs(i-1, j)dfs(i-1, j+1)dfs(i, j-1)dfs(i, j+1)dfs(i+1, j-1)dfs(i+1, j)dfs(i+1, j+1)else:output_image[i, j] = 0for w in range(W):for h in range(H):if visited[w, h] == 1:continueif output_image[w, h] >= threshold2:dfs(w, h)elif output_image[w, h] <= threshold1:output_image[w, h] = 0visited[w, h] = 1for w in range(W):for h in range(H):if visited[w, h] == 0:output_image[w, h] = 0return output_imageif __name__ == "__main__":# code to read imageimage = cv.imread('test.jpg',0)cv.imshow("Original",image)smoothed_image = smooth(image)cv.imshow("GaussinSmooth(5*5)",smoothed_image)gradients, direction = get_gradient_and_direction(smoothed_image)# print(gradients)# print(direction)nms = NMS(gradients, direction)output_image = double_threshold(nms, 40, 100)cv.imshow("outputImage",output_image)cv.waitKey(0)結果如下圖:
 
參考文章
-  
數字圖像處理—高斯濾波
 -  
canny算法——百度百科
 -  
Python實現Canny算子邊緣檢測
 -  
Canny邊緣檢測算法解析
 -  
Python+opencv利用sobel進行邊緣檢測(細節講解)
 -  
Python - Opencv 之 Canny 邊緣檢測
 -  
Python實現Canny邊緣檢測算法
 -  
非極大值抑制(Non-Maximum Suppression)
 
總結
以上是生活随笔為你收集整理的Canny边缘检测算法(python 实现)的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: 计算机组成原理-唐朔飞 学习指导与习题解
 - 下一篇: 【实践与问题解决29】苹果系统如何做脚本