【opencv】卷积与自定义线性滤波和卷积边缘处理

一:卷积:

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

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

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

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

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

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

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

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

设:f(x),g(x)是R1上的两个可积函数,作积分:
在这里插入图片描述
这个积分就定义了一个新函数h(x),称为函数f与g的卷积,记为h(x)=(f*g)(x)。

接下来我们看离散数据。
在这里插入图片描述
我们知道,积分就是连续的无限的求和运算。讲了这么多,可能大家还是对具体的不太清楚,那我们通过离散来理解一下卷积的过程。通过一个具体的例子来说明一下:
在这里插入图片描述
左边是一个图像,后面是经过卷积操作之后的图像,中间的3×3的二维矩阵就是一个卷积核。具体计算流程如下:
31 = (151 + 171 + 191 + 561 + 181 + 201 + 971 + 191 + 20*1) / 9
我们在掩膜操作之中没有平均,而且我们的掩膜操作和卷积操作的核实不一样的,但是计算过程非常类似,大家不要弄混。

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

二:自定义线性滤波

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

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

#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的全一卷积核,这个全一卷积核中的全一是不考虑最后要除以的核大小的。如下图,我们在最外面还是要除以核大小
在这里插入图片描述
我们的输出结果如下:
在这里插入图片描述

三:自定义线性滤波所用到的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。

四:代码:

#include<iostream>
#include<opencv2/opencv.hpp>
#include<highgui/highgui_c.h>

using namespace std;
using namespace cv;

int main()
{
	Mat src,dst;
	src = imread("F:/pic/lw.png");
	if (src.empty())
	{
		cout << "Could not load the image!";
		return -1;
	}
	namedWindow("input image", CV_WINDOW_AUTOSIZE);
	imshow("input image", src);
	//sobel X方向
	Mat dst_x;
	Mat kernel_x = (Mat_<int>(3, 3) << -1, 0, 1, -2,0,2,-1,0,1);
	filter2D(src, dst_x,-1, kernel_x, Point(-1, -1), 0.0);
	//Sobel Y方向
	Mat dst_y;
	Mat kernel_y= (Mat_<int>(3, 3) << -1,-2,-1,0,0,0,1,2,1);
	filter2D(src, dst_y, -1, kernel_y,Point(-1, -1), 0.0);
	imshow("sobel_x", dst_x);
	imshow("sobel_y", dst_y);
	//拉普拉斯算子
	Mat kernel_L = (Mat_<int>(3, 3) << 0, -1, 0, -1, 4, -1, 0, -1, 0);
	filter2D(src, dst, -1, kernel_L, Point(-1, -1), 0.0);
	imshow("laplance", dst);
	waitKey(0);
	return 0;
}

实验现象:
在这里插入图片描述
五:卷积边缘处理:
卷积边界问题:

图像进行卷积操作时,图像的边界像素并不能被卷积操作到,原因在于边界像素没有完全跟 kernel 重叠,例如当33滤波时有1个像素的边缘没有被处理,55滤波时有2个像素的边缘没有被处理。
边缘问题解决方法:

在卷积操作开始之前,先增加边缘像素,比如3*3滤波时,在图像四周各填充1个像素的边缘,这样就确保图像的边缘能被处理,在卷积处理之后再去掉这些边缘。
常用方法有
BORDER_DEFAULT - 对称法,也就是以最边缘像素为轴对称复制(通常为默认方法)。
BORDER_CONSTANT - 常量法就是以一个常量像素值(由参数 value给定)填充扩充的边界值,这种方式在仿射变换,透视变换中非常常见。
BORDER_REPLICATE - 复制法,也就是复制最边缘像素。
BORDER_WRAP - 用另外一边的像素补偿填充。

相关API

copyMakeBorder()
函数原型:
void copyMakeBorder( const Mat& src, Mat& dst,int top, int bottom, int left, int right,int borderType, const Scalar& value=Scalar() );

src:输入的数组。

dst:输出的拓展边界后的数组。

top:在src上边界向上拓展的行数。

bottom:在src下边界向下拓展的行数。

left:在src的左边界向左拓展的列数。

right:在src的右边界向右拓展的列数。

borderType:上一节中的边界拓展策略中的一个。

value:当你的边界策略使用的是BORDER_CONSTANT的时候,此处是指边界处填写的常数值。

代码如下:

#include<iostream>
#include<opencv2/opencv.hpp>
#include<highgui/highgui_c.h>

using namespace std;
using namespace cv;

int main()
{
	Mat src, dst;
	src = imread("F:/pic/lw.png");
	if (src.empty())
	{
		cout << "Could not load the image!";
		return -1;
	}
	namedWindow("input image", CV_WINDOW_AUTOSIZE);
	imshow("input image", src);

	int top = (int)(0.05 * src.rows);
	int buttom = (int)(0.05 * src.rows);
	int left = (int)(0.05 * src.cols);
	int right = (int)(0.05 * src.cols);
	RNG rng(12345);
	int borderType = BORDER_DEFAULT;

	int c = 0;
	while (true)
	{
		c = waitKey(500);
		if ((char)c == 27)
		{
			break;
		}
		if ((char)c == 'r') {
			borderType = BORDER_REPLICATE;

		}
		else if ((char)c == 'v') {
			borderType = BORDER_WRAP;
		}
		else if ((char)c == 'c') {
			borderType = BORDER_CONSTANT;
		}
		Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
		//
		copyMakeBorder(src, dst, top, buttom, left, right, borderType, color);
		imshow("output", dst);
	}
	//GaussianBlur(src, dst, Size(5, 5), 0, 0, BORDER_WRAP);
	//imshow("output", dst);
	waitKey(0);
	return 0;
}

实验结果:
1:默认(BORDER_DEFAULT)
在这里插入图片描述

2:BORDER_REPLICATE 结果
在这里插入图片描述
3:BORDER_WRAP;
在这里插入图片描述
4:BORDER_CONSTANT
在这里插入图片描述

应用:
例如在图像模糊时
blur(src, dst, Size(5, 5), Point(-1, -1),BORDER_DEFAULT);

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值