边缘检测一直在用,opencv对这一些个边缘检测算子也都做了实现和封装,而且相信经历这么多年,算法都已经优化到了接近极限。以前都是拿来就用,真正去窥探实现细节还是第一次。之所以选择看canny算子,还是因为个人认为canny算子拥有一些其他算子没有的优势,在其他算子的基础上剔去了边缘上的多余点,并且也检测和实现了边缘的封闭,所以最方便并适合运用于实际工程。
canny算子的工作流程其实就四步:
高斯滤波-梯度计算-非极大值抑制-阈值迟滞(双阈值检测)
①高斯滤波
印象里高斯滤波是会平滑图像细节弱化边缘的,按理来说滤波处理和边缘检测应该是冲突的操作,为什么canny算子在提取边缘之前会进行一次高斯滤波呢?个人认为算法是希望在提取边缘和去噪之间进行折衷,在不丢失图像的主要边缘信息的同时滤除部分高频噪声。(不过粗略的看了一下opencv里canny算子的实现,貌似是没有高斯滤波这一步的。也有资料说sobel算子本身有高斯滤波的特性了,opencv里canny算子的梯度计算是用的是sobel算子内核做图像卷积)网上找的一些大牛们自己实现的canny算子一般都会有高斯滤波这一步。
高斯滤波的实现细节想必都很熟悉了,就不再赘述。 高斯滤波模板例:
②梯度计算
canny一般用的其他算子的核来做梯度幅值和方向的计算, 比如Roberts:
或者Sobel:
梯度幅值的计算公式:
G(x,y) = sqrt(Sx^2+Sy^2)
梯度方向计算公式:
R(x,y) = acrtan(Sy/Sx)
梯度的幅值和方向是极大值抑制会用到的数据。
③非极大值抑制
做完前两步后其实我们其实已经提取到了图像的大致轮廓了,但是将幅值反向投影回灰度空间后可以看到,大多数边缘呈两边暗中间亮的屋脊状,为了取得单个像素宽度的边缘,canny算子对图像的幅值矩阵进行了非极大值抑制。
非极大值一直的工作原理是什么?引用likezhaobin大神的一段解释:
要进行非极大值抑制,就首先要确定像素点C的灰度值在其8值邻域内是否为最大。下图中蓝色的线条方向为C点的梯度方向,这样就可以确定其局部的最大值肯定分布在这条线上,也即出了C点外,梯度方向的交点dTmp1和dTmp2这两个点的值也可能会是局部最大值。因此,判断C点灰度与这两个点灰度大小即可判断C点是否为其邻域内的局部最大灰度点。如果经过判断,C点灰度值小于这两个点中的任一个,那就说明C点不是局部极大值,那么则可以排除C点为边缘。这就是非极大值抑制的工作原理。
但是像素点的分布是离散而非线性的,dTmp1和dTmp2两个交点位置大多数情况下并不实际存在像素点,所以canny算子在做非极大值抑制时比对的像素点是距离dTmp1和dTmp2最近的两个像素点,也就是下文所说的g1,g2和g3,g4。
非极大值抑制步骤:
1.将梯度方向R(x,y)以就近原则归类到四个角度类中(0-45,45-90,90-135,135-180)。
2.获取该点所处的8值领域内其他8个点中距离梯度向量最近的两个点对(g1,g2)、(g3,g4)。
还是以95-135的梯度向量(上图)为例,如上图,蓝线为c点的梯度方向,该方向引出的正反两条射线分别穿过8值领域的两条边(红线),则在这两条边上距离射线(蓝线)最近的两个点即为g1,g2及g3,g4。
3.将c点处的梯度幅值分别与g1,g2,g3,g4点做对比,如果小于其中的任何一个,则置c点处幅值为0,否则认为它是潜在边缘,保留其幅值。
处理完后即可得到一张包含所有潜在边缘的梯度幅值-灰度映射图。
④阈值迟滞
阈值迟滞是翻译过来的叫法(threshold Hysteresis),通俗点可以叫双阈值法检测,目的是去除第三步后结果中的一些噪声边缘及一些假边缘,算法和很简单:
0.取一高一低两个阈值。
1.假如该像素点处灰度值大于高阈值,保留作为边缘。
2.假如该像素点处灰度值小于低阈值,舍弃。
3.假如该像素点处灰度值大于低阈值小于高阈值,当其与一个大于高阈值的像素点相邻时,保留作为边缘,否则舍弃。
OK,以上就是canny算子做边缘检测的流程啦!