数字图像处理(20): 边缘检测算子(Canny算子)
目录
1 边缘检测算子分类
2 Canny算子
2.1 基本理论
2.2 代码示例
3 各类算子实验比较
参考资料
前面已经介绍了 边缘检测算子(Roberts算子、Prewitt算子、Sobel算子 和 Laplacian算子), 下面会介绍Canny算子。介绍Canny算子之前,还是看一下边缘检测算子分类。
1 边缘检测算子分类
(1)一阶导数的边缘检测算子
通过模板作为核与图像的每个像素点做卷积和运算,然后选取合适的阈值来提取图像的边缘。常见的有Roberts算子、Sobel算子和Prewitt算子。
(2)二阶导数的边缘算子
依据于二阶导数过零点,常见的有Laplacian 算子,此类算子对噪声敏感。
(3)Canny算子
前面两类均是通过微分算子来检测图像边缘,还有一种就是Canny算子,其是在满足一定约束条件下推导出来的边缘检测最优化算子。
2 Canny算子
2.1 基本理论
John F.Canny于1986年发明了一个多级边缘检测算法——Canny边缘检测算子,并创立了边缘检测计算理论(Computational theory of edge detection),该理论有效地解释了这项技术的工作理论。
通常情况下边缘检测的目的是在保留原有图像属性的情况下,显著减少图像的数据规模。目前有多种算法可以进行边缘检测,虽然Canny算法年代久远,但可以说它是边缘检测的一种标准算法,而且仍在研究中广泛使用。
Canny算法是一种被广泛应用于边缘检测的标准算法,其目标是找到一个最优的边缘检测解或找寻一幅图像中灰度强度变化最强的位置。最优边缘检测主要通过低错误率、高定位性和最小响应三个标准进行评价。Canny算子的简要步骤如下:
(1)去噪声:应用高斯滤波来平滑图像,目的是去除噪声
(2)梯度:找寻图像的梯度
(3)非极大值抑制:应用非最大抑制技术来过滤掉非边缘像素,将模糊的边界变得清晰。该过程保留了每个像素点上梯度强度的极大值,过滤掉其他的值。
(4)应用双阈值的方法来决定可能的(潜在的)边界;
(5)利用滞后技术来跟踪边界。若某一像素位置和强边界相连的弱边界认为是边界,其他的弱边界则被删除。
Canny算子的具体步骤如下:
1 去噪声
边缘检测容易受到噪声的影像。因此,在进行边缘检测前,通常需要进行去噪。通常,使用高斯滤波来去除噪声,例如去高斯滤波的核大小为 55,公式如下所示:
关于高斯滤波去除噪声可以参见博客:图像平滑 (均值滤波、中值滤波和高斯滤波)
2 梯度:找寻图像的梯度
按照Sobel算子计算梯度幅值和方向,寻找图像的梯度。先将卷积模板分别作用和
方向,再计算梯度幅值和方向,其公式如下所示:
例如,下图为计算好的梯度和方向:
梯度的方向一般总是与边界垂直,梯度的方向被归为四类:垂直、水平和两个对角线(即,0度、45度、90度和135度四个方向)。
3 非极大值抑制
对于每个像素点,它进行如下操作:应用非最大抑制技术来过滤掉非边缘像素,将模糊的边界变得清晰。该过程保留了每个像素点上梯度强度的极大值,过滤掉其他的值。
1)将其梯度方向近似为以下值中的一个,包括0、45、90、135、180、225、270和315,即表示上下左右和45度方向。
2)比较该像素点和其梯度正负方向的像素点的梯度强度,如果该像素点梯度强度最大则保留,否则抑制(删除,即置为0)。
4 应用双阈值的方法来决定可能的(潜在的)边界
经过非极大抑制后图像中仍然有很多噪声点。Canny算法中应用了一种叫双阈值的技术。即设定一个阈值上界和阈值下界(opencv中通常由人为指定的),图像中的像素点如果大于阈值上界则认为必然是边界(称为强边界,strong edge),小于阈值下界则认为必然不是边界,两者之间的则认为是候选项(称为弱边界,weak edge),需进行进一步处理。过程如下图所示:
经过双阈值处理的图像如下图所示:
5 利用滞后技术来跟踪边界。若某一像素位置和强边界相连的弱边界认为是边界,其他的弱边界则被删除
最后Canny算子检测到的边缘,如下图所示:
2.2 代码示例
在OpenCV中,Canny() 函数用法如下所示:
edges = Canny(image, threshold1, threshold2[, edges[, apertureSize[, L2gradient]]])
其中,参数:
mage 表示输入图像;
edges 表示输出的边缘图,其大小和类型与输入图像相同;
threshold1 表示第一个滞后性阈值;
threshold2 表示第二个滞后性阈值;
apertureSize 表示应用Sobel算子的孔径大小,其默认值为3;
L2gradient 表示一个计算图像梯度幅值的标识,默认值为false。
代码如下所示:
# -*- coding: utf-8 -*-
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取图像
img = cv2.imread('zxp.jpg')
lenna_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 灰度化处理图像
grayImage = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 高斯滤波降噪
gaussian = cv2.GaussianBlur(grayImage, (5, 5), 0)
# Canny算子
Canny = cv2.Canny(gaussian, 50, 150)
# 用来正常显示中文标签
plt.rcParams['font.sans-serif'] = ['SimHei']
# 显示图形
titles = [u'原始图像', u'Canny算子']
images = [lenna_img, Canny]
for i in range(2):
plt.subplot(1, 2, i + 1), plt.imshow(images[i], 'gray')
plt.title(titles[i])
plt.xticks([]), plt.yticks([])
plt.show()
运行结果如下图所示:
3 各类算子实验比较
边缘检测算法主要是基于图像强度的一阶导数和二阶导数,但导数通常对噪声很敏感,因此需要采用滤波器来过滤噪声,并调用图像增强或阈值化算法进行处理,最后再进行边缘检测。下面是采用高斯滤波去噪和阈值化处理之后(Canny检测之前没有阈值化处理),再进行边缘检测的过程,并对比了5种常见的边缘提取算法。
代码如下所示:
# -*- coding: utf-8 -*-
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取图像
img = cv2.imread('zxp.jpg')
img_RGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 转成RGB 方便后面显示
# 灰度化处理图像
grayImage = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 高斯滤波
gaussianBlur = cv2.GaussianBlur(grayImage, (3, 3), 0)
# 阈值处理
ret, binary = cv2.threshold(gaussianBlur, 127, 255, cv2.THRESH_BINARY)
# Roberts算子
kernelx = np.array([[-1, 0], [0, 1]], dtype=int)
kernely = np.array([[0, -1], [1, 0]], dtype=int)
x = cv2.filter2D(binary, cv2.CV_16S, kernelx)
y = cv2.filter2D(binary, cv2.CV_16S, kernely)
absX = cv2.convertScaleAbs(x)
absY = cv2.convertScaleAbs(y)
Roberts = cv2.addWeighted(absX, 0.5, absY, 0.5, 0)
# Prewitt算子
kernelx = np.array([[1, 1, 1], [0, 0, 0], [-1, -1, -1]], dtype=int)
kernely = np.array([[-1, 0, 1], [-1, 0, 1], [-1, 0, 1]], dtype=int)
x = cv2.filter2D(binary, cv2.CV_16S, kernelx)
y = cv2.filter2D(binary, cv2.CV_16S, kernely)
absX = cv2.convertScaleAbs(x)
absY = cv2.convertScaleAbs(y)
Prewitt = cv2.addWeighted(absX, 0.5, absY, 0.5, 0)
# Sobel算子
x = cv2.Sobel(binary, cv2.CV_16S, 1, 0)
y = cv2.Sobel(binary, cv2.CV_16S, 0, 1)
absX = cv2.convertScaleAbs(x)
absY = cv2.convertScaleAbs(y)
Sobel = cv2.addWeighted(absX, 0.5, absY, 0.5, 0)
# Laplacian算子
dst = cv2.Laplacian(binary, cv2.CV_16S, ksize=3)
Laplacian = cv2.convertScaleAbs(dst)
# Canny算子
Canny = cv2.Canny(gaussianBlur, 50, 150)
# 用来正常显示中文标签
plt.rcParams['font.sans-serif'] = ['SimHei']
# # 显示图形
plt.subplot(231), plt.imshow(img_RGB), plt.title('原始图像'), plt.axis('off') # 坐标轴关闭
plt.subplot(232), plt.imshow(Canny, cmap=plt.cm.gray), plt.title('Canny算子'), plt.axis('off')
plt.subplot(233), plt.imshow(Roberts, cmap=plt.cm.gray), plt.title('Roberts算子'), plt.axis('off')
plt.subplot(234), plt.imshow(Prewitt, cmap=plt.cm.gray), plt.title('Prewitt算子'), plt.axis('off')
plt.subplot(235), plt.imshow(Sobel, cmap=plt.cm.gray), plt.title('Sobel算子'), plt.axis('off')
plt.subplot(236), plt.imshow(Laplacian, cmap=plt.cm.gray), plt.title('Laplacian算子'), plt.axis('off')
plt.show()
运行结果如下图所示:
多测试几幅图像,看看效果
参考资料
[1] https://blog.csdn.net/Eastmount/article/details/89056240
[2] Python+OpenCV图像处理
[3] 冈萨雷斯. 数字图像处理(第三版)
还没有评论,来说两句吧...