边缘检测(edge detection)是最重要的图像处理技术之一,图像边缘检测大幅度地减少了数据量,并且剔除了可以认为不相关的信息,保留了图像重要的结构属性,为后续图像理解方法提供了基础。
边缘检测方法
从视觉上看,图像中的边缘处亮度较周围强,比如对一垂直方向的边缘,可以通过水平方向像素亮度的一阶微分(导数)来增强亮度变化。所以边缘检测可以通过计算图像水平和垂直两个方向的亮度变化梯度,从而得到亮度变化的幅度和方向。
设Ix和Iy分别是两个方向的亮度梯度向量,两个向量的模就是梯度幅度:
两个向量的夹角就是变化方向:
简单的亮度变化计算也可以使用差分方法,比如对于水平方向的某像素,将它左右相邻像素的差值作为亮度变化的估算,垂直方向类推,也可以估算出图像的边缘。由所有边缘增强像素组成的新图像,称为边缘增强图像。边缘增强图像可以通过群运算来得出。
群运算与算子
如果对原图像进行像素运算得出新图像,新图像的每一个像素点需要从原图像周围点基于某个算法计算出来的,这种运算叫群运算。群运算通常以图像和模板的卷积形式来表示,这里说的模板,就是决定周围像素如何综合计算的算法,也叫算子(operator)。上篇笔记介绍的高斯模糊介绍过高斯平均算子,算子以一个小矩阵的形式表示,每个元素标明了对应像素的权值或系数。下面这张示意图可以帮助理解:
图中的convolution mask指的就是模板或算子,它就像一个移动的遮罩层一样,遮罩在原图像上面,遮罩部分的每个像素,各自与模板对应位置的权值做乘积,最后全部加起来作为模板中心点对应的原图像位置的新像素值。
Roberts交叉算子
上述使用差分方法得出亮度变化梯度其实就是一阶微分的近似值。差分除了可以使用沿坐标轴方向的两个像素来计算,也可以使用对角像素来计算。Roberts交叉算子就属于这种算子,是最早的边缘检测算子之一。Roberts交叉算子可以准确定位边缘处,但这些只检测亮度增强位置的基本算子,对噪声敏感,容易将噪声误当成边缘。
Prewitt算子
在计算亮度变化之前,先对周围像素进行均值处理,这样对噪声有一定的抑制作用,但是,边缘处会产生模糊,边缘的定位不如Roberts算子。
Sobel算子
Sobel是应用比较多的算子,它也考虑均值处理来抑制噪声,但它的技巧是只在一条轴上进行均值处理,而在另一条轴上加大权值来计算亮度变化。这样得出来的效果比前两者更好。虽然使用Sobel算子得出的边缘不如Canny算子的准确,但它在实际应用中效率比Canny高,在很多实际应用的场合成为首选,尤其是对效率要求较高,而对细纹理不太关心的时候。
Prewitt和Sobel在计算导数方法上都存在一些缺陷:滤波器的尺度需要随着图像分辨率的变化而变化。为了在图像噪声方面更稳健,以及在任意尺度上计算导数,我们可以使用高斯导数滤波器。即我们上个笔记用到的高斯模糊滤波器:scipy.ndimage.filters.gaussian_filter 还可以用来计算一阶、二阶和三阶导数,通过order参数来指定,order可取以下值的组合:
0: 表示使用高斯核做卷积
1: 使用一阶高斯导数
2: 使用二阶导数
3: 使用三阶导数
比如order = (1,0)表示对输入数据的每一维,先用1阶导数做卷积,然后再与高斯核做卷积。
scipy.ndimage除了高斯滤波器,还提供了Prewitt和Sobel滤波器,下面我们将使用这三种方法来生成边缘增强图像:
from PIL import Image
import numpy as np
from scipy.ndimage import filters
import matplotlib.pyplot as plt
im = np.array(Image.open('Valve_original.png').convert('L'))
#prewitt
pwimx = np.zeros(im.shape)
filters.prewitt(im, 1, pwimx)
pwimy = np.zeros(im.shape)
filters.prewitt(im, 0, pwimy)
pwmagnitude = np.sqrt(pwimx ** 2 + pwimy ** 2) #计算两个向量的模,同:np.hypot(pwimx, pwimy)
#sobel
sbimx = np.zeros(im.shape)
filters.sobel(im, 1, sbimx)
sbimy = np.zeros(im.shape)
filters.sobel(im, 0, sbimy)
sbmagnitude = np.sqrt(sbimx ** 2 + sbimy ** 2)
#gaussian
gsimx = np.zeros(im.shape)
filters.gaussian_filter(input = im, sigma = 1, order = (0,1), output = gsimx)
gsimy = np.zeros(im.shape)
filters.gaussian_filter(input = im, sigma = 1, order = (1,0), output = gsimy)
gsmagnitude = np.sqrt(gsimx ** 2 + gsimy ** 2)
plt.gray()
index = 221
plt.subplot(index)
plt.imshow(im)
plt.title('original')
plt.axis('off')
plt.subplot(index + 1)
plt.imshow(pwmagnitude)
plt.title("prewitt")
plt.axis('off')
plt.subplot(index + 2)
plt.imshow(sbmagnitude)
plt.title("sobel")
plt.axis('off')
plt.subplot(index + 3)
plt.imshow(gsmagnitude)
plt.title("gaussian")
plt.axis('off')
plt.show()
效果图如下:
光从上面效果图很难看出Prewitt和Sobel的区别,也许他们在噪声大的图像上才能显出区别。
Canny算子
Canny是目前定义最严谨的边缘检测算法,用Cannel计算出的边缘很细小,连接性好,有一定的抗噪作用,同时具有精准的边缘定位。它的计算过程相比以上介绍的方法复杂:
先使用高斯滤波平滑图像,去除一些噪声,同时,一些极细小的边缘也会丢掉。
计算图像的亮度变化梯度
运用非极大值抑制(non-maximum suppression)(一种边缘细化技术)减小梯度变化幅度
使用双阈值检测边缘
边缘连接检测
下面这张图是从维基找到的,我们可以看到边缘效果比上面的要细得多:
在scipy.ndimage里面,没有提供Canny滤波器,但有一个图像处理库叫scikit-image(简称skimage)有提供Canny函数和示例。
以上介绍的是一阶微分算子,以下是二阶微分算子:
Laplacian算子
Laplacian算子是基于二阶微分的边缘检测,二阶微分相比一阶微分产生的边缘更细,因二阶微分处理对细节有较强的响应,所以应用Laplacian产生的边缘增强图像保留了原图像较多的背景细节。
所以,Laplacian算子也可用于图像锐化处理和斑点检测。
LoG(也叫 Marr-Hildreth )算子
这是一种把高斯滤波和Laplacian二阶导数结合起来的算子。值得一提的是上面介绍的Canny算法也是参考了LoG,在边缘检测之前,先对原图像应用高斯滤波来平滑图像。
其他
边缘检测算法主要是基于图像强度的一阶和二阶导数,但导数的计算对噪声很敏感,因此必须使用滤波器来改善与噪声有关的边缘检测器的性能。需要指出,大多数滤波器在降低噪声的同时也导致了细小边缘的丢失,目前也存在一种叫各向异性扩散的平滑技术,使得平滑不在边缘上作用,因此,使用时需要折中选择。
另外,计算出像素的亮度导数之后,下一步要做的就是给出一个阈值来确定哪里是边缘位置。阈值越低,能够检测出的边线越多,结果也就越容易受到图片噪声的影响。与此相反,一个高的阈值将会遗失细的或者短的边缘线段。所以,阈值的选取与实际的应用结合起来考虑会让检测结果更好。目前,也有根据背景自动计算阈值的算法。
小结
下一节介绍图像处理的形态学基本运算。
你还可以查看其它笔记。
参数资料
图像处理常用边缘检测算子总结
A Comparison of various Edge Detection Techniques used in Image Processing
Image Filtering & Edge Detection