【opencv450 Image Processing】Canny Edge Detector

Goal

在本教程中,您将学习如何:

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

Theory

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

低错误率Low error rate:意味着对仅存在的边缘的良好检测。

良好的定位Good localization:必须最小化检测到的边缘像素和真实边缘像素之间的距离。

最小响应Minimal response:每个边缘只有一个检测器响应。

Steps

1. 过滤掉任何噪音高斯滤波器用于此目的。 可能使用的大小为 5 的高斯核的示例如下所示:

2.找到图像的强度梯度。 为此,我们遵循类似于 Sobel 的程序:

a. 应用一对卷积掩码(在 x 和 y 方向:

 2. 找到梯度强度和方向:

 方向四舍五入到四个可能的角度之一(即 0、45、90 或 135)

3. Non-maximum应用非最大抑制。 这会删除不被视为边缘一部分的像素。 因此,只保留细线(候选边缘)。

4. Hysteresis滞后:最后一步。 Canny 确实使用了两个阈值(上限和下限):

       如果像素梯度高于上限阈值,则该像素被接受为边缘

如果像素梯度值低于下阈值,则将其拒绝

如果像素梯度在两个阈值之间,那么只有当它连接到高于上限阈值的像素时才会被接受

Canny 推荐了 2:1 和 3:1 之间的上下比例。

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

Code

教程代码如下所示。 你也可以从这里opencv/CannyDetector_Demo.cpp at 4.x · opencv/opencv · GitHub 下载

/**
 * @file CannyDetector_Demo.cpp
 * @brief Sample code showing how to detect edges using the Canny Detector
 * @author OpenCV team
 */

#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>

using namespace cv;

//![variables]
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;//sobel内核尺寸
const char* window_name = "Edge Map";
//![variables]

/**
 * @function CannyThreshold
 * @brief Trackbar callback - Canny thresholds input with a ratio 1:3
 * Trackbar 回调 - Canny 阈值输入,比率为 1:3
 */
static void CannyThreshold(int, void*)
{
    //![reduce_noise]
    /// 使用 3x3 内核降低噪音
    blur( src_gray, detected_edges, Size(3,3) );
    //![reduce_noise]

    //![canny]
    /// Canny 边缘检测 
    Canny( detected_edges, detected_edges, lowThreshold, lowThreshold*ratio, kernel_size );
    //![canny]

    /// 使用 Canny 的输出作为掩码,我们显示我们的结果Using Canny's output as a mask, we display our result
    //![fill]
    dst = Scalar::all(0);
    //![fill]

    //![copyto]
    src.copyTo( dst, detected_edges);
    //![copyto]

    //![display]
    imshow( window_name, dst );
    //![display]
}


/**
 * @function main
 */
int main( int argc, char** argv )
{
  //![load]
  CommandLineParser parser( argc, argv, "{@input | fruits.jpg | input image}" );
  src = imread( samples::findFile( parser.get<String>( "@input" ) ), IMREAD_COLOR ); // 加载图像

  if( src.empty() )
  {
    std::cout << "Could not open or find the image!\n" << std::endl;
    std::cout << "Usage: " << argv[0] << " <Input image>" << std::endl;
    return -1;
  }
  //![load]

  //![create_mat]
  /// 创建与 src 相同类型和大小的矩阵(用于 dst)
  dst.create( src.size(), src.type() );//创建目标矩阵
  //![create_mat]

  //![convert_to_gray]
  cvtColor( src, src_gray, COLOR_BGR2GRAY );//灰度图
  //![convert_to_gray]

  //![create_window]
  namedWindow( window_name, WINDOW_AUTOSIZE );//创建窗口
  //![create_window]

  //![create_trackbar]
  /// 为用户创建一个 Trackbar 以输入阈值
  createTrackbar( "Min Threshold:", window_name, &lowThreshold, max_lowThreshold, CannyThreshold );
  //![create_trackbar]

  /// 显示图像
  CannyThreshold(0, 0);

  /// 等到用户通过按键退出程序 Wait until user exit program by pressing a key
  waitKey(0);

  return 0;
}

这个程序有什么作用?

要求用户输入数值以设置我们的 Canny 边缘检测器的下限阈值(通过 Trackbar 的方式)。

应用 Canny Detector 并生成一个mask(代表黑色背景上的边缘的亮线)。

在原始图像上应用获得的mask并在窗口中显示。

Explanation (C++ code)

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";

请注意以下事项:

我们建立了一个 3:1 的下限:上限阈值的比率(具有可变比率)。

我们将内核大小设置为 3(Sobel 操作由 Canny 函数在内部执行)。

我们为下限阈值设置最大值 100

2. 加载源图像:

CommandLineParser parser( argc, argv, "{@input | fruits.jpg | input image}" );
src = imread( samples::findFile( parser.get<String>( "@input" ) ), IMREAD_COLOR ); // Load an image
if( src.empty() )
{
std::cout << "Could not open or find the image!\n" << std::endl;
std::cout << "Usage: " << argv[0] << " <Input image>" << std::endl;
return -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.为用户创建一个 Trackbar 以输入我们的 Canny 检测器的下限阈值

createTrackbar( "Min Threshold:", window_name, &lowThreshold, max_lowThreshold, CannyThreshold );

请注意以下事项:

Trackbar 控制的变量是 lowThreshold,限制为 max_lowThreshold(我们之前设置为 100)

每次Trackbar注册一个动作,都会调用回调函数CannyThreshold。

7. 让我们一步一步检查 CannyThreshold 函数

首先,我们使用内核大小为 3 的过滤器对图像进行模糊处理

blur( src_gray, detected_edges, Size(3,3) );

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

Canny( detected_edges, detected_edges, lowThreshold, lowThreshold*ratio, kernel_size );

参数是:

detected_edges:源图像,灰度

detected_edges:检测器的输出(可以和输入一样)

lowThreshold:用户移动Trackbar输入的值

highThreshold:在程序中设置为下限阈值的三倍(按照 Canny 的推荐)

kernel_size:我们定义为3(内部使用的Sobel内核的大小)

8. 我们用填充 dst 图像(意味着图像完全是黑色的)

dst = Scalar::all(0);

9. 最后,我们将使用函数 cv::Mat::copyTo 仅映射图像中被识别为边缘的区域(在黑色背景上)。 cv::Mat::copy 将 src 图像复制到 dst。 但是,它只会复制具有非零值位置的像素。 由于 Canny 检测器的输出是黑色背景上的边缘轮廓,因此除了检测到的边缘之外,所得的 dst 在所有区域中都是黑色的

src.copyTo( dst, detected_edges);

10.我们展示我们的结果

imshow( window_name, dst );

Result

编译上面的代码后,我们可以运行它,将图像的路径作为参数。 例如,使用以下图像作为输入:

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

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

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值