双边滤波(Bilateral Filtering)-程序员宅基地

技术标签: 计算机视觉  图像处理  图像处理及三维重建  opencv  

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

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

首先,两个 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;
             }
         }
     }
 }
 ​
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/liangfei868/article/details/124680471

智能推荐

HTML5颜色渐变3D文字特效-程序员宅基地

文章浏览阅读243次。在线演示 本地下载 _html5 文字3d

架构设计与模式之:容器化与云原生架构设计模式_云原生的容器架构-程序员宅基地

文章浏览阅读1.9k次。当今,企业越来越依赖云计算服务来获得快速、经济和弹性伸缩的能力。云原生架构正逐渐成为主流,而容器技术也已经在为企业提供更灵活、更高效的开发环境。本文将从云原生架构和容器技术的角度出发,结合实际应用场景,系统全面剖析容器化及云原生架构的设计模式及优缺点,并为读者提供参考指导。云原生(Cloud Native)的概念源于 Google 在 Kubernetes 上构建的容器编排系统 - Google Container Engine(GKE)。它是一种关于基础设施如何被设计来支持应用的观念。_云原生的容器架构

东大计算机博士待遇,【东南大学工资】博士研究生待遇-看准网-程序员宅基地

文章浏览阅读359次。东南大学坐落于南京市,是中央直管、教育部直属的副部级全国重点大学,中国著名的建筑老八校之一,国家“211工程”、“985工程”、“卓越工程师教育培养计划”、“卓越医生教育培养计划”和“111计划”重点建设的大学,“卓越大学联盟”、“中俄工科大学联盟”、“中欧工程教育平台”主要成员。学校是中国最早建立的高等学府之一,素有“学府圣地”和“东南学府第一流”之美誉。东南大学前身是创建于1902年的三江师范..._东南大学计算机博士待遇

Spring中Bean的配置方式之通过全类名(反射)-程序员宅基地

文章浏览阅读338次。反射转载于:https://www.cnblogs.com/zhlzy/p/7271772.html_bean 库是否全类名

GBase8c GDCA题库_如何查看用户test被恶意锁定 (多选题,3分) a.打开运行日志,搜索关键字进行查询 b.-程序员宅基地

文章浏览阅读1.2k次,点赞21次,收藏32次。GBase8c GDCA题库_如何查看用户test被恶意锁定 (多选题,3分) a.打开运行日志,搜索关键字进行查询 b.

有限增量公式、泰勒公式、泰勒级数、傅里叶级数的关系_整体泰勒公式和局部泰勒公式区别-程序员宅基地

文章浏览阅读2.2k次,点赞4次,收藏11次。参考资料:1.泰勒公式、泰勒定理、泰勒级数、泰勒展开式之间的关系2.这一切都从指数函数开始(2)——Fourier级数和变换3.傅里叶级数_整体泰勒公式和局部泰勒公式区别

随便推点

UnicodeDecodeError: ‘gb2312‘ codec can‘t decode byte 0xc4 in position 21635: illegal multibyte seque_in position 4:illegal multibyte sequence-程序员宅基地

文章浏览阅读154次。UnicodeDecodeError: 'gb2312' codec can't decode byte 0xc4 in position 21635: illegal multibyte seque_in position 4:illegal multibyte sequence

第1章 统计学习方法概论(LeastSquaresMethod)代码实现-程序员宅基地

文章浏览阅读3k次,点赞7次,收藏6次。上一篇:【目录】====== 【回到目录】====== 下一篇:【第一章课后习题参考解答】import numpy as npfrom scipy.optimize import leastsqimport matplotlib.pyplot as plt# 目标函数def real_func(x): return np.sin(2*np.pi*x)# 多项式def fi...

saas 测试_为什么SaaS公司需要进行安全测试?-程序员宅基地

文章浏览阅读4k次。saas 测试 SaaS公司在为客户提供基本软件解决方案时提供了很大的灵活性。 它们具有易于访问的附加优点,并且在所有类型的设备上也很容易访问。 结果,现代企业正在Swift转向SaaS供应商提供的这些解决方案。 根据KBV Research最近发布的一份报告,到2024年 ,全球SaaS市场规模有望突破 1850亿美元。这些令人鼓舞的数字已经引发了市场上的同类竞争。 大多数软件公司现在都计..._saas产品需要做测试码

基于STM32单片机智能公交车语音报站系统毕业设计RFID射频识别100X_基于stm32的rfid读卡器可以播报吗-程序员宅基地

文章浏览阅读601次,点赞9次,收藏9次。基于STM32单片机的智能公交车语音报站系统设计RFID射频识别站台信息JR6001语音播报电机控制开关门DIY开发板套件100。_基于stm32的rfid读卡器可以播报吗

【干货】网易云音乐歌单的推荐算法解析-程序员宅基地

文章浏览阅读722次。网易云音乐的歌单推荐算法是怎样的呢?最近有很多人关心这个问题。调查了一些网易云音乐的重度患者,小咖带你来看一些路过大神的精辟分析。分析一:“商品推荐”系统的算法( Collaborati..._网易云音乐推荐算法csdn

JavaScript深入理解系列(2):手写bind方法_js手写bind-程序员宅基地

文章浏览阅读1.4k次,点赞6次,收藏10次。定义bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。由定义可知,函数调用bind()方法的时候,会返回一个新函数,并且this指向bind函数的第一个参数,简单来表示。fn.bind(obj对象)执行 返回一个函数,想调用的时候,fn.bind(obj)()这样来执行;举个例子:var name = '炒米粉';var obj = { name: '程序员米粉'};f_js手写bind