基于fpga的双边滤波。

1、前言。

        双边滤波是一种非线性滤波器,它既可以达到降噪平滑,同时又保持边缘的效果。和其他滤波的原理一样,双边滤波也是采用加权平均的方法,用周边像素亮度值的加权平均来代表某个像素的强度,所用的加权平均也是基于高斯分布的。

        双边滤波的权重,不仅考虑了像素的空间距离(如高斯滤波),还考虑了像素范围的 辐射差异(如像素与中心像素的相似程度,也是高斯分布的),结合空间距离与相似度, 计算得到最终的权重(空间距离与相似度的高斯分布)。

      

数学原理:对于每一个像素p,输出值b(p)是其领域像素内的加权平均。

  • I(p):原图像在像素p处的值;
  • Gd(||p-q||):空间距离权重(越远的像素权重越小);
  • Gr​(∣I(p)−I(q)∣):像素值差异权重(像素差越大权重越小);
  • Ω:邻域窗口(如3×3、5×5);

Wp​:归一化系数(所有权重之和)。

2、高斯核和相似度权重。

        由前一章的高斯滤波教程我们知道。高斯滤波的高斯核我们可以确定。高斯核就不过多阐述了,这里主要是相似度权重是如何确定的:相邻两个像素的差值的绝对值, 一定属于[0,255],所以我们可以提前计算好G。并定点化,这样就可以用查找表的方 法来实现像素相似度权重的计算了 。通过matlab求出查找表。

% 参数设置 n = 3; sigma_d = 1.0; sigma_r = 0.3; w = floor(n / 2); % 窗口半径 G_spatial = zeros(n, n); % 空间模板初始化 % 生成空间高斯模板 G(i,j) for i = -w:w for j = -w:w G_spatial(i+w+1, j+w+1) = exp(-(i^2 + j^2) / (2 * sigma_d^2)); end end % 归一化模板(总和为1) G_spatial_norm = G_spatial / sum(G_spatial(:)); % 定点化模板(乘1024并取整) G_spatial_fixed = floor(G_spatial_norm * 1024); % 打印结果 disp('【空间高斯模板 G(归一化)】:'); disp(G_spatial_norm); disp('【空间高斯模板 G(定点整数×1024)】:'); disp(G_spatial_fixed); % ----------------------------------------------- % 构造灰度相似性查表 H[0~255] H_range = zeros(1, 256); for i = 0:255 diff_norm = i / 255; % 灰度差归一化 H_range(i+1) = exp(-(diff_norm)^2 / (2 * sigma_r^2)); end H_range_norm = H_range / max(H_range); % 可选:归一化为1 H_range_fixed = floor(H_range * 1023); % 放大到10bit并取整 % 构造表格数据 gray_diff = (0:255)'; % 灰度差(作为第一列) H_table = table(gray_diff, H_range_fixed', ... 'VariableNames', {'Gray_Diff', 'Weight_Fixed1023'}); % 导出到 Excel 文件 filename = 'H_range_lookup_table.xlsx'; writetable(H_table, filename); disp(['相似度权重查表已导出为文件:', filename]); 

通过此matlab代码可以求出sigma_r(σ)=0.3的相似度查找表。

然后将上述查找表转化为FPGA代码(LUT) 

/* Sigma_r = 0.300000 相似度权重系数查找表 */ module bilateral_similarity_lut ( input [7:0] pre_data, output reg [9:0] post_data ); always@(*) begin case(pre_data) 8'h00 : post_data = 10'h3FF; 8'h01 : post_data = 10'h3FE; 8'h02 : post_data = 10'h3FE; 8'h03 : post_data = 10'h3FE; 8'h04 : post_data = 10'h3FD; 8'h05 : post_data = 10'h3FC; 8'h06 : post_data = 10'h3FB; 8'h07 : post_data = 10'h3FA; 8'h08 : post_data = 10'h3F9; 8'h09 : post_data = 10'h3F7; 8'h0A : post_data = 10'h3F6; 8'h0B : post_data = 10'h3F4; 8'h0C : post_data = 10'h3F2; 8'h0D : post_data = 10'h3F0; 8'h0E : post_data = 10'h3EE; 8'h0F : post_data = 10'h3EB; 8'h10 : post_data = 10'h3E8; 8'h11 : post_data = 10'h3E6; 8'h12 : post_data = 10'h3E3; 8'h13 : post_data = 10'h3DF; 8'h14 : post_data = 10'h3DC; 8'h15 : post_data = 10'h3D9; 8'h16 : post_data = 10'h3D5; 8'h17 : post_data = 10'h3D1; 8'h18 : post_data = 10'h3CD; 8'h19 : post_data = 10'h3C9; 8'h1A : post_data = 10'h3C5; 8'h1B : post_data = 10'h3C1; // 8'h1C : post_data = 10'h3BC; 8'h1D : post_data = 10'h3B8; 8'h1E : post_data = 10'h3B3; 8'h1F : post_data = 10'h3AE; 8'h20 : post_data = 10'h3A9; 8'h21 : post_data = 10'h3A4; 8'h22 : post_data = 10'h39E; 8'h23 : post_data = 10'h399; 8'h24 : post_data = 10'h393; 8'h25 : post_data = 10'h38E; 8'h26 : post_data = 10'h388; 8'h27 : post_data = 10'h382; 8'h28 : post_data = 10'h37C; 8'h29 : post_data = 10'h376; 8'h2A : post_data = 10'h36F; 8'h2B : post_data = 10'h369; 8'h2C : post_data = 10'h363; 8'h2D : post_data = 10'h35C; 8'h2E : post_data = 10'h355; 8'h2F : post_data = 10'h34F; 8'h30 : post_data = 10'h348; 8'h31 : post_data = 10'h341; 8'h32 : post_data = 10'h33A; 8'h33 : post_data = 10'h333; 8'h34 : post_data = 10'h32B; 8'h35 : post_data = 10'h324; 8'h36 : post_data = 10'h31D; 8'h37 : post_data = 10'h316; 8'h38 : post_data = 10'h30E; 8'h39 : post_data = 10'h307; 8'h3A : post_data = 10'h2FF; 8'h3B : post_data = 10'h2F7; 8'h3C : post_data = 10'h2F0; 8'h3D : post_data = 10'h2E8; 8'h3E : post_data = 10'h2E0; 8'h3F : post_data = 10'h2D8; 8'h40 : post_data = 10'h2D0; 8'h41 : post_data = 10'h2C9; 8'h42 : post_data = 10'h2C1; 8'h43 : post_data = 10'h2B9; 8'h44 : post_data = 10'h2B1; 8'h45 : post_data = 10'h2A9; 8'h46 : post_data = 10'h2A1; 8'h47 : post_data = 10'h299; 8'h48 : post_data = 10'h290; 8'h49 : post_data = 10'h288; 8'h4A : post_data = 10'h280; 8'h4B : post_data = 10'h278; 8'h4C : post_data = 10'h270; 8'h4D : post_data = 10'h268; 8'h4E : post_data = 10'h260; 8'h4F : post_data = 10'h258; 8'h50 : post_data = 10'h250; 8'h51 : post_data = 10'h248; 8'h52 : post_data = 10'h23F; 8'h53 : post_data = 10'h237; 8'h54 : post_data = 10'h22F; 8'h55 : post_data = 10'h227; 8'h56 : post_data = 10'h21F; 8'h57 : post_data = 10'h217; 8'h58 : post_data = 10'h20F; 8'h59 : post_data = 10'h207; 8'h5A : post_data = 10'h200; 8'h5B : post_data = 10'h1F8; 8'h5C : post_data = 10'h1F0; 8'h5D : post_data = 10'h1E8; 8'h5E : post_data = 10'h1E0; 8'h5F : post_data = 10'h1D9; 8'h60 : post_data = 10'h1D1; 8'h61 : post_data = 10'h1C9; 8'h62 : post_data = 10'h1C2; 8'h63 : post_data = 10'h1BA; 8'h64 : post_data = 10'h1B3; 8'h65 : post_data = 10'h1AB; 8'h66 : post_data = 10'h1A4; 8'h67 : post_data = 10'h19D; 8'h68 : post_data = 10'h196; 8'h69 : post_data = 10'h18E; 8'h6A : post_data = 10'h187; 8'h6B : post_data = 10'h180; 8'h6C : post_data = 10'h179; 8'h6D : post_data = 10'h172; 8'h6E : post_data = 10'h16B; 8'h6F : post_data = 10'h165; 8'h70 : post_data = 10'h15E; 8'h71 : post_data = 10'h157; 8'h72 : post_data = 10'h151; 8'h73 : post_data = 10'h14A; 8'h74 : post_data = 10'h144; 8'h75 : post_data = 10'h13D; 8'h76 : post_data = 10'h137; 8'h77 : post_data = 10'h131; 8'h78 : post_data = 10'h12A; 8'h79 : post_data = 10'h124; 8'h7A : post_data = 10'h11E; 8'h7B : post_data = 10'h118; 8'h7C : post_data = 10'h113; 8'h7D : post_data = 10'h10D; 8'h7E : post_data = 10'h107; 8'h7F : post_data = 10'h101; 8'h80 : post_data = 10'h0FC; 8'h81 : post_data = 10'h0F6; 8'h82 : post_data = 10'h0F1; 8'h83 : post_data = 10'h0EC; 8'h84 : post_data = 10'h0E6; 8'h85 : post_data = 10'h0E1; 8'h86 : post_data = 10'h0DC; 8'h87 : post_data = 10'h0D7; 8'h88 : post_data = 10'h0D2; 8'h89 : post_data = 10'h0CD; 8'h8A : post_data = 10'h0C9; 8'h8B : post_data = 10'h0C4; 8'h8C : post_data = 10'h0BF; 8'h8D : post_data = 10'h0BB; 8'h8E : post_data = 10'h0B6; 8'h8F : post_data = 10'h0B2; 8'h90 : post_data = 10'h0AD; 8'h91 : post_data = 10'h0A9; 8'h92 : post_data = 10'h0A5; 8'h93 : post_data = 10'h0A1; 8'h94 : post_data = 10'h09D; 8'h95 : post_data = 10'h099; 8'h96 : post_data = 10'h095; 8'h97 : post_data = 10'h091; 8'h98 : post_data = 10'h08E; 8'h99 : post_data = 10'h08A; 8'h9A : post_data = 10'h086; 8'h9B : post_data = 10'h083; 8'h9C : post_data = 10'h07F; 8'h9D : post_data = 10'h07C; 8'h9E : post_data = 10'h079; 8'h9F : post_data = 10'h075; 8'hA0 : post_data = 10'h072; 8'hA1 : post_data = 10'h06F; 8'hA2 : post_data = 10'h06C; 8'hA3 : post_data = 10'h069; 8'hA4 : post_data = 10'h066; 8'hA5 : post_data = 10'h063; 8'hA6 : post_data = 10'h061; 8'hA7 : post_data = 10'h05E; 8'hA8 : post_data = 10'h05B; 8'hA9 : post_data = 10'h059; 8'hAA : post_data = 10'h056; 8'hAB : post_data = 10'h054; 8'hAC : post_data = 10'h051; 8'hAD : post_data = 10'h04F; 8'hAE : post_data = 10'h04C; 8'hAF : post_data = 10'h04A; 8'hB0 : post_data = 10'h048; 8'hB1 : post_data = 10'h046; 8'hB2 : post_data = 10'h044; 8'hB3 : post_data = 10'h042; 8'hB4 : post_data = 10'h040; 8'hB5 : post_data = 10'h03E; 8'hB6 : post_data = 10'h03C; 8'hB7 : post_data = 10'h03A; 8'hB8 : post_data = 10'h038; 8'hB9 : post_data = 10'h036; 8'hBA : post_data = 10'h035; 8'hBB : post_data = 10'h033; 8'hBC : post_data = 10'h031; 8'hBD : post_data = 10'h030; 8'hBE : post_data = 10'h02E; 8'hBF : post_data = 10'h02D; 8'hC0 : post_data = 10'h02B; 8'hC1 : post_data = 10'h02A; 8'hC2 : post_data = 10'h029; 8'hC3 : post_data = 10'h027; 8'hC4 : post_data = 10'h026; 8'hC5 : post_data = 10'h025; 8'hC6 : post_data = 10'h023; 8'hC7 : post_data = 10'h022; 8'hC8 : post_data = 10'h021; 8'hC9 : post_data = 10'h020; 8'hCA : post_data = 10'h01F; 8'hCB : post_data = 10'h01E; 8'hCC : post_data = 10'h01D; 8'hCD : post_data = 10'h01C; 8'hCE : post_data = 10'h01B; 8'hCF : post_data = 10'h01A; 8'hD0 : post_data = 10'h019; 8'hD1 : post_data = 10'h018; 8'hD2 : post_data = 10'h017; 8'hD3 : post_data = 10'h016; 8'hD4 : post_data = 10'h015; 8'hD5 : post_data = 10'h015; 8'hD6 : post_data = 10'h014; 8'hD7 : post_data = 10'h013; 8'hD8 : post_data = 10'h012; 8'hD9 : post_data = 10'h012; 8'hDA : post_data = 10'h011; 8'hDB : post_data = 10'h010; 8'hDC : post_data = 10'h010; 8'hDD : post_data = 10'h00F; 8'hDE : post_data = 10'h00F; 8'hDF : post_data = 10'h00E; 8'hE0 : post_data = 10'h00E; 8'hE1 : post_data = 10'h00D; 8'hE2 : post_data = 10'h00D; 8'hE3 : post_data = 10'h00C; 8'hE4 : post_data = 10'h00C; 8'hE5 : post_data = 10'h00B; 8'hE6 : post_data = 10'h00B; 8'hE7 : post_data = 10'h00A; 8'hE8 : post_data = 10'h00A; 8'hE9 : post_data = 10'h009; 8'hEA : post_data = 10'h009; 8'hEB : post_data = 10'h009; 8'hEC : post_data = 10'h008; 8'hED : post_data = 10'h008; 8'hEE : post_data = 10'h008; 8'hEF : post_data = 10'h007; 8'hF0 : post_data = 10'h007; 8'hF1 : post_data = 10'h007; 8'hF2 : post_data = 10'h006; 8'hF3 : post_data = 10'h006; 8'hF4 : post_data = 10'h006; 8'hF5 : post_data = 10'h006; 8'hF6 : post_data = 10'h005; 8'hF7 : post_data = 10'h005; 8'hF8 : post_data = 10'h005; 8'hF9 : post_data = 10'h005; 8'hFA : post_data = 10'h004; 8'hFB : post_data = 10'h004; 8'hFC : post_data = 10'h004; 8'hFD : post_data = 10'h004; 8'hFE : post_data = 10'h004; 8'hFF : post_data = 10'h003; endcase end endmodule 

 3、fpga实现双边滤波

1、那么首先第一步就要求每个像素对应的相似度权重(代码太多,第一个像素点为例)

always @(posedge sys_clk) if(!sys_rst_n) pre_data11<=8'd0; else pre_data11<=(matrix_p11>matrix_p22) ? (matrix_p11-matrix_p22) : (matrix_p22-matrix_p11); bilateral_similarity_lut bilateral_similarity_lut_un1 ( .pre_data (pre_data11 ), .post_data (post_data11) ); 

2、3x3高斯模板和3x3相似度对应相乘

//扩大了2^7*2^10 delay 2 clk always @(posedge sys_clk) if(!sys_rst_n||fram_syn) begin s11_mult_g11 <= 17'd0; s12_mult_g12 <= 17'd0; s13_mult_g13 <= 17'd0; s21_mult_g21 <= 17'd0; s22_mult_g22 <= 17'd0; s23_mult_g23 <= 17'd0; s31_mult_g31 <= 17'd0; s32_mult_g32 <= 17'd0; s33_mult_g33 <= 17'd0; end else begin s11_mult_g11 <= post_data11 * g11; s12_mult_g12 <= post_data12 * g12; s13_mult_g13 <= post_data13 * g13; s21_mult_g21 <= post_data21 * g21; s22_mult_g22 <= post_data22 * g22; s23_mult_g23 <= post_data23 * g23; s31_mult_g31 <= post_data31 * g31; s32_mult_g32 <= post_data32 * g32; s33_mult_g33 <= post_data33 * g33; end

3、求出归一化总权重,为后续求值做准备。

//归一化 delay 3 clk reg [20:0] weight_sum; always @(posedge sys_clk) if(!sys_rst_n||fram_syn) weight_sum<=21'd0; else weight_sum<=s11_mult_g11+s12_mult_g12+s13_mult_g13+ s21_mult_g21+s22_mult_g22+s23_mult_g23+ s31_mult_g31+s32_mult_g32+s33_mult_g33;

4、求出双边权重与对应像素相乘的结果,并以三行为单位进行求和。

//第四个时钟周期 always@(posedge sys_clk) if(!sys_rst_n)begin sum_line1_data<=27'd0; sum_line2_data<=27'd0; sum_line3_data<=27'd0; end else begin sum_line1_data<=matrix_p11_reg[23:16]*s11_mult_g11_reg+matrix_p12_reg[23:16]*s12_mult_g12_reg+matrix_p13_reg[23:16]*s13_mult_g13_reg; sum_line2_data<=matrix_p21_reg[23:16]*s21_mult_g21_reg+matrix_p22_reg[23:16]*s22_mult_g22_reg+matrix_p23_reg[23:16]*s23_mult_g23_reg; sum_line3_data<=matrix_p31_reg[23:16]*s31_mult_g31_reg+matrix_p32_reg[23:16]*s32_mult_g32_reg+matrix_p33_reg[23:16]*s33_mult_g33_reg; end

5、求出输出像素点

//由于除法器非常耗资源,这里算出结果后再归一化。由8次除法器变为一次,大大减小资源开销 reg [31:0] bil_data; always@(posedge sys_clk) if(!sys_rst_n) bil_data<=32'd0; else if(weight_sum1!=21'd0) bil_data<=(sum_line1_data+sum_line2_data+sum_line3_data)/weight_sum1; else bil_data<=32'd0;

4、特别注意点

1、首先我们要注意上述过程消耗了几个时钟周期,输出行场信号就要延迟几个时钟周期。

2、我们发现第一个时钟周期我们已经使用了输出矩阵像素,但是后面我们发现我们又需要用到此周期内的数据,因此有些数据也需要我们做延迟处理。

5、仿真结果

首先我们观察下图发现我们在第一个时钟周期内消耗了第一组3x3数据,求出了相似度权重,

然后如下又耗费一个时钟周期求的结合高斯核和相似度的双边权重:

之后在耗费一个时钟周期求出归一化权重和,为后面的输出数据做准备。

再者,我们通过移位寄存器延迟数据获得我们需要的像素矩阵,然后通过耗费一个时钟周期此求和:

最后我们求出输出像素点,并对输出行场信号进行相应的后延:

6、systemverilog 验证

我们也可以通过编写systemverilog代码验证。设定小量数据写入文件然后通过代码对文件读取和结果导入验证算法。以下是部分核心代码:

task image_input; bit [9:0] row_cnt; bit [9:0] col_cnt; bit [7:0] mem [image_width*image_height-1:0];//4*4-1 $readmemh("./img_Gray1.dat",mem); for(row_cnt = 0;row_cnt < image_height;row_cnt++) begin repeat(4) @(posedge sys_clk); rgb_vs = 1'b1; repeat(4) @(posedge sys_clk); for(col_cnt = 0;col_cnt < image_width;col_cnt++) begin rgb_hr = 1'b1; rgb_data = mem[row_cnt*image_width+col_cnt]; @(posedge sys_clk); end rgb_hr = 1'b0; end repeat(4) @(posedge sys_clk); rgb_vs = 1'b0; @(posedge sys_clk); endtask : image_input 

Read more

AIGC-Fooocus部署实践:从本地手动配置到云端一键启用的深度剖析

AIGC-Fooocus部署实践:从本地手动配置到云端一键启用的深度剖析

摘要: 本文旨在为人工智能生成内容(AIGC)领域的爱好者和开发者提供一份详尽的Fooocus部署指南。Fooocus作为一款基于Gradio的开源图像生成软件,凭借其简化的操作和高质量的输出,受到了广泛关注。我们将通过两种截然不同的部署路径——传统的本地手动环境配置与现代化的云平台一键部署——来全面探索Fooocus的落地过程。本文将深入剖析手动部署中的每一个步骤、每一条命令及其背后的技术逻辑,详细记录可能遇到的环境冲突与解决方案,并将其与云端部署的流畅体验进行客观对比,为读者在不同场景下选择最合适的部署策略提供坚实的技术参考。 第一章:引言——Fooocus与AIGC部署的挑战 随着Stable Diffusion等底层模型的开源,AIGC技术,特别是文生图领域,迎来了爆发式的增长。各种应用和WebUI层出不穷,极大地降低了普通用户接触和使用前沿AI模型的门槛。在众多工具中,由lllyasviel(ControlNet的作者)开发的Fooocus,以其独特的哲学脱颖而出。Fooocus的设计理念是“化繁为简”,它在保留Stable Diffusion XL(SDXL)强大能力的

LLaMA Factory多模态微调实践

LLaMA Factory 多模态微调实践 一、前提准备:环境与数据深度适配 (一)运行环境技术规格 1. 硬件配置底层逻辑 * GPU 选型依据: * 推荐 24GB 显存的 A10(ecs.gn7i-c8g1.2xlarge)。 * 核心原因:Qwen2-VL-2B 模型加载后显存占用约 8-10GB,全参微调过程中梯度计算、优化器状态存储需额外 10-12GB 显存,24GB 可避免显存溢出(OOM)。 * 若使用 16GB 显存的 T4 等型号,需启用梯度检查点(gradient checkpointing),但会增加约 20% 训练时间。 * CPU 与内存配套: * 建议 8 核 CPU + 32GB 内存,避免数据加载(

终极语音转文字与说话人分离完整指南:Whisper Diarization快速入门

终极语音转文字与说话人分离完整指南:Whisper Diarization快速入门 【免费下载链接】whisper-diarizationAutomatic Speech Recognition with Speaker Diarization based on OpenAI Whisper 项目地址: https://gitcode.com/GitHub_Trending/wh/whisper-diarization 在当今数字化办公环境中,语音转文字与说话人分离技术正成为提升工作效率的关键工具。Whisper Diarization作为基于OpenAI Whisper的开源项目,完美解决了多说话人场景下的语音识别难题,让您能够快速获得带说话人标签的完整转录文本。 🎯 项目核心价值:为什么选择Whisper Diarization 传统语音识别工具在处理多人对话时往往无法区分不同说话者,导致转录结果难以阅读和分析。Whisper Diarization通过整合顶尖的语音处理技术,提供了以下独特价值: * 智能说话人识别:自动区分音频中的不同说话者 * 精准时间戳对齐

llama.cpp加载多模态gguf模型

llama.cpp预编译包还不支持cuda12.6 llama.cpp的编译,也有各种坑 llama.cpp.python的也需要编译 llama.cpp命令行加载多模态模型 llama-mtmd-cli -m Qwen2.5-VL-3B-Instruct-q8_0.gguf --mmproj Qwen2.5-VL-3B-Instruct-mmproj-f16.gguf -p "Describe this image." --image ./car-1.jpg **模型主gguf文件要和mmporj文件从一个库里下载,否则会有兼容问题,建议从ggml的官方库里下载 Multimodal GGUFs官方库 llama.cpp.python加载多模态模型 看官方文档 要使用LlamaChatHandler类,官方已经写好了不少多模态模型的加载类,比如qwen2.5vl的写法: from llama_cpp import Llama