文章目录
1.算法原理
双边滤波(Bilateral Filter)结合了空间域高斯滤波和值域相似性加权,在平滑噪声的同时保留边缘。
经典双边滤波权重:
w(i,j) = G_spatial(||p-q||) × G_range(|I(p)-I(q)|)
上面双边滤波权重公式,它定义了如何计算邻域内像素对中心像素的"影响力(权重)"。
说白了,它要表达了一个核心思想:一个像素要想对中心像素产生影响,它必须同时满足"离得近"和"长得像"这两个条件。我们从下面2个方面再理解下:
1. 符号的含义
-
w(i,j)) :最终计算出的综合权重。它决定了邻域内的像素 q 对中心像素 p 的影响程度。
-
p和q : p 代表图像中的中心像素(正在被处理的像素), q 代表中心像素周围的邻域像素。
-
∣∣p−q∣∣ :代表像素 p 和 q 之间的空间距离(即它们在图像上的物理坐标距离,比如横纵坐标的差值)。
-
∣I( p)−I(q)∣ :代表像素 p 和 q 之间的像素值差异( I 代表强度或颜色,比如灰度值从 0 到 255 的差异)。
2.为什么要相乘
上面已经从空间上和值上分别给**"离得近"和"长得像"的元素给与最大的权重,那么他们相乘,意味着一票否决制,有点"与"**运算的味道.
- 如果两个像素离得很近,但颜色差异很大(比如跨越了物体边缘), G_range的值会非常小,导致最终权重 w 趋近于 0。
- 如果两个像素颜色很像,但离得非常远, G_spatial的值会非常小,最终权重 w 同样趋近于 0。
- 只有当两个像素既在空间上相邻,又在颜色上相似时,它们的乘积才会产生显著的权重。
算法值域权重量化
openISP 默认值:差值范围 rthres=[128,32,8],权重rw=[0,8,16,32](差异越大权重越小)
在算法中计算的是5x5邻域内像素值的权重,所以最后得到也是一个5x5的矩阵. 下面就是判断中心像素域5x5邻域像素插值,根据插值范围,分别赋予不同的rw权重.
if rdiff >= rthres[0]: rw = rw[0] (差异很大,权重最小)
elif rdiff ∈ [rthres[1], rthres[0]): rw = rw[1]
elif rdiff ∈ [rthres[2], rthres[1]): rw = rw[2]
elif rdiff < rthres[2]: rw = rw[3] (差异很小,权重最大)
空间权重(dw 矩阵)
符合高斯分布,中间像素的权重自然最大
8 12 32 12 8
12 64 128 64 12
32 128 1024 128 32 (高斯分布近似)
12 64 128 64 12
8 12 32 12 8
高斯函数:

2.算法核心代码
实现细节
- 仅处理 Y(亮度)通道,5×5 窗口
- 逐像素双重循环,逐邻居计算值域差,计算量大
- padding 2 像素(reflect 模式)
关键参数
| 参数 | 说明 |
|---|---|
dw |
5×5 空间距离权重矩阵 |
rw |
4 级值域权重 rw0, rw1, rw2, rw3 |
rthres |
值域阈值 rthres0, rthres1, rthres2 |
clip |
输出最大值 |
最终像素输出
output = Σ(pixel × dw × rw) / Σ(dw × rw)
算法代码实现
python
class BNF:
'Bilateral Noise Filtering'
def __init__(self, img, dw, rw, rthres, clip):
self.img = img
self.dw = dw
self.rw = rw
self.rthres = rthres
self.clip = clip
def padding(self):
img_pad = np.pad(self.img, (2, 2), 'reflect')
return img_pad
def clipping(self):
np.clip(self.img, 0, self.clip, out=self.img)
return self.img
def execute(self):
img_pad = self.padding()
img_pad = img_pad.astype(np.uint16)
raw_h = self.img.shape[0]
raw_w = self.img.shape[1]
bnf_img = np.empty((raw_h, raw_w), np.uint16)
rdiff = np.zeros((5,5), dtype='uint16')
for y in range(img_pad.shape[0] - 4):
for x in range(img_pad.shape[1] - 4):
#print("[x,y]:["+str(x)+','+str(y)+']')
for i in range(5):
for j in range(5):
rdiff[i,j] = abs(img_pad[y+i,x+j].astype(int) - img_pad[y+2, x+2].astype(int))
# rdiff[i,j] = abs(img_pad[y+i,x+j] - img_pad[y+2, x+2])
if rdiff[i,j] >= self.rthres[0]:
rdiff[i,j] = self.rw[0]
elif rdiff[i,j] < self.rthres[0] and rdiff[i,j] >= self.rthres[1]:
rdiff[i,j] = self.rw[1]
elif rdiff[i,j] < self.rthres[1] and rdiff[i,j] >= self.rthres[2]:
rdiff[i,j] = self.rw[2]
elif rdiff[i,j] < self.rthres[2]:
rdiff[i,j] = self.rw[3]
weights = np.multiply(rdiff, self.dw)
bnf_img[y,x] = np.sum(np.multiply(img_pad[y:y+5,x:x+5], weights[:,:])) / np.sum(weights)
self.img = bnf_img
return self.clipping()
3.测试代码
测试是用千问帮忙写的,代码生产力一个张30x30随机图,然后使用BNF算法后,能看到图像平滑了许多.
python
import cv2
import numpy as np
import matplotlib.pyplot as plt
from bnf import *
# 假设 BNF 类已经存在于当前环境(即您提供的代码)
# from your_module import BNF
# 1. 准备测试图像 (强烈建议裁剪或缩放,否则纯Python循环会运行极慢)
img = cv2.imread('test.jpg', cv2.IMREAD_GRAYSCALE)
if img is None:
# 如果没有图片,生成一张带噪点的测试图
img = np.random.randint(0, 256, (30, 30), dtype=np.uint8)
else:
# 裁剪为 100x100 大小以加速测试
img = img[0:100, 0:100]
# 2. 配置双边滤波参数 (与算法原理一致)
dw = np.array([
[8, 12, 32, 12, 8],
[12, 64, 128, 64, 12],
[32, 128, 1024, 128, 32],
[12, 64, 128, 64, 12],
[8, 12, 32, 12, 8]
], dtype=np.uint16)
rthres = [128, 32, 8]
rw = [0, 8, 16, 32]
clip_val = 255
# 3. 执行算法
bnf_filter = BNF(img, dw, rw, rthres, clip_val)
processed_img = bnf_filter.execute()
# 4. 左右对比显示
plt.figure(figsize=(12, 6))
# 左侧:处理前
plt.subplot(1, 2, 1)
plt.imshow(img, cmap='gray')
plt.title('Original Image')
plt.axis('off')
# 右侧:处理后
plt.subplot(1, 2, 2)
plt.imshow(processed_img, cmap='gray')
plt.title('BNF Processed Image')
plt.axis('off')
plt.tight_layout()
plt.show()
- 测试效果

- 测试效果2:
说实话,虽然主要的边缘保留了,但是感觉这个算法小姑有点差强人意,图像中一些细节都抹去了.
