灰度图像均值滤波算法的HDL实现介绍

1.1  均值滤波算法介绍
首先要做的是最简单的均值滤波算法。均值滤波是典型的线性滤波算法,它是指在图像上对目标像素给一个模板,该模板包括了其周围的临近像素(以目标象素为中心的周围 8 个像素,构成一个滤波模板,即去掉目标像素本身),再用模板中的全体像素的平均值来代替原来像素值。
p11 p12 p13
p21   p23
p31 p32 p33
中值滤波算法可以形象的用上述表格来描述,即对于每个 3*3 的阵列而言,中间像素的值,等于边缘 8 个像素的平均值。 算法的理论很简单,对于 c 处理器而言,一幅640*480图像的均值滤波, 可以很方便的通过数组获得 3*3 的阵列,但对于我们的 verilog hdl 而言,着实不易。
1.2 3*3 像素阵列的 hdl 实现
3*3 阵列的获取,大概有以下三种方式:
(1) 通过 2 个或 3 个 ram 的存储,来实现 3*3 像素阵列;
(2) 通过 2 个或 3 个 fifo 的存储,来实现 3*3 像素阵列;
( 3) 通过 2 行或 3 行 shift_ram 的移位存储,来实现 3*3 像素阵列。
不过经验告诉大家,最方便的实现方式,非 shift_ram 莫属了,都感觉 shift_ram 甚至是为实现 3*3 阵列而生的!
在quartus ii中,可以利用下图方式产生,很方便:
首先介绍一下 shift_ram, 宏定义模块如下图所示:
图6‑1 quartusii shift_ram ip使用界面
shift_ram 可定义数据宽度、 移位的行数、 每行的深度。 这里我们固然需要8bit,640 个数据每行,同时移位寄存 2 行即可( 原因看后边)。 同时选择时钟使能端口clken。 详细的关于 shift_ram 的手册参数,可在宏定义窗口右上角document 中查看,如下:
手册给出了一个非常形象的移位寄存示意图,如下所示:
图6‑2移位寄存示意图
实现3*3像素阵列的大概的思路是这样子的, shift_ram 中存 2 行数据,同时与当前输入行的数据,组成3 行的阵列。
在vivado中就没有类似的ip,但是难不倒我,可以利用成熟的ip核实现类似上诉的移位寄存器。
在《image�06_ov5640_ddr3_gray_mean_filterov5640_demouserlinebuffer_wapper》
中实现的就是移位寄存器功能的ip,使用方法类似上诉quartusii中的使用。
例化方式如下:
代码6‑1
1.  //---------------------------------------  
2.  //module of shift ram for raw data  
3.  wire    shift_clk_en = per_frame_clken;  
4.    
5.  linebuffer_wapper#  
6.  (  
7.      .no_of_lines(2),  
8.      .samples_per_line(640),  
9.      .data_width(8)  
10.)  
11. linebuffer_wapper_m0(  
12.    .ce         (1'b1       ),  
13.    .wr_clk     (clk       ),  
14.    .wr_en      (shift_clk_en   ),  
15.    .wr_rst     (rst_n       ),  
16.    .data_in    (row3_data    ),  
17.    .rd_en      (shift_clk_en   ),  
18.    .rd_clk     (clk        ),  
19.    .rd_rst     (rst_n        ),  
20.    .data_out   ({row2_data,row1_data} )  
21.   );
源码vip_matrix_generate_3x3_8bit 文件中实现 8bit 宽度的 3*3 像素阵列功能。 具体的实现步骤如下:
(1) 首先,将输入的信号用像素使能时钟同步一拍,以保证数据与shift_ram 输出的数据保持同步,如下:
代码6‑2
1.  //generate 3*3 matrix   
2.  //--------------------------------------------------------------------------  
3.  //--------------------------------------------------------------------------  
4.  //--------------------------------------------------------------------------  
5.  //sync row3_data with per_frame_clken & row1_data & raw2_data  
6.  wire    [7:0]   row1_data;  //frame data of the 1th row  
7.  wire    [7:0]   row2_data;  //frame data of the 2th row  
8.  reg     [7:0]   row3_data;  //frame data of the 3th row  
9.  always@(posedge clk or negedge rst_n)  
10.begin  
11.    if(!rst_n)  
12.        row3_data <= 0;  
13.    else   
14.        begin  
15.        if(per_frame_clken)  
16.            row3_data <= per_img_y;  
17.        else  
18.            row3_data <= row3_data;  
19.        end   
20.end
(2) 接着,例化并输入 row3_data,此时可从 modelsim 中观察到 3 行数据同时存在了, hdl 如下:
代码6‑3 quartusii例化移位寄存器代码
1.  //---------------------------------------  
2.  //module of shift ram for raw data  
3.  wire shift_clk_en = per_frame_clken;  
4.  line_shift_ram_8bit  
5.  #(  
6.  .ram_length (img_hdisp)  
7.  )  
8.  u_line_shift_ram_8bit  
9.  (  
10..clock (clk),  
11..clken (shift_clk_en), //pixel enable clock  
12.// .aclr (1'b0),  
13..shiftin (row3_data), //current data input  
14..taps0x (row2_data), //last row data  
15..taps1x (row1_data), //up a row data  
16..shiftout ()  
17.);
代码6‑4 vivado例化移位寄存器代码
22.//---------------------------------------  
23.//module of shift ram for raw data  
24.wire    shift_clk_en = per_frame_clken;  
25.  
26.linebuffer_wapper#  
27.(  
28.    .no_of_lines(2),  
29.    .samples_per_line(640),  
30.    .data_width(8)  
31.)  
32. linebuffer_wapper_m0(  
33.    .ce         (1'b1       ),  
34.    .wr_clk     (clk       ),  
35.    .wr_en      (shift_clk_en   ),  
36.    .wr_rst     (rst_n       ),  
37.    .data_in    (row3_data    ),  
38.    .rd_en      (shift_clk_en   ),  
39.    .rd_clk     (clk        ),  
40.    .rd_rst     (rst_n        ),  
41.    .data_out   ({row2_data,row1_data} )  
42.   );
在经过 shift_ramd 移位存储后,我们得到的 row0_data, row1_data,row2_data的仿真示意图如下所示:
图6‑3 modelsim仿真截图
数据从 row3_data 输入, 满 3 行后刚好唯一 3 行阵列的第一。 从图像第三行输入开始,到图像的最后一行,我们均可从 row_data 得到完整的 3 行数据, 基为实现3*3阵列奠定了基础。 不过这样做有2个不足之处, 即第一行与第二行不能得到完整的 3*3 阵列。 但从主到次,且不管算法的完美型,我们先验证 3x3模板实现的正确性。 因此直接在行有效期间读取 3*3 阵列,机器方便快捷的实现了我们的目的。
(3) row_data 读取信号的分析及生成
这里涉及到了一个问题,数据从shift_ram存储耗费了一个时钟,因此3*3阵列的读取使能与时钟,需要进行一个 clock 的偏移,如下所示:
代码6‑5
1.  //------------------------------------------  
2.  //lag 2 clocks signal sync    
3.  reg [1:0]   per_frame_vsync_r;  
4.  reg [1:0]   per_frame_href_r;     
5.  reg [1:0]   per_frame_clken_r;  
6.  always@(posedge clk or negedge rst_n)  
7.  begin  
8.      if(!rst_n)  
9.          begin  
10.        per_frame_vsync_r <= 0;  
11.        per_frame_href_r <= 0;  
12.        per_frame_clken_r <= 0;  
13.        end  
14.    else  
15.        begin  
16.        per_frame_vsync_r   <=   {per_frame_vsync_r[0],  per_frame_vsync};  
17.        per_frame_href_r    <=   {per_frame_href_r[0],   per_frame_href};  
18.        per_frame_clken_r    p32 -> p33 -> ]   ---> [ p11 p12 p13 ]  
6.                        [ p21 -> p22 -> p23 -> ]   ---> [ p21 p22 p23 ] 
7.                        [ p11 -> p12 -> p11 -> ]   ---> [ p31 p32 p33 ] 
8.        ******************************************************************************/  
9.        //---------------------------------------------------------------------------  
10.      //---------------------------------------------------  
11.      /*********************************************** 
12.          (1) read data from shift_ram 
13.          (2) caculate the sobel 
14.          (3) steady data after sobel generate 
15.      ************************************************/  
16.      //wire  [23:0]  matrix_row1 = {matrix_p11, matrix_p12, matrix_p13}; //just for test  
17.      //wire  [23:0]  matrix_row2 = {matrix_p21, matrix_p22, matrix_p23};  
18.      //wire  [23:0]  matrix_row3 = {matrix_p31, matrix_p32, matrix_p33};  
19.      always@(posedge clk or negedge rst_n)  
20.      begin  
21.          if(!rst_n)  
22.              begin  
23.              {matrix_p11, matrix_p12, matrix_p13} <= 24'h0;  
24.              {matrix_p21, matrix_p22, matrix_p23} <= 24'h0;  
25.              {matrix_p31, matrix_p32, matrix_p33} <= 24'h0;  
26.              end  
27.          else if(read_frame_href)  
28.              begin  
29.              if(read_frame_clken)    //shift_ram data read clock enable  
30.                  begin  
31.                  {matrix_p11, matrix_p12, matrix_p13} <= {matrix_p12, matrix_p13, row1_data}; //1th shift input  
32.                  {matrix_p21, matrix_p22, matrix_p23} <= {matrix_p22, matrix_p23, row2_data}; //2th shift input  
33.                  {matrix_p31, matrix_p32, matrix_p33} <= {matrix_p32, matrix_p33, row3_data}; //3th shift input  
34.                  end  
35.              else  
36.                  begin  
37.                  {matrix_p11, matrix_p12, matrix_p13} <= {matrix_p11, matrix_p12, matrix_p13};  
38.                  {matrix_p21, matrix_p22, matrix_p23} <= {matrix_p21, matrix_p22, matrix_p23};  
39.                  {matrix_p31, matrix_p32, matrix_p33} <= {matrix_p31, matrix_p32, matrix_p33};  
40.                  end   
41.              end  
42.          else  
43.              begin  
44.              {matrix_p11, matrix_p12, matrix_p13} <= 24'h0;  
45.              {matrix_p21, matrix_p22, matrix_p23} <= 24'h0;  
46.              {matrix_p31, matrix_p32, matrix_p33} <= 24'h0;  
47.              end  
48.      end
最后得到的 matrix_p11、 p12、 p13、 p21、 p22、 p23、 p31、 p32、 p33 即为得到的 3*3 像素阵列,仿真时序图如下所示:
前面shift_ram存储耗费了一个时钟,同时 3*3 阵列的生成耗费了一个时钟,因此我们需要人为的将行场信号、像素使能读取信号移动 2 个时钟,如下所示:
assign matrix_frame_vsync =per_frame_vsync_r[1];
assign matrix_frame_href =per_frame_href_r[1];
assign matrix_frame_clken =per_frame_clken_r[1];
至此我们得到了完整的 3*3 像素阵列的模块,同时行场、使能时钟信号与时序保持一致,modelsim 仿真图如下所示:
1.3 mean_filter 均值滤波算法的实现
不过相对于 3*3 像素阵列的生成而言,均值滤波的算法实现反而难度小的多,只是技巧性的问题。
继续分析上面这个表格。其实 hdl 完全有这个能力直接计算 8 个值相加的均值,不过为了提升电路的速度,建议我们需要通过以面积换速度的方式来实现。 so 这里需要 3 个步骤:
(1) 分别计算 3 行中相关像素的和;
(2) 计算(1) 中三个结果的和;
在(2) 运算后,我们不能急着用除法去实现均值的运算。记住,能用移位替换的,绝对不用乘除法来实现。这里 8 个像素, 即除以 8, 可以方便的用右移动 3bit 来实现。不过这里更方便的办法是,直接提取 mean_value4[10:3]。
这一步我们不单独作为一个 step, 而是直接作为结果输出。分析前面的运算,总共耗费了 2 个时钟, 因此需要将行场信号、像素读取信号偏移 2 个时钟,同时像素时钟,根据行信号使能,直接读取 mean_value4[10:3],如下所示:
这样,我们便得到了运算后的时序,实现了均值滤波算法。
最后,在 video_image_processor 顶层文件中例化gray_mean_filter算法模块,完成算法的添加。
最后直接将生成的 post_img_y 输入给 24bit 的 ddr 控制器即可。


南卡Runner cc ll携五大升级体验高调出道,“小”公司的大产品!
汽车电子点火电路 (Electronic car ignit
中国移动简勤:2020年双百亿计划公布,明年要销售1亿部5G手机
工业RFID应用之基础篇(七):6个参数反映RFID标签的优劣
基于LAN接口的仪器扩展(LXI)在汽车电子测试中的应用
灰度图像均值滤波算法的HDL实现介绍
长安汽车宣布正式进军电池自研领域
研华将全力推动工业物联网的发展
Verbix提供了一种工具,可以分析文本并猜测其语言
首都航空将于2019年10月27日起全面转场至北京大兴国际机场
ANYMESH-SDR-A4 MES无线自组网 室外固定电台-万蓝通信
格芯推出GF Connex解决方案 助力实现下一代无线连接
预测今年全球光伏装机规模为104GW,中国仍然可以达到35至40GW
网络存储怎么样虚拟化
慧城市照明集成服务商龙腾照明距离正式IPO更近一步
自制冷风机diy图解
轮廓仪,你真的了解吗?
Spartan-6 FPGA中的DCM功能介绍
Silicon Labs推出增强UV防护和手势识别的新一代光学传感器
TE全新直流快速连接器持续助力光伏电站的发展