阅读(4234) (8)

OpenCV Canny边缘检测器

2017-09-09 13:56:22 更新

目标

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

  • 使用OpenCV函数cv :: Canny来实现Canny Edge Detector。

理论

该Canny边缘检测是由约翰·F·坎尼在1986年也知道很多的开发最佳的检测,坎尼算法旨在满足三个主要标准:

  • 低错误率:意味着只有现有边缘的良好检测。
  • 良好的定位:检测到的边缘像素与实际边缘像素之间的距离必须最小化。
  • 最小响应:每个边缘只有一个检测器响应。

步骤

  • 滤除任何噪音。高斯滤波器用于此目的。可能使用的的高斯内核的示例如下所示:size=5

Canny边缘检测器

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

应用一对卷积面罩 (在 x 和 y directions:

Canny边缘检测器

查找梯度强度和方向:

Canny边缘检测器

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

  • 应用非最大抑制。这将删除不被认为是边缘的一部分的像素。因此,只有细线(候选边)将保留。
  • 滞后:最后一步。Canny确实使用两个阈值(上限和下限):
  1. 如果像素梯度高于上限阈值,则像素被接受为边缘
  2. 如果像素梯度值低于较低阈值,则会被拒绝。
  3. 如果像素梯度在两个阈值之间,那么只有当它连接到高于上限阈值的像素时才被接受。

Canny推荐上限:2:1和3:1之间的较低比例。

  • 有关更多详细信息,您可以随时咨询您最喜爱的Computer Vision书籍。

Code

  • 这个程序是做什么的?
  1. 要求用户输入数值以设置我们的Canny Edge Detector的下限(通过Trackbar)。
  2. 应用Canny Detector并生成一个蒙版(亮线表示黑色背景上的边缘)。
  3. 应用在原始图像上获得的蒙版,并将其显示在窗口中。
  • 教程代码如下所示。您也可以从这里下载

#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
using namespace cv;
Mat src, src_gray;
Mat dst, detected_edges;
int edgeThresh = 1;
int lowThreshold;
int const max_lowThreshold = 100;
int ratio = 3;
int kernel_size = 3;
const char* window_name = "Edge Map";
static void CannyThreshold(int, void*)
{
    blur( src_gray, detected_edges, Size(3,3) );
    Canny( detected_edges, detected_edges, lowThreshold, lowThreshold*ratio, kernel_size );
    dst = Scalar::all(0);
    src.copyTo( dst, detected_edges);
    imshow( window_name, dst );
}
int main( int, char** argv )
{
  src = imread( argv[1], IMREAD_COLOR ); // Load an image
  if( src.empty() )
    { return -1; }
  dst.create( src.size(), src.type() );
  cvtColor( src, src_gray, COLOR_BGR2GRAY );
  namedWindow( window_name, WINDOW_AUTOSIZE );
  createTrackbar( "Min Threshold:", window_name, &lowThreshold, max_lowThreshold, CannyThreshold );
  CannyThreshold(0, 0);
  waitKey(0);
  return 0;
}

说明

  • 创建一些必需的变量:
Mat src, src_gray;
Mat dst, detected_edges;
int edgeThresh = 1;
int lowThreshold;
int const max_lowThreshold = 100;
int ratio = 3;
int kernel_size = 3;
const char* window_name = "Edge Map";

请注意以下事项:

  1. 我们建立一个较低的上限阈值3:1(与可变比率)的比率。
  2. 我们设置内核大小为(Sobel操作由Canny函数内部执行)。3
  3. 我们为的下限阈值设置最大值。100
  • 加载源图像:
  src = imread( argv[1], IMREAD_COLOR ); // Load an image
  if( src.empty() )
    { return -1; }
  • 创建一个相同类型和大小的src(为dst)的矩阵:
  dst.create(src.size(),src.type());
  cvtColor(src,src_gray,COLOR_BGR2GRAY);
  • 创建一个窗口来显示结果:
  namedWindow(window_name,WINDOW_AUTOSIZE);
  • 创建一个跟踪栏,供用户输入Canny检测器的下限:
  createTrackbar(“Min Threshold:”,window_name,&lowThreshold,max_lowThreshold,CannyThreshold);

观察以下内容:

  1. Trackbar要控制的变量是lowThreshold,最大值为max_lowThreshold(我们先前设置为100)
  2. 每次Trackbar注册一个动作时,将调用回调函数CannyThreshold。
  • 让我们检查CannyThreshold函数,一步一步:
  1. 首先,我们用内核大小为3的过滤器模糊图像:    
  1.     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内核的大小)
  • 我们用零填充dst图像(意味着图像是完全黑色的)。
    dst = Scalar :: all(0);
  • 最后,我们将使用函数cv :: Mat :: copyTo仅映射被识别为边缘的图像的区域(在黑色背景上)。cv :: Mat :: copy将src映像复制到dst上。但是,它只会复制它们具有非零值的位置中的像素。由于Canny检测器的输出是黑色背景上的边缘轮廓,因此所得到的dst在所有区域都将为黑色,但检测到的边缘为黑色。
  •     src.copyTo(dst,detected_edges);
    
  • 我们显示结果:
    imshow(window_name,dst);

结果

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

Canny边缘检测器

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

Canny边缘检测器

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