OpenCV-高斯低通&高通滤波器(C++)

Posted 翟大宝Steven

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OpenCV-高斯低通&高通滤波器(C++)相关的知识,希望对你有一定的参考价值。

场景需求

       做图像处理,滤波是家常便饭,今天给大家分享高斯滤波器实现。

       众所周知,在频谱中,低频主要对应图像在平滑区域的总体灰度级分布,高频对应图像细节部分,如边缘和噪声。高斯滤波是一种线性平滑滤波,适用于消除高斯噪声,广泛应用于图像处理的减噪过程。公式和具体原理就不再罗列了,百度一下全都有,接下来是硬货——C++&OpenCV代码实现。

相关功能函数的C++实现代码

// 高斯低通滤波核函数
cv::Mat gaussian_low_pass_kernel(cv::Mat scr, float sigma)
{
	cv::Mat gaussianBlur(scr.size(), CV_32FC1); //,CV_32FC1
	float d0 = sigma;//高斯函数参数,越小,频率高斯滤波器越窄,滤除高频成分越多,图像就越平滑
	for (int i = 0; i < scr.rows; i++) {
		for (int j = 0; j < scr.cols; j++) {
			float d = pow(float(i - scr.rows / 2), 2) + pow(float(j - scr.cols / 2), 2);//分子,计算pow必须为float型
			gaussianBlur.at<float>(i, j) = expf(-(d*d) / (2 * d0*d0));//expf为以e为底求幂(必须为float型)
		}
	}
	return gaussianBlur;
}
// 高斯低通滤波
cv::Mat gaussian_low_pass_filter(cv::Mat &src, float d0)
{
	cv::Mat padded = image_make_border(src);
	cv::Mat gaussian_kernel = gaussian_low_pass_kernel(padded, d0);
	cv::Mat result = frequency_filter(padded, gaussian_kernel);
	return result;
}
// 高斯高通滤波核函数
cv::Mat gaussian_high_pass_kernel(cv::Mat scr, float sigma)
{
	cv::Mat gaussianBlur(scr.size(), CV_32FC1); //,CV_32FC1
	float d0 = sigma;
	for (int i = 0; i < scr.rows; i++) {
		for (int j = 0; j < scr.cols; j++) {
			float d = pow(float(i - scr.rows / 2), 2) + pow(float(j - scr.cols / 2), 2);//分子,计算pow必须为float型
			gaussianBlur.at<float>(i, j) = 1 - expf(-(d*d) / (2 * d0*d0));;
		}
	}
	return gaussianBlur;
}
// 高斯高通滤波
cv::Mat gaussian_high_pass_filter(cv::Mat &src, float d0)
{
	cv::Mat padded = image_make_border(src);
	cv::Mat gaussian_kernel = gaussian_high_pass_kernel(padded, d0);//理想低通滤波器
	cv::Mat result = frequency_filter(padded, gaussian_kernel);
	return result;
}
// 频率域滤波
cv::Mat frequency_filter(cv::Mat &scr, cv::Mat &blur)
{
	cv::Mat mask = scr == scr;
	scr.setTo(0.0f, ~mask);

	//创建通道,存储dft后的实部与虚部(CV_32F,必须为单通道数)
	cv::Mat plane[] = { scr.clone(), cv::Mat::zeros(scr.size() , CV_32FC1) };

	cv::Mat complexIm;
	cv::merge(plane, 2, complexIm); // 合并通道 (把两个矩阵合并为一个2通道的Mat类容器)
	cv::dft(complexIm, complexIm); // 进行傅立叶变换,结果保存在自身

	// 分离通道(数组分离)
	cv::split(complexIm, plane);

	// 以下的操作是频域迁移
	fftshift(plane[0], plane[1]);

	// *****************滤波器函数与DFT结果的乘积****************
	cv::Mat blur_r, blur_i, BLUR;
	cv::multiply(plane[0], blur, blur_r);  // 滤波(实部与滤波器模板对应元素相乘)
	cv::multiply(plane[1], blur, blur_i);  // 滤波(虚部与滤波器模板对应元素相乘)
	cv::Mat plane1[] = { blur_r, blur_i };

	// 再次搬移回来进行逆变换
	fftshift(plane1[0], plane1[1]);
	cv::merge(plane1, 2, BLUR); // 实部与虚部合并

	cv::idft(BLUR, BLUR);       // idft结果也为复数
	BLUR = BLUR / BLUR.rows / BLUR.cols;

	cv::split(BLUR, plane);//分离通道,主要获取通道

	return plane[0];
}
// 图像边界处理
cv::Mat image_make_border(cv::Mat &src)
{
	int w = cv::getOptimalDFTSize(src.cols); // 获取DFT变换的最佳宽度
	int h = cv::getOptimalDFTSize(src.rows); // 获取DFT变换的最佳高度

	cv::Mat padded;
	// 常量法扩充图像边界,常量 = 0
	cv::copyMakeBorder(src, padded, 0, h - src.rows, 0, w - src.cols, cv::BORDER_CONSTANT, cv::Scalar::all(0));
	padded.convertTo(padded, CV_32FC1);

	return padded;
}
// fft变换后进行频谱搬移
void fftshift(cv::Mat &plane0, cv::Mat &plane1)
{
	// 以下的操作是移动图像  (零频移到中心)
	int cx = plane0.cols / 2;
	int cy = plane0.rows / 2;
	cv::Mat part1_r(plane0, cv::Rect(0, 0, cx, cy));  // 元素坐标表示为(cx, cy)
	cv::Mat part2_r(plane0, cv::Rect(cx, 0, cx, cy));
	cv::Mat part3_r(plane0, cv::Rect(0, cy, cx, cy));
	cv::Mat part4_r(plane0, cv::Rect(cx, cy, cx, cy));

	cv::Mat temp;
	part1_r.copyTo(temp);  //左上与右下交换位置(实部)
	part4_r.copyTo(part1_r);
	temp.copyTo(part4_r);

	part2_r.copyTo(temp);  //右上与左下交换位置(实部)
	part3_r.copyTo(part2_r);
	temp.copyTo(part3_r);

	cv::Mat part1_i(plane1, cv::Rect(0, 0, cx, cy));  //元素坐标(cx,cy)
	cv::Mat part2_i(plane1, cv::Rect(cx, 0, cx, cy));
	cv::Mat part3_i(plane1, cv::Rect(0, cy, cx, cy));
	cv::Mat part4_i(plane1, cv::Rect(cx, cy, cx, cy));

	part1_i.copyTo(temp);  //左上与右下交换位置(虚部)
	part4_i.copyTo(part1_i);
	temp.copyTo(part4_i);

	part2_i.copyTo(temp);  //右上与左下交换位置(虚部)
	part3_i.copyTo(part2_i);
	temp.copyTo(part3_i);
}

测试代码

#include<iostream>
#include<opencv2/opencv.hpp>
#include<ctime>
using namespace std;
using namespace cv;

cv::Mat gaussian_low_pass_kernel(cv::Mat scr, float sigma);
cv::Mat gaussian_low_pass_filter(cv::Mat &src, float d0);
cv::Mat gaussian_high_pass_kernel(cv::Mat scr, float sigma);
cv::Mat gaussian_high_pass_filter(cv::Mat &src, float d0);
cv::Mat frequency_filter(cv::Mat &scr, cv::Mat &blur);
cv::Mat image_make_border(cv::Mat &src);
void fftshift(cv::Mat &plane0, cv::Mat &plane1);

int main(void)
{
	Mat test = imread("tangsan.jpg", 0);
	float D0 = 50.0f;
	float D1 = 5.0f;
	Mat lowpass = gaussian_low_pass_filter(test, D0);
	Mat highpass = gaussian_high_pass_filter(test, D1);

	imshow("original", test);
	imshow("low pass", lowpass / 255);     // lowpass的数据有正有负,又因为数据的模值大于1,imshow显示出来就是大于1的就是白,小于0的都是黑
	imshow("high pass", highpass / 255);   // highpass的数据都比较大,0-255,imshow对于float型Mat显示需要除以255
	waitKey(0);

	system("pause");
	return 0;
}

// 高斯低通滤波核函数
cv::Mat gaussian_low_pass_kernel(cv::Mat scr, float sigma)
{
	cv::Mat gaussianBlur(scr.size(), CV_32FC1); //,CV_32FC1
	float d0 = sigma;//高斯函数参数,越小,频率高斯滤波器越窄,滤除高频成分越多,图像就越平滑
	for (int i = 0; i < scr.rows; i++) {
		for (int j = 0; j < scr.cols; j++) {
			float d = pow(float(i - scr.rows / 2), 2) + pow(float(j - scr.cols / 2), 2);//分子,计算pow必须为float型
			gaussianBlur.at<float>(i, j) = expf(-(d*d) / (2 * d0*d0));//expf为以e为底求幂(必须为float型)
		}
	}
	return gaussianBlur;
}
// 高斯低通滤波
cv::Mat gaussian_low_pass_filter(cv::Mat &src, float d0)
{
	cv::Mat padded = image_make_border(src);
	cv::Mat gaussian_kernel = gaussian_low_pass_kernel(padded, d0);
	cv::Mat result = frequency_filter(padded, gaussian_kernel);
	return result;
}
// 高斯高通滤波核函数
cv::Mat gaussian_high_pass_kernel(cv::Mat scr, float sigma)
{
	cv::Mat gaussianBlur(scr.size(), CV_32FC1); //,CV_32FC1
	float d0 = sigma;
	for (int i = 0; i < scr.rows; i++) {
		for (int j = 0; j < scr.cols; j++) {
			float d = pow(float(i - scr.rows / 2), 2) + pow(float(j - scr.cols / 2), 2);//分子,计算pow必须为float型
			gaussianBlur.at<float>(i, j) = 1 - expf(-(d*d) / (2 * d0*d0));;
		}
	}
	return gaussianBlur;
}
// 高斯高通滤波
cv::Mat gaussian_high_pass_filter(cv::Mat &src, float d0)
{
	cv::Mat padded = image_make_border(src);
	cv::Mat gaussian_kernel = gaussian_high_pass_kernel(padded, d0);//理想低通滤波器
	cv::Mat result = frequency_filter(padded, gaussian_kernel);
	return result;
}
// 频率域滤波
cv::Mat frequency_filter(cv::Mat &scr, cv::Mat &blur)
{
	cv::Mat mask = scr == scr;
	scr.setTo(0.0f, ~mask);

	//创建通道,存储dft后的实部与虚部(CV_32F,必须为单通道数)
	cv::Mat plane[] = { scr.clone(), cv::Mat::zeros(scr.size() , CV_32FC1) };

	cv::Mat complexIm;
	cv::merge(plane, 2, complexIm); // 合并通道 (把两个矩阵合并为一个2通道的Mat类容器)
	cv::dft(complexIm, complexIm); // 进行傅立叶变换,结果保存在自身

	// 分离通道(数组分离)
	cv::split(complexIm, plane);

	// 以下的操作是频域迁移
	fftshift(plane[0], plane[1]);

	// *****************滤波器函数与DFT结果的乘积****************
	cv::Mat blur_r, blur_i, BLUR;
	cv::multiply(plane[0], blur, blur_r);  // 滤波(实部与滤波器模板对应元素相乘)
	cv::multiply(plane[1], blur, blur_i);  // 滤波(虚部与滤波器模板对应元素相乘)
	cv::Mat plane1[] = { blur_r, blur_i };

	// 再次搬移回来进行逆变换
	fftshift(plane1[0], plane1[1]);
	cv::merge(plane1, 2, BLUR); // 实部与虚部合并

	cv::idft(BLUR, BLUR);       // idft结果也为复数
	BLUR = BLUR / BLUR.rows / BLUR.cols;

	cv::split(BLUR, plane);//分离通道,主要获取通道

	return plane[0];
}
// 图像边界处理
cv::Mat image_make_border(cv::Mat &src)
{
	int w = cv::getOptimalDFTSize(src.cols); // 获取DFT变换的最佳宽度
	int h = cv::getOptimalDFTSize(src.rows); // 获取DFT变换的最佳高度

	cv::Mat padded;
	// 常量法扩充图像边界,常量 = 0
	cv::copyMakeBorder(src, padded, 0, h - src.rows, 0, w - src.cols, cv::BORDER_CONSTANT, cv::Scalar::all(0));
	padded.convertTo(padded, CV_32FC1);

	return padded;
}
// fft变换后进行频谱搬移
void fftshift(cv::Mat &plane0, cv::Mat &plane1)
{
	// 以下的操作是移动图像  (零频移到中心)
	int cx = plane0.cols / 2;
	int cy = plane0.rows / 2;
	cv::Mat part1_r(plane0, cv::Rect(0, 0, cx, cy));  // 元素坐标表示为(cx, cy)
	cv::Mat part2_r(plane0, cv::Rect(cx, 0, cx, cy));
	cv::Mat part3_r(plane0, cv::Rect(0, cy, cx, cy));
	cv::Mat part4_r(plane0, cv::Rect(cx, cy, cx, cy));

	cv::Mat temp;
	part1_r.copyTo(temp);  //左上与右下交换位置(实部)
	part4_r.copyTo(part1_r);
	temp.copyTo(part4_r);

	part2_r.copyTo(temp);  //右上与左下交换位置(实部)
	part3_r.copyTo(part2_r);
	temp.copyTo(part3_r);

	cv::Mat part1_i(plane1, cv::Rect(0, 0, cx, cy));  //元素坐标(cx,cy)
	cv::Mat part2_i(plane1, cv::Rect(cx, 0, cx, cy));
	cv::Mat part3_i(plane1, cv::Rect(0, cy, cx, cy));
	cv::Mat part4_i(plane1, cv::Rect(cx, cy, cx, cy));

	part1_i.copyTo(temp);  //左上与右下交换位置(虚部)
	part4_i.copyTo(part1_i);
	temp.copyTo(part4_i);

	part2_i.copyTo(temp);  //右上与左下交换位置(虚部)
	part3_i.copyTo(part2_i);
	temp.copyTo(part3_i);
}

测试效果

图1 频域高斯低通滤波器示意图
图2 低通高通效果图

       不同的滤波参数导致的滤波器尺寸大小不一,得到的结果也就不一样~

       另外,如果我的代码有什么问题,欢迎大家提出异议批评指正,一同进步~

       如果文章帮助到你了,可以点个赞让我知道,我会很快乐~加油!

以上是关于OpenCV-高斯低通&高通滤波器(C++)的主要内容,如果未能解决你的问题,请参考以下文章

OpenCV-理想高通&低通滤波器(C++)

OpenCV-巴特沃斯低通&高通滤波器(C++)

OpenCV——基础集锦

【转载】opencv 频域高通、低通滤波演示

OpenCV实战(13)——高通滤波器及其应用

python实现直方图均衡化,理想高通滤波与高斯低通滤波