边缘检测
之前我们使用了sobel算子和kircsh算子来进行边缘检测,得到了一个还算不错的结果,本文将介绍canny算子来检测边缘,Canny把边缘检测问题转换为检测单位函数极大值的问题来考虑。完整实现代码和opencv自带canny算法实现的函数在文章末尾。
canny算子的三个准则
信噪比准则
信噪比准则是为了要求算子具有低失误概率,即既要少将真正的边缘丢失,也要少将非边缘判为边缘。
定位精度准则
高位置精度,检测出的边缘应在真正的边界上。
单边缘相应准则
即对每一个边缘有唯一的响应,得到的边界为单像素宽。
canny算子的工作步骤
1. 在空间进行低通滤波,使用高斯滤波平滑图像以减轻噪声影响
使用高斯滤波减轻噪声的影响,滤波使用的高斯算子模板越大,图像越模糊,能够检测更为突出的边缘。
这里使用三阶高斯算子,其表示为
[
1
2
1
2
8
2
1
2
1
]
\left[ \begin{matrix} 1 & 2& 1 \\ 2 & 8 & 2 \\ 1 & 2 & 1 \end{matrix} \right]
121282121
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import cv2 as cv
import numpy as np
import math
image = cv.imread("images/pai_mon_01.jpg")
# image = cv.resize(image, None, None, 0.25, 0.25)
R = image[:, :, 0]
G = image[:, :, 1]
B = image[:, :, 2]
Y = R * 0.299 + G * 0.587 + B * 0.114
gray = image.copy()
gray[:, :, 0] = Y
gray[:, :, 1] = Y
gray[:, :, 2] = Y
height, width = image.shape[0:2]
cv.imshow("gray1", gray)
'''
scanny 算子工作的一般步骤
1. 在空间进行低通滤波,使用高斯滤波平滑图像以减轻噪声影响
2. 使用一阶微分检测滤波图像中灰度梯度的大小和方向
3. 非最大消除,细化借助梯度检测得到的边缘像素所构成的边界
4. 滞后阈值化,选取两个阈值并借助滞后阈值化方法最后确定边缘点
'''
# 低通滤波(平滑处理)
for i in range(1, height-1):
for j in range(1, width-1):
result = (int(image[i-1, j-1, 0]) + 2*int(image[i-1, j, 0]) + int(image[i-1, j+1, 0]) +
2*int(image[i, j-1, 0]) + 8*int(image[i, j, 0]) + 2*int(image[i, j+1, 0]) +
int(image[i+1, j-1, 0]) + 2*int(image[i+1, j, 0]) + int(image[i+1, j+1, 0]))/20
image[i, j, 0] = result
image[i, j, 1] = result
image[i, j, 2] = result
cv.imshow("blur", image) # 显示高斯模糊后的图像
原图
模糊得到的图像
2. 使用一阶微分检测滤波图像中灰度梯度的大小和方向
这里我使用的是sobel算子检测,同时将灰度梯度信息储存起来用于后面的细化和滞后阈值化。
关于灰度梯度的大小和方向检测的基本原理:
假设精确响应矢量是由两个分量 m1 和 m2 组成的,其朝向由与X轴的夹角
α
\alpha
α 决定。
图片来源:章毓晋.计算机视觉教程[M].北京:人民邮电出版社,2021.1:49
由图可见
m
1
=
ρ
c
o
s
α
m
2
=
ρ
c
o
s
β
m_1 = \rho cos \alpha \\ m_2 = \rho cos \beta \\
m1=ρcosαm2=ρcosβ
两个分量的比值为
m
1
m
2
=
c
o
s
α
c
o
s
β
=
c
o
s
(
γ
−
α
)
s
e
c
α
=
c
o
s
γ
+
s
i
n
γ
t
a
n
α
\frac{m_1}{m_2} = \frac{cos \alpha}{cos \beta} = cos(\gamma - \alpha)sec \alpha = cos \gamma + sin \gamma tan \alpha \\
m2m1=cosβcosα=cos(γ−α)secα=cosγ+sinγtanα
解出
α
\alpha
α
α
=
a
r
c
t
a
n
[
(
m
2
m
1
)
c
s
e
γ
−
c
o
t
γ
]
\alpha = arctan[(\frac{m_2}{m_1})cse \gamma - cot \gamma]
α=arctan[(m1m2)cseγ−cotγ]
由
α
\alpha
α解出
ρ
\rho
ρ
ρ
=
(
m
1
2
+
m
2
2
−
2
m
1
m
2
c
o
s
γ
)
1
/
2
c
s
e
γ
\rho = (m_1^2 + m_2^2 - 2m_1m_2cos \gamma)^{1/2}cse \gamma
ρ=(m12+m22−2m1m2cosγ)1/2cseγ
当
γ
=
9
0
。
\gamma = 90^。
γ=90。时,就是使用sobel算子进行边缘检测的结果,也就是本文所实现所依据的算法原理
α
=
a
r
c
t
a
n
(
m
2
m
1
)
ρ
=
(
m
1
2
+
m
2
2
)
1
/
2
\alpha = arctan(\frac{m_2}{m_1}) \\ \rho = (m_1^2 + m_2^2)^{1/2}
α=arctan(m1m2)ρ=(m12+m22)1/2
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
下面是使用sobel算子进行灰度梯度的方向和大小检测的代码实现
# 边缘检测(使用sobel算子)
gradient_direction = np.zeros((height, width), np.float64)
gradient_range = np.zeros((height, width), np.int32)
for i in range(1, height - 1):
for j in range(1, width - 1):
gradient_x = int(image[i - 1, j + 1, 0]) - int(image[i - 1, j - 1, 0]) + \
2 * int(image[i, j + 1, 0]) - 2 * int(image[i, j - 1, 0]) + \
int(image[i + 1, j + 1, 0]) - int(image[i + 1, j - 1, 0])
gradient_y = int(image[i - 1, j - 1, 0]) - int(image[i + 1, j - 1, 0]) + \
2 * int(image[i - 1, j, 0]) - 2 * int(image[i + 1, j, 0]) + \
int(image[i - 1, j + 1, 0]) - int(image[i + 1, j + 1, 0])
gradient = int(math.sqrt(gradient_x * gradient_x + gradient_y * gradient_y))
if gradient < 0:
gradient = min(255, abs(gradient))
elif gradient > 255:
gradient = 255
gray[i, j, 0] = gradient
gray[i, j, 1] = gradient
gray[i, j, 2] = gradient
# 计算图像中灰度梯度的大小和方向
gradient_direction[i, j] = math.atan2(gradient_y, gradient_x)
gradient_range[i, j] = gradient
cv.imshow("gray", gray)
sobel算子边缘检测过后的图像
3. 非最大消除,细化借助梯度检测得到的边缘像素所构成的边界
进行非最大消除主要有两种办法,一种是用模板进行非最大消除,一种是用插值进行非最大消除。用插值进行非最大消除得到的结果比用模板进行非最大消除要精确些,所需要的计算量也要大一些,这种方法的基本思路是通过对相邻单元的梯度幅度的插值来在当前位置的周围估计梯度的幅度。这里我采用的是用模板进行非最大消除。
使用模板进行非最大消除的基本原理:
对于每一个像素,检测该像素沿其灰度梯度方向上的两个相邻像素的灰度值大小,若小于当前像素灰度值,则不变当前像素,否则将其灰度值置为0。这里我是将三个像素中最大的保留,其余的置为0。分为四个模板,即水平、垂直、左对角、右对角。
水平:
337.
5
。
≤
θ
≤
22.
5
。
和
157.
5
。
≤
θ
≤
202.
5
。
337.5^。\leq \theta \leq22.5^。和 157.5^。\leq \theta \leq202.5^。
337.5。≤θ≤22.5。和157.5。≤θ≤202.5。
垂直:
67.
5
。
≤
θ
≤
122.
5
。
和
247.
5
。
≤
θ
≤
337.
5
。
67.5^。\leq \theta \leq122.5^。和 247.5^。\leq \theta \leq337.5^。
67.5。≤θ≤122.5。和247.5。≤θ≤337.5。
左对角:
22.
5
。
≤
θ
≤
67.
5
。
和
202.
5
。
≤
θ
≤
247.
5
。
22.5^。\leq \theta \leq67.5^。和 202.5^。\leq \theta \leq247.5^。
22.5。≤θ≤67.5。和202.5。≤θ≤247.5。
右对角:
122.
5
。
≤
θ
≤
157.
5
。
和
292.
5
。
≤
θ
≤
337.
5
。
122.5^。\leq \theta \leq157.5^。和 292.5^。\leq \theta \leq337.5^。
122.5。≤θ≤157.5。和292.5。≤θ≤337.5。
图片来源:章毓晋.计算机视觉教程[M].北京:人民邮电出版社,2021.1:56
下面是使用模板进行非最大消除的代码实现
# check_img = gray_gradient.copy() * 0
# 非最大消除
for i in range(1, height - 1):
for j in range(1, width - 1):
# 竖直方向
if 0.375 * math.pi < gradient_direction[i, j] <= 0.625 * math.pi\
or -0.625 * math.pi < gradient_direction[i, j] <= -0.375 * math.pi:
# check_img[i, j, 0] = 0
# check_img[i, j, 1] = 255
# check_img[i, j, 2] = 0
if gray[i, j, 0] > max(gray[i - 1, j, 0], gray[i + 1, j, 0]):
gray_gradient[i - 1, j, 0] = 0
gray_gradient[i - 1, j, 1] = 0
gray_gradient[i - 1, j, 2] = 0
gray_gradient[i + 1, j, 0] = 0
gray_gradient[i + 1, j, 1] = 0
gray_gradient[i + 1, j, 2] = 0
elif gray[i - 1, j, 0] > max(gray[i, j, 0], gray[i + 1, j, 0]):
gray_gradient[i, j, 0] = 0
gray_gradient[i, j, 1] = 0
gray_gradient[i, j, 2] = 0
gray_gradient[i + 1, j, 0] = 0
gray_gradient[i + 1, j, 1] = 0
gray_gradient[i + 1, j, 2] = 0
elif gray[i + 1, j, 0] >= max(gray[i - 1, j, 0], gray[i, j, 0]):
gray_gradient[i, j, 0] = 0
gray_gradient[i, j, 1] = 0
gray_gradient[i, j, 2] = 0
gray_gradient[i - 1, j, 0] = 0
gray_gradient[i - 1, j, 1] = 0
gray_gradient[i - 1, j, 2] = 0
# 左对角方向
elif 0.625 * math.pi < gradient_direction[i, j] <= 0.875 * math.pi\
or -0.375 * math.pi < gradient_direction[i, j] <= -0.125 * math.pi:
# check_img[i, j, 0] = 0
# check_img[i, j, 1] = 0
# check_img[i, j, 2] = 255
if gray[i, j, 0] > max(gray[i - 1, j - 1, 0], gray[i + 1, j + 1, 0]):
gray_gradient[i - 1, j - 1, 0] = 0
gray_gradient[i - 1, j - 1, 1] = 0
gray_gradient[i - 1, j - 1, 2] = 0
gray_gradient[i + 1, j + 1, 0] = 0
gray_gradient[i + 1, j + 1, 1] = 0
gray_gradient[i + 1, j + 1, 2] = 0
elif gray[i - 1, j - 1, 0] > max(gray[i, j, 0], gray[i + 1, j + 1, 0]):
gray_gradient[i, j, 0] = 0
gray_gradient[i, j, 1] = 0
gray_gradient[i, j, 2] = 0
gray_gradient[i + 1, j + 1, 0] = 0
gray_gradient[i + 1, j + 1, 1] = 0
gray_gradient[i + 1, j + 1, 2] = 0
elif gray[i + 1, j + 1, 0] >= max(gray[i - 1, j - 1, 0], gray[i, j, 0]):
gray_gradient[i, j, 0] = 0
gray_gradient[i, j, 1] = 0
gray_gradient[i, j, 2] = 0
gray_gradient[i - 1, j - 1, 0] = 0
gray_gradient[i - 1, j - 1, 1] = 0
gray_gradient[i - 1, j - 1, 2] = 0
# 右对角方向
elif 0.125 * math.pi < gradient_direction[i, j] <= 0.375 * math.pi\
or -0.875 * math.pi <= gradient_direction[i, j] <= -0.625 * math.pi:
# check_img[i, j, 0] = 255
# check_img[i, j, 1] = 255
# check_img[i, j, 2] = 255
if gray[i, j, 0] > max(gray[i - 1, j + 1, 0], gray[i + 1, j - 1, 0]):
gray_gradient[i - 1, j + 1, 0] = 0
gray_gradient[i - 1, j + 1, 1] = 0
gray_gradient[i - 1, j + 1, 2] = 0
gray_gradient[i + 1, j - 1, 0] = 0
gray_gradient[i + 1, j - 1, 1] = 0
gray_gradient[i + 1, j - 1, 2] = 0
elif gray[i - 1, j + 1, 0] > max(gray[i, j, 0], gray[i + 1, j - 1, 0]):
gray_gradient[i, j, 0] = 0
gray_gradient[i, j, 1] = 0
gray_gradient[i, j, 2] = 0
gray_gradient[i + 1, j - 1, 0] = 0
gray_gradient[i + 1, j - 1, 1] = 0
gray_gradient[i + 1, j - 1, 2] = 0
elif gray[i + 1, j - 1, 0] >= max(gray[i - 1, j + 1, 0], gray[i, j, 0]):
gray_gradient[i, j, 0] = 0
gray_gradient[i, j, 1] = 0
gray_gradient[i, j, 2] = 0
gray_gradient[i - 1, j + 1, 0] = 0
gray_gradient[i - 1, j + 1, 1] = 0
gray_gradient[i - 1, j + 1, 2] = 0
# 水平方向
else:
# check_img[i, j, 0] = 255
# check_img[i, j, 1] = 0
# check_img[i, j, 2] = 0
if gray[i, j, 0] > max(gray[i, j + 1, 0], gray[i, j - 1, 0]):
gray_gradient[i, j + 1, 0] = 0
gray_gradient[i, j + 1, 1] = 0
gray_gradient[i, j + 1, 2] = 0
gray_gradient[i, j - 1, 0] = 0
gray_gradient[i, j - 1, 1] = 0
gray_gradient[i, j - 1, 2] = 0
elif gray[i, j - 1, 0] > max(gray[i, j + 1, 0], gray[i, j, 0]):
gray_gradient[i, j + 1, 0] = 0
gray_gradient[i, j + 1, 1] = 0
gray_gradient[i, j + 1, 2] = 0
gray_gradient[i, j, 0] = 0
gray_gradient[i, j, 1] = 0
gray_gradient[i, j, 2] = 0
elif gray[i, j + 1, 0] >= max(gray[i, j - 1, 0], gray[i, j, 0]):
gray_gradient[i, j - 1, 0] = 0
gray_gradient[i, j - 1, 1] = 0
gray_gradient[i, j - 1, 2] = 0
gray_gradient[i, j, 0] = 0
gray_gradient[i, j, 1] = 0
gray_gradient[i, j, 2] = 0
cv.imshow("max_cancel", gray_gradient)
其中check_img用来将不同灰度梯度方向的像素用不同的颜色进行标注,这里我将其注释了。
非最大消除后的图片结果:
可以看出边界细化还是较为明显。
4. 滞后阈值化,选取两个阈值并借助滞后阈值化方法最后确定边缘点
滞后阈值化的两个阈值分别是高阈值和低阈值,将梯度值大于高阈值的像素标记出来,认为它们肯定是边缘像素,然后检测这些像素的相邻像素,如果其相邻像素大于低阈值,那么我们也将这些像素认为是边缘像素,这样可以减弱噪声在最终边缘图像的影响,并可避免产生由于阈值过低导致的虚假边缘和阈值过高导致的边缘丢失。
下面是滞后阈值化的代码实现
# 滞后阈值化
# 设置两个阈值,高阈值为High,低阈值为Low
# 认为高阈值肯定是边缘像素,认为大于低阈值且与大于高的阈值的像素邻接的像素也是边缘像素
High = 20
Low = 0
gray_3 = gray_gradient.copy() * 0
for i in range(1, height - 1):
for j in range(1, width - 1):
if gray_gradient[i, j, 0] > High:
gray_3[i, j, 0] = 255
gray_3[i, j, 1] = 255
gray_3[i, j, 2] = 255
elif Low < gray_gradient[i, j, 0] < High:
judge = 0
for x in [-1, 0, 1]:
for y in [-1, 0, 1]:
if gray_gradient[i+x, j+y, 0] > High and \
gradient_direction[i+x, j+y] - gradient_direction[i, j] < math.pi * 0.125:
judge = 1
break
if judge == 1:
gray_3[i, j, 0] = 255
gray_3[i, j, 1] = 255
gray_3[i, j, 2] = 255
cv.imshow("gray_3", gray_3)
现在边缘被凸显了出来,不同的阈值的设置对图片最终生成的影响结果也不同。
5. 为了方便观察,将图片黑白进行转化
# 黑白转换
gray_4 = gray_3.copy()
for i in range(1, height - 1):
for j in range(1, width - 1):
if gray_3[i, j, 0] == 255:
gray_4[i, j, 0] = 0
gray_4[i, j, 1] = 0
gray_4[i, j, 2] = 0
elif gray_3[i, j, 0] == 0:
gray_4[i, j, 0] = 255
gray_4[i, j, 1] = 255
gray_4[i, j, 2] = 255
cv.imshow("gray_3_white", gray_4)
# cv.imshow("check_img", check_img)
# cv.imwrite("images/img2_canny", gray_4, [int(cv.IMWRITE_JPEG_QUALITY), 100])
cv.waitKey()
最终图片结果:
完整代码
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import cv2 as cv
import numpy as np
import math
image = cv.imread("images/img_3.png")
# image = cv.resize(image, None, None, 0.25, 0.25)
R = image[:, :, 0]
G = image[:, :, 1]
B = image[:, :, 2]
Y = R * 0.299 + G * 0.587 + B * 0.114
gray = image.copy()
gray[:, :, 0] = Y
gray[:, :, 1] = Y
gray[:, :, 2] = Y
height, width = image.shape[0:2]
cv.imshow("gray1", gray)
'''
scanny 算子工作的一般步骤
1. 在空间进行低通滤波,使用高斯滤波平滑图像以减轻噪声影响
2. 使用一阶微分检测滤波图像中灰度梯度的大小和方向
3. 非最大消除,细化借助梯度检测得到的边缘像素所构成的边界
4. 滞后阈值化,选取两个阈值并借助滞后阈值化方法最后确定边缘点
'''
# 低通滤波(平滑处理)
for i in range(1, height-1):
for j in range(1, width-1):
result = (int(image[i-1, j-1, 0]) + 2*int(image[i-1, j, 0]) + int(image[i-1, j+1, 0]) +
2*int(image[i, j-1, 0]) + 8*int(image[i, j, 0]) + 2*int(image[i, j+1, 0]) +
int(image[i+1, j-1, 0]) + 2*int(image[i+1, j, 0]) + int(image[i+1, j+1, 0]))/20
image[i, j, 0] = result
image[i, j, 1] = result
image[i, j, 2] = result
# 边缘检测(使用sobel算子)
gradient_direction = np.zeros((height, width), np.float64)
gradient_range = np.zeros((height, width), np.int32)
for i in range(1, height - 1):
for j in range(1, width - 1):
gradient_x = int(image[i - 1, j + 1, 0]) - int(image[i - 1, j - 1, 0]) + \
2 * int(image[i, j + 1, 0]) - 2 * int(image[i, j - 1, 0]) + \
int(image[i + 1, j + 1, 0]) - int(image[i + 1, j - 1, 0])
gradient_y = int(image[i - 1, j - 1, 0]) - int(image[i + 1, j - 1, 0]) + \
2 * int(image[i - 1, j, 0]) - 2 * int(image[i + 1, j, 0]) + \
int(image[i - 1, j + 1, 0]) - int(image[i + 1, j + 1, 0])
gradient = int(math.sqrt(gradient_x * gradient_x + gradient_y * gradient_y))
if gradient < 0:
gradient = min(255, abs(gradient))
elif gradient > 255:
gradient = 255
gray[i, j, 0] = gradient
gray[i, j, 1] = gradient
gray[i, j, 2] = gradient
# 计算图像中灰度梯度的大小和方向
gradient_direction[i, j] = math.atan2(gradient_y, gradient_x)
gradient_range[i, j] = gradient
# cv.imshow("gray", gray)
# cv.imwrite("sobelp.jpg", gray, [int(cv.IMWRITE_JPEG_QUALITY), 100])
gray_gradient = gray.copy()
# check_img = gray_gradient.copy() * 0
# 非最大消除
for i in range(1, height - 1):
for j in range(1, width - 1):
# 竖直方向
if 0.375 * math.pi < gradient_direction[i, j] <= 0.625 * math.pi\
or -0.625 * math.pi < gradient_direction[i, j] <= -0.375 * math.pi:
# check_img[i, j, 0] = 0
# check_img[i, j, 1] = 255
# check_img[i, j, 2] = 0
if gray[i, j, 0] > max(gray[i - 1, j, 0], gray[i + 1, j, 0]):
gray_gradient[i - 1, j, 0] = 0
gray_gradient[i - 1, j, 1] = 0
gray_gradient[i - 1, j, 2] = 0
gray_gradient[i + 1, j, 0] = 0
gray_gradient[i + 1, j, 1] = 0
gray_gradient[i + 1, j, 2] = 0
elif gray[i - 1, j, 0] > max(gray[i, j, 0], gray[i + 1, j, 0]):
gray_gradient[i, j, 0] = 0
gray_gradient[i, j, 1] = 0
gray_gradient[i, j, 2] = 0
gray_gradient[i + 1, j, 0] = 0
gray_gradient[i + 1, j, 1] = 0
gray_gradient[i + 1, j, 2] = 0
elif gray[i + 1, j, 0] >= max(gray[i - 1, j, 0], gray[i, j, 0]):
gray_gradient[i, j, 0] = 0
gray_gradient[i, j, 1] = 0
gray_gradient[i, j, 2] = 0
gray_gradient[i - 1, j, 0] = 0
gray_gradient[i - 1, j, 1] = 0
gray_gradient[i - 1, j, 2] = 0
# 左对角方向
elif 0.625 * math.pi < gradient_direction[i, j] <= 0.875 * math.pi\
or -0.375 * math.pi < gradient_direction[i, j] <= -0.125 * math.pi:
# check_img[i, j, 0] = 0
# check_img[i, j, 1] = 0
# check_img[i, j, 2] = 255
if gray[i, j, 0] > max(gray[i - 1, j - 1, 0], gray[i + 1, j + 1, 0]):
gray_gradient[i - 1, j - 1, 0] = 0
gray_gradient[i - 1, j - 1, 1] = 0
gray_gradient[i - 1, j - 1, 2] = 0
gray_gradient[i + 1, j + 1, 0] = 0
gray_gradient[i + 1, j + 1, 1] = 0
gray_gradient[i + 1, j + 1, 2] = 0
elif gray[i - 1, j - 1, 0] > max(gray[i, j, 0], gray[i + 1, j + 1, 0]):
gray_gradient[i, j, 0] = 0
gray_gradient[i, j, 1] = 0
gray_gradient[i, j, 2] = 0
gray_gradient[i + 1, j + 1, 0] = 0
gray_gradient[i + 1, j + 1, 1] = 0
gray_gradient[i + 1, j + 1, 2] = 0
elif gray[i + 1, j + 1, 0] >= max(gray[i - 1, j - 1, 0], gray[i, j, 0]):
gray_gradient[i, j, 0] = 0
gray_gradient[i, j, 1] = 0
gray_gradient[i, j, 2] = 0
gray_gradient[i - 1, j - 1, 0] = 0
gray_gradient[i - 1, j - 1, 1] = 0
gray_gradient[i - 1, j - 1, 2] = 0
# 右对角方向
elif 0.125 * math.pi < gradient_direction[i, j] <= 0.375 * math.pi\
or -0.875 * math.pi <= gradient_direction[i, j] <= -0.625 * math.pi:
# check_img[i, j, 0] = 255
# check_img[i, j, 1] = 255
# check_img[i, j, 2] = 255
if gray[i, j, 0] > max(gray[i - 1, j + 1, 0], gray[i + 1, j - 1, 0]):
gray_gradient[i - 1, j + 1, 0] = 0
gray_gradient[i - 1, j + 1, 1] = 0
gray_gradient[i - 1, j + 1, 2] = 0
gray_gradient[i + 1, j - 1, 0] = 0
gray_gradient[i + 1, j - 1, 1] = 0
gray_gradient[i + 1, j - 1, 2] = 0
elif gray[i - 1, j + 1, 0] > max(gray[i, j, 0], gray[i + 1, j - 1, 0]):
gray_gradient[i, j, 0] = 0
gray_gradient[i, j, 1] = 0
gray_gradient[i, j, 2] = 0
gray_gradient[i + 1, j - 1, 0] = 0
gray_gradient[i + 1, j - 1, 1] = 0
gray_gradient[i + 1, j - 1, 2] = 0
elif gray[i + 1, j - 1, 0] >= max(gray[i - 1, j + 1, 0], gray[i, j, 0]):
gray_gradient[i, j, 0] = 0
gray_gradient[i, j, 1] = 0
gray_gradient[i, j, 2] = 0
gray_gradient[i - 1, j + 1, 0] = 0
gray_gradient[i - 1, j + 1, 1] = 0
gray_gradient[i - 1, j + 1, 2] = 0
# 水平方向
else:
# check_img[i, j, 0] = 255
# check_img[i, j, 1] = 0
# check_img[i, j, 2] = 0
if gray[i, j, 0] > max(gray[i, j + 1, 0], gray[i, j - 1, 0]):
gray_gradient[i, j + 1, 0] = 0
gray_gradient[i, j + 1, 1] = 0
gray_gradient[i, j + 1, 2] = 0
gray_gradient[i, j - 1, 0] = 0
gray_gradient[i, j - 1, 1] = 0
gray_gradient[i, j - 1, 2] = 0
elif gray[i, j - 1, 0] > max(gray[i, j + 1, 0], gray[i, j, 0]):
gray_gradient[i, j + 1, 0] = 0
gray_gradient[i, j + 1, 1] = 0
gray_gradient[i, j + 1, 2] = 0
gray_gradient[i, j, 0] = 0
gray_gradient[i, j, 1] = 0
gray_gradient[i, j, 2] = 0
elif gray[i, j + 1, 0] >= max(gray[i, j - 1, 0], gray[i, j, 0]):
gray_gradient[i, j - 1, 0] = 0
gray_gradient[i, j - 1, 1] = 0
gray_gradient[i, j - 1, 2] = 0
gray_gradient[i, j, 0] = 0
gray_gradient[i, j, 1] = 0
gray_gradient[i, j, 2] = 0
# cv.imshow("max_cancel", gray_gradient)
# cv.imwrite("pai_mon_01_scanny_01.jpg", gray_gradient, [int(cv.IMWRITE_JPEG_QUALITY), 100])
# 滞后阈值化
# 设置两个阈值,高阈值为High,低阈值为Low
# 认为高阈值肯定是边缘像素,认为大于低阈值且与大于高的阈值的像素邻接的像素也是边缘像素
High = 20
Low = 0
gray_3 = gray_gradient.copy() * 0
for i in range(1, height - 1):
for j in range(1, width - 1):
if gray_gradient[i, j, 0] > High:
gray_3[i, j, 0] = 255
gray_3[i, j, 1] = 255
gray_3[i, j, 2] = 255
elif Low < gray_gradient[i, j, 0] < High:
judge = 0
for x in [-1, 0, 1]:
for y in [-1, 0, 1]:
if gray_gradient[i+x, j+y, 0] > High and \
gradient_direction[i+x, j+y] - gradient_direction[i, j] < math.pi * 0.125:
judge = 1
break
if judge == 1:
gray_3[i, j, 0] = 255
gray_3[i, j, 1] = 255
gray_3[i, j, 2] = 255
# cv.imshow("gray_3", gray_3)
# cv.imwrite("pai_mon_01_scanny_02.jpg", gray_3, [int(cv.IMWRITE_JPEG_QUALITY), 100])
# 黑白转换
gray_4 = gray_3.copy()
for i in range(1, height - 1):
for j in range(1, width - 1):
if gray_3[i, j, 0] == 255:
gray_4[i, j, 0] = 0
gray_4[i, j, 1] = 0
gray_4[i, j, 2] = 0
elif gray_3[i, j, 0] == 0:
gray_4[i, j, 0] = 255
gray_4[i, j, 1] = 255
gray_4[i, j, 2] = 255
cv.imshow("gray_3_white", gray_4)
# cv.imshow("check_img", check_img)
# cv.imwrite("images/img2_canny", gray_4, [int(cv.IMWRITE_JPEG_QUALITY), 100])
cv.waitKey()
如果需要加快速度,图像灰度化时可以直接将图像转为单通道,后面的出来也就只处理一次。
opencv自带的canny算子的实现
canny算子作为一个经典的边缘检测算子,opencv里面也有自带的函数去实现边缘检测,下面是opencv里的canny算子实现,具体的函数使用和参数设置请参考opencv的官方文档。
import cv2 as cv
img = cv.imread("./images/pai_mon_01.jpg")
canny = cv.Canny(img, threshold1=30, threshold2=100)
cv.imshow("canny", canny)
cv.waitKey(0)
得到的效果图:
结语
本文实现的代码主要是从学习角度出现,用于理解canny算子工作的原理和算法的流程,未做优化,仅作为学习摘要和参考交流使用,如有错误,敬请指正和谅解。
注:本文算法阐述部分部分摘要于 章毓晋.计算机视觉教程[M].北京:人民邮电出版社,2021.1:49~56
参考文献
[1] 章毓晋.计算机视觉教程[M].北京:人民邮电出版社,2021.1:53-57