• 【OpenCV】-算子(Sobel、Canny、Laplacian)学习


    开头一下:
    本篇博客主要介绍边缘检测所涉及的三大算子,分别是Sobel算子、Canny算子、Laplacian算子)。上篇博客python版CV也介绍了这三个算子的用法。



    首先介绍一下边缘检测的步骤:

    (1)滤波

    边缘检测的算法主要是基于图像强度的一阶和二阶导数,但导数通常对噪声很敏感,因此必须采用滤波器来改善与噪声有关的边缘检测器的性能。常见的滤波方法主要有高斯滤波,即采用离散化的高斯函数产生一组归一化的高斯核,然后基于高斯核函数对图像灰度矩阵的每一点进行加权求和。

    (2)增强

    增强边缘的基础是确定图像各点邻域强度的变化值。增强算法可以将图像灰度点邻域强度值有显著变化的点凸显出来。在具体编程通过梯度幅值来确定

    (3)检测

    经过增强的图像,往往邻域中有很多点的梯度值比较大,而在特定的应用中,这些点并不是要找的边缘点,所以应该采用某种方法来对这些点进行取舍。实际工程中,常用的方法是通过阈值化方法来检测


    1、sobel算子

    1.1 sobel算子的基本概念

    Sobel算子是一个主要用于边缘检测的离散微分算子。它结合了高斯平滑核微分求导,用来计算图像灰度函数的近视似梯度。在图像的任何一点使用此算子,都将会产生对于的梯度矢量或其法矢量

    1.2 sobel算子的计算过程

    (1)分别在x和y两个方向求导

    • 水平变化:将I与一个奇数大小的内核Gx进行卷积。比如,当内核大小为3时,Gx的计算结果:
      G x = I 3 − I 1 + 2 ∗ I 6 − 2 ∗ I 4 + I 9 − I 7 Gx=I3-I1+2*I6-2*I4+I9-I7 Gx=I3I1+2I62I4+I9I7
      在这里插入图片描述

    • 垂直变化:将I与一个奇数大小的内核进行卷积。比如,当内核大小为3时,Gx的计算结果:
      G y = I 3 − 2 ∗ I 2 − I 1 + I 9 + 2 ∗ I 8 + I 7 Gy=I3-2*I2-I1+I9+2*I8+I7 Gy=I32I2I1+I9+2I8+I7
      在这里插入图片描述

    (2)在图像的每一点,结合以上两个今儿个求出近似梯度:
    G = s q r t ( G x 2 + G y 2 ) G=sqrt(Gx^2+Gy^2) G=sqrt(Gx2+Gy2)
    有时也可以用下面公式代替:
    G = ∣ G x ∣ + ∣ G y ∣ G=|Gx|+|Gy| G=Gx+Gy

    1.3 使用Sobel算子:Sobel()函数

    void Sobel(InputArray src,OutputArray dst,int ddepth,int dx,int dy,
               int kszie=3,double scale=1,double delta=0,
               int borderType=BORDER_DEFAULT);
    
    • 1
    • 2
    • 3
    • 第一个参数:输入图像,填Mat类型即可

    • 第二个参数:目标图像,函数的输出参数,需要和源图片有一样的尺寸和类型

    • 第三个参数:输出图像的深度,支持如下组合:

      • 若src.depth()=CV_8U,取ddepth=-1/CV_16S/CV_32F/CV_64F
      • 若src.depth()=CV_16U/CV_16S,取ddepth=-1/CV_32F/CV_64F
      • 若src.depth()=CV_32F,取ddepth=-1/CV_32F/CV_64F
      • 若src.depth()=CV_64F,取ddepth=-1/CV_64F
    • 第四个参数:x方向上的差分阶数

    • 第五个参数:y方向上的差分阶数

    • 第六个参数:核大小,取1,3,5,7

    • 第七个参数:double类型的scale,计算导数值时可选的缩放因子,默认值是1,表示默认情况下是没有应用缩放的。

    • 第八个参数:double类型的delta,表示存入目标图

    • 第九个参数:边界模式。有默认值:BORDER_DEFAULT

    补充说明:

    (1)当内核大小为3时,Sobel内核可能产生比较明显的误差。所以OpenCV提供了Scharr函数,但该函数仅作用于大小为3的内核。比Sobel函数的结果更加精确

    在这里插入图片描述

    (2)Sobel算子结合了高斯平滑核分化,因此结果会具有更多的抗噪性:

    计算图像X方向的导数,取【xorder=1,yorder=0,ksize=3】,对应内核:

    在这里插入图片描述

    计算图像Y方向的导数,取【xorder=0,yorder=1,ksize=3】,对应内核:

    在这里插入图片描述

    1.4 示例程序

    #include
    #include
    #include
    #include
    using namespace std;
    using namespace cv;
    int main()
    {
    	//创建grad_x和grad_y矩阵
    	Mat grad_x, grad_y;
    	Mat abs_grad_x, abs_grad_y, dst;
    	//载入原始图
    	Mat src = imread("E:\\Pec\\fushiyuan.jpg");
    	imshow("【原始图】", src);
    
    	//求X方向的梯度
    	Sobel(src, grad_x, CV_16S, 1, 0, 3, 1, 1, BORDER_DEFAULT);
    	//白到黑是正数,黑到白是负数,所有的负数都会截断成0,所以要取绝对值
    	convertScaleAbs(grad_x, abs_grad_x);
    	imshow("【效果图】X方向Sobel", abs_grad_x);
    	//求Y方向的梯度
    	Sobel(src, grad_y, CV_16S, 0, 1, 3, 1, 1, BORDER_DEFAULT);
    	//白到黑是正数,黑到白是负数,所有的负数都会截断成0,所以要取绝对值
    	convertScaleAbs(grad_y, abs_grad_y);
    	imshow("【效果图】Y方向Sobel", abs_grad_y);
    
    	//合并梯度
    	addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0, dst);
    	imshow("【效果图】正题方向", dst);
    	waitKey(0);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    在这里插入图片描述

    2、canny算子

    2.1 Canny边缘检测的步骤

    (1)【第一步】图像灰度化

    canny算法处理的图像为灰度图,因此如果摄像机获取的是彩色图像,那首先就得进行灰度化。对一幅彩色图进行灰度化,就是根据图像各个通道的采样值进行加权平均。

    G a r y = ( R + G + B ) / 3 Gary=(R+G+B)/3 Gary=(R+G+B)/3
    (2)【第二步】消除噪声

    一般情况下,使用高斯平滑滤波器卷积降噪,以下显示一个size=5的高斯内核示例:

    在这里插入图片描述

    (3)【第三步】计算梯度幅值和方向

    此处:按照Sobel滤波器的步骤来操作,索贝尔算子(Sobel
    operator)是图像处理的算子之一,主要用作边缘检测。技术上是离散型差分算子,用来运算图像亮度函数的梯度之近视值。

    • 运用一对卷积阵列(分别作用于x和y方向)

      在这里插入图片描述

    • 使用下列公式计算梯度赋值和方向,梯度方向取的角度一般是0°,45°,90°,135°

    在这里插入图片描述

    (3)【第三步】非极大值抑制

    这一步排除边缘像素,仅仅保留了一些细线条(候选边缘)。在求出的幅值图像中,可能存在多个较大幅值临近的情况,但真正的边缘点只有一个,针对这样的情况进行非极大值抑制,找出局部最大值,从而可以剔除大部分非边缘点。

    (4)【第四步】滞后阈值

    这是最后一步,Canny使用滞后阈值,滞后阈值需要两个阈值(高阈值和低阈值):

    • 若某一像素位置的幅值超过高阈值,该像素被保留为边缘像素
    • 若某一像素位置的幅值小于低阈值,该像素排除
    • 若某一像素位置的幅值在两个阈值之间,该像素仅仅在连接到一个高于高阈值的像素时被保留

    2.2 Canny边缘检测:Canny()函数

    void Canny(InputArray image,OutputArray edges,double threshold1,double threshold2,int apertureSize=3,
    bool L2gradient=false)
    
    • 1
    • 2
    • 第一个参数:输入图像,即源图像,填Mat类的对象即可,且需为单通道8为图像
    • 第二个参数:输出的边缘图,需要和源图片有一样的尺寸和类型
    • 第三个参数:第一个滞后阈值
    • 第四个参数:第二个滞后阈值
    • 第五个参数:int类型的 apertureSize,表示应用Sobel算子的孔径大小,其默认值3
    • 第六个参数:bool 类型的L2gradient,一个计算图像梯度幅值的标识,有默认值false

    注意:函数阈值1和阈值2两者较小的值用于边缘连接,而较大的值用来控制强边缘的初始段,推荐的高低阈值比在2 :1到3 :1之间

    2.3 示例程序

    #include
    #include
    #include
    #include
    using namespace std;
    using namespace cv;
    int main()
    {
    	//载入原始图
    	Mat src = imread("E:\\Pec\\灭霸.jpg");
    	Mat src1 = src.clone();
    	imshow("【原始图】", src);
    	Mat dst,dst1, edge, gray;
    	//创建与src同样类型和大小的矩阵
    	dst.create(src.size(), src1.type());
    	//将原图转换为灰度图像
    	cvtColor(src1, gray, COLOR_BGR2GRAY);
    	//先使用3x3内核来降噪
    	blur(gray, edge, Size(3, 3));
    	//运行Canny算子
    	Canny(gray, edge, 3, 9, 3);
    	//将dst内的所有元素设置为0
    	dst = Scalar::all(0);
    	dst1 = Scalar::all(0);
    	gray.copyTo(dst, edge);
    	src1.copyTo(dst1, edge);
    	imshow("【彩色检测图】", dst1);
    	imshow("【灰度检测图】", dst);
    	waitKey(0);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    3、Laplacian算子

    3.1 Laplacian算子简介

    Laplacian算子是n维欧几里得空间中的一个二阶微分算子,定义为梯度grad的散度div。因此如果f是二阶可微的实函数,则f的拉普拉斯算子定义如下:

    (1)f是拉普拉斯算子,也是笛卡坐标系xi中的所有非混合二阶偏导数求和

    (2)作为一个二阶微分算子,拉普拉斯算子把C函数映射到C函数。根据图像处理的原理可知,二阶导数可以用来进行检测边缘。因为图像是“二维”,需要在两个方向进行求导。

    在这里插入图片描述

    3.2 拉普拉斯变换:Laplacian()函数

    void Laplacian(InputArray src,OutputArray dst,int ddepth,int ksize=1,double scale=1,double delta=0,
    intborderType=BORDER_DEFAULT);
    
    • 1
    • 2
    • 第一个参数:输入图像,填Mat类型即可

    • 第二个参数:目标图像,函数的输出参数,需要和源图片有一样的尺寸和类型

    • 第三个参数:目标图像的深度

      • 若src.depth()=CV_8U,取ddepth=-1/CV_16S/CV_32F/CV_64F
      • 若src.depth()=CV_16U/CV_16S,取ddepth=-1/CV_32F/CV_64F
      • 若src.depth()=CV_32F,取ddepth=-1/CV_32F/CV_64F
      • 若src.depth()=CV_64F,取ddepth=-1/CV_64F
    • 第四个参数:int类型的ksize,用于计算二阶导数的滤波器的孔径尺寸,大小必须为正奇数,默认值为1

    • 第五个参数:double类型的scale,计算拉普拉斯值的时候可选的比例因子,默认值1

    • 第六个参数:doubel类型的delta,表示在结果存入目标图,默认值0

    • 第七个参数:边界模式,默认值为BORDER_DEFAULT

    注意:Laplacian()函数主要利用sobel算子的运算。

    3.3 示例程序

    #include
    #include
    #include
    #include
    using namespace std;
    using namespace cv;
    int main()
    {
    	
    	Mat src_gray,dst, abs_dst;
    	//载入原始图
    	Mat src = imread("E:\\Pec\\fushi.jpg");
    	imshow("【原始图】", src);
    
    	//使用高斯滤波消除噪声
    	GaussianBlur(src, src, Size(3, 3), 0, 0, BORDER_DEFAULT);
    	//转为灰度图
    	cvtColor(src, src_gray, COLOR_RGB2GRAY);
    	//使用拉普拉斯函数
    	Laplacian(src_gray, dst, CV_16S, 3, 1, 0, BORDER_DEFAULT);
    	//计算绝对值,并将结果转换成8位
    	convertScaleAbs(dst, abs_dst);
    	imshow("【效果图】", abs_dst);
    	waitKey(0);
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    在这里插入图片描述

  • 相关阅读:
    【java学习—九】模板方法(TemplateMethod)设计模式(4)
    购买发票自动化软件(或者文档管理系统)需要注意的问题
    【产品新体验】CSDN开发云·云IDE使用教程
    「数据结构详解·二」二叉树的初步
    透过Redis源码探究字符串的实现
    C++基础入门
    docker:已启动容器修改添加端口映射
    纵向分栏
    APP授权
    SpringMVC的常用注解有哪些?
  • 原文地址:https://blog.csdn.net/qq_44859533/article/details/126019753