1 边缘检测介绍
图像边缘检测技术是图像处理和计算机视觉等领域最基本的问题,也是经典的技术难题之一。如何快速、精确地提取图像边缘信息,一直是国内外的研究热点,同时边缘的检测也是图像处理中的一个难题。早期的经典算法包括边缘算子方法、曲面拟合的方法、模板匹配方法、阈值法等。
近年来,随着数学理论与人工智能技术的发展,出现了许多新的边缘检测方法,如Roberts、Laplacan、Canny等图像的边缘检测方法。这些方法的应用对于高水平的特征提取、特征描述、目标识别和图像理解有重大的影响。然而,在成像处理的过程中投影、混合、失真和噪声等会导致图像模糊和变形,这使得人们一直致力于构造具有良好特性的边缘检测算子。
1.1 什么是边缘检测
边缘检测是图像处理和计算机视觉中的基本问题,边缘检测的目的是标识数字图像中亮度变化明显的点。图像属性中的显著变化通常反映了属性的重要事件和变化,包括深度不连续、表面方向不连续、物质属性变化和场景照明变化。边缘检测特征是提取中的一个研究领域。图像边缘检测大幅度地减少了数据量,并且剔除了可以认为不相关的信息,保留了图像重要的结构属性。
1.2 边缘检测的方法
人类视觉系统认识目标的过程分为两步:首先,把图像边缘与背景分离出来;然后,到图像的细节,辨认出图像的轮廓。计算机视觉正是模仿人类视觉的过程。
因此,在检测物体边缘时先对轮廓点进行粗略检测,然后通过链接规则把原来检测到的轮廓点连接起来,同时检测和连接遗漏的边界点及去除虚假的边界点。图像的边缘是图像的重要特征,是计算机视觉、模式识别等的基础,因此边缘检测是图像处理中一个重要的环节。然而,边缘检测是图像处理中的一个难题,因为实际景物图像的边缘往往是各种类型的边缘及它们模糊化后结果的组合,且实际图像信号存在噪声。噪声和边缘都属于高频信号,很难用频带做取舍。
边缘是指图像周围像素灰度有阶跃变化或屋顶状变化的像素集合,存在于目标与背景、目标与目标、区域与区域、基元与基元之间。边缘具有方向和幅度两个特征,沿边缘走向,像素值变化比较平缓;垂直于边缘走向,像素值变化比较剧烈,可能呈现阶跃状,也可能呈现斜坡状。因此,边缘可以分为两种:
-
一种为阶跃性边缘,两边的像素灰度值有着明显的不同;
-
另一种为屋顶状边缘,位于灰度值从增加到减少的变化转折点。
对于阶跃性边缘,二阶方向导数在边缘处呈零交叉;对于屋顶状边缘,二阶方向导数在边缘处取极值。有许多方法可以用于边缘检测,绝大部分可以划分为两类:基于搜索的一类和基于零穿越的一类。
- 基于搜索:通过寻找图像一阶导数中的最大值来检测边界,然后利用计算结果估计边缘的局部方向,通常采用梯度的方向,并利用此方向找到局部梯度模的最大值,代表算法是Sobel算子和Scharr算子。
- 基于零穿越:通过寻找图像二阶导数零穿越来寻找边界,代表算法是Laplacian算子。
1.3 典型算子比较
|-----------|------------------------------------------------------------------------------------------------------------------------------------------------|
| 算子 | 优缺点 |
| Roberts | 对具有陡峭的低噪声的图像处理效果较好,但利用Roberts算子提取边缘的结果是边缘比较粗,因此边缘定位不是很准确 |
| Sobel | 对灰度渐变和噪声较多的图像处理效果比较好,Sobel算子对边缘定位比较准确 |
| Kirsch | 对灰度渐变和噪声较多的图像处理效果较好 |
| Prewitt | 对灰度渐变和噪声较多的图像处理效果较好 |
| Laplacian | 对图像中的阶跃性边缘点定位准确,对噪声非常敏感,丢失一部分边缘的方向信息,造成一些不连续的检测边缘 |
| LoG | LoG算子经常出现双边缘像素边界,而且该检测算法对噪声比较敏感,所以很少用LoG算子检测边缘,而是用来判断边缘像素是位于图像的明区还是暗区 |
| Canny | 此方法不容易受噪声的干扰,能够检测到真正的弱边缘。在edge函数中,最有效的边缘检测方法是Canny方法。该方法的优点在于使用两种不同的阈值分别检测强边缘和弱边缘,并且仅当弱边缘和强边缘相连时,才将弱边缘包含在输出图像中。因此,这种方法不容易被噪声"填充",更容易检测出真正的弱边缘。 |
2 opencv中叠加Sobel算子与Laplacian算子实现边缘检测
openCV 还可以将不同算法结合起来,达到更好的效果,本例使用Sobel算子 结合 Laplacian算子 实现图像边缘检测。
2.1 Sobel算子检测原理
对于不连续的函数,一阶导数可以写作:
或
所以有
假设要处理的图像为I,在两个方向求导:
- 水平变化: 将图像I 与奇数大小的模版进行卷积,结果为Gx 。比如,当模板大小为3时, Gx为:
- 垂直变化: 将图像I 与奇数大小的模版进行卷积,结果为Gy 。比如,当模板大小为3时,Gy为:
在图像的每一点,结合以上两个结果求出:
统计极大值所在的位置,就是图像的边缘。
注意:当内核大小为3时, 以上Sobel内核可能产生比较明显的误差, 为解决这一问题,我们使用Scharr函数,但该函数仅作用于大小为3的内核。该函数的运算与Sobel函数一样快,但结果却更加精确,其计算方法为:
scharr算子和sobel的原理一致,就是Gx和Gy参数的大小不同,也就是卷积核中各元素的权不同,其他都一样,scharr算子对于边界的梯度计算效果更精确。
Laplacian算子是一种图像处理中常用的边缘检测算子,它用于检测图像中的边缘和轮廓。该算子计算图像中每个像素点的二阶导数,从而突出图像中灰度值变化较大的区域,这些区域通常对应图像的边缘或者轮廓。
2.2 Laplacian算子检测原理
Laplacian是利用二阶导数来检测边缘 。 因为图像是 "2维", 我们需要在两个方向求导,如下式所示:
那不连续函数的二阶导数是:
那使用的卷积核是:
Laplacian算子的一种常用形式是二维离散Laplacian算子,表示为:
0 1 0
1 -4 1
0 1 0
该算子是一个3x3的矩阵,它对应的是图像中每个像素点的上、下、左、右四个方向的像素值以及像素点本身的像素值之间的差值。
应用Laplacian算子的过程如下:
-
将3x3的Laplacian算子依次对图像中的每个像素点进行卷积操作。
-
将卷积结果作为图像的每个像素点的新像素值。
-
对于卷积结果,如果像素值较大,则表示该像素点周围灰度值变化较大,可能是图像中的边缘或轮廓。
Laplacian 算子类似二阶 Sobel 导数,需要计算两个方向的梯度值。
Laplacian 算子类似二阶 Sobel 导数,需要计算两个方向的梯度值。例如,在图 9-25 中:
-
左图是 Laplacian 算子。
-
右图是一个简单图像,其中有 9 个像素点。
计算像素点 P5 的近似导数值,如下:
P5lap = (P2 + P4 + P6 + P8) - 4·P5
图 9-26 展示了像素点与周围点的一些实例,其中:
-
在左图中,像素点 P5 与周围像素点的值相差较小,得到的计算结果值较小,边缘不明显。
-
在中间的图中,像素点 P5 与周围像素点的值相差较大,得到的计算结果值较大,边缘较明显。
-
在右图中,像素点 P5 与周围像素点的值相差较大,得到的计算结果值较大,边缘较明显。
需要注意,在上述图像中,计算结果的值可能为正数,也可能为负数。所以,需要对计算结果取绝对值,以保证后续运算和显示都是正确的。
2.3 sobel函数原型
Sobel_x_or_y = cv2.Sobel(src, ddepth, dx, dy, dst, ksize, scale, delta, borderType)
参数:
-
src:传入的图像
-
ddepth: 图像的深度
-
dx和dy: 指求导的阶数,0表示这个方向上没有求导,取值为0、1。
-
ksize: 是Sobel算子的大小,即卷积核的大小,必须为奇数1、3、5、7,默认为3。
-
注意:如果ksize=-1,就演变成为3x3的Scharr算子。
-
scale:缩放导数的比例常数,默认情况为没有伸缩系数。
-
borderType:图像边界的模式,默认值为cv2.BORDER_DEFAULT。
Sobel函数求完导数后会有负值,还有会大于255的值。而原图像是uint8,即8位无符号数,所以Sobel建立的图像位数不够,会有截断。因此要使用16位有符号的数据类型,即cv2.CV_16S。处理完图像后,再使用cv2.convertScaleAbs()函数将其转回原来的uint8格式,否则图像无法显示。
Sobel算子是在两个方向计算的,最后还需要用cv2.addWeighted( )函数将其组合起来
Scale_abs = cv2.convertScaleAbs(x) # 格式转换函数
result = cv2.addWeighted(src1, alpha, src2, beta) # 图像混合
2.4 Laplacian函数原型
laplacian = cv2.Laplacian(src, ddepth[, dst[, ksize[, scale[, delta[, borderType]]]]])
参数:
-
Src
: 需要处理的图像, -
Ddepth
: 图像的深度,-1
表示采用的是原图像相同的深度,目标图像的深度必须大于等于原图像的深度; -
ksize
:算子的大小,即卷积核的大小,必须为1,3,5,7
。
2.5 叠加函数原型
out = cv2.add(src1, src2, dst=None, mask=None, dtype=None)
参数说明
-
src1、src2:需要相加的两副大小和通道数相等的图像或一副图像和一个标量(标量即单一的数值)
-
dst:可选参数,输出结果保存的变量,默认值为None。
- 如果为非None,输出图像保存到dst对应实参中,其大小和通道数与输入图像相同,图像的深度(即图像像素的位数)由dtype参数或输入图像确认
-
mask:图像掩膜,可选参数,为8位单通道的灰度图像,用于指定要更改的输出图像数组的元素,即输出图像像素只有mask对应位置元素不为0的部分才输出,否则该位置像素的所有通道分量都设置为0
-
dtype:可选参数,输出图像数组的深度,即图像单个像素值的位数(如RGB用三个字节表示,则为24位)。
返回值
- out:相加的结果图像
2.3 算子叠加代码
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
# 1 读取图像
img = cv.imread('../data/lena.jpeg')
img = cv.cvtColor(img, cv.COLOR_BGR2RGB)
# 2 计算Sobel卷积结果
x = cv.Sobel(img, cv.CV_16S, 1, 0)
y = cv.Sobel(img, cv.CV_16S, 0, 1)
# 3 将数据进行转换
Scale_absX = cv.convertScaleAbs(x) # convert 转换 scale 缩放
Scale_absY = cv.convertScaleAbs(y)
# 4 结果合成
result = cv.addWeighted(Scale_absX, 0.5, Scale_absY, 0.5, 0)
# 5 laplacian转换
result2 = cv.Laplacian(img, cv.CV_16S)
Scale_abs = cv.convertScaleAbs(result)
# 6 叠加图像
final = cv.add(result, Scale_abs)
# 7 图像显示
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(final, cmap=plt.cm.gray), plt.title('final')
plt.xticks([]), plt.yticks([])
plt.show()
运行代码显示: