在这一篇文章里我们将去学习在计算机视觉中边缘检测的知识,并且去使用OpenCV来实现Canny边缘检测算法。
一:什么是边缘检测
边缘检测是计算机视觉领域非常重要的一种图像特征提取方法,同样也是比较好用的特征提取方法。我们通过边缘检测就是为了找到图像中像素亮度发生剧烈变化像素点集合,通常这些集合表现出来往往是轮廓。如果我们可以将物体的轮廓表现出来,拓展一下思路,我们可以把物体的面积,形状等等特征表示出来。在现实情况中,我们得到的边缘往往会分为4大类情况:
①:深度的不连续(物体处在不同的物平面上);
②: 表面方向的不连续(如正方体的不同的两个面);
③:物体材料不同(这样会导致光的反射系数不同);
④:场景中光照不同(如被树萌投向的地面);
二:边缘检测的方法
边缘检测与上一篇文章中的图像金字塔其实有关联,因为边缘的提取本身就是一个滤波的过程,通过不同的算子来去提取不同的特征。每种算子都有他的特点,抛开现在神经网络方法不谈,传统的方法一般有三种:Sobel算子,Laplacian算子,Canny算子。
1:Sobel算子
在这篇文章中,比较详细的介绍了Sobel算子的原理。
CV学习笔记(十三):图像梯度 - 云时之间的文章 https://zhuanlan.zhihu.com/p/113397988
在目标检测中,Sobel算子对灰度渐变和噪声较多的图像处理效果较好,但是Sobel算子对边缘定位不是很准确(图像的边缘不止一个像素),因此精度要求不是很高时,Sobel比较常用。
2:Laplacian算子
Laplacian算子其实就是使用二阶微分的算子,更通俗来说就是梯度的散度。
二阶微分:
令
然后带入卷积模板:
由于Laplacian算子法对噪声比较敏感,所以很少用该算子检测边缘,而是用来判断边缘像素视为与图像的明区还是暗区。拉普拉斯高斯算子是一种二阶导数算子,将在边缘处产生一个陡峭的零交叉, Laplacian算子是各向同性的,能对任何走向的界线和线条进行锐化,无方向性。这是拉普拉斯算子区别于其他算法的最大优点。
3:Canny算子
Canny边缘检测算子是John F. Canny于 1986 年开发出来的一个多级边缘检测算法。
论文在这:https://dl.acm.org/doi/10.1109/TPAMI.1986.4767851
与上边的Laplacian算子类似,Canny算子实际上是一种一阶微分算子检测算法,因为他用的比较多,个人认为这是最好的边缘检测算子(滤波类),很多边缘检测算法都是在此基础上进行改进的,学习它有利于一通百通,所以文章将主要介绍Canny算子。
Canny最厉害的地方在于,他提出了一套对于边缘检测算法的标准:
1)以低的错误率检测边缘,也即意味着需要尽可能准确的捕获图像中尽可能多的边缘。
2) 检测到的边缘应精确定位在真实边缘的中心。
3) 图像中给定的边缘应只被标记一次,并且在可能的情况下,图像的噪声不应产生假的边缘。
概括来说,边缘要全,位置要准,抵抗噪声要强
Canny边缘检测主要分四步进行:
去噪声;计算梯度与方向角;非最大值抑制;滞后阈值化;
1:去噪声
在论文中使用的是高斯平滑滤波来去除噪声,在论文中作者说高斯滤波也是因为在众多噪声滤波器中,高斯表现最好(如果有兴趣可以试试均值滤波,中值滤波等等),在文中给出了一个大小为(2k+1)x(2k+1)的高斯滤波器核,用公式表示:
其中高斯卷积核的大小将影响Canny检测器的性能。尺寸越大,去噪能力越强,因此噪声越少,但图片越模糊,canny检测算法抗噪声能力越强,但模糊的副作用也会导致定位精度不高,一般情况下,推荐尺寸5*5,3*3尺寸
2:计算梯度与方向角
边缘的最重要的特征是灰度值剧烈变化,如果把灰度值看成二元函数值,那么灰度值的变化可以用二元函数的”导数“(或者称为梯度)来描述。由于图像是离散数据,导数可以用差分值来表示,差分在实际工程中就是灰度差,说人话就是两个像素的差值。一个像素点有8邻域,那么分上下左右斜对角,因此Canny算法使用四个算子来检测图像中的水平、垂直和对角边缘。算子是以图像卷积的形式来计算梯度,比如Roberts,Prewitt,Sobel等
3:非最大值抑制
图像梯度幅值矩阵中的元素值越大,说明图像中该点的梯度值越大,但这不能说明该点就是边缘(这仅仅是属于图像增强的过程)。在Canny算法中,非极大值抑制是进行边缘检测的重要步骤,通俗意义上是指寻找像素点局部最大值,将非极大值点所对应的灰度值置为0,这样可以剔除掉一大部分非边缘的点。
根据上图可知,要进行非极大值抑制,就首先要确定像素点C的灰度值在其8值邻域内是否为最大。图中蓝色的线条方向为C点的梯度方向,这样就可以确定其局部的最大值肯定分布在这条线上,也即出了C点外,梯度方向的交点dTmp1和dTmp2这两个点的值也可能会是局部最大值。因此,判断C点灰度与这两个点灰度大小即可判断C点是否为其邻域内的局部最大灰度点。如果经过判断,C点灰度值小于这两个点中的任一个,那就说明C点不是局部极大值,那么则可以排除C点为边缘。这就是非极大值抑制的工作原理。
在理解的过程中需要注意以下两点:
1:非极大值抑制是解决“当前的梯度值在梯度方向上是一个局部最大值吗?”这个问题的, 所以要把当前位置的梯度值与梯度方向上两侧的梯度值进行比较
2:梯度方向是垂直于边缘方向。
但实际上,我们只能得到C点邻域的8个点的值,而dTmp1和dTmp2并不在其中,要得到这两个值就需要对该两个点两端的已知灰度进行线性插值,也即根据图中的g1和g2对dTmp1进行插值,根据g3和g4对dTmp2进行插值,这要用到其梯度方向。
4:滞后阈值化
我们假设两类边缘:经过非极大值抑制之后的边缘点中,梯度值超过T1的称为强边缘,梯度值小于T1大于T2的称为弱边缘,梯度小于T2的不是边缘。可以肯定的是,强边缘必然是边缘点,因此必须将T1设置的足够高,以要求像素点的梯度值足够大(变化足够剧烈),而弱边缘可能是边缘,也可能是噪声,如何判断呢?当弱边缘的周围8邻域有强边缘点存在时,就将该弱边缘点变成强边缘点,以此来实现对强边缘的补充。
概述归纳一下:
①:如果该像素的梯度值小于TlTl,则该像素为非边缘像素;
②:如果该像素的梯度值大于ThTh,则该像素为边缘像素;
③:如果该像素的梯度值介于TlTl与ThTh之间,需要进一步检测该像素的3×33×3邻域内的8个点,如果这8个点内有一个或以上的点梯度超过了ThTh,则该像素为边缘像素,否则不是边缘像素。
三:Canny代码实现
代码如下:
我们使用OpenCV的Canny函数用于在图像中查找边缘,其函数原型有两种:
其函数原型为:
Canny(image, threshold1, threshold2[, edges[, apertureSize[, L2gradient]]])
image参数表示8位输入图像。
threshold1参数表示设置的低阈值。
threshold2参数表示设置的高阈值,一般设定为低阈值的3倍 (根据Canny算法的推荐)。
edges参数表示输出边缘图像,单通道8位图像。
apertureSize参数表示Sobel算子的大小。
L2gradient参数表示一个布尔值,如果为真,则使用更精确的L2范数进行计算(即两个方向的倒数的平方和再开方),否则使用L1范数(直接将两个方向导数的绝对值相加)。
②使用带自定义图像渐变的Canny算法在图像中查找边缘,
其函数原型为:
Canny(dx, dy, threshold1, threshold2[, edges[, L2gradient]]
dx参数表示输入图像的x导数(x导数满足16位,选择CV_16SC1或CV_16SC3)
dy参数表示输入图像的y导数(y导数满足16位,选择CV_16SC1或CV_16SC3)。
threshold1参数表示设置的低阈值。
threshold2参数表示设置的高阈值,一般设定为低阈值的3倍 (根据Canny算法的推荐)。
edges参数表示输出边缘图像,单通道8位图像。
L2gradient参数表示L2gradient参数表示一个布尔值,如果为真,则使用更精确的L2范数进行计算(即两个方向的倒数的平方和再开方),否则使用L1范数(直接将两个方向导数的绝对值相加)。
实现结果如下:
原图
轮廓提取: