理论
Canny算法旨在满足三个主要标准:
- 低错误率:意味着只检测存在的边缘。
- 良好的定位:必须最小化检测到的边缘像素和真实边缘像素之间的距离。
- 最小响应:每个边缘只有一个检测器响应。
步骤
- 过滤掉任何噪音。 高斯滤波器用于此目的。 可能使用的大小为5的高斯内核的示例如下所示:
- 找到图像的强度梯度。 为此,我们遵循类似Sobel的程序:
- 应用一对卷积掩模(在x和y方向):
- 找到梯度强度和方向:
- 应用非最大抑制。 这将删除不被视为边缘一部分的像素。 因此,仅保留细线(候选边缘)。
- 迟滞:最后一步。 Canny确实使用了两个阈值(上限和下限):
- 如果像素梯度高于上阈值,则该像素被接受为边缘
- 如果像素梯度值低于下阈值,则拒绝它
- 如果像素梯度在两个阈值之间,则仅当它连接到高于上阈值的像素时才接受它。
代码
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <stdlib.h>
#include <stdio.h>
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] );
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;
}
解释
- 创建一些所需的变量:
- 我们建立一个较低的比率:上限阈值为3:1(可变比例)
- 我们将内核大小设置为3(用于由Canny函数在内部执行的Sobel操作)
- 我们为下限阈值设置了最大值100。
- 加载源图像:
- 创建一个src相同类型和大小的矩阵(将是dst)
- 将图像转换为灰度(使用函数cv :: cvtColor:)
- 创建一个窗口以显示结果
- 创建一个滑块栏,供用户输入Canny探测器的下限:
- 滑动条控制的变量是低阈值,具有最大低阈值限制(我们之前设置为100)
- 每次滑动条注册一个动作时,都会调用回调函数CannyThreshold。
- 让我们一步一步检查CannyThreshold函数:
- 首先,我们使用内核大小为3的过滤器模糊图像:
- 其次,我们应用OpenCV函数cv :: Canny:
- 我们用零填充dst图像(意味着图像是完全黑色的)。
- 最后,我们将使用函数cv :: Mat :: copyTo仅映射图像中标识为边的区域(在黑色背景上)。
- 我们显示结果:
效果
在编译上面的代码之后,我们可以运行它作为参数给出图像的路径。 例如,使用以下图像作为输入:
移动滑块,尝试不同的阈值,我们得到以下结果: