边缘检测算法

边缘检测

边缘是图像最基本的特征,所谓边缘就是指周围灰度强度有反差变化的那些像素的集合,是图像分割所依赖的重要基础,也是纹理分析和图像识别的重要基础。理想的边缘检测应当正确解决边缘的有无、真假、和定向定位。

从人的直观感受来说,边缘对应于物体的几何边界。图像上灰度变化剧烈的区域比较符合这个要求,一般会以这个特征来提取图像的边缘。

当遇到包含纹理的图像,例如图像中的人穿了黑白格子的衣服,一般的算法提取的边缘会把衣服上的纹理提取,如图1所示。但是这种图像我们往往不希望提取出来的边缘包括衣服上的方格,这就又涉及到纹理图像的处理等方法。

从数学上解决该问题最直观的方法就是微分(对于数字图像来说就是差分),在信号处理的角度来看,也可以说是用高通滤波器,即保留高频信号。用于图像识别的边缘提取往往需要输出的边缘是二值图像,即只有黑白两个灰度的图像,其中一个灰度代表边缘,另一个代表背景。

一个常用的边缘检测流程的基本步骤为:

图像滤波:边缘检测的算法主要是基于图像强度的一阶和二阶导数,但是导数通常对噪声很敏感,因此必须采用滤波器来平滑噪声。常见的滤波器有高斯滤波器,即采用离散化的高斯函数产生一组归一化的高斯核,然后基于高斯核函数对图像的灰度矩阵进行卷积操作;
边缘增强:增强边缘的基础是确定图像各点领域强度的变化值。增强算法可以将图像灰度点邻阈强度值有显著变化的点凸显出来。在具体编程过程中,可以通过计算梯度幅值来确定;
边缘检测:经过增强的图像,往往邻域中有很多点的梯度值比较大,而在特定应用中,这些点并不是要找的边缘点,所以应该采用某些方法对这些点进行取舍。实际工程中,常用的方法是通过阈值化的方法进行检测。

图像梯度

边缘检测的实质是采用某种算法来提取出图像中对象与背景间的交界线,一般将边缘定义为图像中灰度发生急剧变化的区域边界。图像灰度的变化情况可以用图像灰度分布的梯度来反映,因此可以用局部图像微分技术来获得边缘检测算子。

这种灰度变化明显不明显怎样去定义呢?从微积分的知识中,可以知道微分就是求函数的变化率,即导数(梯度)。那么对于图像来说,可不可以用微分来表示图像灰度的变化率呢?答案是肯定的,因为图像实际上就是函数。

经典的边界提取技术大都基于微分运算。首先通过平滑来滤除图像中的噪声,然后进行一阶微分或二阶微分运算,求得梯度最大值或二阶导数的过零点,最后选取适当的阈值来提取边界。

边缘检测算法
边缘检测算法
边缘检测算法

; 使用梯度对图像增强

图像梯度可以对图像中的有用信息进行增强,它可以是一个失真的过程,其目的是要改善图像的视觉效果,针对给定图像的应用场合。
有目的地强调图像的整体或局部特性,将原来不清晰的图像变得清晰或强调某些感兴趣的特征,扩大图像中不同物体特征之间的差别,抑制不感兴趣的特征,使之改善图像质量、丰富信息量,加强图像判读和识别效果,满足某些特殊分析的需要。
在这里插入图片描述

边缘检测算法

使用梯度增强图像只需要把梯度与原图做加法即可。例如,求出图4中图像的梯度。首先要读取图片,使用灰度图模式读取:

earth = cv2.imread(“earth.png”, 0)
读取的灰度图如图5所示:

边缘检测算法
图 5 示例图像的灰度图
对图5使用前面介绍的图像梯度公式:
gx = abs(earth[x + 1, y] - earth[x, y])
gy = abs(earth[x, y + 1] - earth[x, y])
gradient[x, y] = gx + gy

可以得到梯度图像如图6所示。很明显的可以看出,计算得到的梯度把明暗边界提出了出来。

边缘检测算法

使用该梯度,对图5的灰度图进行图像增强:

sharp = moon + gradient

效果图见图7所示:

边缘检测算法

可以看出,增强后的图7要比图5更加明亮,并且细节部分更加突出。

Roberts 算子的基本原理

Roberts 算子是利用局部差分寻找边缘的一种算子,是最简单的边缘检测算子。Roberts 算子利用对角线方向相邻两像素之差近似梯度幅值来检测边缘,检测垂直边缘的效果要优于其他方向边缘,定位精度高,但对噪声的抑制能力较弱。边缘检测算子检查每个像素的邻域并对灰度变化率进行量化,同时也包含方向的确定。

边缘检测算法
Roberts 算子边缘定位准,但是对噪声敏感。常用来处理具有陡峭的低噪声图像,当图像边缘接近于正45度或负45度时,该算法处理效果更理想。它适用于边缘明显而且噪声较少的图像分割,在应用中经常用 Roberts 算子来提取道路。其缺点是对边缘的定位不太准确,提取的边缘线条较粗。

在 Python 中,Roberts 算子主要通过 Numpy 定义卷积模板,再调用 OpenCV 的 filter2D() 函数实现边缘提取。该函数主要是利用内核实现对图像的卷积运算,其函数原型如下所示:

dst = filter2D(src, ddepth, kernel[, dst[, anchor[, delta[, borderType]]]])

其中参数为:

src:表示输入图像;
ddepth:表示目标图像所需的深度;
kernel:表示卷积核,一个单通道浮点型矩阵;
dst:表示输出的边缘图,其大小和通道数与输入图像相同;
anchor:表示内核的基准点,其默认值为(-1,-1),位于中心位置;
delta:表示在储存目标图像前可选的添加到像素的值,默认值为0;
borderType:表示边框模式,更多详细信息查阅 BorderTypes。

使用 Roberts 算子提取边缘

下面通过一个例子来演示如何使用 Roberts 算法提取图像边缘。
1) 首先读取图1示例图片:

img = cv2.imread('hangmu.png')

边缘检测算法
2) 对读取的图做灰度处理:
grayImage = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

处理后的图像如图2所示。

边缘检测算法
接下来就是定义 Roberts 算子。该算子的数学定义已经介绍,在这里使用 Numpy 来定义卷积模板:
kernelx = np.array([[-1, 0], [0, 1]], dtype=int)
kernely = np.array([[0, -1], [1, 0]], dtype=int)

输出模板:

kernelx:
[[-1  0]
 [ 0  1]]
kernely:
[[ 0 -1]
 [ 1  0]]

4) 分别用两个模板对图像进行卷积操作,使用filter2D()函数即可:

x = cv2.filter2D(grayImage, cv2.CV_16S, kernelx)
y = cv2.filter2D(grayImage, cv2.CV_16S, kernely)

因为 Roberts 卷积模板含有负值,因此在filter2D()操作后会有负值,还有会大于255的值。而原图像是uint8,即8位无符号数,因此要使用16位有符号的数据类型,即cv2.CV_16S。

在经过处理后,别忘了用 convertScaleAbs()函数将其转回原来的uint8形式,否则将无法显示图像。convertScaleAbs()的原型为:

dst = cv2.convertScaleAbs(src[, dst[, alpha[, beta]]])

其中:

src:源图像;
dst:结果返回uint8类型的图片;
alpha:是伸缩系数;
beta:是加到结果上的一个值。

因此对两个模板处理后的结果进行数据形式转换:

absX = cv2.convertScaleAbs(x)
absY = cv2.convertScaleAbs(y)

边缘检测算法

边缘检测算法
两个模板转换的图像 absX 和 absY 如图3和4所示。

5) 由于 Roberts 算子是在两个方向计算的,最后还需要用 cv2.addWeighted()函数将其组合起来,两个方向使用相同的权值和0偏置:

Roberts = cv2.addWeighted(absX, 0.5, absY, 0.5, 0)

组合图像如图5所示:

边缘检测算法

Prewitt 算子的基本原理

Prewitt 是一种图像边缘检测的微分算子,其原理是利用特定区域内像素灰度值产生的差分实现边缘检测。由于 Prewitt 算子采用3×3大小的模板对区域内的像素值进行计算,而 Roberts 算子的模板为2×2,故 Prewitt 算子的边缘检测结果在水平方向和垂直方向均比 Roberts 算子更加明显。Prewitt 算子适合用来识别噪声较多、灰度渐变的图像,其卷积模板如下所示:

边缘检测算法
Prewitt 算子在一个方向求微分,而在另一个方向求平均,因而对噪声相对不敏感,有抑制噪声的作用。但是像素平均相当于对图像的低通滤波,所以 Prewitt 算子对边缘的定位不如 Roberts 算子。

; Sobel 算子的基本原理

Sobel 算子是一种用于边缘检测的离散微分算子,它结合了高斯平滑和微分求导。该算子用于计算图像明暗程度近似值,根据图像边缘旁边明暗程度把该区域内超过某个数的特定点记为边缘。Sobel 算子在 Prewitt 算子的基础上增加了权重的概念,认为相邻点的距离远近对当前像素点的影响是不同的,距离越近的像素点对应当前像素的影响越大,从而实现图像锐化并突出边缘轮廓。

Sobel 算子的边缘定位更准确,常用于噪声较多、灰度渐变的图像。其算法模板如公式所示,其中 dx 表示水平方向,dy 表示垂直方向。

边缘检测算法
Sobel 算子根据像素点上下、左右邻点灰度加权差,在边缘处达到极值这一现象检测边缘。对噪声具有平滑作用,提供较为精确的边缘方向信息。因为 Sobel 算子结合了高斯平滑和微分求导(分化),因此结果会具有更多的抗噪性,当对精度要求不是很高时, Sobel 算子是一种较为常用的边缘检测方法。

Sobel 算子认为邻域的像素对当前像素产生的影响不是等价的,所以距离不同的像素具有不同的权值,对算子结果产生的影响也不同。一般来说,距离越大,产生的影响越小。

在 OpenCV 中,提供了 Sobel() 函数来对图像进行 Sobel 算子处理,其函数原型如下:

dst = Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]])
src:表示输入图像;
ddepth:表示目标图像所需的深度,针对不同的输入图像,输出目标图像有不同的深度;
dx:表示 x 方向上的差分阶数,取值1或0;
dy:表示 y 方向上的差分阶数,取值1或0;
dst:表示输出的边缘图,其大小和通道数与输入图像相同;
ksize:表示 Sobel 算子的大小,其值必须是正数和奇数;
scale:表示缩放导数的比例常数,默认情况下没有伸缩系数;
delta:表示将结果存入目标图像之前,添加到结果中的可选增量值;
borderType:表示边框模式,更多详细信息查阅 BorderTypes。
在进行 Sobel 算子处理之后,还需要调用convertScaleAbs()函数计算绝对值,并将图像转换为8位图进行显示。

其算法原型如下:

dst = convertScaleAbs(src[, dst[, alpha[, beta]]])
src:表示原数组;
dst:表示输出数组,深度为 8 位;
alpha:表示比例因子;
beta:表示原数组元素按比例缩放后添加的值。

使用 Sobel 算子提取边缘

下面通过一个例子来演示如何使用 Sobel 算法提取图像边缘。
1) 首先读取示例图片:

img = cv2.imread('hangmu.png')

边缘检测算法
2) 对读取的图做灰度处理:
grayImage = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

边缘检测算法
) 接下来就是使用 Sobel 算子。OpenCV 中直接使用 Sobel() 函数即可,在灰度图的x,y方向求一阶导数即可:
x = cv2.Sobel(grayImage, cv2.CV_16S, 1, 0)
y = cv2.Sobel(grayImage, cv2.CV_16S, 0, 1)

输出模板:

kernelx:
[[-1  0]
 [ 0  1]]
kernely:
[[ 0 -1]
 [ 1  0]]

4) 分别用两个模板对图像进行卷积操作,使用filter2D()即可:

x = cv2.filter2D(grayImage, cv2.CV_16S, kernelx)
y = cv2.filter2D(grayImage, cv2.CV_16S, kernely)

Sobel()函数的第二个参数这里使用了 cv2.CV_16S,在OpenCV文档中对算子描述这样描述:” in the case of 8-bit input images it will result in truncated derivatives”。即 Sobel() 函数求完导数后会有负值,还有会大于255的值。而原图像是uint8,即8位无符号数,所以 Sobel 建立的图像位数不够而导致像素值的截断。因此要使用16位有符号的数据类型,即cv2.CV_16S。

在经过处理后,需要用convertScaleAbs()函数将其转回原来的uint8形式。否则将无法显示图像,而只是一副灰色的窗口。 convertScaleAbs()的原型为:

dst = cv2.convertScaleAbs(src[, dst[, alpha[, beta]]])

其中:

src:源图像;
dst:结果返回uint8类型的图片;
alpha:是伸缩系数;
beta:是加到结果上的一个值。

对两个模板处理后的结果进行数据形式转换:

absX = cv2.convertScaleAbs(x)
absY = cv2.convertScaleAbs(y)

两个模板转换的图像 absX 和 absY 如图3和4所示:

边缘检测算法
边缘检测算法
边缘检测算法

Laplacian 算子的基本原理

拉普拉斯(Laplacian)算子是 n 维欧几里德空间中的一个二阶微分算子,常用于图像增强领域和边缘提取。它通过灰度差分计算邻域内的像素,基本流程是:判断图像中心像素灰度值与它周围其他像素的灰度值,如果中心像素的灰度更高,则提升中心像素的灰度;反之降低中心像素的灰度,从而实现图像锐化操作。在算法实现过程中,Laplacian 算子通过对邻域中心像素的四方向或八方向求梯度,再将梯度相加起来判断中心像素灰度与邻域内其他像素灰度的关系,最后通过梯度运算的结果对像素灰度进行调整。

拉普拉斯算子一种二阶边缘检测算子,它是一个线性的、移位不变算子。它是对二维函数进行运算的二阶导数算子,对一个连续函数f(x,y)它在图像中的位置(x,y),拉普拉斯值定义为:

边缘检测算法
Laplacian 算子利用二阶导数信息,具有各向同性,即与坐标轴方向无关,坐标轴旋转后梯度结果不变。使得图像经过二阶微分后,在边缘处产生一个陡峭的零交叉点,根据这个对零交叉点判断边缘。

Laplacian 算子分为四邻域和八邻域,四邻域是对邻域中心像素的四方向求梯度,八邻域是对八方向求梯度。其中,四邻域模板为:

边缘检测算法
通过模板可以发现,当邻域内像素灰度相同时,模板的卷积运算结果为0;当中心像素灰度高于邻域内其他像素的平均灰度时,模板的卷积运算结果为正数;当中心像素的灰度低于邻域内其他像素的平均灰度时,模板的卷积为负数。对卷积运算的结果用适当的衰弱因子处理并加在原中心像素上,就可以实现图像的锐化处理。

Laplacian算子的八邻域模板如下:

边缘检测算法
Python 和 OpenCV 将 Laplacian 算子封装在Laplacian()函数中,其函数原型如下所示:
dst = Laplacian(src, ddepth[, dst[, ksize[, scale[, delta[, borderType]]]]])

参数为:

src:表示输入图像;
dst:表示输出的边缘图,其大小和通道数与输入图像相同;
ddepth:表示目标图像所需的深度;
ksize:表示用于计算二阶导数的滤波器的孔径大小,其值必须是正数和奇数,且默认值为1,更多详细信息查阅 getDerivKernels ;
scale:表示计算拉普拉斯算子值的可选比例因子。默认值为1,更多详细信息查阅 getDerivKernels ;
delta:表示将结果存入目标图像之前,添加到结果中的可选增量值,默认值为0;
borderType:表示边框模式,更多详细信息查阅 BorderTypes。

注意,Laplacian 算子其实主要是利用 Sobel 算子的运算,通过加上 Sobel 算子运算出的图像 x 方向和 y 方向上的导数,得到输入图像的图像锐化结果。同时,在进行 Laplacian 算子处理之后,还需要调用convertScaleAbs()函数计算绝对值,并将图像转换为8位图进行显示。

convertScaleAbs()的原型为:

dst = cv2.convertScaleAbs(src[, dst[, alpha[, beta]]])

其中:

src:源图像;
dst:结果返回uint8类型的图片;
alpha:是伸缩系数;
beta:是加到结果上的一个值。

使用 Laplacian 算子提取边缘

下面通过一个例子来演示如何使用 Laplacian 算法提取图像边缘。
1) 首先读取示例图片:

img = cv2.imread('shizi.png')

边缘检测算法
2) 对读取的图做灰度处理:

grayImage = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
处理后的图像如图2所示:

边缘检测算法
3) 接下来就是使用 Laplacian 算子对图像进行处理,直接调用 OpenCV 的 Laplacian() 函数,为了让结果更清晰,这里的ksize设为3:
dst = cv2.Laplacian(grayImage, cv2.CV_16S, ksize=3)

4) 因为 Laplacian 卷积模板含有负值,因此在Laplacian()函数操作后会有负值,还有会大于255的值。而原图像是uint8,即8位无符号数,因此要使用16位有符号的数据类型,即cv2.CV_16S。

在经过处理后,要用convertScaleAbs()函数将其转回原来的uint8形式,否则将无法显示图像。因此对 Laplacian 模板处理后的结果进行数据形式转换:

Laplacian = cv2.convertScaleAbs(dst)

转换后显示图像的效果如图3所示。

边缘检测算法
从图3中可以看出,图像边缘中有很多的毛刺,这是因为原图像未经过去噪就直接处理了。图像的阈值与平滑 介绍了图像平滑的操作,对图像平滑后进行 Laplacian 算子处理后,可以很好的去除掉毛刺。在步骤3之前使用5×5的卷积核的中值滤波先平滑下图像,再使用 Laplacian 算子处理后的图像如图4所示。
边缘检测算法

几种边缘算子的比较

Laplacian 算子对噪声比较敏感,由于其算法可能会出现双像素边界,常用来判断边缘像素位于图像的明区或暗区,很少用于边缘检测;Roberts 算子对陡峭的低噪声图像效果较好,尤其是边缘正负45度较多的图像,但定位准确率较差;Prewitt 算子对灰度渐变的图像边缘提取效果较好,而没有考虑相邻点的距离远近对当前像素点的影响;Sobel 算子考虑了综合因素,对噪声较多的图像处理效果更好。

在数字图像处理中,Lena 是一张被广泛使用的标准图片。图1显示了使用四种边缘检测算子处理后的图像,可以看到 Roberts 算子表现最差,其它的三个方法提取的边缘比较好。

边缘检测算法
LoG 算子的基本原理
现在介绍一种利用图像强度二阶导数的零交叉点来求边缘点的算法。首先回顾一下 Laplacian 算子,Laplacian 算子定义为图像f在x,y方向上的二阶导数的和:
边缘检测算法
模板实际上是根据离散数据求近似二阶导数的公式,整理得到的矩阵形式。Laplacian 算子对噪声比较敏感,Laplacian 算子有一个缺点是它对图像中的某些边缘产生双重响应。所以图像一般先经过平滑处理,通常把 Laplacian 算子和平滑算子结合起来生成一个新的模板。

为此,马尔(Marr)和希尔得勒斯(Hildreth)根据人类视觉特性提出了一种边缘检测的方法,该方法将高斯滤波和拉普拉斯检测算子结合在一起进行边缘检测的方法,故称为 LoG(Laplacian of Gassian )算法,也称之为拉普拉斯高斯算法。

边缘检测算法
LoG 的特点是图像首先与高斯滤波器进行卷积,这样既平滑了图像又降低了噪声,孤立的噪声点和较小的结构组织将被滤除。但是由于平滑会造成图像边缘的延伸,因此边缘检测器只考虑那些具有局部梯度最大值的点为边缘点,这一点可以用二阶导数的零交叉点来实现。拉普拉斯函数用二维二阶导数的近似,是因为它是一种无方向算子。

在实际应用中为了避免检测出非显著边缘,应选择一阶导数大于某一阈值的零交叉点作为边缘点。由于对平滑图像g(x,y)进行拉普拉斯运算可等效为G(x,y)的拉普拉斯运算与f(x,y)的卷积,故h(x,y)变为:

边缘检测算法
这样就有两种方法求图像边缘:
① 先求图像与高斯滤波器的卷积,再求卷积的拉普拉斯的变换,然后再进行过零判断。
② 求高斯滤波器的拉普拉斯的变换,再求与图像的卷积,然后再进行过零判断。
这两种方法在数学上是等价的。上式就是马尔和希尔得勒斯提出的边缘检测算子(简称 M-H 算子),由于 LoG 滤波器在(x,y)空间中的图形与墨西哥草帽形状相似,所以又称为墨西哥草帽算子,如图2所示:
边缘检测算法
拉普拉斯算子对图像中的嗓声相当敏感,而且它常产生双像素宽的边缘,也不能提供边缘方向的信息。高斯-拉普拉斯算子是效果较好的边沿检测器,二维高斯拉普拉斯算子可以通过任何一个方形核进行逼近,只要保证该核的所有元素的和或均值为0,图3是两个常用的5×5模板的高斯拉普拉斯算子:
边缘检测算法

; 使用 LoG 算子提取边缘

下面通过一个例子来演示如何使用 LoG 算法提取图像边缘。
1) 首先读取示例图片:

img = cv2.imread('shizi.png')

读取的图片如图4所示:

边缘检测算法
2) 对读取的图做灰度处理:
grayImage = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

处理后的图像如图5所示:

边缘检测算法
3.根据 LoG 算子处理图像的步骤,使用3×3的卷积核的高斯滤波先对图像进行卷积运算。为了使每个像素都被卷积运算到,需要先扩充边缘。我们使用BORDER_REPLICATE扩展边缘,然后使用高斯滤波平滑处理:

image = cv2.copyMakeBorder(img, 2, 2, 2, 2, borderType=cv2.BORDER_REPLICATE)

image = cv2.GaussianBlur(img,(3,3),0,0)

4) 接下来就是使用 LoG 算子对图像进行处理。首先要定义一个 LoG 算子,根据前文所述,可以使用 Numpy 定义如图3右形状的算子:

[[ 0  0 -1  0  0]
[ 0 -1 -2 -1  0]
[-1 -2 16 -2 -1]
[ 0 -1 -2 -1  0]
[ 0  0 -1  0  0]]

然后使用定义的算子对图像进行卷积计算,图6描述了二维图像的卷积算法,即使用卷积核对原像素的相同大小部分的对应位置相乘,把相乘的结果相加,放在中心的像素位置上替代原像素。

边缘检测算法
根据二维图像卷积计算的原始定义,直观上使用循环写出该算法:
for i in range(2,rows-2):
    for j in range(2,cols-2):

        image1[i, j] = np.sum((m1 * image[i - 2:i + 3, j - 2:j + 3]))

5) 因为 LoG 卷积模板含有负值,因此在卷积操作后会有负值,还有会大于255的值。而原图像是uint8,即8位无符号数,因此要使用16位有符号的数据类型,即cv2.CV_16S。

在经过处理后,要用convertScaleAbs()函数将其转回原来的uint8形式,否则将无法显示图像。因此对 Laplacian 模板处理后的结果进行数据形式转换:

image1=cv2.convertScaleAbs(image1)

处理后的效果如图7所示:

边缘检测算法
(1)可能产生假边缘(false edges);
(2)对一些曲线边缘(curved edges)的定位误差较大。

尽管该算法存在以上不足,但对未来图像特征研究起到了积极作用。尤其对图像先进行高斯滤波(噪声平滑)再进行图像梯度计算的思想的引入,被后来性能优良的 Canny 边缘检测算法 (Canny edge detector) 所采用。同时,这种思想也被后来很多的图像特征检测技术所采纳,如 Harris 角点,尺度不变特征变换(SIFT)等。

Canny 算子的基本原理

图像边缘信息主要集中在高频段,通常说图像锐化或检测边缘,实质就是高频滤波。微分运算是求信号的变化率,具有加强高频分量的作用。对于数字图像的离散信号,微分运算就变成计算差分或梯度。图像处理中有多种边缘检测(梯度)算子,常用的包括普通一阶差分,Roberts 算子(交叉差分),Sobel 算子等等,是基于寻找梯度强度。拉普拉斯算子(二阶差分)是基于过零点检测。通过计算梯度,设置阈值,得到边缘图像。

Canny 边缘检测算子是一种多级检测算法。

1986年由 John F. Canny 提出,同时提出了边缘检测的三大准则:

低错误率的边缘检测:检测算法应该精确地找到图像中的尽可能多的边缘,尽可能的减少漏检和误检;
最优定位:检测的边缘点应该精确地定位于边缘的中心;
图像中的任意边缘应该只被标记一次,同时图像噪声不应产生伪边缘。
Canny 算法出现以后一直是作为一种标准的边缘检测算法,此后也出现了各种基于 Canny 算法的改进算法。时至今日,Canny 算法及其各种改进依旧是一种优秀的边缘检测算法。而且除非前提条件很适合,你很难找到一种边缘检测算子能显著地比 Canny 算子做的更好。

Canny 算法有如下特点:

(1) 具有既能滤去噪声又保持边缘特性的边缘检测最优滤波器,其采用一阶微分滤波器。采用二维高斯函数的任意方向上的一阶方向导数为噪声滤波器,通过与图像卷积进行滤波;然后对滤波后的图像寻找图像梯度的局部最大值,以此来确定图像边缘。根据对信噪比与定位乘积进行测度,得到最优化逼近算子。这就是 Canny 边缘检测算子;
(2) 类似与 LoG 边缘检测方法,也属于先平滑后求导数的方法。

Canny 边缘检测算法

接下来将使用数学对 Canny 边缘检测算法进行步骤描述:

step 1. 滤波平滑噪声。
边缘检测的算法主要是基于图像强度的一阶和二阶微分操作,但导数通常对噪声很敏感。边缘检测算法常常需要根据图像源的数据进行预处理操作,因此必须采用滤波器来改善与噪声有关的边缘检测的性能。
在进行 Canny 算子边缘检测前,应当先对原始数据与高斯模板进行卷积操作,得到的图像与原始图像相比有些轻微的模糊。通常使用高斯平滑滤波器卷积降噪,下面显示了一个 size=5 的高斯内核示例:

边缘检测算法
tep 3. 非极大值抑制。

在 Canny 算法中,非极大值抑制是进行边缘检测的重要步骤。通俗意义上是指寻找像素点局部的最大值,将非极大值点所对应的灰度值设置为背景像素点,像素邻域区域满足梯度值的局部最优值判断为该像素的边缘,对其余非极大值的相关信息进行抑制,利用该准则可以剔除大部分非边缘点。这一步骤主要排除非边缘像素,仅仅保存候选图像边缘。

step 4. 双阈值法抑制假边缘,连接真边缘。上一步骤得到的疑似边缘中存在伪边缘,由于单阈值处理边缘选取操作较难,Canny 算法中减少伪边缘数量的方法是采用滞后阈值法。

Canny 使用了滞后阈值,滞后阈值需要高阈值和低阈值,在进行边缘确定时依据下面的步骤:第一,如果某一像素位置的幅值超过高阈值,该像素被保留为边缘像素:第二,如果某一像素位置的幅值小于低阈值,该像素被排除;第三,如果某一像素位置的幅值在两个阈值之间,该像素仅仅在连接到一个高于高阈值的像素时被保留。Canny 算法的双阈值中,高阈值检测出的图像去除了大部分噪声,但是也损失了有用的边缘信息,低阈值检测得到的图像则保留着较多的边缘信息。

; Canny 边缘检测过程

下面通过一个例子来演示如何使用 Canny 算法提取图像边缘。
1) 首先读取示例图片:
img = cv2.imread(‘shizi.png’)

边缘检测算法
2) 在 Canny 边缘检测的最开始,要使用高斯滤波去除图像噪声。使用如下代码进行高斯滤波操作:

blur = cv2.GaussianBlur(image, (3, 3), 0)
3) 对读取的图做灰度处理:

grayImage = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

处理后的图像如图2所示:

边缘检测算法
4) 接下来就是使用 Sobel 算子计算图像梯度,在 OpenCV 中,提供了Sobel()函数来对图像进行 Sobel 算子处理,其函数原型如下:
dst = Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]])
src:表示输入图像;
dst:表示输出的边缘图,其大小和通道数与输入图像相同;
ddepth:表示目标图像所需的深度,针对不同的输入图像,输出目标图像有不同的深度;
dx:表示 x 方向上的差分阶数,取值1或0;
dy:表示 y 方向上的差分阶数,取值1或0;
ksize:表示 Sobel 算子的大小,其值必须是正数和奇数;
scale:表示缩放导数的比例常数,默认情况下没有伸缩系数;
delta:表示将结果存入目标图像之前,添加到结果中的可选增量值;
borderType:表示边框模式,更多详细信息查阅 BorderTypes。

使用如下代码,分别求出 x 和 y 方向的梯度:

gradx = cv2.Sobel(blur, cv2.CV_16SC1, 1, 0)
grady = cv2.Sobel(blur, cv2.CV_16SC1, 0, 1)

5) Canny 边缘检测接下来需要非最大值抑制、使用双阈值、对滞后边界跟踪的步骤,实现过程请打开参考代码。OpenCV 提供了Canny()函数来进行高效地使用 Canny 算法在图像中查找边缘。该函数原型为:

Canny(dx, dy, threshold1, threshold2, edges, L2gradient)

该函数使用了自定义的梯度,参数为:

dx:该参数表示输入图像的 x 导数( x 导数数据格式选择CV_16SC1或CV_16SC3);
dy:该参数表示输入图像的 y 导数( y 导数数据格式选择CV_16SC1或CV_16SC3);
threshold1:该参数表示设置的低阈值;
threshold2:该参数表示设置的高阈值,一般设定为低阈值的 3 倍 (根据 Canny 算法的推荐);
edges:该参数表示输出边缘图像;
L2gradient:该参数表示一个布尔值,如果为真,则使用更精确的 L2 范数进行计算(即两个方向的倒数的平方和再开方),否则使用 L1 范数(直接将两个方向导数的绝对值相加)
使用该函数对灰度图进行处理,参数为x、y两个方向的导数gradx, grady,低阈值为50,高阈值为150:
edge_output = cv2.Canny(gradx, grady, 50, 150)

使用该代码处理过的图像如图3所示。可以很明显看出,Canny 边缘检测的效果是很显著的。相比普通的梯度算法大大抑制了噪声引起的伪边缘,而且是细化过的边缘,易于后续处理。对于对比度较低的图像,通过调节参数,Canny 算法也能有很好的效果。

边缘检测算法

Original: https://blog.csdn.net/qq_45484797/article/details/120342930
Author: 努力的小琪
Title: 边缘检测算法

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/649661/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球