使用opencv的Canny算子实现图像边缘检测

 

1 边缘检测介绍

图像边缘检测技术是图像处理和计算机视觉等领域最基本的问题,也是经典的技术难题之一。如何快速、精确地提取图像边缘信息,一直是国内外的研究热点,同时边缘的检测也是图像处理中的一个难题。早期的经典算法包括边缘算子方法、曲面拟合的方法、模板匹配方法、阈值法等。

近年来,随着数学理论与人工智能技术的发展,出现了许多新的边缘检测方法,如Roberts、Laplacan、Canny等图像的边缘检测方法。这些方法的应用对于高水平的特征提取、特征描述、目标识别和图像理解有重大的影响。然而,在成像处理的过程中投影、混合、失真和噪声等会导致图像模糊和变形,这使得人们一直致力于构造具有良好特性的边缘检测算子。

1.1 什么是边缘检测

边缘检测是图像处理和计算机视觉中的基本问题,边缘检测的目的是标识数字图像中亮度变化明显的点。图像属性中的显著变化通常反映了属性的重要事件和变化,包括深度不连续、表面方向不连续、物质属性变化和场景照明变化。边缘检测特征是提取中的一个研究领域。图像边缘检测大幅度地减少了数据量,并且剔除了可以认为不相关的信息,保留了图像重要的结构属性。

1.2 边缘检测的方法

人类视觉系统认识目标的过程分为两步:首先,把图像边缘与背景分离出来;然后,到图像的细节,辨认出图像的轮廓。计算机视觉正是模仿人类视觉的过程。

因此,在检测物体边缘时先对轮廓点进行粗略检测,然后通过链接规则把原来检测到的轮廓点连接起来,同时检测和连接遗漏的边界点及去除虚假的边界点。图像的边缘是图像的重要特征,是计算机视觉、模式识别等的基础,因此边缘检测是图像处理中一个重要的环节。然而,边缘检测是图像处理中的一个难题,因为实际景物图像的边缘往往是各种类型的边缘及它们模糊化后结果的组合,且实际图像信号存在噪声。噪声和边缘都属于高频信号,很难用频带做取舍。

边缘是指图像周围像素灰度有阶跃变化或屋顶状变化的像素集合,存在于目标与背景、目标与目标、区域与区域、基元与基元之间。边缘具有方向和幅度两个特征,沿边缘走向,像素值变化比较平缓;垂直于边缘走向,像素值变化比较剧烈,可能呈现阶跃状,也可能呈现斜坡状。因此,边缘可以分为两种:

  • 一种为阶跃性边缘,两边的像素灰度值有着明显的不同;

  • 另一种为屋顶状边缘,位于灰度值从增加到减少的变化转折点。

对于阶跃性边缘,二阶方向导数在边缘处呈零交叉;对于屋顶状边缘,二阶方向导数在边缘处取极值。有许多方法可以用于边缘检测,绝大部分可以划分为两类:基于搜索的一类和基于零穿越的一类。

  • 基于搜索:通过寻找图像一阶导数中的最大值来检测边界,然后利用计算结果估计边缘的局部方向,通常采用梯度的方向,并利用此方向找到局部梯度模的最大值,代表算法是Sobel算子和Scharr算子。

ee342b69286545f89d6153c9d55bde58.png

  • 基于零穿越:通过寻找图像二阶导数零穿越来寻找边界,代表算法是Laplacian算子。

91276000e2b84fd49624342fac4045ee.png

1.3 典型算子比较

算子 优缺点
Roberts 对具有陡峭的低噪声的图像处理效果较好,但利用Roberts算子提取边缘的结果是边缘比较粗,因此边缘定位不是很准确
Sobel 对灰度渐变和噪声较多的图像处理效果比较好,Sobel算子对边缘定位比较准确
Kirsch 对灰度渐变和噪声较多的图像处理效果较好
Prewitt  对灰度渐变和噪声较多的图像处理效果较好
Laplacian 对图像中的阶跃性边缘点定位准确,对噪声非常敏感,丢失一部分边缘的方向信息,造成一些不连续的检测边缘
LoG LoG算子经常出现双边缘像素边界,而且该检测算法对噪声比较敏感,所以很少用LoG算子检测边缘,而是用来判断边缘像素是位于图像的明区还是暗区
Canny 此方法不容易受噪声的干扰,能够检测到真正的弱边缘。在edge函数中,最有效的边缘检测方法是Canny方法。该方法的优点在于使用两种不同的阈值分别检测强边缘和弱边缘,并且仅当弱边缘和强边缘相连时,才将弱边缘包含在输出图像中。因此,这种方法不容易被噪声”填充“,更容易检测出真正的弱边缘。

2 使用opencv的Canny算子实现边缘检测

Canny边缘检测是一种经典的边缘检测算法,由John F.在1986年提出。它被广泛应用于计算机视觉和图像处理领域,是一种多阶段的边缘检测算法,能够有效地检测图像中的边缘并抑制噪声,得到清晰准确的边缘信息,并且对噪声具有一定的鲁棒性。Canny被认为是最优的边缘检测算法。

2.1 检测原理

2.1.1 应用高斯滤波去除图像噪声

由于图像边缘非常容易受到噪声的干扰,因此为了避免检测到错误的边缘信息,通常需要对图像进行滤波以去除噪声。滤波的目的是平滑一些纹理较弱的非边缘区域,以便得到更准确的边缘。在实际处理过程中,通常采用高斯滤波去除图像中的噪声。

图 10-1 演示了使用高斯滤波器 T 对原始图像 O 中像素值为 226 的像素点进行滤波,得到该点在滤波结果图像 D 内的值的过程。

使用opencv的Canny算子实现图像边缘检测_第1张图片

在滤波过程中,我们通过滤波器对像素点周围的像素计算加权平均值,获取最终滤波结果。对于高斯滤波器 T,越临近中心的点,权值越大。在图 10-1 中,对图像 O 中像素值为 226 的像素点,使用滤波器 T 进行滤波的计算过程及结果为:

结果 = 156×(197×1+25×1+106×2+156×1+159×1
 +149×1+40×3+107×4+5×3+71×1
 +163×2+198×4+226×8+223×4+156×2
 +222×1+37×3+68×4+193×3+157×1
 +42×1+72×1+250×2+41×1+75×1)
 = 138

 当然,高斯滤波器(高斯核)并不是固定的,例如它还可以是:

使用opencv的Canny算子实现图像边缘检测_第2张图片
滤波器的大小也是可变的,高斯核的大小对于边缘检测的效果具有很重要的作用。滤波器的核越大,边缘信息对于噪声的敏感度就越低。不过,核越大,边缘检测的定位错误也会随之增加。通常来说,一个 5×5 的核能够满足大多数的情况。

2.1.2 计算梯度幅值和方向

前面一节讲了如何计算图像梯度的幅度。在这里,我们关注梯度的方向,梯度的方向与边缘的方向是垂直的。

边缘检测算子返回水平方向的Gx和垂直方向的Gy。梯度的幅度和方向(用角度值表示)为:

4727f5e6240a40dd94fe5ec6c59d2ce0.png

e672aca992c243778bd6af160e74e330.png

如果某个像素点是边缘,则其梯度方向总是垂直与边缘垂直。梯度方向被归为四类:垂直,水平,和两个对角线方向。

因此,在计算梯度时,我们会得到梯度的幅度和角度(代表梯度的方向)两个值。

图 10-2 展示了梯度的表示法。其中,每一个梯度包含幅度和角度两个不同的值。为了方便观察,这里使用了可视化表示方法。例如,左上角顶点的值“2↑”实际上表示的是一个二元数对“(2, 90)”,表示梯度的幅度为 2,角度为 90°。
使用opencv的Canny算子实现图像边缘检测_第3张图片

2.1.3 非极大值抑制

在获得了梯度的幅度和方向后,遍历图像中的像素点,去除所有非边缘的点。

在具体实现时,逐一遍历像素点,判断当前像素点是否是周围像素点中具有相同梯度方向的最大值,并根据判断结果决定是否抑制该点。

通过以上描述可知,该步骤是边缘细化的过程。针对每一个像素点:

  • 如果该点是正/负梯度方向上的局部最大值,则保留该点。

  • 如果不是,则抑制该点(归零)。

在图 10-3 中,A、B、C 三点具有相同的方向(梯度方向垂直于边缘)。判断这三个点是否为各自的局部最大值:如果是,则保留该点;否则,抑制该点(归零)。
使用opencv的Canny算子实现图像边缘检测_第4张图片

经过比较判断可知,A 点具有最大的局部值,所以保留 A 点(称为边缘),其余两点(B和 C)被抑制(归零)。

在图 10-4 中,黑色背景的点都是向上方向梯度(水平边缘)的局部最大值。因此,这些点会被保留;其余点被抑制(处理为 0)。这意味着,这些黑色背景的点最终会被处理为边缘点,而其他点都被处理为非边缘点。
使用opencv的Canny算子实现图像边缘检测_第5张图片

“正/负梯度方向上”是指相反方向的梯度方向。例如,在图 10-5 中,黑色背景的像素点都是垂直方向梯度(向上、向下)方向上(即水平边缘)的局部最大值。这些点最终会被处理为边缘点。

使用opencv的Canny算子实现图像边缘检测_第6张图片

经过上述处理后,对于同一个方向的若干个边缘点,基本上仅保留了一个,因此实现了边缘细化的目的。

2.1.4 应用双阈值确定边缘

完成上述步骤后,图像内的强边缘已经在当前获取的边缘图像内。但是,一些虚边缘可能也在边缘图像内。这些虚边缘可能是真实图像产生的,也可能是由于噪声所产生的。对于后者,必须将其剔除。

设置两个阈值,其中一个为高阈值 maxVal,另一个为低阈值 minVal。根据当前边缘像素的梯度值(指的是梯度幅度,下同)与这两个阈值之间的关系,判断边缘的属性。具体步骤为:

  • 如果当前边缘像素的梯度值大于或等于 maxVal,则将当前边缘像素标记为强边缘。

  • 如果当前边缘像素的梯度值介于 maxVal 与 minVal 之间,则将当前边缘像素标记为虚边缘(需要保留)。

  • 如果当前边缘像素的梯度值小于或等于 minVal,则抑制当前边缘像素。

在上述过程中,我们得到了虚边缘,需要对其做进一步处理。一般通过判断虚边缘与强边
缘是否连接,来确定虚边缘到底属于哪种情况。通常情况下,如果一个虚边缘:

  • 与强边缘连接,则将该边缘处理为边缘。

  • 与强边缘无连接,则该边缘为弱边缘,将其抑制。

在图 10-6 中,左图显示的是三个边缘信息,右图是对边缘信息进行分类的示意图,具体划分如下:

  • A 点的梯度值值大于 maxVal,因此 A 是强边缘。

  • B 和 C 点的梯度值介于 maxVal 和 minVal 之间,因此 B、C 是虚边缘。

  • D 点的梯度值小于 minVal,因此 D 被抑制(抛弃)。

使用opencv的Canny算子实现图像边缘检测_第7张图片

图 10-7 显示了对图 10-6 中的虚边缘 B 和 C 的处理结果。其中:

  • B 点的梯度值介于 maxVal 和 minVal 之间,是虚边缘,但该点与强边缘不相连,故将其抛弃。

  • C 点的梯度值介于 maxVal 和 minVal 之间,是虚边缘,但该点与强边缘 A 相连,故将其保留。

使用opencv的Canny算子实现图像边缘检测_第8张图片

注意,高阈值 maxVal 和低阈值 minVal 不是固定的,需要针对不同的图像进行定义。

10-8 给出了一个 Canny 边缘检测的效果图。

使用opencv的Canny算子实现图像边缘检测_第9张图片

2.1.5 检测边缘连接

Canny边缘检测的边缘连接是指将非极大值抑制得到的边缘点连接成连续的边缘线。这个过程是Canny边缘检测算法中的最后一步,它的目的是去除由于非极大值抑制产生的间断的边缘点,从而得到更加准确和连续的边缘检测结果。

Canny边缘检测的边缘连接过程:

  • 遍历梯度幅值图像:首先,遍历经过非极大值抑制后的梯度幅值图像,即只有边缘上的像素点的梯度幅值被保留,其他像素点的梯度幅值为零。

  • 标记边缘点:对于每个强边缘像素点(梯度幅值大于高阈值),将其标记为边缘点。强边缘像素点是图像中梯度值较大的像素点,它们代表了图像中明显的边缘。

  • 边缘连接:对于每个边缘点的相邻像素点,如果其梯度幅值大于低阈值,并且没有被标记为边缘点,则将其标记为弱边缘点,并递归地进行边缘连接。这一步的目的是将与强边缘像素点相邻的弱边缘像素点连接到边缘线上。

  • 递归连接:在边缘连接过程中,如果某个弱边缘像素点被标记为边缘点,则会继续检查该像素点的相邻像素点,以便将所有与强边缘像素点相邻的弱边缘像素点连接到边缘线上。

  • 结束条件:边缘连接的递归过程会一直进行,直到所有与强边缘像素点相邻的弱边缘像素点都被标记为边缘点,没有更多的像素点可以连接。

  • 非边缘点处理:经过边缘连接后,所有未被标记为边缘点的像素点视为非边缘点,并抑制其梯度幅值为零,从而得到最终的边缘图像。

2.2 Canny函数原型

edges = cv.Canny( image, threshold1, threshold2[, apertureSize[, L2gradient]])

参数:

  • edges 为计算得到的边缘图像。

  • image 为 8 位输入图像。

  • threshold1 表示处理过程中的第一个阈值。

  • threshold2 表示处理过程中的第二个阈值。

  • apertureSize 表示 Sobel 算子的孔径大小。

  • L2gradient 为计算图像梯度幅度(gradient magnitude)的标识。其默认值为 False。如果为 True,则使用更精确的 L2 范数进行计算(即两个方向的导数的平方和再开方),否则使用 L1 范数(直接将两个方向导数的绝对值相加)。

 

2.3 检测代码

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt

# 1 读取图像
img = cv.imread('../data/cat_dog.jpg')

# 2 将图片由BGR转为RGB
img = cv.cvtColor(img, cv.COLOR_BGR2RGB)

# 2 边缘检测,canny算子,阈值128-200,低于128的像素点认为是边缘,
# 高于200的像素点认为是边缘,中间值的像素点如果与边缘点相连则认为是边缘
edge = cv.Canny(img, 128, 200)

# 3 图像显示
plt.figure(figsize=(10, 8), dpi=100)
plt.subplot(121), plt.imshow(img), plt.title('original')
plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(edge, cmap=plt.cm.gray), plt.title('Canny')
plt.xticks([]), plt.yticks([])
plt.show()

运行代码显示:

使用opencv的Canny算子实现图像边缘检测_第10张图片

 

你可能感兴趣的:(opencv,人工智能,计算机视觉,canny)