前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【从零学习OpenCV 4】图像卷积

【从零学习OpenCV 4】图像卷积

作者头像
小白学视觉
发布2019-12-24 18:52:58
6810
发布2019-12-24 18:52:58
举报

过几个月的努力,小白终于完成了市面上第一本OpenCV 4入门书籍《从零学习OpenCV 4》。为了更让小伙伴更早的了解最新版的OpenCV 4,小白与出版社沟通,提前在公众号上连载部分内容,请持续关注小白。

卷积常用在信号处理中,而图像数据也可以看作是一种信号数据,例如图像中的每一行可以看作测量亮度变化的信号数据,每一列也可以看作亮度变化的信号数据,因此也可以对图像进行卷积操作。在信号处理中卷积操作需要给出一个卷积函数与信号进行计算,图像的卷积形式与其相同,需要给出一个卷积模板与原图像进行卷积计算。整个过程可以看成是一个卷积模板在另外一个大的图像上移动,对每个卷积模板覆盖的区域进行点乘,得到的值作为中心像素点的输出值。卷积首先需要将卷积模板旋转180°,之后从图像的左上角开始移动旋转后的卷积模板,从左到右,从上到下依次进行卷积计算,最终得到卷积后的图像。卷积模板又被称为卷积核或者内核,是一个固定大小的二维矩阵,矩阵中存放着预先设定的数值。

图像卷积过程大致可以分为以下5个步骤:

Step1:将卷积模板旋转180°,由于多数情况中卷积模板中的数据是中心对称的,因此有时这步可以省略,但是如果卷积模板不是中心对称的,必须将模板进行旋转。

Step2:将卷积模板中心放在原图像中需要计算卷积的像素上,卷积模板中其余部分对应在原图像相应的像素上,如图5-1所示,卷积模板和待卷积矩阵中黄色区域分别是卷积模板的中心和对应点,定位结果中阴影区域为模板覆盖的区域。

图5-1 图像卷积步骤Step2

Step3:用卷积模板中的系数乘以图像中对应位置的像素数值,并对所有结果求和,针对图5-1表示的卷积步骤,其计算过程如式所示,最终计算结果为84.

Step4:将计算结果存放在原图像中与卷积模板中心点像对应的像素处,即图5-1里待卷积矩阵中的黄色像素处,结果如图5-2所示。

图5-2 图像卷积步骤Step4

Step5:将卷积模板在图像中从左至右从上到下移动,重复以上3个步骤,直到处理完所有的像素值,每一次循环的处理结果如图5-3所示。

图5-3 图像卷积步骤Step5

通过前面的4个步骤已经完成了图像卷积的主要部分,不过从图5-3中的结果可以发现这种方法只能对图像中心区域进行卷积,而由于卷积模板中心无法放置在图像的边缘像素处,因此图像边缘区域没有进行卷积运算。卷积模板的中心无法放置在图像边缘的原因是当卷积模板的中心与图像边缘对应时,模板中部分数据会出现没有图像中的像素与之对应的情况,因此为了解决这个问题,我们主动将图像的边缘外推出去,例如与3×3的卷积模板运算时,用0在原图像周围增加一层像素,从而解决模板图像中部分数据没有对应像素的问题。

通过卷积的计算结果可以发现,最后一个像素值已经接近CV_8U数据类型的最大值,因此如果卷积模板选取不当,极有可能造成卷积结果超出数据范围的情况发生,因此图像卷积操作常将卷积模板通过缩放使得所有数值的和值为1,进而解决卷积后数值越界的情况发生,例如将图5-1中卷积模板的所有数值除以12后再进行卷积操作。

针对上面的卷积过程,OpenCV 4中提供了filter2D()函数用于实现图像和卷积模板之间的卷积运算,该函数的函数原型在代码清单5-1中给出。

代码语言:javascript
复制
代码清单5-1 filter2D()函数原型
1.  void cv::filter2D(InputArray src,
2.                        OutputArray dst,
3.                        int  ddepth,
4.                        InputArray kernel,
5.                        Point anchor = Point(-1,-1),
6.                        double  delta = 0,
7.                        int  borderType = BORDER_DEFAULT
8.                        )
  • src:输入图像。
  • dst:输出图像,与输入图像具有相同的尺寸和通道数。
  • ddepth:输出图像的数据类型(深度),根据输入图像的数据类型不同拥有不同的取值范围,具体的取值范围在表5-1给出,当赋值为-1时,输出图像的数据类型自动选择。
  • kernel:卷积核,CV_32FC1类型的矩阵。
  • anchor:内核的基准点(锚点),默认值(-1,-1)代表内核基准点位于kernel的中心位置。基准点即卷积核中与进行处理的像素点重合的点,其位置必须在卷积核的内部。
  • delta:偏值,在计算结果中加上偏值。
  • borderType:像素外推法选择标志,可以选取的参数及含义已经在表3-5中给出。默认参数为BORDER_DEFAULT,表示不包含边界值倒序填充。

该函数用于实现图像和卷积模板之间的卷积运算,函数第一个参数为输入的待卷积图像,允许输入图像为多通道图像,图像中的不同通道的卷积模板是同一个卷积模板,如果需要用不同的卷积模板对不同的通道进行卷积操作,需要先使用split()函数将图像多个通道分离之后单独对每一个通道求取卷积运算。函数第二个参数为输出图像,尺寸和通道数与第一个参数保持一致。输出图像的数据类型由第三个参数进行选择,根据输入图像数据类型的不同,可供选择的输出数据类型也不相同,详细取值范围在表5-1给出。函数第四个参数为卷积模板矩阵,多数情况下该模板都是一个奇数尺寸的模板,例如3×3、5×5等。函数第五个参数指定卷积模板的中心位置,即图5-1里卷积模板中黄色像素,中心点的位置可以在卷积模板中任意指定。函数最后两个参数分别为计算卷积的偏值和像素外推方法选择的标志,卷积偏值表示在卷积步骤Step2计算结果的基础上再加上偏值delta作为最终结果。

注意

filter2D()函数不会将卷积模板进行旋转,如果卷积模板不对称,需要首先将卷积模板旋转180°后再输入给函数的第四个参数。

表5-1 fillter2D()函数输出图像数据类型与输入图像数据类型的联系

输入图像数据类型

输出图像可选数据类型

CV_8U

-1 / CV_16S / CV_32F / CV_64F

CV_16U / CV_16S

-1 / CV_32F / CV_64F

CV_32F

-1 / CV_32F / CV_64F

CV_64F

-1 / CV_64F

为了了解函数filter2D()使用方式,在代码清单5-2中给出了图5-1中的两个矩阵之间卷积的代码实现方法,并且对卷积模板进行了归一化操作。由于给出的卷积模板是中心对称的,因此可以省略卷积过程中模板旋转180°的操作。程序卷积计算的结果如图5-4所示,未归一化的卷积结果与图5-3给出的结果一致,归一化后矩阵中的每个元素的数值都在一定的范围内。另外在例程中利用相同的卷积模板对彩色图像进行卷积,输出结果在图5-5给出,虽然卷积前后图像内容一致,但是图像整体变得模糊一些,可见图像卷积具有对图像模糊的作用。

代码语言:javascript
复制
代码清单5-2 myFillter.cpp图像卷积
1.  #include <opencv2\opencv.hpp>
2.  #include <iostream>
3.  
4.  using namespace cv;
5.  using namespace std;
6.  
7.  int main()
8. {
9.    //待卷积矩阵
10.    uchar points[25] = { 1,2,3,4,5,
11.      6,7,8,9,10,
12.      11,12,13,14,15,
13.      16,17,18,19,20,
14.      21,22,23,24,25 };
15.    Mat img(5, 5, CV_8UC1, points);
16.    //卷积模板
17.    Mat kernel = (Mat_<float>(3, 3) << 1, 2, 1,
18.      2, 0, 2,
19.      1, 2, 1);
20.    Mat kernel_norm = kernel / 12; //卷积模板归一化
21.                    //未归一化卷积结果和归一化卷积结果
22.    Mat result, result_norm;
23.    filter2D(img, result, CV_32F, kernel, Point(-1, -1), 2, BORDER_CONSTANT);
24.    filter2D(img, result_norm, CV_32F, kernel_norm, Point(-1,-1),2, BORDER_CONSTANT);
25.    cout << "result:" << endl << result << endl;
26.    cout << "result_norm:" << endl << result_norm << endl;
27.    //图像卷积
28.    Mat lena = imread("lena.png");
29.    if (lena.empty())
30.    {
31.      cout << "请确认图像文件名称是否正确" << endl;
32.      return -1;
33.    }
34.    Mat lena_fillter;
35.    filter2D(lena, lena_fillter, -1, kernel_norm, Point(-1, -1), 2, BORDER_CONSTANT);
36.    imshow("lena_fillter", lena_fillter);
37.    imshow("lena", lena);
38.    waitKey(0);
39.    return 0;
40.  }

图5-4 myFillter.cpp程序中矩阵卷积结果

图5-5 myFillter.cpp程序中图像结果

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-12-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 小白学视觉 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档