边缘检测算子Canny原理概述并利用OpenCV的库函数Canny对图像进行边缘检测

时间:2024-11-11 08:21:00

图像边缘检测的概念和大概原理可以参考我的另一篇博文,链接如下:
/wenhao_ir/article/details/51743382

本篇博文介绍边缘检测算子Canny,并利用OpenCV的库函数Canny()对图像进行边缘检测。

Canny算子是John Canny在1986年发表的论文中首次提出的边缘检测算子,该算子检测性能比较好,应用广泛。Canny 算法被推崇为当今最优的边缘检测的算法。

Canny算子进行边缘检测的原理和步骤如下

⑴消除噪声。边缘检测的算法主要是基于图像强度的一阶和二阶微分操作,但导数通常对噪声很敏感,边缘检测算法常常需要根据图像源的数据进行预处理操作,因此采用滤波器来改善与噪声有关的边缘检测性能,比如在进行边缘检测前,可以对原始数据先作高斯滤波处理。如果不做滤波平滑处理,不仅是噪声,原图片中不是边缘但是灰度变化频率较高的部分也容易被认为是边缘,这样会导致边缘检测性能的下降。

⑵找到图片的强度梯度。在对图像进行平滑处理后,Canny边缘算法的第二步是找到图片的强度梯度。尽管“强度梯度”这个名词可能听起来很复杂,其实很简单,它是指边缘的方向。一条边实际上可以指向任何方向,但该算法只查看四个方向以简化事情。方向是水平、垂直和两个对角线方向。在数学中,我们将其写为 [0 ° , 90 ° , 45 ° , 135 ° ]。
OpenCV中的函数Canny()使用3x3 Sobel内核来确定水平方向的导数,然后将其转置以确定垂直方向的导数,这些导数可用于在所需的四个方向上找到我们的边缘。

⑶非极大值抑制。非极大值抑制的目的是剔除第⑵部中计算出来的结果中的大部分非边缘点。其原理是通过像素的八邻域来判断要不要将这个像素置为边缘点,如果不置为边缘点,那么就置为背景色。判断的方法如下:

①判断范围是像素的八邻域,所以是局部最优判断法;

②判断的标准是如果某个像素在其八邻域内,既是最大值,梯度值也最大,那么可判断该点为像素边缘点,否则就不是。具体判断的方法如下:

首先,梯度值的判断是很好判断的,用边缘检测微分算子得到的结果直接比较就可以了,但是最大值的判断可不是只比较其旁边的八个点哦,还要比较另外两个点,详情如下:

如果已经判断出上图中的C点比其旁边的8个点的像素值都大,那么接下来判断上图中dTmp1和dTmp2的值是否也小于C点的值,那么dTmp1和dTmp2的值怎么求呢?上图中蓝色的线条方向为C点的梯度方向,由于是梯度方向,所以可以确定其局部的最大值肯定分布在这条线上,也即除了C点外,梯度方向的交点dTmp1和dTmp2这两个点的值也可能会是局部最大值。因此,判断C点灰度与这两个点灰度大小即可判断C点是否为其邻域内的局部最大灰度点。如果经过判断,C点灰度值小于这两个点中的任一个,那就说明C点不是局部极大值,那么则可以排除C点为边缘。

⑷用滞后阈值算法求解图像边缘。上一步对边缘检测算子的结果进行了非极大值抑制,接下来我们用二值化的方法来求解图像边缘。单阈值处理边缘效果不好,所以Cannny算法中采用滞后阈值法求解。滞后阈值法需要设置一个高阈值和一个低阈值,解后按如下法则进行:

第一,如果某一像素位置的梯度幅值超过高阈值,则像素被保留为边缘像素;

第二,如果某一像素位置的梯度幅值小于低阈值,则像素被排除;

第三,如果某一像素位置的幅值在两个阈值之间,该像素仅仅在连接到一个高于高阈值的像素时被保留。

在以上的法则中,推荐的高阈值与低阈值比在2:1到3:1之间。

通过消除噪声、计算梯度幅度与方向、非极大值抑制及用滞后阈值算法求解图像边缘四个步骤就可实现Canny边缘检测。

OpenCV提供了函数Canny()实现Canny算子,其原型如下:

void Canny( InputArray image, 
			OutputArray edges, 
			double threshold1, 
			double threshold2, 
			int apertureSize=3, 
			bool L2gradient=false 
		  )

Image---输入图像,要求是数据深度为8bit的图像,官方文档中并没有说输入图像必须为单通道,但很多资料上都说要求是单通道的图像。

edges---输出图像,单通道8bit的图像,尺寸与输入图像相同。

 threshold1---滞后阈值算法的低阈值;

 threshold2---滞后阈值算法的高阈值;

apertureSize---Sobel算子的大小,为什么是Sobel算子,请看上面对Canny算子描述的第二点。

L2gradient---是否使用L2范数来计算图像梯度幅值。

以下是使用函数Canny()实现图像边缘检测的示例代码:

代码中用

//博主微信/QQ 2487872782
//有问题可以联系博主交流
//有图像处理需求也可联系博主
//图像处理技术交流QQ群 271891601

//OpenCV版本:3.0
//VS版本:2012

#include <opencv2/core/>
#include <opencv2/highgui/>
#include<opencv2/imgcodecs/>
#include <opencv2/imgproc/>
 
using namespace cv;
 
int main()
{
        //载入原始图    
        Mat src = imread("F:/material/images/P0042-building_edge_detection.jpg");  
        Mat src_1 = imread("F:/material/images/P0042-building_edge_detection.jpg", 0);
        Mat gray = imread("F:/material/images/P0042-building_edge_detection.jpg", 0);
 
        imshow("原图", src);
 
        //----------------------------------------------------------------------------------  
        //  一、最简单的canny用法,拿到原图的灰度图后直接用。  
        //----------------------------------------------------------------------------------  
        Canny(src_1, src_1, 100, 150, 3);
        imshow("简单用法的Canny边缘检测结果", src_1);
 
        //----------------------------------------------------------------------------------  
        //  二、高级的canny用法,转成灰度图,降噪,用canny,最后将得到的边缘作为掩码,拷贝原图到效果图上,得到彩色的边缘图  
        //----------------------------------------------------------------------------------  
        Mat dst, edge;
 
        // 【1】创建与src同类型和大小的矩阵(dst)  
        ((), ());
 
        // 【2】先用使用 3x3内核来降噪  
        blur(gray, edge, Size(3, 3));
 
        // 【3】运行Canny算子  
        Canny(edge, edge, 100, 150, 3);
 
        imshow("高级用法的Canny边缘检测结果", edge);
 
        //【4】将g_dstImage内的所有元素设置为0   
        dst = Scalar::all(0);
 
        //【5】使用Canny算子输出的边缘图g_cannyDetectedEdges作为掩码,来将原图g_srcImage拷到目标图g_dstImage中  
        (dst, edge);
 
        //【6】显示效果图   
        imshow("高级用法的合成图", dst);
 
        waitKey(0);
 
        return 0;
}

运行结果如下图所示: