姓名:张右润
学号:19021210648
转载自:https://blog.csdn.net/sinat_36264666/article/details/77990790
【嵌牛导读】引导滤波是由何凯明等人于2010年发表在ECCV的文章《Guided Image Filtering》中提出的,后续于2013年发表了改进算法快速引导滤波的实现。它与双边滤波最大的相似之处,就是同样具有保持边缘特性。该模型认为,某函数上一点与其邻近部分的点成线性关系,一个复杂的函数就可以用很多局部的线性函数来表示,当需要求该函数上某一点的值时,只需计算所有包含该点的线性函数的值并做平均即可。这种模型,在表示非解析函数上,非常有用。
【嵌牛鼻子】引导滤波器 数字图像处理
【嵌牛提问】引导滤波如何用opencv实现?
【嵌牛正文】
常言道“温故而知新”,写此文章就是对自己目前学习内容的小小的总结与记录。
本文力求用最简洁的语言,详细的代码将此部分内容讲解清楚,但由于博主同样是刚刚接触OpenCV,或许表达上有些瑕疵,还望读者能够指教探讨,大家共同进步。
博主机器配置为:VS2013+opencv2.4.13+Win-64bit。
若本文能给读者带来一点点启示与帮助,我就很开心了。
====================分割线====================
1-引导滤波/导向滤波(Guided Filter)
引导滤波(导向滤波)是一种图像滤波技术,通过一张引导图,对初始图像p(输入图像)进行滤波处理,使得最后的输出图像大体上与初始图像P相似,但是纹理部分与引导图相似。其典型应用有两个:保边图像平滑,抠图。
引导滤波(导向滤波)的目的是,保持双边滤波的优势(有效保持边缘,非迭代计算),而克服双边滤波的缺点(设计一种时间复杂度为 O(1) 的快速滤波器,而且在主要边缘附近没有梯度的变形)。
引导滤波(导向滤波)不仅能实现双边滤波的边缘平滑,而且在检测到边缘附近有很好的表现,可应用在图像增强、HDR压缩、图像抠图及图像去雾等场景。
导向图滤波的应用1. 保边图像平滑
当引导图与输入图 p 为同一个图像的时候,导向滤波的效果与双边滤波的效果类似,但是不同于双边滤波的是,导向滤波可以很容易设计一个与滤波半径无关的优化算法。其中窗口半径为平滑半径,参数为平滑项参数,其值越大平滑的越明显。2. 抠图
当输入图p为一个初始的mask图像时,导向滤波的效果类似于抠图算法,其中窗口半径为抠图的半径,参数为平滑项。
==================分割线=============
2-引导滤波算法原理【重点部分】
引导滤波是由何凯明等人于2010年发表在ECCV的文章《Guided Image Filtering》中提出的,后续于2013年发表了改进算法快速引导滤波的实现。它与双边滤波最大的相似之处,就是同样具有保持边缘特性。在引导滤波的定义中,用到了局部线性模型,至于该模型,可以暂时用下图简单的理解:
该模型认为,某函数上一点与其邻近部分的点成线性关系,一个复杂的函数就可以用很多局部的线性函数来表示,当需要求该函数上某一点的值时,只需计算所有包含该点的线性函数的值并做平均即可。这种模型,在表示非解析函数上,非常有用。
----------------------------------【1步】----------------------------------
同理,我们可以认为图像是一个二维函数,而且没法写出解析表达式,因此我们假设该引导滤波函数的输出与输入在一个二维窗口内满足线性关系,如下:
其中,q是输出像素的值,是输入图像的值,i和k是像素索引,a和b是当窗口中心位于k时该线性函数的系数。
其实,输入图像不一定是待滤波的图像本身,也可以是其它的图像即引导图像,这也是为何称为引导滤波的原因。
对上式两边取梯度,可以得到:
即当输入图像有梯度时,输出 q 也有类似的梯度,现在可以解释为什么引导滤波有边缘保持特性了。
这与在去雾、超分辨率、抠图等研究中使用的模型是一致的。
q 即 p去除噪声或者纹理之后的图像:
其中,表示噪声。
引导滤波的简易流程见下图:
----------------------------------【2步】----------------------------------
下一步是求出线性函数的系数,也就是线性回归,即希望拟合函数的输出值 q与真实值 p 之间的差距最小,转化为最优化问题,也就是让下式最小:
这里p只能是待滤波图像,并不像那样可以是其它图像。
同时,a之前的系数是用于防止求得的a过大,也是调节滤波器滤波效果的重要参数。
通过最小二乘法,我们可以得到:
其中,是图像在窗口中的平均值,是待滤波图像p在窗口中的均值,是在窗口中的方差,是窗口中像素的数量。
----------------------------------【3步】----------------------------------
在计算每个窗口的线性系数时,我们可以发现一个像素会被多个窗口包含,也就是说,每个像素都由多个线性函数所描述。因此,如之前所说,要具体求某一点的输出值时,只需将所有包含该点的线性函数值平均即可,如下:
这里,是所有包含像素i的窗口,k是其中心位置。
------------------------------------------------------------------------------------------------------
其中的一些说明:
当引导图为输入图像时,引导滤波就成为一个保持边缘的滤波操作,即规整参数 = p。
如果规整参数=0,显然a=1, b=0是E(a,b)为最小值的解,从上式可以看出,这时的滤波器没有任何作用,将输入原封不动的输出。
如果规整参数>0,在像素强度变化小的区域(或单色区域),有a近似于(或等于)0,而b近似于(或等于),即做了一个加权均值滤波;
而在变化大的区域,a近似于1,b近似于0,对图像的滤波效果很弱,有助于保持边缘。
而的作用就是界定什么是变化大,什么是变化小。在窗口大小不变的情况下,随着的增大,滤波效果越明显。
在滤波效果上,引导滤波和双边滤波差不多,在一些细节上,引导滤波较好。引导滤波最大的优势在于,可以写出时间复杂度与窗口大小无关的算法,因此在使用大窗口处理图片时,其效率更高。
==============分割线=================
3-引导滤波实现步骤说明
整体实现步骤如下:
1.利用boxFilter滤波器完成相关系数参数,其中均值包括引导图像均值、原始待滤波图像均值、互相关均值及自相关均值。
2.根据均值计算相关系数参数,包括自相关方差var,互相关协方差cov。
3.计算窗口线性变换参数系数a、b。
4.根据公式计算参数a、b的均值。
5.利用参数得到引导滤波输出图像q。
--------------------------------------【1步】---------------------------------------
对步骤的内容参数计算解释:
实现这种算法的关键思想是盒式滤波(box filter),而且必须是通过积分图来实现的盒式滤波,否则不可能与窗口大小无关,好在OpenCV的boxFilter()函数满足这个要求。
首先来看看引导滤波的公式:
我们主要是求a与b的值。
先计算的分子部分:
分子第一项, 是在窗口中的和,再除以窗口中像素的个数,刚好就是盒式滤波,因此我们可以将输入的引导图像和待滤波图像 p 相乘,并对相乘后的图像做box filtering,即得第一项的结果。
分子第二项 ,和分别为和 p 在窗口中均值,因此分别对和 p 进行box filtering,再将box filtering之后的结果相乘即可。
实际上,ak的分子就是在窗口中的协方差。
接下来计算ak的分母部分。
是引导图在窗口中的方差,学过概率论与数理统计的同学应该知道,方差和期望(均值)之间是有关系的,如下式:
因此在计算 的方差时,我们可以先计算的均值,再减去均值的平方即的平方。在方法上,计算的均值和计算的均值是一样的。
最后,对计算出来的方差图像,加上常量(每个元素都加),分母就计算完了,自然,在所有窗口中的值也就得到了。
而的计算类似:
第一项:为 p 在窗口中均值,因此对 p 进行box filtering。
第二项:为在窗口中均值,因此对进行box filtering,并与相乘。
第一项减去第二项即为的值。
注意,我们的计算都是对整个图像的,以图像为单位进行计算。此时我们将得到两张图,的图(左边)和的图(右边),如下:
在图中可以看到,在边缘部分或变化剧烈的部分,的值接近于1(白色),的值接近为0(黑色);
而在变化平坦的区域,的值接近0(黑色),的值为平坦区域像素的均值。这与上一部分,橘红色标准的说明中所说的规律是一致的。
---------------------------------------【2步】----------------------------------------
接下来看看第二个公式,即我们最后要得到的滤波后图像 q :
其中,输出值 q 又与两个均值有关,分别为a和b在窗口中的均值,所以还是box filtering,我们将上一步得到两个图像都进行盒式滤波,得到两个新图:和。然后用i乘以引导图像 ,再加上,即得最终滤波之后的输出图像q。
代码:
/*
功能:图像引导滤波操作
*/
#include
#include
#include
#include
using namespace std;
using namespace cv;
//-------------------【全局函数声明部分】--------------------------------------
Mat guidedFilter(Mat &srcMat, Mat &guidedMat, int radius, double eps);//引导滤波器
int main()
{
//------------【0】定义相关变量-------------
Mat resultMat; //最后结果图像
vector
//------------【1】读取源图像并检查图像是否读取成功------------
Mat srcImage = imread("D:\\OutPutResult\\ImageTest\\boat1.jpg");
if (!srcImage.data)
{
cout << "读取图片错误,请重新输入正确路径!\n";
system("pause");
return -1;
}
imshow("【源图像】", srcImage);
//-------【2】对源图像进行通道分离,并对每个分通道进行导向滤波操------
split(srcImage, vSrcImage);
for (int i = 0; i < 3; i++)
{
Mat tempImage;
vSrcImage[i].convertTo(tempImage, CV_64FC1, 1.0 / 255.0);//将分通道转换成浮点型数据
Mat cloneImage = tempImage.clone(); //将tempImage复制一份到cloneImage
Mat resultImage = guidedFilter(tempImage, cloneImage, 5, 0.01);//对分通道分别进行导向滤波,半径为1、3、5...等奇数
vResultImage.push_back(resultImage);//将分通道导向滤波后的结果存放到vResultImage中
}
//----------【3】将分通道导向滤波后结果合并-----------------------
merge(vResultImage, resultMat);
resultMat.convertTo(resultMat, CV_8UC3, 255);//将归一化的数字,回到0-255区间
imshow("【引导滤波/导向滤波】", resultMat);
waitKey(0);
return 0;
}
//导向滤波器
Mat guidedFilter(Mat &srcMat, Mat &guidedMat, int radius, double eps)
{
//------------【0】转换源图像信息,将输入扩展为64位浮点型,以便以后做乘法------------
srcMat.convertTo(srcMat, CV_64FC1);
guidedMat.convertTo(guidedMat, CV_64FC1);
//--------------【1】各种均值计算----------------------------------
Mat mean_p, mean_I, mean_Ip, mean_II;
boxFilter(srcMat, mean_p, CV_64FC1, Size(radius, radius));//生成待滤波图像均值mean_p
boxFilter(guidedMat, mean_I, CV_64FC1, Size(radius, radius));//生成引导图像均值mean_I
boxFilter(srcMat.mul(guidedMat), mean_Ip, CV_64FC1, Size(radius, radius));//生成互相关均值mean_Ip
boxFilter(guidedMat.mul(guidedMat), mean_II, CV_64FC1, Size(radius, radius));//生成引导图像自相关均值mean_II
//--------------【2】计算相关系数,计算Ip的协方差cov和I的方差var------------------
Mat cov_Ip = mean_Ip - mean_I.mul(mean_p);
Mat var_I = mean_II - mean_I.mul(mean_I);
//---------------【3】计算参数系数a、b-------------------
Mat a = cov_Ip / (var_I + eps);
Mat b = mean_p - a.mul(mean_I);
//--------------【4】计算系数a、b的均值-----------------
Mat mean_a, mean_b;
boxFilter(a, mean_a, CV_64FC1, Size(radius, radius));
boxFilter(b, mean_b, CV_64FC1, Size(radius, radius));
//---------------【5】生成输出矩阵------------------
Mat dstImage = mean_a.mul(srcMat) + mean_b;
return dstImage;
}
5-显示结果
=========================分割线======================
6-程序说明
在本程序中,半径为5,参数为0.01 ,处理效果见上图。
代码中mul() 的说明:形如A.mul(B):Opencv中mul()会计算两个Mat矩阵对应位的乘积,所以要求参与运算的矩阵A的行列和B的行列数一致。计算结果是跟A或B行列数一致的一个Mat矩阵。
最后我们来看下,双边滤波与引导滤波在细节的一些差异,见下图:
可以发现,双边滤波在细节波动比较大,因为它们周围的像素几乎没有相似的颜色,高斯加权平均数据很少,变得不可靠。
另一方面,引导滤波更好地保留了图像中的梯度信息。
参考文献:
1.Guided Image Filtering 2010年
2.Guided Image Filtering 2013年
3.双边滤波与引导滤波