OpenCV Canny边缘检测器
目标
在本教程中,您将学习如何:
- 使用OpenCV函数cv :: Canny来实现Canny Edge Detector。
理论
该Canny边缘检测是由约翰·F·坎尼在1986年也知道很多的开发最佳的检测,坎尼算法旨在满足三个主要标准:
- 低错误率:意味着只有现有边缘的良好检测。
- 良好的定位:检测到的边缘像素与实际边缘像素之间的距离必须最小化。
- 最小响应:每个边缘只有一个检测器响应。
步骤
- 滤除任何噪音。高斯滤波器用于此目的。可能使用的的高斯内核的示例如下所示:size=5
- 找到图像的强度梯度。为此,我们遵循类似于Sobel的程序:
应用一对卷积面罩 (在 x 和 y directions:
查找梯度强度和方向:
方向四舍五入为四个可能的角度之一(即0,45,90或135)
- 应用非最大抑制。这将删除不被认为是边缘的一部分的像素。因此,只有细线(候选边)将保留。
- 滞后:最后一步。Canny确实使用两个阈值(上限和下限):
- 如果像素梯度高于上限阈值,则像素被接受为边缘
- 如果像素梯度值低于较低阈值,则会被拒绝。
- 如果像素梯度在两个阈值之间,那么只有当它连接到高于上限阈值的像素时才被接受。
Canny推荐上限:2:1和3:1之间的较低比例。
- 有关更多详细信息,您可以随时咨询您最喜爱的Computer Vision书籍。
Code
- 这个程序是做什么的?
- 要求用户输入数值以设置我们的Canny Edge Detector的下限(通过Trackbar)。
- 应用Canny Detector并生成一个蒙版(亮线表示黑色背景上的边缘)。
- 应用在原始图像上获得的蒙版,并将其显示在窗口中。
- 教程代码如下所示。您也可以从这里下载
#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";
请注意以下事项:
- 我们建立一个较低的上限阈值3:1(与可变比率)的比率。
- 我们设置内核大小为(Sobel操作由Canny函数内部执行)。3
- 我们为的下限阈值设置最大值。100
- 加载源图像:
src = imread( argv[1], IMREAD_COLOR ); // Load an image
if( src.empty() )
{ return -1; }
- 创建一个相同类型和大小的src(为dst)的矩阵:
dst.create(src.size(),src.type());
- 将图像转换为灰度级(使用函数cv :: cvtColor):
cvtColor(src,src_gray,COLOR_BGR2GRAY);
- 创建一个窗口来显示结果:
namedWindow(window_name,WINDOW_AUTOSIZE);
- 创建一个跟踪栏,供用户输入Canny检测器的下限:
createTrackbar(“Min Threshold:”,window_name,&lowThreshold,max_lowThreshold,CannyThreshold);
观察以下内容:
- Trackbar要控制的变量是lowThreshold,最大值为max_lowThreshold(我们先前设置为100)
- 每次Trackbar注册一个动作时,将调用回调函数CannyThreshold。
- 让我们检查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:用户移动轨迹栏输入的值
- 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);
结果
- 在编译上面的代码之后,我们可以运行它作为参数作为图像的路径。例如,使用以下图像作为输入:
- 移动滑块,尝试不同的阈值,我们得到以下结果:
- 注意图像如何与边缘区域上的黑色背景叠加。