不太理解目标函数的意义,等后续有新的理解来补充
-
引导滤波
1.1 概念讲解
引导滤波与双边滤波最大的相似之处,就是同样具有保持边缘特性。在引导滤波的定义中,用到了局部线性模型,至于该模型,可以暂时用下图简单的理解
![](https://i-blog.csdnimg.cn/blog_migrate/2ac2a40199ae4b7d484c6153dfd9b3c7.png)
该模型认为,某函数上一点与其邻近部分的点成线性关系,一个复杂的函数就可以用很多局部的线性函数来表示,当需要求该函数上某一点的值时,只需计算所有包含该点的线性函数的值并做平均即可。这种模型,在表示非解析函数上,非常有用。
同理,我们可以认为图像是一个二维函数,而且没法写出解析表达式,因此我们假设该函数的输出与输入在一个二维窗口内满足线性关系,如下
![](https://i-blog.csdnimg.cn/blog_migrate/0392a934b4eaf91b7440dca6a700c71d.png)
其中,q是输出像素的值,I是输入图像的值,i和k是像素索引,a和b是当窗口中心位于k时该线性函数的系数。其实,输入图像不一定是待滤波的图像本身,也可以是其他图像即引导图像,这也是为何称为引导滤波的原因。对上式两边取梯度,可以得到
![](https://i-blog.csdnimg.cn/blog_migrate/14ebd5b96cae850addb4724c5830950b.png)
即当输入图像I有梯度时,输出q也有类似的梯度,现在可以解释为什么引导滤波有边缘保持特性了。
下一步是求出线性函数的系数,也就是线性回归,即希望拟合函数的输出值与真实值p之间的差距最小,也就是让下式最小
![](https://i-blog.csdnimg.cn/blog_migrate/b1a5150743f76d88ab9b286f46d49298.png)
这里p只能是待滤波图像,并不像I那样可以是其他图像。同时,a之前的系数(以后都写为e)用于防止求得的a过大,也是调节滤波器滤波效果的重要参数。通过最小二乘法,我们可以得到
![](https://i-blog.csdnimg.cn/blog_migrate/419d6af6967c9ebf0f269fc102eb3999.png)
其中,是I在窗口w_k中的平均值,
是I在窗口w_k中的方差,
是窗口w_k中像素的数量,
是待滤波图像p在窗口w_k中的均值。
在计算每个窗口的线性系数时,我们可以发现一个像素会被多个窗口包含,也就是说,每个像素都由多个线性函数所描述。因此,如之前所说,要具体求某一点的输出值时,只需将所有包含该点的线性函数值平均即可,如下
![](https://i-blog.csdnimg.cn/blog_migrate/b5e24ece4ca29bb8d59491ef17500b6f.png)
这里,w_k是所有包含像素i的窗口,k是其中心位置。
1.2 参数作用
当把引导滤波用作边缘保持滤波器时,往往有 I = p ,如果e=0,显然a=1, b=0是E(a,b)为最小值的解,从上式可以看出,这时的滤波器没有任何作用,将输入原封不动的输出。如果e>0,在像素强度变化小的区域(或单色区域),有a近似于(或等于)0,而b近似于(或等于),即做了一个加权均值滤波;而在变化大的区域,a近似于1,b近似于0,对图像的滤波效果很弱,有助于保持边缘。而e的作用就是界定什么是变化大,什么是变化小。在窗口大小不变的情况下,随着e的增大,滤波效果越明显。
在滤波效果上,引导滤波和双边滤波差不多,在一些细节上,引导滤波较好。引导滤波最大的优势在于,可以写出时间复杂度与窗口大小无关的算法(打算在之后的文章中讨论),因此在使用大窗口处理图片时,其效率更高。
2. 时间复杂度与窗口大小
我们也知道引导滤波可以写出时间复杂度与窗口大小无关的算法,现在就来使用C++并借助OpenCV实现这一算法。
实现这种算法的关键思想是盒式滤波(box filter),而且必须是通过积分图来实现的盒式滤波,否则不可能与窗口大小无关,好在OpenCV的boxFilter函数满足这个要求。
再看看引导滤波的公式
![](https://i-blog.csdnimg.cn/blog_migrate/d3f58986f6adb516c9024163eebeca16.png)
先计算a_k的分子,Ip 在窗口w_k中的和,再除以窗口中像素的个数,刚好就是盒式滤波,因此我们可以将输入的引导图像 I 和滤波图像 p 相乘,并对相乘后的图像做box filtering,即得第一项的结果。后面的和分别为 I 和 p 在窗口w_k中均值,因此分别对 I 和 p 进行box filtering,再将box filtering之后的结果相乘即可。实际上,a_k的分子就是 Ip 在窗口w_k中的协方差。
接下来计算a_k的分母部分。是引导图 I 在窗口w_k中的方差,学过概率论与数理统计的朋友应该知道,方差和期望(均值)之间是有关系的,如下式
![](https://i-blog.csdnimg.cn/blog_migrate/d3c85cd7baa38d2c7003578639fffc19.png)
因此在计算 I 的方差时,我们可以先计算 I*I 的均值,再减去 I 均值的平方即的平方。在方法上,计算 I*I 的均值和计算 Ip 的均值是一样的。最后,对计算出来的方差图像,加上常量e(每个元素都加e),分母就计算完了,自然,a_k在所有窗口中的值也就得到了。b_k的计算太简单了,大家都懂的。
注意,我们的计算都是对整个图像的,以图像为单位进行计算,所以最后算出的也是两张图,a_k的图(左边)和b_k的图(右边),如下
![](https://i-blog.csdnimg.cn/blog_migrate/618d6d7758e42c35c1197cec1172907b.png)
在图中可以看到,在边缘部分或变化剧烈的部分,a的值接近于1(白色),b的值接近为0(黑色),而在变化平坦的区域,a的值接近0(黑色),b的值为平坦区域像素的均值。这与上一篇文章中所说的规律是一致的。
下面看第二个公式
![](https://i-blog.csdnimg.cn/blog_migrate/65f3c6593760f9022634347d6515dfaa.png)
输出值q又与两个均值有关,分别为a和b在窗口w_i中的均值(不是w_k),所以还是box filtering,我们将上一步得到两个图像都进行盒式滤波,得到两个新图:a_i和b_i,然后用a_i乘以引导图像 I ,再加上b_i,即得最终滤波之后的输出,如下(左边为原图,右边为滤波之后的图像,其中滤波窗口半径为8,e的值为500):
![](https://i-blog.csdnimg.cn/blog_migrate/62fc162a80fbc1b0016a354351fd98f0.png)
3. 代码
下面是整个算法的代码,仅供参考
void guidedFilter(Mat& source, Mat& guided_image, Mat& output, int radius, float epsilon)
{
CV_Assert(radius >= 2 && epsilon > 0);
CV_Assert(source.data != NULL && source.channels() == 1);
CV_Assert(guided_image.channels() == 1);
CV_Assert(source.rows == guided_image.rows && source.cols == guided_image.cols);
Mat guided;
if (guided_image.data == source.data)
{
//make a copy
guided_image.copyTo(guided);
}
else
{
guided = guided_image;
}
//将输入扩展为32位浮点型,以便以后做乘法
Mat source_32f, guided_32f;
makeDepth32f(source, source_32f);
makeDepth32f(guided, guided_32f);
//计算I*p和I*I
Mat mat_Ip, mat_I2;
multiply(guided_32f, source_32f, mat_Ip);
multiply(guided_32f, guided_32f, mat_I2);
//计算各种均值
Mat mean_p, mean_I, mean_Ip, mean_I2;
Size win_size(2*radius + 1, 2*radius + 1);
boxFilter(source_32f, mean_p, CV_32F, win_size);
boxFilter(guided_32f, mean_I, CV_32F, win_size);
boxFilter(mat_Ip, mean_Ip, CV_32F, win_size);
boxFilter(mat_I2, mean_I2, CV_32F, win_size);
//计算Ip的协方差和I的方差
Mat cov_Ip = mean_Ip - mean_I.mul(mean_p);
Mat var_I = mean_I2 - mean_I.mul(mean_I);
var_I += epsilon;
//求a和b
Mat a, b;
divide(cov_Ip, var_I, a);
b = mean_p - a.mul(mean_I);
//对包含像素i的所有a、b做平均
Mat mean_a, mean_b;
boxFilter(a, mean_a, CV_32F, win_size);
boxFilter(b, mean_b, CV_32F, win_size);
//计算输出 (depth == CV_32F)
output = mean_a.mul(guided_32f) + mean_b;
}
void makeDepth32f(Mat& source, Mat& output)
{
if (source.depth() != CV_32F ) > FLT_EPSILON)
source.convertTo(output, CV_32F);
else
output = source;
}
参考
引导滤波的OpenCV实现_实现这种算法的关键思想是盒式滤波(box filter),而且必须是通过积分图来实现的盒-CSDN博客
求导过程:导向滤波(Guided Filter)公式详解-CSDN博客
论文:Microsoft Research – Emerging Technology, Computer, and Software Research