⚠️由于自己的拖延症,3.4.3翻到一半,OpenCV发布了4.0.0了正式版,所以接下来是按照4.0.0翻译的。
⚠️除了版本之外,其他还是照旧,Canny Edge Detection,附原文。
目标
在这一节,我们将学到:
- 坎尼边缘检测的概念
- OpenCV对此的函数:cv.Canny()
理论
坎尼边缘检测是一个很受欢迎的边缘检测算法。它是被 John F. Canny 发明的:
- 它是一个多阶段的算法,我们将会把每个阶段都过一遍。
-
降低噪声
由于边缘检测容易受到图像噪声的影响,第一步是用5x5高斯滤波器去除图像中的噪声。我们在前几章已经看到了这一点。
-
找到图像的强烈梯度
(通过上一步高斯)平滑后的图像,在通过索贝尔内核在水平和垂直方向滤波,来获取两个方向的一阶导数(Gx)和(Gy)。从这两个图像我们可以找到每个像素边缘的梯度和方向,如下:
梯度方向总是垂直于边缘。圆角为代表垂直、水平和两个对角线方向的四个角之一。
-
非极大值抑制
在获取到了梯度的幅值和方向之后,对图像进行全扫描,去除可能不构成边缘的不需要的像素。要达到这个目的,遍历每个像素,如果像素是梯度方向上其邻域内的局部最大值,则该像素点通过检测。查看下面的图示:
点A处于边缘之上(在垂直方向上的边缘)。梯度方向垂直于边缘,点B和点C在梯度方向上,因此点A得通过点B和点C在水平方向上来检测。如果如此(A点确实是局部最大值得话),那么就进入下个环节,否则就抑制A点(把A点设置为0(这样A点就和经过前几步处理的背景色一样了))。
简单来说,你这一步得到的结果就是一张具有“细边”的二元图像。
-
滞后阈值法
这一个步骤,决定了(之前计算出来通过检测的点组成的)所有的边缘,哪些是真正的边缘,哪些不是。要达到这个目的,我们需要最大、最小两个阈值。任何强度梯度大于最大阈值的边被认为是边缘,小于最小阈值的边一定是非边边缘,所以丢弃掉。位于这两个阈值之间的边界,根据它们的连通性分为边缘和非边缘。如果它们被连接到“确定边缘”像素,它们被认为是边缘的一部分。否则,它们也会被丢弃。见下图:
边缘A在最大阈值之上,因为我们认为它是确定边缘。尽管边缘C在最大阈值之下(同时在最小阈值之上),但它与A边缘是连通的,所以C也被认为是有效的边缘我们也得到了完整的弧线。而B边虽然在最小阈值之上,处于和C边相同的区域,但却并未与(像A边这样的)确定边缘连通,所以它就被丢弃了。因此,我们必须相应地选择最大阈值和最小阈值来得到正确的结果,这是非常重要的。
加入边缘是长线段的话,这一个步骤还会消除一些小像素的噪声。
因此我们最终得到的是图像中的强边界。
OpenCV里的坎尼边缘检测
OpenCV 把上面的所有步骤放进了同一个方法中,cv.Canny()。我们来看看怎么使用它。第一个参数是输入图像。第二和第三个参数分别是我们的最大和最小阈值。第三个参数是孔径大小,它是用于寻找图像梯度的索贝尔内核的大小,默认情况下是3。最后 L2gradient 参数指定了求梯度大小的方程。如果为True,则使用之前讲的公式,算出来更准确。否则使用这个函数:边梯度(G) = |Gx| + |Gy|。默认情况下,这个值是False。
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('messi5.jpg',0)
edges = cv.Canny(img,100,200)
plt.subplot(121),plt.imshow(img,cmap = 'gray')
plt.title('Original Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(edges,cmap = 'gray')
plt.title('Edge Image'), plt.xticks([]), plt.yticks([])
plt.show()
看看以下结果:
额外资源
- 维基百科上的坎尼边缘检测
- 坎尼边缘检测教程 比尔.格林2002年写的
Exercises
- 写一个小应用来做坎尼边缘检测,用两个拖动条来控制最大最小阈值,这样你就可以理解这两个阈值对结果造成的影响。