bilateral_filter_paper

双边滤波器(Bilateral Filter)FPGA 实现详解

一、公式在说什么

1.1 双边滤波想干什么

图像里有噪声,我们希望把噪声抹掉,但又不想把图像边缘也抹糊。

最简单的想法是:把一个像素和周围几个像素取平均。但这样做的问题是------如果当前像素在边缘上,比如左边是白墙、右边是黑衣服,取平均后边缘会变灰,边缘就糊掉了。

双边滤波的想法是:

平均的时候,不光看"你离我近不近",还要看"你和我像不像"。

  • 如果邻居离我近,可以参与平均;
  • 如果邻居灰度和我差很多(像边缘两侧),就不让它参与。

这样噪声被平均掉了,边缘被保留下来。

1.2 数学公式

双边滤波输出一个像素的值,公式如下:

O ( i , j ) = ∑ k , l w ( i , j , k , l ) ⋅ I ( k , l ) ∑ k , l w ( i , j , k , l ) O(i,j) = \frac{\sum_{k,l} w(i,j,k,l) \cdot I(k,l)}{\sum_{k,l} w(i,j,k,l)} O(i,j)=∑k,lw(i,j,k,l)∑k,lw(i,j,k,l)⋅I(k,l)

先别慌,拆开看:

符号 意思
O ( i , j ) O(i,j) O(i,j) 中心像素 ( i , j ) (i,j) (i,j) 滤波后的输出值
I ( k , l ) I(k,l) I(k,l) 邻居像素 ( k , l ) (k,l) (k,l) 的原始灰度值
w ( i , j , k , l ) w(i,j,k,l) w(i,j,k,l) 中心 ( i , j ) (i,j) (i,j) 和邻居 ( k , l ) (k,l) (k,l) 之间的"重要程度",叫权重
∑ k , l \sum_{k,l} ∑k,l 对中心周围某个范围内的所有邻居求和
分子 所有"权重 × 像素灰度"加起来的总和
分母 所有权重的总和

这其实就是一个加权平均:

输出 = 权重 × 灰度的总和 权重的总和 \text{输出} = \frac{\text{权重} \times \text{灰度的总和}}{\text{权重的总和}} 输出=权重的总和权重×灰度的总和

如果你把每个权重都当成 1,就是普通的算术平均。

1.3 权重 w w w 由哪两部分组成

w ( i , j , k , l ) = exp ⁡ ( − ( i − k ) 2 + ( j − l ) 2 2 σ d 2 ) ⏟ 空间权重 g ⋅ exp ⁡ ( − ( I ( i , j ) − I ( k , l ) ) 2 2 σ r 2 ) ⏟ 相似度权重 s w(i,j,k,l) = \underbrace{\exp\left(-\frac{(i-k)^2 + (j-l)^2}{2\sigma_d^2}\right)}{\text{空间权重 } g} \cdot \underbrace{\exp\left(-\frac{(I(i,j) - I(k,l))^2}{2\sigma_r^2}\right)}{\text{相似度权重 } s} w(i,j,k,l)=空间权重 g exp(−2σd2(i−k)2+(j−l)2)⋅相似度权重 s exp(−2σr2(I(i,j)−I(k,l))2)

空间权重 g g g

( i − k ) 2 + ( j − l ) 2 (i-k)^2 + (j-l)^2 (i−k)2+(j−l)2 是中心像素和邻居像素的距离平方。邻居离得越远,这个值越大;前面有个负号,所以整个指数就越小。

意思就是:离得越近,越应该参考。

这和普通高斯滤波一样。

相似度权重 s s s

I ( i , j ) − I ( k , l ) I(i,j) - I(k,l) I(i,j)−I(k,l) 是中心像素和邻居像素的灰度差。灰度差越大,指数越小。

意思就是:颜色越像,越应该参考;颜色差太多,就滚蛋。

这就是双边滤波和普通高斯滤波的区别------普通高斯滤波只有 g g g,没有 s s s。

1.4 举个生活例子

假设你站在操场中央,要判断"这里是不是草地"。你会问周围的同学:

  • 离你 1 米的同学(空间距离近)→ 参考意见大;
  • 离你 50 米的同学(空间距离远)→ 参考意见小;
  • 穿同样校服的同学(灰度相似)→ 他说的话可信;
  • 穿一身红衣服的老师(灰度差大)→ 虽然他也在旁边,但他可能站在跑道上,他的话对"草地判断"权重降低。

双边滤波就是同时用"距离"和"穿着 similarity"来决定听谁的。


二、从公式到硬件:每一步是怎么来的

公式本身只定义了数学运算。要在 FPGA 上实现,必须把每个数学操作翻译成电路。翻译过程中会遇到问题,每个问题对应一个步骤。

问题 1:公式里有 ∑ k , l \sum_{k,l} ∑k,l,意味着要同时拿到很多像素

公式不是只算中心像素自己,而是要把中心像素和周围一堆像素一起算。FPGA 接收图像是串行的------一次来一个像素,像水龙头流水一样。

解决:先存几行像素,等凑够一个局部窗口再一起算。代码选了 3×3 窗口,就是存上一行、当前行、下一行,每次取 9 个像素。

→ 对应 Step 1:生成 3×3 矩阵

注意:公式没指定 3×3,5×5、7×7 都可以。选 3×3 是因为资源少、延迟低,是工程选择。


问题 2:公式里需要 I ( i , j ) − I ( k , l ) I(i,j) - I(k,l) I(i,j)−I(k,l),即中心像素减每个邻居的灰度差

有了 3×3 矩阵,中心是中间那个像素,周围 8 个是邻居。要算 9 个差值。

解决:放 9 个减法器,大减小,得到 9 个绝对值差。

→ 对应 Step 2:9 路绝对值差


问题 3:公式里有 exp ⁡ ( ⋯   ) \exp(\cdots) exp(⋯),FPGA 不会算指数函数

FPGA 只会加、减、乘、移位,不会算 exp ⁡ \exp exp、 ln ⁡ \ln ln、 sin ⁡ \sin sin 这些。

解决:把指数函数的结果提前算好,做成一张表。输入灰度差,查表直接输出相似度权重。

→ 对应 Step 3:LUT 查表

只有灰度相似度权重 s s s 需要 LUT。空间权重 g g g 只和位置有关,3×3 里每个位置到中心的距离固定,可以提前算好写成常量。


问题 4:公式里 w = g × s w = g \times s w=g×s,两个权重要乘起来

每个像素都有一个空间权重 g g g(常量)和一个查表得到的相似度权重 s s s,总权重要把它们相乘。

解决:9 个乘法器,每个像素一路。

→ 对应 Step 4:9 路乘法,算总权重 w w w


问题 5:公式分母是 ∑ w \sum w ∑w,所有权重要加起来

加权平均需要分母。9 个权重加起来。

解决:两级加法树,先三三相加,再把三个和相加。

→ 对应 Step 5:两级加法树求和


问题 6:公式里有除法 ∑ ( w ⋅ I ) ∑ w \frac{\sum(w \cdot I)}{\sum w} ∑w∑(w⋅I),FPGA 除法很慢

FPGA 的除法器不是一拍出来的。这段代码里一个除法要 34 个时钟周期。

解决 :先算 w / ∑ w w / \sum w w/∑w 得到归一化权重,再和原始像素相乘。9 个权重各除一次,可以并行做。

→ 对应 Step 7:9 个并行除法器

也可以先算 w ⋅ I w \cdot I w⋅I 再加起来最后除一次,但代码选了"先除后乘":9 个除法器可以并行、流水线更顺、单个除法器位宽更小。


问题 7:等除法器算出结果时,原来的像素已经没了

从输入像素到除法结果出来要 34 拍。34 拍后,原始像素早就被新进来的数据覆盖了。

解决:给原始像素数据也建一条 34 拍的延迟链,让它和除法结果在同一拍对齐,然后再相乘相加。

→ 对应 Step 6 + Step 8:延迟对齐


问题 8:公式分子是 ∑ ( w ⋅ I ) \sum(w \cdot I) ∑(w⋅I),归一化权重要和像素相乘再加总

现在有了归一化权重和延迟后的原始像素,把它们相乘,9 路结果加起来。

解决:9 个乘法器 + 两级加法树。

→ 对应 Step 9:9 路乘法 + 加法树


问题 9:图像边缘凑不出完整 3×3 窗口

公式假设每个像素都有完整邻居。但图像第一行、最后一行、第一列、最后一列没有。

解决:边缘直接输出原始中心像素,不做滤波。

→ 对应 Step 10:边界处理


问题 10:除法结果是定点小数,要回到 8 位灰度

为了避免小数,代码里把被除数先左移 10 位(相当于乘 1024)再除,结果变成 0~1024 的整数。最后要除以 1024 并四舍五入。

解决:取高 8 位 + 第 9 位做进位,完成四舍五入。

→ 对应 Step 10:四舍五入输出


三、推导总表

推导 公式里出现的东西 硬件实现遇到的问题 解决方案 对应 Step
1 ∑ k , l \sum_{k,l} ∑k,l 需要多个像素 图像是串行输入的,一次只有一个像素 加行缓存,生成 3×3 窗口 Step 1
2 I ( i , j ) − I ( k , l ) I(i,j) - I(k,l) I(i,j)−I(k,l) 需要算中心与每个邻居的灰度差 9 路绝对值减法 Step 2
3 exp ⁡ ( − Δ I 2 / 2 σ r 2 ) \exp(-\Delta I^2 / 2\sigma_r^2) exp(−ΔI2/2σr2) FPGA 不会算指数 LUT 查表 Step 3
4 w = g ⋅ s w = g \cdot s w=g⋅s 空间权重和相似度权重要相乘 9 路乘法器 Step 4
5 ∑ w \sum w ∑w 9 个权重要加起来当分母 两级加法树 Step 5
6 除法延迟 除法器要 34 拍,原始像素会丢失 给原始像素建 34 拍延迟链 Step 6/8
7 w / ∑ w w / \sum w w/∑w 需要权重归一化 9 个并行除法器 Step 7
8 ∑ ( w ⋅ I ) \sum(w \cdot I) ∑(w⋅I) 归一化权重和像素相乘再加总 9 路乘法 + 加法树 Step 9
9 3×3 窗口 边缘像素没有完整邻居 边缘直接输出原值 Step 10
10 定点数还原 结果要回到 8 位灰度 取高位 + 四舍五入 Step 10

四、实现步骤(代码对应)

Step 1:生成 3×3 滑窗矩阵

双边滤波的前提是每一个像素位置都要有完整的 3×3 邻域。但 RAW 图像是逐行逐列流式进入 FPGA 的,某个时刻只有一个像素。所以必须用行缓存 + 列缓存把前后两行、前后两列的数据暂存下来,凑成 3×3 矩阵。

verilog 复制代码
Matrix_Generate_3X3_8Bit #(
    .IMG_HDISP (IMG_HDISP),   // 图像宽
    .IMG_VDISP (IMG_VDISP)    // 图像高
) u_Matrix_Generate_3X3_8Bit (
    .clk       (clk),
    .rst_n     (rst_n),
    .din_vsync (per_img_vsync),
    .din_href  (per_img_href),
    .din       (per_img_Y),
    .dout_vsync(matrix_img_vsync),
    .dout_href (matrix_img_href),
    .dout      (matrix_img_data)
);

Matrix_Generate_3X3_8Bit 内部做了两件事:

  1. 用两个 FIFO/行缓存存前两行;
  2. 每行用移位寄存器存相邻三列。

输出端同时给出 9 个像素:matrix_p11 ~ matrix_p33。

Step 2:算 9 路绝对值差

中心像素是 matrix_p22,其余 8 个是邻居。相似度权重需要知道每个邻居和中心的灰度差。

verilog 复制代码
// 中心像素
assign matrix_p22 = matrix_p12_r0;

// 9 路绝对值差
assign abs_p11_p22 = (matrix_p11 >= matrix_p22) ? (matrix_p11 - matrix_p22) : (matrix_p22 - matrix_p11);
assign abs_p12_p22 = (matrix_p12 >= matrix_p22) ? (matrix_p12 - matrix_p22) : (matrix_p22 - matrix_p12);
// ... 共 9 路

Step 3:LUT 查相似度权重

用灰度差查表,得到相似度权重 s s s。

verilog 复制代码
// 9 个 LUT,输入是灰度差,输出是相似度权重
rom_bilateral_filter_8bit u0 (.clka(clk), .addra(abs_p11_p22), .douta(similarity11));
rom_bilateral_filter_8bit u1 (.clka(clk), .addra(abs_p12_p22), .douta(similarity12));
// ... 共 9 个

LUT 的内容提前由 s = exp ⁡ ( − Δ I 2 / 2 σ r 2 ) s = \exp(-\Delta I^2 / 2\sigma_r^2) s=exp(−ΔI2/2σr2) 计算好。

Step 4:总权重 w = g × s w = g \times s w=g×s

空间权重 g g g 是预计算的常量,和 LUT 输出的 s s s 相乘。

verilog 复制代码
// 9 路乘法
assign weight11 = g11 * similarity11;
assign weight12 = g12 * similarity12;
// ... 共 9 路

Step 5:权重求和

把 9 个权重加起来,得到分母 ∑ w \sum w ∑w。

verilog 复制代码
assign weight_sum_stage1_1 = weight11 + weight12 + weight13;
assign weight_sum_stage1_2 = weight21 + weight22 + weight23;
assign weight_sum_stage1_3 = weight31 + weight32 + weight33;
assign weight_sum = weight_sum_stage1_1 + weight_sum_stage1_2 + weight_sum_stage1_3;

Step 6 + Step 8:延迟对齐

除法器要 34 拍。为了让像素数据和权重在同一拍相遇,给它们都建延迟链。

verilog 复制代码
// 原始像素延迟 34 拍
always @(posedge clk) matrix_p11_r1[0] <= matrix_p11;
always @(posedge clk) matrix_p11_r1[1] <= matrix_p11_r1[0];
// ... 直到 [33]

同步信号 matrix_img_vsync、matrix_img_href 也要延迟同样拍数,保证输出时序正确。

Step 7:9 个并行除法器

每个权重除以权重和,得到归一化权重。高位补 10 个 0 是为了把结果放大 1024 倍,避免小数。

verilog 复制代码
// 9 个除法器并行
// dividend = {10'd0, weightXX},相当于 weightXX * 1024
// divisor  = weight_sum
// 商 norm_weightXX 范围约 0~1024

Step 9:归一化权重乘像素再加总

verilog 复制代码
mult_p11_w11 <= matrix_p11_r1[C_CLK_LATENCY-1] * norm_weight11;
mult_p12_w12 <= matrix_p12_r1[C_CLK_LATENCY-1] * norm_weight12;
// ... 共 9 路

sum_result <= mult_p11_w11 + mult_p12_w12 + ... + mult_p33_w33;

Step 10:边界处理 + 四舍五入

verilog 复制代码
always @(posedge clk) begin
    if (matrix_img_href_d) begin
        if (edge_flag_d)            // 图像边界,不滤波
            post_img_data <= matrix_p22_r1[C_CLK_LATENCY-1];
        else                        // 有效区域,四舍五入
            post_img_data <= sum_result[17:10] + sum_result[9];
    end
end

五、关键参数说明

verilog 复制代码
parameter IMG_HDISP = 11'd640;   // 图像宽度 640
parameter IMG_VDISP = 11'd480;   // 图像高度 480
localparam C_CLK_LATENCY = 34;   // 除法器延迟(拍),用于对齐数据延迟链

3×3 高斯空间权重(预计算好的常量,不是 LUT):

verilog 复制代码
// [g11, g12, g13]   [109, 115, 109]
// [g21, g22, g23] = [115, 122, 115]
// [g31, g32, g33]   [109, 115, 109]
localparam g11=7'd109, g12=7'd115, g13=7'd109;
localparam g21=7'd115, g22=7'd122, g23=7'd115;
localparam g31=7'd109, g32=7'd115, g33=7'd109;

这些值来源于 σ d = 1.5 \sigma_d=1.5 σd=1.5 的高斯核,7 位定点。中心 g22=122 最大,四角 g11=109 最小,符合"离中心越远权重越小"。

相关推荐
≮傷£≯√7 小时前
opencv 图像处理(一)
图像处理·人工智能·opencv
sali-tec8 小时前
C# 基于OpenCv的视觉工作流-章114-视觉与http通讯
图像处理·人工智能·opencv·计算机视觉·c#
楚山之石1 天前
【Medical Image Analysis】深度学习医学图像合成综述:伪CT、合成MRI与合成PET的架构、性能与临床边界
图像处理·人工智能·学习·mr·mri
深圳安锐科技有限公司1 天前
从成像尺度到亚像素解算:变焦视觉位移监测如何让远近断面都稳
图像处理·人工智能·深度学习·算法·计算机视觉·系统架构·结构健康监测
W_326001 天前
Python 图像处理第二十八章|OpenCV 形态学操作进阶:腐蚀、膨胀、开运算、闭运算、梯度、顶帽黑帽
图像处理·人工智能·python·opencv·计算机视觉
ck0135791 天前
视频添加水印的实践:文字叠加、透明 PNG 与时间区间设置
图像处理·测试工具·音视频·视频
Angeldream1231 天前
第 9 章 医学影像项目实战
大数据·图像处理·人工智能·python·医学影像处理
小豆庄2 天前
视频素材处理时的隐私盲区:云处理与本地处理的边界在哪
数据库·图像处理·视频·敏捷流程
FPGA小迷弟2 天前
FPGA开发Vivado Block Design/Vitis/SDK/xlinx/开发教程,保姆级别,一看就会!
网络协议·tcp/ip·fpga开发·verilog·fpga