图像边缘的种类
图像中的边缘是像素灰度值发生加速变化而不连续的结果,边缘检测是常见的图像基元检测的基础,也是所有基于边界的图像分割方法的第一步。
图片来源:章毓晋.计算机视觉教程[M].北京:人民邮电出版社,2021.1:46
图像的边缘通常分为阶梯状边缘,脉冲状边缘和屋顶状边缘,通常用一阶导数的幅度值或二阶导数的过零点来检测图像的边缘,本文主要介绍一阶导数算子中的sobel算子和kirsch算子来检测边缘。
算子与模板卷积
广义的讲,对任何函数进行某一项操作都可以认为是一个算子,狭义的算子实际上是指从一个函数空间到另一个函数空间(或它自身)的映射。我们这里使用的算子都是用模板卷积的方式进行计算的。
例如,考虑一个 3 × 3 3\times3 3×3的算子 A A A
[
a
11
a
12
a
13
a
21
a
22
a
23
a
31
a
32
a
33
]
\left[ \begin{matrix} a_{11} & a_{12} & a_{13} \\ a_{21} & a_{22} & a_{23} \\ a_{31} & a_{32} & a_{33} \end{matrix} \right]
a11a21a31a12a22a32a13a23a33
待处理的图像为图像
F
F
F
F
=
[
f
11
f
12
⋯
f
1
j
f
21
f
22
⋯
f
2
j
⋮
⋮
⋮
f
i
1
f
i
2
⋯
f
i
j
]
F = \left[ \begin{matrix} f_{11} & f_{12} &\cdots & f_{1j} \\ f_{21} & f_{22} &\cdots & f_{2j} \\ \vdots & \vdots & &\vdots \\ f_{i1} & f_{i2} & \cdots & f_{ij} \end{matrix} \right]
F=
f11f21⋮fi1f12f22⋮fi2⋯⋯⋯f1jf2j⋮fij
将这个算子和图像
F
F
F 进行卷积操作,得到的图像为
G
G
G
G
=
[
g
11
g
12
⋯
g
1
j
g
21
g
22
⋯
g
2
j
⋮
⋮
⋮
g
i
1
g
i
2
⋯
g
i
j
]
G = \left[ \begin{matrix} g_{11} & g_{12} &\cdots & g_{1j} \\ g_{21} & g_{22} &\cdots & g_{2j} \\ \vdots & \vdots & &\vdots \\ g_{i1} & g_{i2} & \cdots & g_{ij} \end{matrix} \right]
G=
g11g21⋮gi1g12g22⋮gi2⋯⋯⋯g1jg2j⋮gij
考虑 算子
A
A
A 和
f
i
j
f_{ij}
fij 的邻域
[
a
11
a
12
a
13
a
21
a
22
a
23
a
31
a
32
a
33
]
[
f
i
−
1
,
j
−
1
f
i
−
1
,
j
f
i
−
1
,
j
+
1
f
i
,
j
−
1
f
i
,
j
f
i
,
j
+
1
f
i
+
1
,
j
−
1
f
i
+
1
,
j
f
i
+
1
,
j
+
1
]
\left[ \begin{matrix} a_{11} & a_{12} & a_{13} \\ a_{21} & a_{22} & a_{23} \\ a_{31} & a_{32} & a_{33} \end{matrix} \right] \left[ \begin{matrix} f_{i-1,j-1} & f_{i-1,j} & f_{i-1,j+1} \\ f_{i,j-1} & f_{i,j} & f_{i,j+1} \\ f_{i+1,j-1} & f_{i+1,j} & f_{i+1,j+1} \end{matrix} \right]
a11a21a31a12a22a32a13a23a33
fi−1,j−1fi,j−1fi+1,j−1fi−1,jfi,jfi+1,jfi−1,j+1fi,j+1fi+1,j+1
则对于
G
G
G 有
g
i
j
=
a
11
∗
f
i
−
1
,
j
−
1
+
a
12
∗
f
i
−
1
,
j
+
a
13
∗
f
i
−
1
,
j
+
1
+
a
21
∗
f
i
,
j
−
1
+
a
22
∗
f
i
,
j
+
a
23
∗
f
i
,
j
+
1
+
a
31
∗
f
i
+
1
,
j
−
1
+
a
32
∗
f
i
+
1
,
j
+
a
33
∗
f
i
+
1
,
j
+
1
g_{ij} = a_{11}*f_{i-1,j-1} + a_{12}*f_{i-1,j} + a_{13}*f_{i-1,j+1} +\\ a_{21}*f_{i,j-1} + a_{22}*f_{i,j} + a_{23}*f_{i,j+1} +\\ a_{31}*f_{i+1,j-1} + a_{32}*f_{i+1,j} + a_{33}*f_{i+1,j+1}\\
gij=a11∗fi−1,j−1+a12∗fi−1,j+a13∗fi−1,j+1+a21∗fi,j−1+a22∗fi,j+a23∗fi,j+1+a31∗fi+1,j−1+a32∗fi+1,j+a33∗fi+1,j+1
这就是一般情况下算子的模板卷积操作,即将模板在图像上移动,并在每个位置将模板上的各个系数和模板下各对应像素的灰度值相乘求和,来计算对应中心像素的值,边缘检测所得到的像素值就是原像素的梯度值。
sobel算子
sobel算子是一种梯度算子,即所求的最终像素是原始像素的梯度值,它有两个方向的卷积核
沿 X 方向
[
−
1
0
1
−
2
0
2
−
1
0
1
]
\left[ \begin{matrix} -1 & 0 & 1\\ -2 & 0 & 2\\ -1 & 0 & 1 \end{matrix} \right]
−1−2−1000121
沿 Y 方向
[
1
2
1
0
0
0
−
1
−
2
−
1
]
\left[ \begin{matrix} 1 & 2 & 1\\ 0 & 0 & 0\\ -1 & -2 & -1 \end{matrix} \right]
10−120−210−1
它的卷积运算相当于对目标像素求一阶导。
Kirsch算子
kirsch模板除了 X 方向和 Y 方向外还包含了两个对角方向
比如,左上到右下方向
[
3
3
3
3
0
−
5
3
−
5
−
5
]
\left[ \begin{matrix} 3 & 3 & 3\\ 3 & 0 & -5\\ 3 & -5 & -5 \end{matrix} \right]
33330−53−5−5
右上到左下方向
[
−
5
−
5
3
−
5
0
3
3
3
3
]
\left[ \begin{matrix} -5 & -5 & 3\\ -5 & 0 & 3\\ 3 & 3 & 3 \end{matrix} \right]
−5−53−503333
它的卷积运算也相当于对目标像素求一阶导,只是求导的方向不同,类似的,你可以得到其他方向上的kirsch模板。
代码实现
下面我们将用 Python 来实现对图像的边缘检测算法
首先应该获取原始图像的灰度图像,这里我们直接使用之前处理得到的灰度图像
import cv2 as cv
# 获得原始图像
# 所得图像应为之前处理过的灰度图像
image = cv.imread("pai_mon_01_gray.jpg")
原始图像
先用sobel算子进行边缘检测
# 边缘检测
gray_gradient = gray.copy()
gray_height, gray_width = gray.shape[0:2]
for i in range(1, gray_height - 1):
for j in range(1, gray_width - 1):
# 水平方向检测
# sobel 算子 [[-1, 0, 1], [-2, 0 ,2], [-1, 0, 1]]
gradient_x = int(gray[i - 1, j - 1, 0]) - int(gray[i + 1, j - 1, 0]) + \
2 * int(gray[i - 1, j, 0]) - 2 * int(gray[i + 1, j, 0]) + \
int(gray[i - 1, j + 1, 0]) - int(gray[i + 1, j + 1, 0])
# 垂直方向检测
# sobel 算子 [[1, 2, 1], [0, 0, 0], [-1, -2, -1]]
gradient_y = int(gray[i - 1, j + 1, 0]) - int(gray[i - 1, j - 1, 0]) + \
2 * int(gray[i, j + 1, 0]) - 2 * int(gray[i, j - 1, 0]) + \
int(gray[i + 1, j + 1, 0]) - int(gray[i - 1, j - 1, 0])
gradient = max(gradient_x, gradient_y)
# 将 gradient 转到像素值范围内
if gradient < 0:
gradient = abs(min(255, gradient))
elif gradient > 255:
gradient = 255
gray_gradient[i, j, 0] = gradient
gray_gradient[i, j, 1] = gradient
gray_gradient[i, j, 2] = gradient
cv.imshow("gray_gradient", gray_gradient)
cv.waitKey(0)
这里需要注意的是我们要把卷积后得到的梯度值进行处理,使它在0到255这一个灰度值的取值范围内
得到的图像为
如果只用水平方向的sobel算子的话,得到的图像为
可以看到得到的效果明显不如前一张图片好
下面我们再加上kirsch算子观察检测结果
import cv2 as cv
# 获得原始图像
# 所得图像应为之前处理过的灰度图像
gray = cv.imread("pai_mon_01_gray.jpg")
# 边缘检测
gray_gradient = gray.copy()
gray_height, gray_width = gray.shape[0:2]
for i in range(1, gray_height - 1):
for j in range(1, gray_width - 1):
# 水平方向检测
# sobel 算子 [[-1, 0, 1], [-2, 0 ,2], [-1, 0, 1]]
gradient_x = int(gray[i - 1, j - 1, 0]) - int(gray[i + 1, j - 1, 0]) + \
2 * int(gray[i - 1, j, 0]) - 2 * int(gray[i + 1, j, 0]) + \
int(gray[i - 1, j + 1, 0]) - int(gray[i + 1, j + 1, 0])
# 垂直方向检测
# sobel 算子 [[1, 2, 1], [0, 0, 0], [-1, -2, -1]]
gradient_y = int(gray[i - 1, j + 1, 0]) - int(gray[i - 1, j - 1, 0]) + \
2 * int(gray[i, j + 1, 0]) - 2 * int(gray[i, j - 1, 0]) + \
int(gray[i + 1, j + 1, 0]) - int(gray[i - 1, j - 1, 0])
# 西北到东南方向检测
# 基尔希算子 Kirsch = [[3, 3, 3], [3, 0, -5], [3, -5, -5]]
gradient_WN_to_ES = 3 * (int(gray[i - 1, j - 1, 0]) + int(gray[i, j - 1, 0]) + int(gray[i + 1, j - 1, 0]) +
int(gray[i - 1, j, 0]) + int(gray[i - 1, j + 1, 0])) - \
5 * (int(gray[i + 1, j, 0]) + int(gray[i, j + 1, 0]) + int(gray[i + 1, j + 1, 0]))
# 东北到西南方向检测
# 基尔希算子 Kirsch = [[3, 3, 3], [-5, 0, 3], [-5, -5, 3]]
gradient_EN_to_WS = 3 * (int(gray[i - 1, j - 1, 0]) + int(gray[i - 1, j, 0]) + int(gray[i - 1, j + 1, 0]) +
int(gray[i, j + 1, 0]) + int(gray[i + 1, j + 1, 0])) - \
5 * (int(gray[i, j - 1, 0]) + int(gray[i + 1, j - 1, 0]) + int(gray[i + 1, j, 0]))
gradient = int(max(gradient_x, gradient_y, gradient_WN_to_ES, gradient_EN_to_WS))
# 将 gradient 转到像素值范围内
if gradient < 0:
gradient = abs(min(255, gradient))
elif gradient > 255:
gradient = 255
gray_gradient[i, j, 0] = gradient
gray_gradient[i, j, 1] = gradient
gray_gradient[i, j, 2] = gradient
cv.imshow("gray_gradient", gray_gradient)
cv.waitKey(0)
得到的结果为
可以发现和之前仅使用sobel算子相比,更多的边缘被检测出来了,同时边缘也更加明显,同时部分边缘也变得更粗,但总体来说得到的效果还算不错。
总结
图像的边缘是像素灰度值发生加速变化而不连续的结果,一种检测方法是通过计算像素的梯度值来显示边缘,其中sobel算子可以检测水平和竖直方向,kirsch算子可以检测八个方向,各方向间的夹角为45度,检测不同方向的算子对于不同方向上的边缘的响应效果会有所不同,可以更具具体情况进行使用。
参考文献
[1] 章毓晋.计算机视觉教程[M].北京:人民邮电出版社,2021.1:45-49
2] 链接:算子百度百科