【opencv学习笔记】016之卷积与自定义线性滤波

目录

一、前言

二、卷积

1、啥是卷积

2、卷积的数学原理

三、自定义线性滤波

1、讲解

2、API

3、代码展示

4、执行结果

四、borderType

1、为啥要处理边缘

2、borderType

3、代码实战


一、前言

继续填坑。

如果想看其他有关于OpenCV学习方法介绍、学习教程、代码实战、常见报错及解决方案等相关内容,可以直接看我的OpenCV分类:

【OpenCV系列】:https://blog.csdn.net/shuiyixin/article/category/7581855

如果你想了解更多有关于计算机视觉、OpenCV、机器学习、深度学习等相关技术的内容,想与更多大佬一起沟通,那就扫描下方二维码加入我们吧!

二、卷积

1、啥是卷积

我们经常说卷积,那啥是卷积呢?

卷积本身是一个数学概念,但是更多的,我们经常会在深度学习听到:卷积神经网络。但真正研究核心神经网络的人会发现,其实研究的就是数学。除了在深度学习中,在计算机视觉中,卷积也会经常见到,它常用于图像处理。

想要更加深入的理解卷积,我们还是通过数学了解一下,卷积到底是个什么鬼。

2、卷积的数学原理

因为卷积的概念还是比较容易理解的,所以在这里,我们来详细讲一下卷积的数学原理。

首先我们先来看一下卷积的定义:

在泛函分析中,卷积、旋积或摺积(英语:Convolution)是通过两个函数f 和g 生成第三个函数的一种数学算子,表征函数f 与g经过翻转和平移的重叠部分函数值乘积对重叠长度的积分

通过定义,我们再来理解一下卷积这个名词(注:只是为了帮助大家理解,这是我的个人理解,不一定具有严格的准确性,初学者可以借用这个理解方式快速了解卷积,如果有更完整,更准确的理解方式,还望大家能够评论一起交流):

卷:两个函数的反转和平移,可以理解为两个函数通过运算纠缠到了一起,卷到了一起。

积:积分(本质就是运算的求和)

理解了这两个字,对于卷积操作,我们就可以更好地理解他们的公式了。

首先我们先来看连续数据:

设:f(x),g(x)是R1上的两个可积函数,作积分:

这个积分就定义了一个新函数h(x),称为函数f与g的卷积,记为h(x)=(f*g)(x)。

接下来我们看离散数据。

我们知道,积分就是连续的无限的求和运算。讲了这么多,可能大家还是对具体的不太清楚,那我们通过离散来理解一下卷积的过程。通过一个具体的例子来说明一下:

卷积示例

左边是一个图像,后面是经过卷积操作之后的图像,中间的3×3的二维矩阵就是一个卷积核。具体计算流程如下:

31 = (15*1 + 17*1 + 19*1 + 56*1 + 18*1 + 20*1 + 97*1 + 19*1 + 20*1) / 9

大家对这个图相对比较熟悉了,因为我们在之前也见到过,就是我们之前学的图像的掩膜操作掩膜操作实现图像对比度调整

全部做掩膜操作

 掩膜操作的计算过程如下:

1 * 0 + 2 * (-1) + 3 * 0 + 2 * (-1) + 3 * 5 + 4 * (-1) + 3 * 0 + 4 * (-1) + 5 * 0 = 3

我们在掩膜操作之中没有平均,而且我们的掩膜操作和卷积操作的核实不一样的,但是计算过程非常类似,大家不要弄混。

定义图像为I(x,y),核为G(i,j),其中0<i<Mi-1和0<j<Mj-1,锚点位于相应核的(ai,aj)坐标上。所以对于上面这个我们能得到计算公式如下:

三、自定义线性滤波

1、讲解

通过上面的讲解我们知道了卷积,现在我们来讲一下自定义线性滤波,所谓自定义,就是我们自己定义线性滤波的卷积核。

而我们经常定义的卷积核就是全一的卷积核。

#include<iostream>
#include<opencv2/opencv.hpp>

using namespace std;
using namespace cv;

int main()
{
	Mat  kernel = Mat::ones(Size(3, 3),CV_32F)/9;

	cout << kernel << endl;

	waitKey(0);
	return 0;
}

我们可以定义一个3×3的全一卷积核,这个全一卷积核中的全一是不考虑最后要除以的核大小的。如下图,我们在最外面还是要除以核大小。

 我们的输出结果如下:

2、API

因为我们和掩膜操作过程是一样的,只不过是核不同,所以我们自定义线性滤波的API也是:filter2D,具体如下:

void filter2D( 
    InputArray src, 
    OutputArray dst, 
    int ddepth,                            
    InputArray kernel, 
    Point anchor = Point(-1,-1),                            
    double delta = 0, 
    int borderType = BORDER_DEFAULT 
);

函数参数含义如下:

(1)InputArray类型的src ,输入图像。

(2)OutputArray类型的dst ,输出图像,图像的大小、通道数和输入图像相同。

(3)int类型的ddepth,目标图像的所需深度。

(4)InputArray类型的kernel,卷积核(或者更确切地说是相关核)是一种单通道浮点矩阵;如果要将不同的核应用于不同的通道,请使用split将图像分割成不同的颜色平面,并分别对其进行处理。。

(5)Point类型的anchor,表示锚点(即被平滑的那个点),注意他有默认值Point(-1,-1)。如果这个点坐标是负值的话,就表示取核的中心为锚点,所以默认值Point(-1,-1)表示这个锚点在核的中心。。

(6)double类型的delta,在将筛选的像素存储到dst中之前添加到这些像素的可选值。说的有点专业了其实就是给所选的像素值添加一个值delta。

(7)int类型的borderType,用于推断图像外部像素的某种边界模式。有默认值BORDER_DEFAULT。

3、代码展示

#include<iostream>
#include<opencv2/opencv.hpp>

using namespace std;
using namespace cv;

int main()
{
	Mat img, src, kernel;
	img = imread("E:/image/girl2.png");
	if (!img.data)
	{
		cout << "could not load image !";
		return -1;
	}
	imshow("【输入图像】", img);
	kernel = Mat::ones(Size(3, 3),CV_32F)/9;
	filter2D(img, src, -1, kernel, Point(-1, -1),0);
	cout << kernel << endl;


	imshow("【输出图像】", src);

	waitKey(0);
	return 0;
}

4、执行结果

原图
进行滤波后

 

四、borderType

1、为啥要处理边缘

我们在做卷积操作的时候,我想大家都意识到了一个问题,就是边缘问题:

卷积示例

对于卷积示例中,3×3的卷积核,图像的最外层一个像素宽度的边缘是没有数据的,因为我们通过简单地卷积操作,会在行列分别丢失两个像素。

也就是说,我们要单独处理一下边缘。

2、borderType

这个就涉及到我们的最后一个参数,borderType。opencv中常用的边界类型如下:

enum BorderTypes {
    BORDER_CONSTANT    = 0, //!< `iiiiii|abcdefgh|iiiiiii`  with some specified `i`
    BORDER_REPLICATE   = 1, //!< `aaaaaa|abcdefgh|hhhhhhh`
    BORDER_REFLECT     = 2, //!< `fedcba|abcdefgh|hgfedcb`
    BORDER_WRAP        = 3, //!< `cdefgh|abcdefgh|abcdefg`
    BORDER_REFLECT_101 = 4, //!< `gfedcb|abcdefgh|gfedcba`
    BORDER_TRANSPARENT = 5, //!< `uvwxyz|absdefgh|ijklmno`

    BORDER_REFLECT101  = BORDER_REFLECT_101, //!< same as BORDER_REFLECT_101
    BORDER_DEFAULT     = BORDER_REFLECT_101, //!< same as BORDER_REFLECT_101
    BORDER_ISOLATED    = 16 //!< do not look outside of ROI
};

除了默认的BORDER_DEFAULT。最常用的还有:

 - BORDER_CONSTANT – 填充边缘用指定像素值

 - BORDER_REPLICATE – 填充边缘像素用已知的边缘像素值。

 - BORDER_WRAP – 用另外一边的像素来补偿填充

3、代码实战

我们尝试使用不同的边界类型,看一下它的结果。为了更加明显,我们将卷积核设的更大一些。

原图
BORDER_DEFAULT
BORDER_CONSTANT
BORDER_REPLICATE
BORDER_WRAP

大家也可以自己尝试一下呀,一定要多做练习!

 

 

发布了260 篇原创文章 · 获赞 521 · 访问量 52万+

猜你喜欢

转载自blog.csdn.net/shuiyixin/article/details/104438655