OpencvSharp 算子学习教案之 - Cv2.BilateralFilter
大家好,Opencv在很多工程项目中都会用到,而OpencvSharp则是以C#开发与实现的Opencv操作库,对.NET开发人员友好,但很多API的中文资料、应用场景及常见坑点等缺乏系统性归纳,因此这系列博客将给大家带来Cv2及Mat对象全系列算子学习教案,供大家参考学习。
Cv2.BilateralFilter
- 教案版本:V1.0
- 面向对象:OpenCvSharp 初学者
- 所属模块:imgproc
- 源码位置:OpenCvSharp/Cv2/Cv2_imgproc.cs:191
摘要:BilateralFilter 是保边缘滤波,sigmaColor 和 sigmaSpace 分别控制颜色与空间的影响范围。本文用轻度、中等和自动直径三组参数,帮助初学者看懂它为什么比普通模糊更适合保边缘去噪。
1. 函数名称(带参数签名)
publicstaticvoidBilateralFilter(InputArraysrc,OutputArraydst,intd,doublesigmaColor,doublesigmaSpace,BorderTypesborderType=BorderTypes.Default)2. 函数用途
Cv2.BilateralFilter的作用,是在尽量保住边缘的前提下平滑图像。
这个函数最常见的用途有:
- 去噪但尽量不糊边。
- 让照片看起来更干净,同时保留轮廓。
- 做卡通化或边缘友好的预处理。
- 帮助理解“空间距离”和“颜色相似度”如何共同决定权重。
它比 GaussianBlur 更复杂,也比普通平均滤波更适合保边缘。
3. 函数公式
双边滤波可以理解成带两个权重的加权平均:
dst(p)=1Wp∑q∈ΩI(q)exp(−∥p−q∥22σs2)exp(−∥I(p)−I(q)∥22σc2) dst(p) = \frac{1}{W_p} \sum_{q \in \Omega} I(q) \exp\left(-\frac{\|p-q\|^2}{2\sigma_s^2}\right) \exp\left(-\frac{\|I(p)-I(q)\|^2}{2\sigma_c^2}\right)dst(p)=Wp1q∈Ω∑I(q)exp(−2σs2∥p−q∥2)exp(−2σc2∥I(p)−I(q)∥2)
其中空间权重由sigmaSpace控制,颜色权重由sigmaColor控制,WpW_pWp是归一化系数。
4. 函数原理说明
这个函数会同时考虑两个条件:
- 两个像素是不是离得近。
- 两个像素的颜色是不是相近。
如果两个像素距离很远,或者颜色差得很大,它们的影响就会被明显削弱。
d决定邻域直径。sigmaColor决定颜色相近程度的容忍度。sigmaSpace决定空间相近程度的容忍度。d <= 0时,OpenCV 会根据sigmaSpace推导邻域大小。
5. 参数含义解析
| 参数名 | 类型 | 必填 | 含义 |
|---|---|---|---|
| src | InputArray | 是 | 输入图像 |
| dst | OutputArray | 是 | 输出图像 |
| d | int | 是 | 邻域直径,非正数时会自动推导 |
| sigmaColor | double | 是 | 颜色空间中的标准差 |
| sigmaSpace | double | 是 | 坐标空间中的标准差 |
| borderType | BorderTypes | 否 | 边界外推方式 |
补充说明:
- 官方文档说明它不支持 in-place。
- 输入图像一般要求是 8 位或浮点图像。
- 常见输入是 1 通道或 3 通道图像。
BORDER_WRAP不支持。
6. 应用场景列表
| 场景名 | 场景说明 | 典型用途 |
|---|---|---|
| 场景A:保边缘去噪 | 降噪同时保轮廓 | 照片处理 |
| 场景B:卡通化预处理 | 平滑色块但保边缘 | 风格化 |
| 场景C:人像优化 | 抑制皮肤噪点 | 美颜预处理 |
| 场景D:参数教学 | 对比 d 和 sigma 的作用 | 初学者理解权重 |
7. 函数使用示例
下面的 Console 程序演示Cv2.BilateralFilter。示例会先给图像加入一点高斯噪声,再分别用轻度、中等和自动直径三组参数做对比。
usingSystem;usingSystem.Text;usingOpenCvSharp;internalstaticclassProgram{privatestaticvoidMain(){// 控制台输出切换到 UTF-8,避免中文说明乱码。Console.OutputEncoding=Encoding.UTF8;// 运行完整演示,观察双边滤波如何在保边缘的同时去噪。RunDemo();}/// <summary>/// 演示双边滤波的输出特点。/// </summary>privatestaticvoidRunDemo(){usingvarcleanSource=CreateDemoImage();usingvarnoisySource=AddGaussianNoise(cleanSource,20.0,2026);usingvargentle=newMat();usingvarmedium=newMat();usingvarstrong=newMat();// 这里准备三组参数,分别代表轻度、中等和自动直径三种教学写法。Cv2.BilateralFilter(noisySource,gentle,5,35.0,35.0,BorderTypes.Default);Cv2.BilateralFilter(noisySource,medium,9,60.0,60.0,BorderTypes.Default);Cv2.BilateralFilter(noisySource,strong,0,90.0,90.0,BorderTypes.Default);Console.WriteLine("场景A:BilateralFilter(InputArray src, OutputArray dst, int d, double sigmaColor, double sigmaSpace, BorderTypes borderType = BorderTypes.Default)");Console.WriteLine("BilateralFilter 会同时考虑空间距离和颜色差异,因此比普通模糊更能保住边缘。\n");Console.WriteLine($"带噪声图像:{DescribeMat(noisySource)}");Console.WriteLine();PrintCase("轻度双边滤波",noisySource,gentle,"d=5, sigmaColor=35, sigmaSpace=35","参数较小时,去噪比较温和,边缘也更容易保留。");PrintCase("中等双边滤波",noisySource,medium,"d=9, sigmaColor=60, sigmaSpace=60","这个参数组合会带来更明显的平滑,但仍比普通模糊更保边缘。");PrintCase("自动直径",noisySource,strong,"d=0, sigmaColor=90, sigmaSpace=90","d=0 时,OpenCV 会根据 sigmaSpace 推导邻域直径。");Console.WriteLine("教学结论:sigmaColor 控制颜色相似度,sigmaSpace 控制空间邻域,d 决定或推导邻域直径。\n");}/// <summary>/// 创建一张用于教学的源图。/// </summary>privatestaticMatCreateDemoImage(){// 使用一张人像风格的教学图,便于观察边缘是否被保留。varcanvas=newMat(400,400,MatType.CV_8UC3,newScalar(245,238,234));Cv2.Ellipse(canvas,newPoint(200,170),newSize(118,140),0,0,360,newScalar(35,25,20),-1,LineTypes.AntiAlias);Cv2.Ellipse(canvas,newPoint(200,182),newSize(78,96),0,0,360,newScalar(122,182,228),-1,LineTypes.AntiAlias);Cv2.Ellipse(canvas,newPoint(200,332),newSize(148,72),0,0,360,newScalar(165,100,58),-1,LineTypes.AntiAlias);Cv2.PutText(canvas,"BilateralFilter",newPoint(24,374),HersheyFonts.HersheySimplex,0.8,newScalar(55,40,35),2,LineTypes.AntiAlias);returncanvas;}/// <summary>/// 给图像叠加高斯噪声。/// </summary>privatestaticMatAddGaussianNoise(Matsource,doublesigma,intseed){varrng=newRandom(seed);varresult=source.Clone();for(varrow=0;row<result.Rows;row++){for(varcol=0;col<result.Cols;col++){varpixel=source.At<Vec3b>(row,col);result.At<Vec3b>(row,col)=newVec3b(ClampToByte(pixel.Item0+NextGaussian(rng)*sigma),ClampToByte(pixel.Item1+NextGaussian(rng)*sigma),ClampToByte(pixel.Item2+NextGaussian(rng)*sigma));}}returnresult;}/// <summary>/// 打印一个对比场景。/// </summary>privatestaticvoidPrintCase(stringlabel,Matsource,Matresult,stringtitle,stringcomment){usingvardiff=newMat();Cv2.Absdiff(source,result,diff);Console.WriteLine($"[{label}]{title}");Console.WriteLine(comment);Console.WriteLine($"结果:{DescribeMat(result)}");Console.WriteLine($"与源图的灰度差值统计:{DescribeGrayStatistics(diff)}");Console.WriteLine();}/// <summary>/// 描述 Mat 的核心信息。/// </summary>privatestaticstringDescribeMat(Matmat){return$"Size={mat.Width}x{mat.Height}, Channels={mat.Channels()}, Type={mat.Type()}, Depth={mat.Depth()}";}/// <summary>/// 计算灰度图的基本统计信息。/// </summary>privatestaticstringDescribeGrayStatistics(Matimage){usingvargray=newMat();Cv2.CvtColor(image,gray,ColorConversionCodes.BGR2GRAY);Cv2.MeanStdDev(gray,outvarmean,outvarstddev);Cv2.MinMaxLoc(gray,outdoubleminVal,outdoublemaxVal);return$"灰度均值={mean.Val0:F2}, 灰度标准差={stddev.Val0:F2}, 最小值={minVal:F0}, 最大值={maxVal:F0}";}/// <summary>/// 生成高斯噪声。/// </summary>privatestaticdoubleNextGaussian(Randomrng){varu1=1.0-rng.NextDouble();varu2=1.0-rng.NextDouble();returnMath.Sqrt(-2.0*Math.Log(u1))*Math.Cos(2.0*Math.PI*u2);}/// <summary>/// 把浮点值夹紧到 8 位像素范围。/// </summary>privatestaticbyteClampToByte(doublevalue){varclamped=Math.Clamp((int)Math.Round(value),byte.MinValue,byte.MaxValue);return(byte)clamped;}}8. 注意事项
d为非正数时,OpenCV 会根据sigmaSpace推导邻域直径。- 输入图像一般要求是 8 位或浮点图像。
BilateralFilter不支持 in-place。BORDER_WRAP不支持。
9. 调优建议
- 想保边缘,就不要把 sigma 一下子设得太大。
- 想要更强的去噪,可以逐步增大
sigmaColor和sigmaSpace。 d=0适合用来演示自动直径推导。- 如果图像已经很干净,双边滤波可能会显得偏慢。
10. 运行说明
- 如果你在控制台工程里运行本文示例,直接把代码放进
Program.cs即可。 - 如果你在本仓库里学习,请打开 WPF 控件
Cv2.BilateralFilter,点击“运行场景A”后查看右侧文本框和预览图。 - WPF 示例会把轻度、中等和自动直径三种双边滤波结果放在一起对比。
11. 常见错误排查
- 把双边滤波当成普通均值滤波使用。
- 误以为
d=0不会做任何处理,其实它会触发自动推导。 - 没有意识到 sigmaColor 和 sigmaSpace 的含义不同。
- 忘记它通常比 GaussianBlur 更慢。