• OpenCV图像处理学习十六,解析图像卷积运算原理并应用Sobel算子,Scharr算子和拉普拉斯算子(Laplance)的应用


    一.卷积核的概念

    卷积核,通常也叫算子。用一个设定数值模板去处理一张输入图片,进行卷积运算。目的是使目标与目标之间的差距变得更大。卷积在数字图像处理中最常见的应用为锐化和边缘提取。

    边缘提取:
    当前景目标像素值与周边背景目标的像素值有较大差异时,可以通过卷积核对原图矩阵中的这个位置进行卷积运算,得出的值和该像素点原来的灰度值会产生显著的差异。变化的值超过我们预设的范围后,就可以将图像进行阈值处理,将图像的差异最大化,可以得到了一黑色为背景,白色线条作为边缘或形状的边缘提取效果图。

    锐化算子:
    通过卷积运算,可以增大矩阵每一个元素与周边元素的反差,起到锐化作用。图像锐化是补偿图像的轮廓,增强图像的边缘及灰度跳变的部分,使图像变得清晰,分为空间域处理和频域处理两类。图像锐化是为了突出图像上地物的边缘、轮廓,或某些线性目标要素的特征,也被称为边缘增强。

    对图像求它的一阶导数
    delta = f(x) – f(x-1)delta越大,说明像素在X方向变化越大,边缘信号越强

    =========================================================================

    二.图像卷积的运算原理

    卷积核作为一个设定数值模板矩阵,输入的待处理的图像作为原型矩阵,二者进行图像卷积运算,主要是使模板矩阵的中心像素点(称之为锚点)覆盖在待计算原型矩阵元素上面,中心像素点逐一对齐原型矩阵上的像素点(边缘像素无法对齐锚点,也就无法进行卷积计算,只能通过边缘像素处理后才能进行卷积),然后计算元素值与被覆盖的卷积核中的值的乘积和。将这个和赋值给当前锚点,这就是卷积的运算过程。 ​​​

    --------------------------------------------------------------------------------------------------------------------------------

    第一类:Sobel算子

    Sobel算子的概念
    Sobel算子是离散微分算子(discrete differentiation operator),用来计算图像灰度的近似梯度。sobel算子由两个3X3的卷积核构成,分别用于计算中心像素邻域的灰度加权差。分为垂直方向和水平方向的索伯滤波器Gx 和Gy。Soble算子功能集合高斯平滑和微分求导,又被称为一阶微分算子,求导算子,在水平和垂直两个方向上求导,得到图像X方法与Y方向梯度图像,如下图。
    参数说明:

    1. #参数说明:
    2. I 代表输入图像产生的图像矩阵,
    3. Gx及Gy 分别代表经横向及纵向边缘检测的图像灰度值
    4. G 表示图像的每一个像素的横向及纵向灰度值,有两种计算方式,通常Gx和Gy开方运算比较复杂

    -------------------------------------------------------------------------------------------------------------------

    Sobel算子的卷积计算过程
    为计算图像x方向上的梯度图像,我们需要一个卷积核kernel(Gx)和3*3的像素图片矩阵P。

    1. #卷积核kernel和图像 P
    2. Mat kernel = (Mat_<char>(3, 3) << -10+1,-20+2,-10+1);
    3. Mat P = (Mat_<char>(3, 3) <<P1,P2,P3,P4,P5,P6,P7,P8,P9);

    以卷积核模板的中心像素为锚点,将卷积核与图像上像素值一一对应进行像素遍历,卷积核上的数字相当于加权系数。利用如下公式即可计算出卷积核中心的x方向梯度。

    卷积计算过程为:P5 = (P3-P1)+2*(P6-P4)+(P9-P7)

    同样的原理,在Gy方向上的梯度计算也可以求取
    卷积计算过程为:P5 = (P7-P1)+2*(P8-P2)+(P9-P3),这样就得到了垂直方向和水平方向的图像梯度Gx 和Gy,就可以求出总的图像梯度,通常取|Gx| + |Gy|的和作为总的图像梯度。

    ---------------------------------------------------------------------------------------------------------------------------------

    Sobel算子API函数接口:

    1. #函数API接口:
    2. cv::Sobel (
    3. InputArray Src // 输入图像
    4. OutputArray dst// 输出图像,大小与输入图像一致
    5. int depth // 输出图像深度.
    6. Int dx. // X方向,几阶导数
    7. int dy // Y方向,几阶导数.
    8. int ksize, SOBEL算子kernel大小,必须是1357
    9. double scale = 1
    10. double delta = 0
    11. int borderType = BORDER_DEFAULT
    12. )

    参数说明:

    第一个参数,InputArray 类型的src,为输入图像,填Mat类型即可。
    第二个参数,OutputArray类型的dst,即目标图像,函数的输出参数,要求与源图片有一样的尺寸和类型。
    第三个参数,int类型的ddepth,输出图像的深度,目标图像的深度必须大于原图像的深度,支持如下src.depth()和ddepth的组合:

    1. 若src.depth() = CV_8U, 取ddepth =-1/CV_16S/CV_32F/CV_64F
    2. 若src.depth() = CV_16U/CV_16S, 取ddepth =-1/CV_32F/CV_64F
    3. 若src.depth() = CV_32F, 取ddepth =-1/CV_32F/CV_64F
    4. 若src.depth() = CV_64F, 取ddepth = -1/CV_64F

    第四个参数,int类型的dx,x 方向上的差分阶数。
    第五个参数,int类型的dy,y方向上的差分阶数。
    第六个参数,int类型ksize,有默认值3,表示Sobel核的大小; 必须取1,3,5或7奇数的核。
    第七个参数,double类型的scale,计算导数值时可选的缩放因子,默认值是1,表示默认情况下是没有应用缩放的。我们可以在文档中查阅getDerivKernels的相关介绍,来得到这个参数的更多信息。
    第八个参数,double类型的delta,表示在结果存入目标图(第二个参数dst)之前可选的delta值,有默认值0。
    第九个参数, int类型的borderType,边界模式,默认值为BORDER_DEFAULT。

    1. enum BorderTypes {
    2.     BORDER_CONSTANT    = 0, //使用指定像素值来填充边缘
    3.     BORDER_REPLICATE   = 1, //用已知的边缘像素值来填充边缘
    4.     BORDER_REFLECT     = 2, //使用已知的边缘像素值来反转填充边缘
    5.     BORDER_WRAP        = 3, //用另外一边的像素来补偿填充
    6.     BORDER_REFLECT_101 = 4, //使用已知的边缘像素值来反转填充边缘
    7.     BORDER_TRANSPARENT = 5, //使用黑色进行填充,本质上就是填充0
    8.  
    9.     BORDER_REFLECT101  = BORDER_REFLECT_101,
    10.     BORDER_DEFAULT     = BORDER_REFLECT_101,
    11.     BORDER_ISOLATED    = 16 //

    --------------------------------------------------------------------------------------------------------------------------------

    注意知识点:

    1.图像在经过处理后,需要用cv::函数将其转回原来的uint8形式,否则将无法显示图像,而只是一副灰色的窗口。 

    convertScaleAbs()函数原型:

    1. void cv::convertScaleAbs(
    2. 2 cv::InputArray src, // 输入数组
    3. 3 cv::OutputArray dst, // 输出数组
    4. 4 double alpha = 1.0, // 乘数因子
    5. 5 double beta = 0.0 // 偏移量
    6. 6 );
    7. //结果返回uint8类型的图片

    功能:实现将原图片转换为uint8类型

    2.由于Sobel算子是在X轴,Y轴两个方向计算的,还需要用cv2.addWeighted()函数将其组合起来 

    函数原型:

    1. CV_EXPORTS_W void addWeighted(InputArray src1, double alpha, InputArray src2,
    2. double beta, double gamma, OutputArray dst, int dtype=-1);
    3. src1: 第一幅输入图像
    4. alpha: 线性混合时第一幅图像的权重
    5. src2: 第二幅输入图像
    6. beta: 第二幅输入图像的权重
    7. dst: 图像线性混合后的目标图像
    8. gamma: 添加到每一个线性叠加总和的gamma值
    9. dtype: 目标图像深度,当两幅图像深度相同时可以将dtype置为-1,这样目标图像的深度将与输入图像相同
    10. 其中beta - (1.0 - alpha);
    11. 对于每个像素点其计算公式如下:
    12. dst = a×src1+b×src2+r

    功能:实现以不同的权重将两幅图片叠加,对于不同的权重,叠加后的图像会有不同的透明度

    =========================================================================

    第二类:Scharr算子

    Scharr算子比Sobel算子的值更大,是在Sorbel算子基础上改进的,因此对于灰度变化更为敏感,会得到较强的边缘强度,但是也会损失一些细节。

    1. cv::Scharr (
    2. InputArray Src // 输入图像
    3. OutputArray dst// 输出图像,大小与输入图像一致
    4. int depth // 输出图像深度.
    5. Int dx. // X方向,几阶导数
    6. int dy // Y方向,几阶导数.
    7. double scale = 1
    8. double delta = 0
    9. int borderType = BORDER_DEFAULT
    10. )
    11. 第一个参数,InputArray 类型的src,为输入图像,填Mat类型即可。
    12. 第二个参数,OutputArray类型的dst,即目标图像,函数的输出参数,要求与源图片有一样的尺寸和类型。
    13. 第三个参数,int类型的ddepth,输出图像的深度,目标图像的深度必须大于原图像的深度,支持如下src.depth()和ddepth的组合:
    14. BORDER_CONSTANT    = 0, //使用指定像素值来填充边缘
    15.     BORDER_REPLICATE   = 1, //用已知的边缘像素值来填充边缘
    16.     BORDER_REFLECT     = 2, //使用已知的边缘像素值来反转填充边缘
    17.     BORDER_WRAP        = 3, //用另外一边的像素来补偿填充
    18.     BORDER_REFLECT_101 = 4, //使用已知的边缘像素值来反转填充边缘
    19.     BORDER_TRANSPARENT = 5, //使用黑色进行填充,本质上就是填充0
    20. 第四个参数,int类型的dx,x 方向上的差分阶数。
    21. 第五个参数,int类型的dy,y方向上的差分阶数。
    22. 第六个参数,int类型ksize,有默认值3,表示Sobel核的大小; 必须取1357奇数的核。
    23. 第七个参数,double类型的scale,计算导数值时可选的缩放因子,默认值是1,表示默认情况下是没有应用缩放的。我们可以在文档中查阅getDerivKernels的相关介绍,来得到这个参数的更多信息。
    24. 第八个参数,double类型的delta,表示在结果存入目标图(第二个参数dst)之前可选的delta值,有默认值0
    25. 第九个参数, int类型的borderType,边界模式,默认值为BORDER_DEFAULT

    =========================================================================

    第三类:拉普拉斯(Laplance)算子

    拉普拉斯算子原理

    拉普拉斯算子(Laplance operator) 边缘提取的数学依据:在二阶导数的时候,最大变化处的值为零即边缘是零值。计算图像二阶导数,可以用来梯度计算、提取边缘、检测边缘。

    Laplacian提取边缘流程

    1)高斯模糊 – 去噪声GaussianBlur()
    GaussianBlur(src, dst, Size(3, 3), 0, 0); //高斯模糊
    2)转灰度 - cvtColor()
    cvtColor(dst, gray_src, CV_BGR2GRAY); //转灰度
    3)拉普拉斯 – 二阶导数计算Laplacian()
    Laplacian(gray_src, edge_image, CV_16S, 3); //Laplacian算子
    4)取绝对值 - convertScaleAbs() - 此处即可得到边缘图像
    convertScaleAbs(edge_image, edge_image); //取绝对值
    5)再二值化阈值处理 - 增强边缘特征threshold() - 边缘图像更明显
    threshold(edge_image, edge_image, 0, 255, THRESH_BINARY | THRESH_OTSU);//自动计算二值化otsu阈值,忽略输入的阈值

    --------------------------------------------------------------------------------------------------------------------------------

    Laplacian算子API函数接口

    1. void Laplacian(InputArray src, OutputArray dst, int ddepth, int ksize=1, double scale=1, double delta=0, int borderType=BORDER_DEFAULT )
    2. 参数说明:
    3. 前两个是必须的参数:
    4. 第一个参数是输入的原图像,Mat类型的图片;
    5. 第二个参数是图像的深度,-1表示与原图像相同深度。目标图像深度必须大于等于原图像深度;
    6. 其后是可选的参数:
    7. 第三个参数dst目标图像,Mat类型的图片;
    8. 第四个参数ksize是算子的大小,必须为1357。默认为1
    9. 第五个参数scale是缩放导数的比例常数,默认无伸缩系数;
    10. 第六个参数delta是可选增量,会加到最终的dst中,默认情况下无额外的值加dst;
    11. 第七个参数borderType是判断图像边界模式。缺省cv.BORDER_DEFAULT

    =========================================================================

    代码实现:

    1. #include"stdafx.h"
    2. #include<opencv2/opencv.hpp>
    3. #include<iostream>
    4. #include<math.h>
    5. using namespace std;
    6. using namespace cv;
    7. int main(int argc, char**argv)
    8. {
    9. Mat src, gray_src, dst, dst2;
    10. src = imread("F:/photo/qx.jpg");
    11. if (!src.data) {
    12. printf("can naot load the image ...\n");
    13. return -1;
    14. }
    15. char input_title[] = "input_picture";
    16. char output_title[] = "sobel demo2";
    17. namedWindow(input_title, WINDOW_AUTOSIZE);
    18. imshow(input_title, src);//输出原图像
    19. GaussianBlur(src, dst, Size(3, 3), 0, 0);//高斯模糊
    20. cvtColor(dst, gray_src, COLOR_BGR2GRAY);//输出灰度图
    21. imshow("gray_picture", gray_src);
    22. Mat xgrad, ygrad;
    23. Sobel(gray_src, xgrad, CV_32F, 1, 0,3);//x方向
    24. Sobel(gray_src, ygrad, CV_32F, 0, 1,3);//y方向
    25. Mat xygrad_0;
    26. convertScaleAbs(xgrad, xgrad);//取绝对值
    27. convertScaleAbs(ygrad, ygrad);
    28. imshow("xgrad", xgrad);
    29. imshow("ygrad", ygrad); //叠加图像输出
    30. addWeighted(xgrad, 0.5, ygrad, 0.5, 0, xygrad_0,-1);
    31. imshow("sobel_addweight", xygrad_0);
    32. imwrite("sobel_demo1.jpg", xygrad_0);
    33. //手动实现融合
    34. Mat xygrad_2 = Mat(xgrad.size(), xgrad.type());
    35. int width = xgrad.cols;
    36. int height = ygrad.rows;
    37. for (int row = 0; row < height; row++) {
    38. for (int col = 0; col <width; col++) {
    39. int xg = xgrad.at<uchar>(row, col);
    40. int yg = ygrad.at<uchar>(row, col);
    41. int xy = xg + yg;
    42. xygrad_2.at<uchar>(row, col) = saturate_cast<uchar>(xy);
    43. }
    44. }
    45. imshow(output_title, xygrad_2);
    46. imwrite("sobel_demo2.jpg", xygrad_2);
    47. Mat xgrad_1, ygrad_1;
    48. Scharr(gray_src, xgrad_1, CV_32F, 1, 0);
    49. Scharr(gray_src, ygrad_1, CV_32F, 0, 1);
    50. convertScaleAbs(xgrad_1, xgrad_1);//取绝对值
    51. convertScaleAbs(ygrad_1, ygrad_1);
    52. addWeighted(xgrad_1, 0.5, ygrad_1, 0.5, 0, dst2, -1);
    53. imshow("scharr_demo", dst2);
    54. imwrite("scharr_demo.jpg", dst2);
    55. imshow("schaar_xaddweight", xgrad_1);
    56. imshow("schaar_yaddweight", ygrad_1);
    57. waitKey(0);
    58. return 0;
    59. }

    图像处理效果:

    Sorbel算子图像边缘提取部分

    输入原图和灰度图 

     Sorbel算子在X方向的梯度和Y方向的梯度

      Sorbel算子在X方向的梯度和Y方向的梯度以及addWeighted()整合后的图像

     手动融合的Sorbel图像的图像梯度效果会比ddWeighted()整合后的图像强很多

    =========================================================================

    Scharr算子图像边缘提取部分

     Scharr算子图像边缘提取与Sorbel算子图像边缘提取的比较,可以看出, Scharr算子提取的图像比Sorbel算子提取的图像边缘特征强度更高,具有更强的抗干扰的特性。

     

     

     

  • 相关阅读:
    torch.nn用法
    关于jQuery_DOM操作中的添加,删除,替换标签方法
    结合实战,浅析GB/T28181(七)—— 球机云台控制
    C++调用OpenCV实现图像平滑处理
    字符串去掉()以及()中的文字
    使用tornado实现sse
    Allegro DFM Ravel Rule 丝印线段到PAD 间距检查
    Keras深度学习实战——车辆转弯角度预测
    MYSQL——JBDC实现增删改查
    最重要的传输层
  • 原文地址:https://blog.csdn.net/weixin_44651073/article/details/126395558