Opencvx学习十一非线性滤波与导向滤波

**

前言

**

线性滤波,即两个信号之后的相应和它们各自响应之和相等。但是,不能很好的保留图像特征。
下图看做放在x-y-z空间的图像,x-y是图像像素定义域-----坐标空间,而z是像素值大小-------颜色空间。
在这里插入图片描述
前面学习到的几种滤波方法,卷积的和都是与源图像进行线性加权。看上面这幅图------这是图像值的三维的体现,如果在其边缘处用线性滤波的话,那么上下两个面的距离就会缩小-------边缘特征被减弱了。

  • 所以,需要这样一种算法,在边缘进行滤波的时候,能不能先判断是否是上面那种断崖,也就是两边图像值差异大。如果差异大,那么进行卷积的核只卷积一部分,就是把“核”切“一半”。

**

双边滤波

**
Bilateral filter是一种非线性的滤波方法,是结合图像的空间邻近度和相似度的一种折中处理。同时考虑空域信息和灰度相似性,达到包边取噪的目的,具有简单、非迭代、局部的特点。

双边滤波器的好处是可以做到edge preserving。比高斯滤波多了个sigma-d,它是基于空间分布的高斯滤波函数,所以在边缘附近,离得远的像素不会对边缘上的像素值影响太多,这样就做到了边缘附近像素值的保存。
但是,由于保存了过多的高频信息,对于彩色图像里的高频噪声,双边滤波器不能够干净地过滤掉,只能对于低频信息进行较好的滤波。

输出像素的值依赖邻域像素值的加权:
g ( i , j ) = k , l f ( k , l ) w ( i , j k , l ) k , l w ( i , j , k , l ) g(i,j)=\frac{\sum_{k,l}f(k,l)w(i,jk,l)}{\sum_{k,l}w(i,j,k,l)}

而加权系数w(i,j,k,l)取决于定义域核与值域核的乘积。
定义域核:

d ( i , j , k , l ) = e x p ( ( i k ) 2 + ( j l ) 2 2 σ d 2 ) d(i,j,k,l)=exp(-\frac{(i-k)^2 + (j-l)^2 }{2\sigma_d^2})

值域核:

r ( i , j , k , l ) = e x p ( f ( i , j ) f ( k , l ) 2 2 σ r 2 ) r(i,j,k,l)=exp(-\frac{ \mid\mid f(i,j) - f(k,l) \mid\mid^2 }{2\sigma_r^2})

定义域滤波和值域滤波如下图:
在这里插入图片描述

下面的依赖数据的双边滤波权重函数:

w ( i , j , k , l ) = e x p ( ( i k ) 2 + ( j l ) 2 2 σ d 2 f ( i , j ) f ( k , l ) 2 2 σ r 2 ) w(i,j,k,l)=exp( -\frac{(i-k)^2+(j-l)^2}{2\sigma_d^2} -\frac{\mid\mid f(i,j) - f(k,l)\mid\mid^2}{2\sigma_r^2} )

 void bilateralFilter( InputArray src, 
 						OutputArray dst,
  								int d,   //标准差
                      double sigmaColor, //颜色空间滤波器的sigma
                      double sigmaSpace, //坐标空间滤波器的sigma
                      int borderType = BORDER_DEFAULT );

  • 第三个参数,如果被设定为非正数,那么Opencv会从第五个参数来计算它
  • 第四个参数,这个参数值越大,就表明该像素邻域内有越宽广的颜色会被混合到一起,产生较大的半相等颜色区域
  • 第五个参数,它值越大,意味着越远的像素会相互影响,从而使更大的区域中有足够相似的颜色获取相同的颜色。如果d<=0,那么d正比与sigmaSpace(毕竟是根据sigmaSpace计算d)。
    **

中值滤波

**
中值滤波是基于排序统计理论的一种有效抑制噪声的非线性信号处理技术,原理是把数字图像或数字阵列中的一点的值用该点邻域中的各点值的中值代替。

让周围的像素值接近真实值,从而消除孤立的噪声点。这在处理连续图像窗函数时与线性滤波器的工作方式相同,但滤波过程不是加权运算。

中值滤波在一定条件下,可以克服线性滤波器所带来的图像细节模糊,对滤除脉冲干扰即图像扫描噪声最为有效,而且在实际运算中,并不需要图像的统计特性,给计算带来方便。但是对一些细节(细、尖顶等)多的图像不太合适。

 void medianBlur( InputArray src, OutputArray dst, int ksize );

**

导向滤波

**
在这里插入图片描述
左边是双边滤波器原理,而右边就是导向滤波器原理。这图真是相当形象了。

导向滤波是基本是把函数看做一段段的一次函数,那么图像某处的值就为它周围函数共同作用在该处的均值。由导向图像得到一个
q i = a I i + b q_i=aI_i+b
由源图像去躁得到一个
q i = p i n i q_i=p_i-n_i

为了尽量拟合,应该使
i w k ( ( a k I i + b k p i ) 2 + ϵ a k 2 ) \sum_{i \in w_k}((a_kI_i + b_k - p_i)^2+\epsilon a_k^2)

最小---------后面那项是为了调节滤波器效果,也是为了让 a k a_k 不至于过大。这时候用到最小二乘法。
a k = 1 w k i w k I i p i μ k p k ^ σ k 2 + ϵ a_k=\frac{\frac{1}{\mid w_k\mid}\sum_{i\in w_k }{I_i p_i} - \mu_k \hat{p_k} }{\sigma_k ^2 + \epsilon}
b k = p k ^ a k μ k b_k=\hat{p_k} - a_k\mu_k

μ k \mu_k 是导向图像 I I 在窗口 w k w_k 中的平均值
p k ^ \hat{p_k} 是待滤波图像 p p 在窗口 w k w_k 中的均值
w \mid w\mid 是窗口 w k w_k 中像素数量
σ 2 \sigma^2 是窗口 w k w_k 中的方差
最后,输出图像:
q i = 1 w k k : i w k a k I i + b k q_i=\frac{1}{\mid w_k\mid}\sum_{k:i\in w_k}{a_kI_i + b_k}
q i = a i ^ I i + b i ^ q_i=\hat{a_i}I_i + \hat{b_i}

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

#define WINDOW_NAME "【final img】"



Mat g_Src, g_Dst1, g_Dst2, g_Dst3, g_Dst4, g_Dst5,g_Dst6;
int g_BoxFilterVal = 1;//盒式滤波内核值
int g_MeanBlurVal = 1;//均值滤波内核值
int g_GaussianVal = 1;//高斯滤波内核值
int g_MedianVal = 1;//中值滤波内核值
int g_BilateralVal = 1;//双边滤波内核值
int g_GuidedVal = 1;//导向滤波内核值
const int g_MaxVal = 20;//预设滑动条最大值


static void on_BoxFilter(int, void*);//盒式滤波器
static void on_MeanBlur(int, void*);//均值滤波器
static void on_GaussianBlur(int, void*);//高斯滤波器
static void on_MedianBlur(int, void*);//中值滤波器
static void on_BilateralFilter(int, void*);//双边滤波器
static void on_GuidedFilter(int, void*);//导向滤波器
Mat guideFilter(Mat& src, Mat& guideMat, int radius, double epsilon);//导向滤波器


int main() {

	g_Src = imread("E:/File/002.jpg");
	if (g_Src.empty()) {
		cout << "读取图片有误,请重新输入正确路径\n";
		system("pause");
		return -1;
	}
	imshow("源图像",g_Src);

	namedWindow(WINDOW_NAME);
	createTrackbar("方框滤波", WINDOW_NAME, &g_BoxFilterVal, g_MaxVal, on_BoxFilter);
	on_BoxFilter(g_BoxFilterVal, 0);
	
	createTrackbar("均值滤波", WINDOW_NAME, &g_MeanBlurVal, g_MaxVal, on_MeanBlur);
	on_MeanBlur(g_MeanBlurVal, 0);
	
	createTrackbar("高斯滤波", WINDOW_NAME, &g_GaussianVal, g_MaxVal, on_GaussianBlur);
	on_GaussianBlur(g_GaussianVal, 0);
	
	createTrackbar("中值滤波", WINDOW_NAME, &g_MedianVal, g_MaxVal, on_MedianBlur);
	on_MedianBlur(g_MedianVal, 0);
	
	createTrackbar("双边滤波", WINDOW_NAME, &g_BilateralVal, g_MaxVal, on_BilateralFilter);
	on_BilateralFilter(g_BilateralVal, 0);
	
	createTrackbar("导向滤波", WINDOW_NAME, &g_GuidedVal, g_MaxVal, on_GuidedFilter);
	on_GuidedFilter(g_GuidedVal, 0);

	

	cout << "\t按下\'q\'健,退出程序~  \n\n";
	while (char(waitKey(1)) != 'q') {

	}

	destroyAllWindows();
	//waitKey(0);
}

void on_BoxFilter(int, void*) {//盒式滤波器

	boxFilter(g_Src, g_Dst1, -1, Size(g_BoxFilterVal * 2 + 1, g_BoxFilterVal * 2 + 1));
	cout << "\n当前为【盒式滤波】处理效果,内核大小>>>" << g_BoxFilterVal * 2 + 1 << endl;
	imshow(WINDOW_NAME, g_Dst1);

}
void on_MeanBlur(int, void*) {//均值滤波器

	//参数4:表示锚点,即被平滑的那个点。如果这个点坐标是负值的话,就表示取核的中心点为锚点,所以默认值Point(-1,-1)表示这个锚点在核的中心。
	blur(g_Src, g_Dst2, Size(g_MeanBlurVal * 2 + 1, g_MeanBlurVal * 2 + 1),Point(-1,-1));
	cout << "\n当前为【均值滤波】处理效果,内核大小>>>" << g_MeanBlurVal * 2 + 1 << endl;
	imshow(WINDOW_NAME, g_Dst2);


}
void on_GaussianBlur(int, void*) {//高斯滤波器

	GaussianBlur(g_Src, g_Dst3, Size(g_GaussianVal * 2 + 1, g_GaussianVal * 2 + 1)  ,0,0);
	cout << "\n当前为【高斯滤波】处理效果,内核大小>>>" << g_GaussianVal * 2 + 1 << endl;
	imshow(WINDOW_NAME, g_Dst3);

}
void on_MedianBlur(int, void*) {//中值滤波器


	medianBlur(g_Src, g_Dst4, g_MedianVal * 2 + 1);
	cout << "\n当前为【中值滤波】处理效果,内核大小>>>" << g_MedianVal * 2 + 1 << endl;
	imshow(WINDOW_NAME, g_Dst4);


}
void on_BilateralFilter(int, void*) {//双边滤波器

	bilateralFilter(g_Src, g_Dst5, g_BilateralVal, g_BilateralVal * 2, g_BilateralVal / 2);
	cout << "\n当前为【双边滤波】处理效果,内核大小>>>" << g_BilateralVal  << endl;
	imshow(WINDOW_NAME, g_Dst5);



}
void on_GuidedFilter(int, void*) {//导向滤波器

	vector<Mat>  vSrc, vResult;
	split(g_Src, vSrc);
	for (int i = 0; i < 3; ++i) {
		Mat temp;
		vSrc[i].convertTo(temp, CV_64FC1, 1.0 / 255.0);
		Mat cloneImg = temp.clone();
		Mat dst = guideFilter(temp, cloneImg, g_GuidedVal * 2 + 1, 0.01);
		vResult.push_back(dst);

	}
	merge(vResult, g_Dst6);
	cout << "\n当前处理为【导向滤波】,其内核大小为:" << g_GuidedVal * 2 + 1 << endl;
	imshow(WINDOW_NAME, g_Dst6);


}
Mat guideFilter(Mat& src, Mat& guideMat, int radius, double epsilon) {//导向滤波器


	//转换源图像信息,将输入扩展成64位浮点型,便于之后做乘法
	src.convertTo(src, CV_64FC1);
	guideMat.convertTo(guideMat, CV_64FC1);

	//计算均值
	Mat meanP, meanI, meanIP, meanII;
	//生成待滤波图像均值meanP
	boxFilter(src, meanP, CV_64FC1, Size(radius, radius));
	//生成引导图像均值meanI
	boxFilter(guideMat, meanI, CV_64FC1, Size(radius, radius));
	//生成互相关均值meanIP
	boxFilter(src.mul(guideMat), meanIP, CV_64FC1, Size(radius, radius));
	//生成引导图像自相关均值meanII
	boxFilter(guideMat.mul(guideMat), meanII, CV_64FC1, Size(radius, radius));


	//计算IP的协方差cov和I的方差var
	Mat conIP = meanIP - meanI.mul(meanP);
	Mat varI = meanII - meanI.mul(meanI);


	//计算系数a\b  ---- 通过最小二乘法得到下面公式
	Mat a = conIP / (varI + epsilon);
	Mat b = meanP - a.mul(meanI);

	//计算a,b的均值
	Mat meanA, meanB;
	boxFilter(a, meanA, CV_64FC1, Size(radius, radius));
	boxFilter(b, meanB, CV_64FC1, Size(radius, radius));


	//生成输出矩阵
	Mat dst = meanA.mul(src) + meanB;
	return dst;


}

在这里插入图片描述

参考:《Opencv3编程入门》
https://blog.csdn.net/sinat_36264666/article/details/77990790

猜你喜欢

转载自blog.csdn.net/weixin_41374099/article/details/86618198
今日推荐