文章目錄
- 最優邊緣準則
- 演算法實作步驟
- 1. 應用高斯濾波來平滑(模糊)影像,目的是去除噪聲
- 2. 計算梯度強度和方向
- 3. 應用非最大抑制技術NMS來消除邊誤檢
- 4. 應用雙閾值的方法來決定可能的(潛在的)邊界
- 5. 利用滯后技術來跟蹤邊界
- opencv實作Canny邊緣檢測
- 手寫代碼
- 參考文章
最優邊緣準則
? ? Canny 的目標是找到一個最優的邊緣檢測演算法,最優邊緣檢測的含義是:
? ? (1)最優檢測:演算法能夠盡可能多地標識出影像中的實際邊緣,漏檢真實邊緣的概率和誤檢非邊緣的概率都盡可能小;
? ? (2)最優定位準則:檢測到的邊緣點的位置距離實際邊緣點的位置最近,或者是由于噪聲影響引起檢測出的邊緣偏離物體的真實邊緣的程度最小;
? ? (3)檢測點與邊緣點一一對應:算子檢測的邊緣點與實際邊緣點應該是一 一對應,
演算法實作步驟
? ? Canny邊緣檢測演算法可以分為以下5個步驟:
1. 應用高斯濾波來平滑(模糊)影像,目的是去除噪聲
? ? 高斯濾波器是將高斯函式離散化,將濾波器中對應的橫縱坐標索引代入到高斯函式,從而得到對應的值,
? ? 二維的高斯函式如下:其中 (x , y)為坐標, σ 為標準差
H
(
x
,
y
)
=
1
2
π
σ
2
e
?
x
2
+
y
2
2
σ
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
]
=
1
2
π
σ
2
e
?
(
i
?
k
?
1
)
2
+
(
j
?
k
?
1
)
2
2
σ
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 方向的邊緣
| -1 | 0 | 1 |
|---|---|---|
| -2 | 0 | 2 |
| -1 | 0 | 1 |
? ? 垂直方向的Sobel算子Gy:用來檢測 x 方向的邊緣( 邊緣方向和梯度方向垂直)
| 1 | 2 | 1 |
|---|---|---|
| 0 | 0 | 0 |
| -1 | -2 | -1 |
? ? 采用下列公式計算梯度和方向:
G
=
(
G
x
2
+
G
y
2
)
(3)
G = \sqrt{(G_x^2 + G_y^2)} \tag3
G=(Gx2?+Gy2?)
?(3)
θ
=
a
r
c
t
a
n
G
y
G
x
(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)
M
T
(
m
,
n
)
=
{
M
(
m
,
n
)
,
if M(m,n) > T
0
,
otherwise
M_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
=
∣
G
x
∣
+
∣
G
y
∣
G = |G_x| + |G_y|
G=∣Gx?∣+∣Gy?∣. 該引數默認為 False.
e.g.
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = 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 plt
def smooth(image, sigma = 1.4, length = 5):
""" Smooth the image
Compute 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 image
sigma: the sigma of gaussian filter, default to be 1.4
length: the kernal length, default to be 5
Returns:
the smoothed image
"""
# Compute gaussian filter
k = length // 2
gaussian = 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 Normalization
gaussian = gaussian / np.sum(gaussian)
# Use Gaussian Filter
W, H = image.shape
new_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_image
def get_gradient_and_direction(image):
""" Compute gradients and its direction
Use Sobel filter to compute gradients and direction.
-1 0 1 -1 -2 -1
Gx = -2 0 2 Gy = 0 0 0
-1 0 1 1 2 1
Args:
image: array of grey image
Returns:
gradients: the gradients of each pixel
direction: 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.shape
gradients = 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 / 2
else:
direction[i, j] = np.arctan(dy / dx)
gradients = np.uint8(gradients)
return gradients, direction
def NMS(gradients, direction):
""" Non-maxima suppression
Args:
gradients: the gradients of each pixel
direction: the direction of the gradients of each pixel
Returns:
the output image
"""
W, H = gradients.shape
nms = 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 / weight
elif theta >= 0:
d1 = [1, 0]
d2 = [1, 1]
elif theta >= - np.pi / 4:
d1 = [1, 0]
d2 = [1, -1]
weight *= -1
else:
d1 = [0, -1]
d2 = [1, -1]
weight = -1 / weight
g1 = 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] = 0
return nms
def double_threshold(nms, threshold1, threshold2):
""" Double Threshold
Use two thresholds to compute the edge.
Args:
nms: the input image
threshold1: the low threshold
threshold2: the high threshold
Returns:
The binary image.
"""
visited = np.zeros_like(nms)
output_image = nms.copy()
W, H = output_image.shape
def dfs(i, j):
if i >= W or i < 0 or j >= H or j < 0 or visited[i, j] == 1:
return
visited[i, j] = 1
if output_image[i, j] > threshold1:
output_image[i, j] = 255
dfs(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] = 0
for w in range(W):
for h in range(H):
if visited[w, h] == 1:
continue
if output_image[w, h] >= threshold2:
dfs(w, h)
elif output_image[w, h] <= threshold1:
output_image[w, h] = 0
visited[w, h] = 1
for w in range(W):
for h in range(H):
if visited[w, h] == 0:
output_image[w, h] = 0
return output_image
if __name__ == "__main__":
# code to read image
image = 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)
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/294061.html
標籤:其他
上一篇:俄羅斯方塊(C語言實作)
下一篇:AVS系統7.0版本疑難雜癥修復系列之一(Undefined variable: dir_handle in function_thumbs.php on line 44)
