Canny 边缘检测器
本教程中你将学到如何:
- 使用 OpenCV 函数 cv::Canny 实现 Canny 边缘检测器。
Canny 边缘检测器 [Canny86] 由 John F. Canny 于 1986 年开发。它也被许多人称为最优检测器,Canny 算法旨在满足三个主要准则:
- 低错误率: 意味着仅能很好地检测真实存在的边缘。
- 良好的定位: 检测到的边缘像素与真实边缘像素之间的距离必须最小化。
- 最小响应: 每条边缘只有一个检测响应。
-
滤除任何噪声。为此使用高斯滤波器。一个可能用到的 的高斯核示例如下:
2 & 4 & 5 & 4 & 2 \\ 4 & 9 & 12 & 9 & 4 \\ 5 & 12 & 15 & 12 & 5 \\ 4 & 9 & 12 & 9 & 4 \\ 2 & 4 & 5 & 4 & 2 \end{bmatrix}$$- 求图像的强度梯度。为此,我们遵循一个类似于 Sobel 的过程:
- 应用一对卷积掩膜(分别在 与 方向):
$$G_{x} = \begin{bmatrix}-1 & 0 & +1 \\-2 & 0 & +2 \\-1 & 0 & +1\end{bmatrix}$$$$G_{y} = \begin{bmatrix}-1 & -2 & -1 \\0 & 0 & 0 \\+1 & +2 & +1\end{bmatrix}$$1. 用下式求梯度的幅值与方向:$$\begin{array}{l}G = \sqrt{ G_{x}^{2} + G_{y}^{2} } \\\theta = \arctan(\dfrac{ G_{y} }{ G_{x} })\end{array}$$方向被归入四种可能角度之一(即 0、45、90 或 135 度)
-
应用非极大值抑制(Non-maximum suppression)。这会移除那些不被认为是边缘一部分的像素。因此,只会留下细线(候选边缘)。
-
滞后阈值处理(Hysteresis):最后一步。Canny 确实使用了两个阈值(上阈值与下阈值):
- 若某像素的梯度高于上阈值,则该像素被接受为边缘
- 若某像素的梯度值低于下阈值,则被拒绝。
- 若像素梯度介于两个阈值之间,则仅当它与某个高于上阈值的像素相连时才会被接受。
Canny 建议上:下阈值的比率介于 2:1 与 3:1 之间。
-
更多细节,你总是可以查阅你偏爱的计算机视觉书籍。
-
本教程的代码如下所示。你也可以从 这里 下载。 代码见下方示例
-
本教程的代码如下所示。你也可以从 这里 下载。 代码见下方示例
-
本教程的代码如下所示。你也可以从 这里 下载。 代码见下方示例
-
这个程序做什么?
- 要求用户输入一个数值,用以设置我们 Canny 边缘检测器的下阈值(通过 Trackbar 实现)。
- 应用 Canny 检测器并生成一个掩膜(以亮线表示黑色背景上的边缘)。
- 将所得掩膜应用到原图上,并在一个窗口中显示。
说明(C++ 代码)
Section titled “说明(C++ 代码)”-
创建一些所需变量:
注意以下几点:
- 我们将下阈值:上阈值的比率设为 3:1(通过变量 ratio)。
- 我们把核大小设为 (供 Canny 函数内部执行的 Sobel 运算使用)。
- 我们把下阈值的最大值设为 。
-
载入源图像:
-
创建一个与 src 类型与大小相同的矩阵(作为 dst):
-
将图像转换为灰度图(使用函数 cv::cvtColor):
-
创建一个窗口以显示结果:
-
创建一个 Trackbar,供用户输入我们 Canny 检测器的下阈值: 注意以下几点:
- 由 Trackbar 控制的变量是 lowThreshold,上限为 max_lowThreshold(我们先前将其设为 100)
- 每次 Trackbar 注册一次动作,回调函数 CannyThreshold 就会被调用。
-
我们逐步查看 CannyThreshold 函数:
- 首先,我们用核大小为 3 的滤波器对图像做模糊:

1. 其次,我们应用 OpenCV 函数 cv::Canny:其中参数为:
- detected_edges:源图像,灰度图
- detected_edges:检测器输出(可与输入相同)
- lowThreshold:用户移动 Trackbar 输入的值
- highThreshold:在程序中设为下阈值的三倍(遵循 Canny 的建议)
- kernel_size:我们将其定义为 3(内部所用的 Sobel 核大小)
- 我们用零填充 dst 图像(即图像完全为黑)。
- 最后,我们将使用函数 cv::Mat::copyTo,仅映射图像中被识别为边缘的区域(位于黑色背景之上)。 cv::Mat::copyTo 将 src 图像拷贝到 dst。然而,它只会拷贝那些位置上具有非零值的像素。由于 Canny 检测器的输出是黑色背景上的边缘轮廓,所得 dst 除检测到的边缘外,所有区域都将为黑。
- 我们显示结果:
-
编译上面的代码后,我们可以把图像路径作为参数来运行它。例如,用下图作为输入:
-
移动滑块、尝试不同的阈值,我们得到如下结果:
-
注意图像是如何叠加到边缘区域的黑色背景之上的。