双边滤波(Bilateral Filtering)
1、基本思路
双边滤波(Bilateral Filtering)的基本思路是同时考虑像素点的空域信息和值域信息。即先根据像素值对要用来进行滤波的邻域做一个分割或分类,再给该点所属的类别相对较高的权重,然后进行邻域加权求和,得到最终结果。
在 Bilateral Filtering 中,两个要素即:空域和值域 ,其数学表达方式相近,如下:
其中积分号前面k为归一化因子,这是考虑对所有的像素点进行加权,c 和 s 是closeness 和 similarity函数,x代表要求的点,f(x)代表该点的像素值。f(x) -->h(x)为滤波前后的图像,我们最后的滤波函数为:
由于空域部分,使得滤波特性较好,由于值域部分,使得边缘保持较好。
下图示意了有边缘的时候的权重和最后的滤波结果,可以看出权重在边界有很明显的分界,从而几乎只对自己所属的边缘一侧的像素点进行加权。
实现 c 和 s 两个函数的一种方法即 Gaussian 核,决定其性质的为各自的sigma参数,即 σd 和 σr
其中,其中,
对于空域的Gaussian滤波不需要过多介绍,对于值域滤波,即不考虑空间只考虑像素点的相似性进行加权的结果,值域滤波只是对待滤波图像的直方图的一个变换,而对于单峰值的直方图,值域滤波将值域范围向着峰值的中间即均值方向压缩。
对于参数的选取,进行如下讨论:
首先,两个 sigma 值为 kernel 的方差,方差越大,说明权重差别越小,因此表示不强调这一因素的影响,反之,则表示更强调这一因素导致的权重的不均衡。因此:
两个方面的某个的 sigma 相对变小 表示这一方面相对较重要,得到强调。如 sigma_d 变小,表示更多采用近邻的值作平滑,说明图像的空间信息更重要,即相近相似。如 sigma_r 变小,表示和自己同一类的条件变得苛刻,从而强调值域的相似性。
其次,sigma_d 表示的是空域的平滑,因此对于没有边缘的,变化慢的部分更适合;sigma_r 表示值域的差别,因此强调这一差别,即减小 sigma_r 可以突出边缘。
sigma_d 变大,图像每个区域的权重基本都源于值域滤波的权重,因此对于空间邻域信息不是很敏感;sigma_r 变大,则不太考虑值域,权重多来自于空间距离,因此近似于普通的高斯滤波,图像的保边性能下降。因此如果像更多的去除平滑区域的噪声,应该提高 sigma_d ,如果像保持边缘,则应该减小 sigma_r 。
极端情况,如果 sigma_d 无穷大,相当于值域滤波;sigma_r 无穷大,相当于空域高斯滤波。
其中,和分别是空间域和值域的滤波参数(不确定度),和分别是像素点、的像素值。归一化权重系数为:
双边滤波的核函数是空间域核和像素值域核的综合结果:在图像的平坦区域,像素值变化很小,对应的像素值域权重接近于1,此时空间域权重起主要作用,相当于高斯模糊;在图像的边缘区域,像素值变化很大,像值域权重变大,从而保持了边缘的信息。
void BilateralFilter( const Mat& src, Mat& dst, int d, double sigma_color, double sigma_space, int borderType )
{
int cn = src.channels();
int i, j, k, maxk, radius;
Size size = src.size();
CV_Assert( (src.type() == CV_8UC1 || src.type() == CV_8UC3) &&
src.type() == dst.type() && src.size() == dst.size() &&
src.data != dst.data );
if( sigma_color <= 0 )
{
sigma_color = 1;
}
if( sigma_space <= 0 )
{
sigma_space = 1;
}
// 计算颜色域和空间域的权重的高斯核系数, 均值 μ = 0; exp(-1/(2*sigma^2))
double gauss_color_coeff = -0.5/(sigma_color*sigma_color);
double gauss_space_coeff = -0.5/(sigma_space*sigma_space);
// radius 为空间域的大小: 其值是 windosw_size 的一半
if( d <= 0 )
{
radius = cvRound(sigma_space*1.5);
}
else
{
radius = d/2;
}
radius = MAX(radius, 1);
d = radius*2 + 1;
Mat temp;
copyMakeBorder( src, temp, radius, radius, radius, radius, borderType );
vector _color_weight(cn*256);
vector _space_weight(d*d);
vector _space_ofs(d*d);
float* color_weight = &_color_weight[0];
float* space_weight = &_space_weight[0];
int* space_ofs = &_space_ofs[0];
// 初始化颜色相关的滤波器系数: exp(-1*x^2/(2*sigma^2))
for( i = 0; i < 256*cn; i++ )
{
color_weight[i] = (float)std::exp(i*i*gauss_color_coeff);
}
// 初始化空间相关的滤波器系数和 offset:
for( i = -radius, maxk = 0; i <= radius; i++ )
{
j = -radius;
for( ;j <= radius; j++ )
{
double r = std::sqrt((double)i*i + (double)j*j);
if( r > radius )
{
continue;
}
space_weight[maxk] = (float)std::exp(r*r*gauss_space_coeff);
space_ofs[maxk++] = (int)(i*temp.step + j*cn);
}
}
// 开始计算滤波后的像素值
for( i = 0; i < 0, size.height; i++ )
{
const uchar* sptr = temp->ptr(i+radius) + radius*cn; // 目标像素点
uchar* dptr = dest->ptr(i);
if( cn == 1 )
{
// 按行开始遍历
for( j = 0; j < size.width; j++ )
{
float sum = 0, wsum = 0;
int val0 = sptr[j];
// 遍历当前中心点所在的空间邻域
for( k = 0; k < maxk; k++ )
{
int val = sptr[j + space_ofs[k]];
float w = space_weight[k] * color_weight[std::abs(val - val0)];
sum += val*w;
wsum += w;
}
// 这里不可能溢出, 因此不必使用 CV_CAST_8U.
dptr[j] = (uchar)cvRound(sum/wsum);
}
}
else
{
assert( cn == 3 );
for( j = 0; j < size.width*3; j += 3 )
{
float sum_b = 0, sum_g = 0, sum_r = 0, wsum = 0;
int b0 = sptr[j], g0 = sptr[j+1], r0 = sptr[j+2];
k = 0;
for( ; k < maxk; k++ )
{
const uchar* sptr_k = sptr + j + space_ofs[k];
int b = sptr_k[0], g = sptr_k[1], r = sptr_k[2];
float w = space_weight[k] * color_weight[std::abs(b - b0) + std::abs(g - g0) + std::abs(r - r0)];
sum_b += b*w;
sum_g += g*w;
sum_r += r*w;
wsum += w;
}
wsum = 1.f/wsum;
b0 = cvRound(sum_b*wsum);
g0 = cvRound(sum_g*wsum);
r0 = cvRound(sum_r*wsum);
dptr[j] = (uchar)b0;
dptr[j+1] = (uchar)g0;
dptr[j+2] = (uchar)r0;
}
}
}
}