理论
Canny 边缘检测器 [ 48]由 John F. Canny 于 1986 年开发。Canny 算法也被许多人称为最佳检测器,它旨在满足三个主要标准:
- 低错误率:意味着仅对存在的边缘进行良好的检测。
- 良好的定位:检测到的边缘像素和真实边缘像素之间的距离必须最小化。
- 最小响应:每个边缘只有一个检测器响应。
步骤
-
滤除任何噪音。高斯滤波器就是用于此目的。以下是高斯核的示例s我是埃=5可能用到的如下所示:
- 找到图像的强度梯度。为此,我们遵循与 Sobel 类似的程序:
- 应用一对卷积掩模(十和是说明:
- 使用以下方法查找梯度强度和方向:
该方向四舍五入为四个可能的角度之一(即 0、45、90 或 135)
- 应用一对卷积掩模(十和是说明:
- 应用非最大抑制。这会删除不被视为边缘一部分的像素。因此,只剩下细线(候选边缘)。
-
滞后:最后一步。Canny 确实使用两个阈值(上限和下限):
- 如果像素梯度高于上限,则该像素被接受为边缘
- 如果像素梯度值低于下限,则被拒绝。
- 如果像素梯度介于两个阈值之间,那么只有它与高于上阈值的像素相连时才会被接受。
Canny 建议上限与下限之比在 2:1 至 3:1 之间。
- 欲了解更多详细信息,您可以随时查阅您最喜欢的计算机视觉书籍。
代码
C++
- 教程代码如下所示。您也可以从此处下载
#include "opencv2/imgproc.hpp" #include "opencv2/highgui.hpp" #include <iostream> using namespace cv; 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"; 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 argc, char** argv ) { 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; } 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; }
- 这个程序是做什么的?
- 要求用户输入一个数值来设置我们的Canny 边缘检测器的下限(通过跟踪条)。
- 应用Canny 检测器并生成蒙版(黑色背景上表示边缘的亮线)。
- 将获得的蒙版应用到原始图像上并将其显示在窗口中。
解释(C++代码)
-
创建一些需要的变量:
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。
- 加载源图像:
CommandLineParser解析器(argc,argv,“{@input | fruit.jpg | 输入图像}”);if ( 源文件为空() ){std::cout << "无法打开或找到图像!\n" << std::endl;std::cout << "用法:" << argv[0] << " <输入图像>" << std::endl;返回-1;}
- 创建与src(将成为dst)相同类型和大小的矩阵:
- 将图像转换为灰度(使用函数cv::cvtColor):
cvtColor(src,src_gray,COLOR_BGR2GRAY);
- 创建一个窗口来显示结果:
namedWindow(窗口名称,WINDOW_AUTOSIZE );
- 创建一个 Trackbar 供用户输入我们的 Canny 检测器的下限:
createTrackbar ( "最小阈值:" , window_name, &lowThreshold, max_lowThreshold, CannyThreshold );
- Trackbar 控制的变量是lowThreshold,其限制是max_lowThreshold(我们之前将其设置为 100)
- 每次Trackbar注册一个动作时,都会调用回调函数CannyThreshold 。
- 让我们一步一步检查一下CannyThreshold函数:
- 我们用零填充dst图像(意味着图像完全是黑色)。
dst = 标量::all(0);
- 最后,我们将使用函数cv::Mat::copyTo仅映射被标识为边缘(在黑色背景上)的图像区域。cv::Mat::copyTo将src图像复制到dst。但是,它只会复制具有非零值位置的像素。由于 Canny 检测器的输出是黑色背景上的边缘轮廓,因此生成的dst在除检测到的边缘之外的所有区域中都将是黑色。
src.copyTo ( dst,detected_edges);
- 我们展示结果:
imshow (窗口名称,目标);
结果
- 编译上述代码后,我们可以运行它,并将图像的路径作为参数。例如,使用以下图像作为输入:
- 移动滑块,尝试不同的阈值,我们得到以下结果:
- 注意图像是如何叠加到边缘区域的黑色背景的。