实现常见空域滤波

  • 主要是实现常见的空域滤波,这篇属于造轮子的实验我就简单的贴下代码。

1、原图填充

使用模板进行空域滤波需要对原图进行填充,这样才会让处理后的图片的大小和原图一样大。
首先要获取需要填充的大小,Length 是具体的一个方向比如横向,step是步长,空域滤波的时候默认长度为1,kernelSize是使用模板的长度或宽度。

int getBordValue(int Length, int step, int kernelSize)
{
	return (Length * (step - 1) - step + kernelSize) / 2;
}

进行填充

void autoCopyMakeBorder(Mat& images, int borderType, int step, int kernelSize)
{
	int row = images.rows;
	int col = images.cols;
	int hightValue = getBordValue(row, step, kernelSize);
	int widthValue = getBordValue(col, step, kernelSize);
	copyMakeBorder(images, images, hightValue, hightValue, widthValue, widthValue, borderType);
}

填充的方式有很多种,比如常见的常量填充,101方式填充。

2、定义filter函数

这个功能类似cv库中的filter2D()函数,典型的造轮子,这里传入的模板是一个二维数组,这里我本打算用一个Mat对象来存储模板信息,但是在使用的时候有点问题,我就换成了动态的二维数组。对灰度和彩色只需要单独处理下就好了,就加一个判断语句就行。

void ifilter(Mat& src, Mat& dst, int kernelSize,double** templateMatrix)
{
	assert(src.channels() || src.channels() == 3);
	dst = src.clone();
	//这里默认处理的N*N的filter, 如果长宽不一致,单独获取长宽
	//填充边缘大小
	int row = src.rows;
	int col = src.cols;
	int rowPadding = getBordValue(row, 1, kernelSize);
	int colPadding = getBordValue(col, 1, kernelSize);
	//这里不要直接填充src,因为这里是引用,可能后面的实验会重复填充
	Mat t_src;
	copyMakeBorder(src, t_src, rowPadding, rowPadding, colPadding, colPadding, BORDER_DEFAULT);


	int channels = src.channels();
	int cols = t_src.cols - rowPadding;
	int rows = t_src.rows - colPadding;

	for (int i = rowPadding; i < rows; i++) {
		for (int j = colPadding; j < cols; j++) {
			double sum[3] = { 0 };
			for (int k = -rowPadding; k <= rowPadding; k++) {
				for (int m = -colPadding; m <= colPadding; m++) {
					if (channels == 1) {
						sum[0] += templateMatrix[k+ rowPadding][m+colPadding] * t_src.at<uchar>(i + k, j + m);
					}
					else if (channels == 3) {
						Vec3b rgb = t_src.at<Vec3b>(i + k, j + m);
						auto tmp = templateMatrix[k + rowPadding][m + colPadding];
						sum[0] += tmp * rgb[0];
						sum[1] += tmp * rgb[1];
						sum[2] += tmp * rgb[2];
					}
				}
			}
			//限定像素值在0-255之间
			for (int i = 0; i < channels; i++) {
				if (sum[i] < 0)
					sum[i] = 0;
				else if (sum[i] > 255)
					sum[i] = 255;
			}
			//
			if (channels == 1) {
				dst.at<uchar>(i-rowPadding, j-colPadding) = static_cast<uchar>(sum[0]);
			}
			else if (channels == 3) {
				Vec3b rgb;
				rgb[0] = static_cast<uchar>(sum[0]);
				rgb[1] = static_cast<uchar>(sum[1]);
				rgb[2] = static_cast<uchar>(sum[2]);

				dst.at<Vec3b>(i - rowPadding, j - colPadding) = rgb;
			}
		}
	}
}

3、均值模糊

均值模板,就是模板内每个值得占比都一样,即是将模板覆盖范围内的像素累加起来,再除以模板内的像素的个数。比如3*3的模板:,用于图像的模糊和去噪。

void iMeanBlur(Mat& src, Mat& dst, int kernelSize)
{

	//调用库实现的方法
	/*
	Mat mKernel = Mat::ones(kernelSize, kernelSize, CV_32F) / (float)(kernelSize * kernelSize);
	//调用库函数实现
	filter2D(src, dst, CV_32F, mKernel, Point(-1, -1));
	convertScaleAbs(dst, dst);
	imshow("dst", dst);
	*/

	//先根据kernelSize 创建均值模糊的模板
	double** templateMatrix = new double* [kernelSize];
	for (int i = 0; i < kernelSize; i++) {
		templateMatrix[i] = new double[kernelSize];
	}
	int tmp = kernelSize * kernelSize;
	int origin = kernelSize / 2;
	for (int i = 0; i < kernelSize; i++) {
		for (int j = 0; j < kernelSize; j++) {
			templateMatrix[i][j] = 1.0 / tmp;
		}
	}

	ifilter(src, dst, kernelSize, templateMatrix);
	//删除模板
	for (int i = 0; i < kernelSize; i++) {
		delete[] templateMatrix[i];
	}
	delete[] templateMatrix;

}

实验效果

4、高斯模糊

主要是生成高斯模板,高斯函数,模板的中心权重最大,整个模板系数之和为1。

void iGaussianBlur(Mat& src, Mat& dst, int kernelSize, double sigma) 
{
	//根据高斯模糊的kernelSize创建高斯模糊的模板
	double PI = acos(-1);
	//动态申请二维数组空间
	double** kernel = new double* [kernelSize];

	for (int i = 0; i < kernelSize; i++) {
		kernel[i] = new double[kernelSize];
	}
	
	int origin =kernelSize / 2; // 以模板的中心为原点
	double x2, y2;
	double sum = 0;
	for (int i = 0; i < kernelSize; i++)
	{
		x2 = pow(i - origin, 2);
		for (int j = 0; j < kernelSize; j++)
		{
			y2 = pow(double(j - origin), 2);
			double g = exp(-(x2 + y2) / (2 * sigma * sigma));
			sum += g;
			kernel[i][j] = g;
		}
	}
	//归一化
	double k = 1 / sum;
	for (int i = 0; i < kernelSize; i++) {
		for (int j = 0; j < kernelSize; j++) {
			kernel[i][j] *= k;
		}
	}
	ifilter(src, dst, kernelSize, kernel);
	//删除动态分配的二维数组
	for (int i = 0; i < kernelSize; i++) 
	{
		delete[] kernel[i];
	}
	delete[] kernel;
}

实验效果

5、拉普拉斯算子

拉普拉斯算子属于二阶锐化图像的算子,二阶微分对细节有较强的响应,对于梯度会产生双响应,而一阶微分对有较强的响应。常使用的拉普拉斯算子有以下几个。拉普拉斯算子多用细节提取,当然提取细节也可以用于细节增强,比如常见的非锐化掩蔽(USM)。如果使用这种方式做增强的时候,可以在拉普拉斯算子上做一个小小的改动,比如让这个算子变成,就可以实现拉普拉斯算子增强图片细节。
常用的拉普拉斯算子有多个,这里我选择的是

void iLaplacian(Mat& src, Mat& dst) {
	//拉普拉斯常用的有几个模板,我只选择了其中的一个
	vector<double> list = { -1,-1,-1,-1,8,-1,-1,-1,-1 };
	double** templateMatrix = new double* [3];
	for (int i = 0; i < 3; i++) {
		templateMatrix[i] = new double[3];
	}
	int k = 0;
	for (int i = 0; i < 3; i++) {
		for (int j = 0; j < 3; j++) {
			templateMatrix[i][j] = list[k++];
		}
	}
	ifilter(src, dst, 3, templateMatrix);

	for (int i = 0; i < 3; i++) 
	{
		delete[] templateMatrix[i];
	}
	delete[] templateMatrix;
}

实验效果

Robert算子

Robert算子属于一阶微分,主要用于提取图片中的梯度,所以常见的Robert算子有两个,一个用于提取x方向,一个用于提取y方向。然后将两个方向合并起来。比如,向量(1)。因为梯度向量的分量是微分,所以它们是线性算子,但是向量的幅度不是线性算子,求幅度的时候是做*方和*方根操作。虽然偏微分不是旋转不变的,但是梯度向量的幅度是旋转不变。所以常用绝对值来*似*方和*方根操作,常用以下公式(2)代替*方和*方根。该表达式保留了灰度的相对变化,但是失去了各向同性的特性。像拉普拉斯的情况那种,离散梯度的各项同性仅在有限旋转增量的情况下才被保留,它依赖所用的*似微分的滤波器模板。结果表明,用*似梯度常用的模板在90°的倍数是各向同性的,所以使用(1)和(2)对结果处理并没有影响。
Robert 算子有多个,这里我选择了其中的一对。之前已经自己实现过滤波函数,代表我已经会实现滤波方法了,所以我尝试了下使用filter2D()这个函数,这和前面自己实现的没啥不同。只是我用Mat对象,代替的二维数组模板的生成。也已经说明了为什么可以用*似绝对值代替*方和和*方根。而且*似绝对值之和,运算更快。

void iRobert(Mat& src, Mat& dst_x, Mat& dst_y,Mat& dst) 
{
	Mat Robert_x = (Mat_<int>(2, 2) << 1, 0, 0, -1);
	Mat Robert_y = (Mat_<int>(2, 2) << 0, 1, -1, 0);

	filter2D(src, dst_x, CV_32F, Robert_x, Point(-1, -1), 0, BORDER_DEFAULT);
	convertScaleAbs(dst_x, dst_x);

	filter2D(src, dst_y, CV_32F, Robert_y, Point(-1, -1), 0, BORDER_DEFAULT);
	convertScaleAbs(dst_y, dst_y);

	add(dst_x, dst_y, dst);
	convertScaleAbs(dst, dst);

}

实验效果

6、Sobel算子

Sobel算子和Robert算子原理类似,都是一阶微分,常用于梯度提取和边缘增强,这里我选择这一对

void iSobel(Mat& src, Mat& dst_x, Mat& dst_y, Mat& dst) 
{
	Mat Sobel_x = (Mat_<int>(3, 3) << -1, 0,1,-2, 0,2, -1,0,1);
	Mat Sobel_y = (Mat_<int>(3, 3) << -1,-2,-1,0,0,0,1,2,1);

	filter2D(src, dst_x, CV_32F, Sobel_x, Point(-1, -1), 0, BORDER_DEFAULT);
	convertScaleAbs(dst_x, dst_x);

	filter2D(src, dst_y, CV_32F, Sobel_y, Point(-1, -1), 0, BORDER_DEFAULT);
	convertScaleAbs(dst_y, dst_y);

	add(dst_x, dst_y, dst);
	convertScaleAbs(dst, dst);
}

实验效果


实验有待改进地方

1、在定义模板的时候可以考虑使用Mat对象来替代动态二维数组
2、在实现ifilter()这函数,使用到四个for循环可以,将里面两层单独写成一个函数
3、在规范到0-255 可以使用convertScaleAbs()这个函数
4、在处理比较大的图片和使用比较大的均值模板时候,运算是比较慢的,可以采用小的模板或小的图片代替。比如用33 代替 55 ,7*7模板。

完整代码地址

https://github.com/cyssmile/openCV_learning_notes/tree/master/opencv_test/exercise_7

posted @ 2020-06-10 16:48  cyssmile  阅读(1850)  评论(0编辑  收藏  举报