Skip to content

Canny 边缘检测器

本教程中你将学到如何:

  • 使用 OpenCV 函数 cv::Canny 实现 Canny 边缘检测器。

Canny 边缘检测器 [Canny86] 由 John F. Canny 于 1986 年开发。它也被许多人称为最优检测器,Canny 算法旨在满足三个主要准则:

  • 低错误率: 意味着仅能很好地检测真实存在的边缘。
  • 良好的定位: 检测到的边缘像素与真实边缘像素之间的距离必须最小化。
  • 最小响应: 每条边缘只有一个检测响应。
  1. 滤除任何噪声。为此使用高斯滤波器。一个可能用到的 size=5size = 5 的高斯核示例如下:

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}$$
  1. 求图像的强度梯度。为此,我们遵循一个类似于 Sobel 的过程:
    1. 应用一对卷积掩膜(分别在 xx 与 yy 方向):
$$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 度)

  1. 应用非极大值抑制(Non-maximum suppression)。这会移除那些不被认为是边缘一部分的像素。因此,只会留下细线(候选边缘)。

  2. 滞后阈值处理(Hysteresis):最后一步。Canny 确实使用了两个阈值(上阈值与下阈值):

    1. 若某像素的梯度高于上阈值,则该像素被接受为边缘
    2. 若某像素的梯度值低于下阈值,则被拒绝。
    3. 若像素梯度介于两个阈值之间,则仅当它与某个高于上阈值的像素相连时才会被接受。

    Canny 建议上:下阈值的比率介于 2:1 与 3:1 之间。

  3. 更多细节,你总是可以查阅你偏爱的计算机视觉书籍。

  • 本教程的代码如下所示。你也可以从 这里 下载。 代码见下方示例

  • 本教程的代码如下所示。你也可以从 这里 下载。 代码见下方示例

  • 本教程的代码如下所示。你也可以从 这里 下载。 代码见下方示例

  • 这个程序做什么?

    • 要求用户输入一个数值,用以设置我们 Canny 边缘检测器的下阈值(通过 Trackbar 实现)。
    • 应用 Canny 检测器并生成一个掩膜(以亮线表示黑色背景上的边缘)。
    • 将所得掩膜应用到原图上,并在一个窗口中显示。
  1. 创建一些所需变量:

    注意以下几点:

    1. 我们将下阈值:上阈值的比率设为 3:1(通过变量 ratio)。
    2. 我们把核大小设为 33(供 Canny 函数内部执行的 Sobel 运算使用)。
    3. 我们把下阈值的最大值设为 100100。
  2. 载入源图像:

  3. 创建一个与 src 类型与大小相同的矩阵(作为 dst):

  4. 将图像转换为灰度图(使用函数 cv::cvtColor):

  5. 创建一个窗口以显示结果:

  6. 创建一个 Trackbar,供用户输入我们 Canny 检测器的下阈值: 注意以下几点:

    1. 由 Trackbar 控制的变量是 lowThreshold,上限为 max_lowThreshold(我们先前将其设为 100)
    2. 每次 Trackbar 注册一次动作,回调函数 CannyThreshold 就会被调用。
  7. 我们逐步查看 CannyThreshold 函数:

    1. 首先,我们用核大小为 3 的滤波器对图像做模糊:

Canny_Detector_Tutorial_Original_Image.jpg Canny_Detector_Tutorial_Result.jpg

1. 其次,我们应用 OpenCV 函数 cv::Canny:

其中参数为:

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

  • 移动滑块、尝试不同的阈值,我们得到如下结果:

  • 注意图像是如何叠加到边缘区域的黑色背景之上的。