双边滤波(Bilateral Filtering)

双边滤波(Bilateral Filtering)

1、基本思路

双边滤波(Bilateral Filtering)的基本思路是同时考虑像素点的空域信息和值域信息。即先根据像素值对要用来进行滤波的邻域做一个分割或分类,再给该点所属的类别相对较高的权重,然后进行邻域加权求和,得到最终结果。

2、实现原理

在 Bilateral Filtering 中,两个要素即:空域和值域 ,其数学表达方式相近,如下:

其中积分号前面k为归一化因子,这是考虑对所有的像素点进行加权,c 和 s 是closeness 和 similarity函数,x代表要求的点,f(x)代表该点的像素值。f(x) -->h(x)为滤波前后的图像,我们最后的滤波函数为:

由于空域部分,使得滤波特性较好,由于值域部分,使得边缘保持较好。

下图示意了有边缘的时候的权重和最后的滤波结果,可以看出权重在边界有很明显的分界,从而几乎只对自己所属的边缘一侧的像素点进行加权。

实现 c 和 s 两个函数的一种方法即 Gaussian 核,决定其性质的为各自的sigma参数,即 σd 和 σr

其中,其中,

3、参数讨论

对于空域的Gaussian滤波不需要过多介绍,对于值域滤波,即不考虑空间只考虑像素点的相似性进行加权的结果,值域滤波只是对待滤波图像的直方图的一个变换,而对于单峰值的直方图,值域滤波将值域范围向着峰值的中间即均值方向压缩。

扫描二维码关注公众号,回复: 15135010 查看本文章

对于参数的选取,进行如下讨论:

首先,两个 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 无穷大,相当于空域高斯滤波。

4、离散数学公式模型

其中,和分别是空间域和值域的滤波参数(不确定度),和分别是像素点、的像素值。归一化权重系数为:

双边滤波的核函数是空间域核和像素值域核的综合结果:在图像的平坦区域,像素值变化很小,对应的像素值域权重接近于1,此时空间域权重起主要作用,相当于高斯模糊;在图像的边缘区域,像素值变化很大,像值域权重变大,从而保持了边缘的信息。

5、双边滤波代码实现

 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<float> _color_weight(cn*256);
     vector<float> _space_weight(d*d);
     vector<int> _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;
             }
         }
     }
 }
 ​

猜你喜欢

转载自blog.csdn.net/liangfei868/article/details/124680471