OpenCV  4.10.0
开源计算机视觉
加载...
搜索...
找不到匹配项
Canny 边缘检测器

上一篇教程: 拉普拉斯算子
下一篇教程: 霍夫线变换

原作者Ana Huamán
兼容性OpenCV >= 3.0

目标

在本教程中,你将学习如何使用

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

理论

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

  • 低错误率:即仅对存在的边缘进行良好的检测。
  • 良好的定位:检测到的边缘像素与真实边缘像素之间的距离要最小化。
  • 最小响应:每个边缘只有一个检测器响应。

步骤

  1. 滤除任何噪声。为此,使用高斯滤波器。下面展示了可以使用的\(size = 5\) 高斯核的示例:

\[K = \dfrac{1}{159}\begin{bmatrix} 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. 应用一对卷积掩模(在 \(x\) 和 \(y\) 方向):

      \[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}\]

    2. 使用以下公式找到梯度强度和梯度方向:

      \[\begin{array}{l} G = \sqrt{ G_{x}^{2} + G_{y}^{2} } \\ \theta = \arctan(\dfrac{ G_{y} }{ G_{x} }) \end{array}\]

      将方向四舍五入到四个可能角度之一(即 0、45、90 或 135)
  2. 应用非最大值抑制。这将去除不被认为是边缘一部分的像素。因此,仅会留下细线(候选边缘)。
  3. 滞后:最后一步。canny 使用两个阈值(上限和下限):

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

    Canny 推荐 2:1 到 3:1 之间的上限:下限比率。

  4. 有关更多详细信息,你随时可以查阅你最喜欢的计算机视觉图书。

代码

  • 此程序的作用是什么?
    • 要求用户输入数值以设置我们 Canny 边缘检测器的较低阈值(通过轨迹栏)。
    • 应用 Canny 检测并生成一个蒙版(明亮的线条代表黑色背景上的边缘)。
    • 应用在原始图像上获得的蒙版并在窗口中显示它。
  • 说明(C++ 代码)

    1. 创建某些必需变量

      Mat src, src_gray;
      Mat dst, detected_edges;
      int lowThreshold = 0;
      const int max_lowThreshold = 100;
      const int ratio = 3;
      const int kernel_size = 3;
      const char* window_name = "Edge Map";

      注意以下内容

      1. 我们根据较低阈值:较高阈值的比率 3:1 建立一个比率(使用变量 ratio)。
      2. 我们设置内核大小为 \(3\)(对于 Canny 函数内部将执行的 Sobel 运算)。
      3. 我们为较低阈值设置最大值 \(100\)。
    2. 加载源图像
      CommandLineParser parser( argc, argv, "{@input | fruits.jpg | input image}" );
      src = imread( samples::findFile( parser.get<String>( "@input" ) ), IMREAD_COLOR ); // 加载图像
      if( src.empty() )
      {
      std::cout << "无法打开或找到图像!\n" << std::endl;
      std::cout << "用法: " << argv[0] << " <输入图像>" << std::endl;
      返回 -1;
      }
    3. 创建一个与 src 相同类型和大小的矩阵(作为 dst)
      dst. create( src.size(), src.type() );
    4. 将图像转换为灰度(使用函数 cv::cvtColor
      cvtColor( src, src_gray, COLOR_BGR2GRAY );
    5. 创建一个窗口以显示结果
      namedWindow( window_name, WINDOW_AUTOSIZE );
    6. 为用户创建一个轨迹栏以输入我们 Canny 检测器的较低阈值
      createTrackbar( "最低阈值:", window_name, &lowThreshold, max_lowThreshold, CannyThreshold );
      观察以下内容
      1. 由轨迹栏控制的变量是 lowThreshold,其限制为 max_lowThreshold(我们之前将其设置为 100)
      2. 每次轨迹栏注册一个动作时,都将调用回调函数 CannyThreshold。
    7. 让我们一步步检查 CannyThreshold 函数
      1. 首先,我们使用内核大小为 3 的滤波器模糊图像
        blur( src_gray, detected_edges, Size(3,3) );
      2. 其次,使用 OpenCV 函数 cv::Canny
        Canny( detected_edges, detected_edges, lowThreshold, lowThreshold*ratio, kernel_size );
        其中参数为
        • detected_edges:源图像,灰度
        • detected_edges:检测器输出(可以跟输入相同)
        • lowThreshold:用户通过移动跟踪条输入的值
        • highThreshold:在程序中设置为较低阈值的三倍(遵照 Canny 建议)
        • kernel_size:定义为 3(要在内部使用的 Sobel 核的大小)
    8. 用 0 填充 dst 图像(表示图像完全为黑色)。
      dst = Scalar::all(0);
    9. 最后,使用函数 cv::Mat::copyTo 仅映射为图像中识别为边缘的区域(在黑色背景上)。 cv::Mat::copyTosrc 图像复制到 dst。但是,它只复制在非零值位置处的像素。由于 Canny 检测器的输出是在黑色背景上的边缘轮廓,因此生成的 dst 在所有区域都将为黑色,但检测到的边缘除外。
      src.copyTo( dst, detected_edges);
    10. 展示结果
      imshow( window_name, dst );

    结果

    • 编译完上述代码后,可以提供图像的路径参数来运行它。例如,使用以下图像作为输入
    • 移动滑块,尝试不同的阈值,得到以下结果
    • 请注意图像如何叠加在边缘区域的黑色背景上。