ZYNQ图像处理(6)——均值滤波和中值滤波

一、均值和中值滤波基本原理

首先要做的是最简单的均值滤波算法。均值滤波是典型的线性滤波算法,它是指在图像上对目标像素给一个模板,该模板包括了其周围的临近像素(以目标象素为中心的周围 8 个像素,构成一个滤波模板,即去掉目标像素本身),再用模板中的全体像素的平均值来代替原来像素值。中值滤波算法可以形象的用上述表格来描述,即对于每个 3 _3 的阵列而言,中间像素的值,等于边缘 8 个像素的平均值。

ZYNQ图像处理(6)——均值滤波和中值滤波
《基于FPGA的实时中值滤波器的硬件实现》这篇文章中介绍了一种适合FPGA实现的快速寻找中值的算法,算法过程是这样的:首先计算每一行的最大值、中值以及最小值;之后计算第一列最大值中的最小值,第二列中间值中的中间值,第三列最小值中的最大值;最后再次计算这三个值的中间值就可以得到中值了。
ZYNQ图像处理(6)——均值滤波和中值滤波

; 二、3×3图像矩阵的提取

可以发现,无论是均值滤波还是中值滤波,一个必要的步骤就是得到3×3的图像矩阵。这个图像矩阵在matlab中很容易得到,因为matlab读取的是整一张的图片。但是在FPGA中对图像的实时处理,则需要先缓存两到三行的数据,然后从这3行数据中获得3×3图像矩阵。
查阅了一些资料,发现在VIVADO中实现3×3矩阵有以下几种方法。(1)第一种是利用vivado的IP核:ram based shift register,这个ip核在缓存数据量小的时候可以用。但是数据量很大,为几百个以上的时候,缓存数量就不准了,不知道是不是bug,因此不推荐使用。(2)第二种是利用RAM(3)第三种是利用fifo实现,利用fifo实现野火教程很详细。我这边是利用了两个ram实现数据的缓存和3×3矩阵的提取,接下来介绍实现过程。

2.1、例化ram ip核

首先自然是先需要例化一个ram的IP核,这个ram的配置信息如下如所示,是一个双端口的ram,宽度8位,深度1024位,优先写操作。详细的可以看配置的图。

ZYNQ图像处理(6)——均值滤波和中值滤波
ZYNQ图像处理(6)——均值滤波和中值滤波
ZYNQ图像处理(6)——均值滤波和中值滤波

; 2.2、基于ram的移位寄存器

基于ram 的移位寄存器代码如下图所示。输入端口有时钟信号、时钟使能信号、行同步信号、数据输入信号。输出端口有前一行数据、前前一行的数据。具体实现是这样的,当行同步和时钟使能到来时,地址开始累加了,当行同步结束,地址清零。之后对时钟信号、地址信号、数据信号都延迟三拍。最后两个ram分别存储了前一行和前前一行的图像。
比较关键的一点是,ram是先开始读,延迟几个时钟后才开始写,因为刚开始是读不到数据的,只有下一个行同步信号来的时候,前一行数据才会被读出来,这里比较绕。

module line_shift_RAM_8bit1(
    input clock,

    input          clken,
    input          per_frame_href,

    input   [7:0]  shiftin,  //当前行的数据
    output  [7:0]  taps0x,   //前一行的数据
    output  [7:0]  taps1x    //前前一行的数据
);

//reg define
reg  [2:0]  clken_dly;
reg  [9:0]  ram_rd_addr;
reg  [9:0]  ram_rd_addr_d0;
reg  [9:0]  ram_rd_addr_d1;
reg  [9:0]  ram_rd_addr_d2;
reg  [7:0]  shiftin_d0;
reg  [7:0]  shiftin_d1;
reg  [7:0]  shiftin_d2;
reg  [7:0]  taps0x_d0;

//在数据到来时,RAM的读地址累加
always@(posedge clock)begin
    if(per_frame_href)
        if(clken)
            ram_rd_addr  ram_rd_addr + 1 ;
        else
            ram_rd_addr  ram_rd_addr ;
    else
        ram_rd_addr  0 ;
end

//对时钟延迟3拍
always@(posedge clock) begin
    clken_dly  { clken_dly[1:0] , clken };
end

//将RAM地址延迟3拍
always@(posedge clock ) begin
    ram_rd_addr_d0  ram_rd_addr;
    ram_rd_addr_d1  ram_rd_addr_d0;
    ram_rd_addr_d2  ram_rd_addr_d1;
end

//输入数据延迟3拍送入RAM
always@(posedge clock)begin
    shiftin_d0  shiftin;
    shiftin_d1  shiftin_d0;
    shiftin_d2  shiftin_d1;
end

//用于存储前一行图像的RAM
blk_mem_gen_0  u_ram_1024x8_0(
  .clka   (clock),
  .wea    (clken_dly[2]),
  .addra  (ram_rd_addr_d2),     //在延迟的第三个时钟周期,当前行的数据写入RAM0
  .dina   (shiftin_d2),

  .clkb   (clock),
  .addrb  (ram_rd_addr),
  .doutb  (taps0x)              //延迟一个时钟周期,输出RAM0中前一行图像的数据
);

//寄存前一行图像的数据
always@(posedge clock)begin
    taps0x_d0   taps0x;
end

//用于存储前前一行图像的RAM
blk_mem_gen_0  u_ram_1024x8_1(
    .clka   (clock),
    .wea    (clken_dly[1]),
    .addra  (ram_rd_addr_d1),
    .dina   (taps0x_d0),       //在延迟的第二个时钟周期,将前一行图像的数据写入RAM1

    .clkb   (clock),
    .addrb  (ram_rd_addr),
    .doutb  (taps1x)           //延迟一个时钟周期,输出RAM1中前前一行图像的数据
);

endmodule

我这边结合仿真说一下,当clken[2]信号为高时,开始往里边写信号,可以看到第一次是把十六进制51写到了地址0中,但是可以发现读地址是已经到了3了,所以读速度是比写速度快了三个时钟周期,因为下一个行同步来时才可以得到上一行数据。

ZYNQ图像处理(6)——均值滤波和中值滤波
可以看到,下面这张图就是下一个行同步时间内,下一行数据从taps0x中读了出来。taps1x也是同样的道理,这边不在叙述。
ZYNQ图像处理(6)——均值滤波和中值滤波

2.3、3×3图像矩阵提取

3×3图像矩阵提取的代码如下图所示,代码本身比较简单,就是将输入信号缓存,然后提取出矩阵。这里需要注意信号的同步,得到3×3矩阵信号要比进来的信号慢了两个时钟周期。所以需要延迟两拍。

module  VIP_matrix_generate
(
    input               clk,
    input               rst_n,

    input               per_frame_vsync,
    input               per_frame_href,
    input               per_frame_clken,
    input        [7:0]  per_img_Y,

    output              matrix_frame_vsync,
    output              matrix_frame_href,
    output              matrix_frame_clken,
    output  reg  [7:0]  matrix_p11,
    output  reg  [7:0]  matrix_p12,
    output  reg  [7:0]  matrix_p13,
    output  reg  [7:0]  matrix_p21,
    output  reg  [7:0]  matrix_p22,
    output  reg  [7:0]  matrix_p23,
    output  reg  [7:0]  matrix_p31,
    output  reg  [7:0]  matrix_p32,
    output  reg  [7:0]  matrix_p33
);

//wire define
wire  [7:0]  row1_data;
wire  [7:0]  row2_data;
wire         read_frame_href ;
wire         read_frame_clken;

//reg define
reg  [7:0]  row3_data;
reg  [1:0]  per_frame_vsync_r;
reg  [1:0]  per_frame_href_r;
reg  [1:0]  per_frame_clken_r;

assign  read_frame_href    = per_frame_href_r[0] ;
assign  read_frame_clken   = per_frame_clken_r[0];
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];

//present signal
always@(posedge clk or negedge rst_n) begin
    if(!rst_n)
        row3_data  0;
    else begin
        if(per_frame_clken)
            row3_data  per_img_Y ;
        else
            row3_data  row3_data ;
    end
end

line_shift_RAM_8bit1 u_line_shift_RAM_8bit1(
    .clock          ( clk               ),
    .clken          ( per_frame_clken   ),
    .per_frame_href ( per_frame_href    ),
    .shiftin        ( per_img_Y         ),
    .taps0x         ( row2_data         ),
    .taps1x         ( row1_data         )
);

//delay 2 tclk
always@(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        per_frame_vsync_r  0;
        per_frame_href_r   0;
        per_frame_clken_r  0;
    end
    else begin
        per_frame_vsync_r  { per_frame_vsync_r[0], per_frame_vsync };
        per_frame_href_r   { per_frame_href_r[0],  per_frame_href  };
        per_frame_clken_r  { per_frame_clken_r[0], per_frame_clken };
    end
end

//generate the 3X3 matrix
always@(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        {matrix_p11, matrix_p12, matrix_p13}  24'h0;
        {matrix_p21, matrix_p22, matrix_p23} h0;
        {matrix_p31, matrix_p32, matrix_p33}  24'h0;
    end
    else if(read_frame_href) begin
        if(read_frame_clken) begin
            {matrix_p11, matrix_p12, matrix_p13} h0;
        {matrix_p21, matrix_p22, matrix_p23}  24'h0;
        {matrix_p31, matrix_p32, matrix_p33} h0;
    end
end

endmodule

仿真的结果如下面三张图所示,当pei_img_Y数据来到的时候,延迟两个时钟周期进入到row3_data中,之后在延迟一个周期数据送到p31、p32以及p33中。row2_data和row1_data的得到也是同样的原理。

ZYNQ图像处理(6)——均值滤波和中值滤波
ZYNQ图像处理(6)——均值滤波和中值滤波
ZYNQ图像处理(6)——均值滤波和中值滤波

三、均值滤波的实现

前面已经清楚描述了均值滤波的实现过程,主要就是将图像像素点周围8个像素的值加起来然后除以8就可以了,下面式均值滤波的实现代码。

module mean_filter(
    input clk,
    input rst_n,

    input [23:0] pre_data,
    input per_vsync,
    input per_href,
    input per_clken,

    output [23:0] post_data,
    output post_vsync,
    output post_href,
    output post_clken
);

//-----------------------------
//generate 3×3 picture matrix
//-----------------------------
wire matrix_frame_clken;
wire matrix_frame_href;
wire matrix_frame_vsync;
wire [7:0] matrix_p11;
wire [7:0] matrix_p12;
wire [7:0] matrix_p13;
wire [7:0] matrix_p21;
wire [7:0] matrix_p22;
wire [7:0] matrix_p23;
wire [7:0] matrix_p31;
wire [7:0] matrix_p32;
wire [7:0] matrix_p33;

VIP_matrix_generate u_VIP_matrix_generate(
    .clk                ( clk                ),
    .rst_n              ( rst_n              ),
    .per_frame_vsync    ( per_vsync          ),
    .per_frame_href     ( per_href           ),
    .per_frame_clken    ( per_clken          ),
    .per_img_Y          ( pre_data[7:0]      ),
    .matrix_frame_vsync ( matrix_frame_vsync ),
    .matrix_frame_href  ( matrix_frame_href  ),
    .matrix_frame_clken ( matrix_frame_clken ),
    .matrix_p11         ( matrix_p11         ),
    .matrix_p12         ( matrix_p12         ),
    .matrix_p13         ( matrix_p13         ),
    .matrix_p21         ( matrix_p21         ),
    .matrix_p22         ( matrix_p22         ),
    .matrix_p23         ( matrix_p23         ),
    .matrix_p31         ( matrix_p31         ),
    .matrix_p32         ( matrix_p32         ),
    .matrix_p33         ( matrix_p33         )
);

//-----------------------------
//mean filter function
//-----------------------------
reg [11:0] add_p1;
reg [11:0] add_p2;
reg [11:0] add_p3;
reg [11:0] add_all;
//step1:add every href
always @(posedge clk or negedge rst_n) begin
    if(~rst_n)begin
        add_p1<=12'd0;
        add_p2d0;
        add_p3<=12'd0;
    end
    else begin
      add_p1d0;
    end
    else begin
        add_alladd_p1+add_p2+add_p3;
    end
end
//step3:shift to get mean filter data
assign post_data={3{add_all[10:3]}};

//-----------------------------
//clk signal synchronization
//-----------------------------
reg [1:0] post_clken_dy;
reg [1:0] post_href_dy;
reg [1:0] post_vsync_dy;
always @(posedge clk or negedge rst_n) begin
    if(~rst_n)begin
        post_clken_dy2'd0;
        post_href_dyd0;
        post_vsync_dy2'd0;
    end
    else begin
        post_clken_dy{post_clken_dy[0],matrix_frame_clken};
        post_href_dy{post_href_dy[0],matrix_frame_href};
        post_vsync_dy{post_vsync_dy[0],matrix_frame_vsync};
    end
end
assign post_clken=post_clken_dy[1];
assign post_href=post_href_dy[1];
assign post_vsync=post_vsync_dy[1];

endmodule

然后下面两张图分别是我用vivado仿真的加了椒盐噪声之后的图片以及均值滤波之后的图片。可以发现,均值滤波片让图片变得模糊了,而且椒盐噪声也没有有效滤除。这边的vivado仿真图像处理是用verilog去模拟了摄像头的产生时序,实现过程是参考了b站up主大磊FPGA。

ZYNQ图像处理(6)——均值滤波和中值滤波
ZYNQ图像处理(6)——均值滤波和中值滤波

四、中值滤波的实现

中值滤波的实现和均值滤波相似,也是需要先提取3×3的图像矩阵,然后提取9个值中的中值,提取的方法在上面也讲到了。
首先下面是对三个输出信号进行排序的代码,分别排序并且输出最大值,中间值以及最小值。

module sort_three(
    input clk,
    input rst_n,
    input [7:0] data1,
    input [7:0] data2,
    input [7:0] data3,

    output reg [7:0] max_data,
    output reg [7:0] mid_data,
    output reg [7:0] min_data

);

//find max,mid and min data
//one clk to finish
always @(posedge clk or negedge rst_n) begin
    if(~rst_n)begin
      max_data8'd0;
      mid_datad0;
      min_data8'd0;
    end
    else if (data1>=data2 && data2>=data3)begin
        max_datadata1;
        mid_datadata2;
        min_datadata3;
    end
    else if (data1>=data3 && data3>=data2)begin
        max_datadata1;
        mid_datadata3;
        min_datadata2;
    end
    else if (data2>=data1 && data1>=data3)begin
        max_datadata2;
        mid_datadata1;
        min_datadata3;
    end
    else if (data2>=data3 && data3>=data1)begin
        max_datadata2;
        mid_datadata3;
        min_datadata1;
    end
    else if (data3>=data1 && data1>=data2)begin
        max_datadata3;
        mid_datadata1;
        min_datadata2;
    end
    else if (data3>=data2 && data2>=data1)begin
        max_datadata3;
        mid_datadata2;
        min_datadata1;
    end
end

endmodule

之后是中值滤波的代码,这部分实现的原理就是上文说到的提取中值的过程。

module median_filter(
    input clk,
    input rst_n,

    input [23:0] per_data,
    input per_vsync,
    input per_href,
    input per_clken,

    output [23:0] post_data,
    output post_vsync,
    output post_href,
    output post_clken
);

//-----------------------------
//generate 3×3 picture matrix
//-----------------------------
wire matrix_frame_clken;
wire matrix_frame_href;
wire matrix_frame_vsync;
reg [2:0] post_clken_dy;
reg [2:0] post_href_dy;
reg [2:0] post_vsync_dy;
wire [7:0] matrix_p11;
wire [7:0] matrix_p12;
wire [7:0] matrix_p13;
wire [7:0] matrix_p21;
wire [7:0] matrix_p22;
wire [7:0] matrix_p23;
wire [7:0] matrix_p31;
wire [7:0] matrix_p32;
wire [7:0] matrix_p33;

VIP_matrix_generate u_VIP_matrix_generate(
    .clk                ( clk                ),
    .rst_n              ( rst_n              ),
    .per_frame_vsync    ( per_vsync          ),
    .per_frame_href     ( per_href           ),
    .per_frame_clken    ( per_clken          ),
    .per_img_Y          ( per_data[7:0]      ),
    .matrix_frame_vsync ( matrix_frame_vsync ),
    .matrix_frame_href  ( matrix_frame_href  ),
    .matrix_frame_clken ( matrix_frame_clken ),
    .matrix_p11         ( matrix_p11         ),
    .matrix_p12         ( matrix_p12         ),
    .matrix_p13         ( matrix_p13         ),
    .matrix_p21         ( matrix_p21         ),
    .matrix_p22         ( matrix_p22         ),
    .matrix_p23         ( matrix_p23         ),
    .matrix_p31         ( matrix_p31         ),
    .matrix_p32         ( matrix_p32         ),
    .matrix_p33         ( matrix_p33         )
);

//---------------------------------------------------
//              midian filter function
//---------------------------------------------------
//[a11 a12 a13]             [max1 med1 min1]
//[a21 a22 a23]      to     [max2 med2 min2]
//[a31 a32 a33]             [max3 med3 min3]
//                                 to
//                 [min_of_max med_of_med max_of_min]
//                                to
//                               [med]
wire [7:0] max_data_1;
wire [7:0] mid_data_1;
wire [7:0] min_data_1;
wire [7:0] max_data_2;
wire [7:0] mid_data_2;
wire [7:0] min_data_2;
wire [7:0] max_data_3;
wire [7:0] mid_data_3;
wire [7:0] min_data_3;
wire [7:0] max_data_4;
wire [7:0] mid_data_4;
wire [7:0] min_data_4;
wire [7:0] max_data_5;
wire [7:0] mid_data_5;
wire [7:0] min_data_5;
wire [7:0] max_data_6;
wire [7:0] mid_data_6;
wire [7:0] min_data_6;
wire [7:0] max_data_7;
wire [7:0] mid_data_7;
wire [7:0] min_data_7;

//step1:every line find max,mid and min data
sort_three u_sort_three_1(
    .clk      ( clk              ),
    .rst_n    ( rst_n            ),
    .data1    ( matrix_p11       ),
    .data2    ( matrix_p12       ),
    .data3    ( matrix_p13       ),
    .max_data ( max_data_1       ),
    .mid_data ( mid_data_1       ),
    .min_data ( min_data_1       )
);
sort_three u_sort_three_2(
    .clk      ( clk              ),
    .rst_n    ( rst_n            ),
    .data1    ( matrix_p21       ),
    .data2    ( matrix_p22       ),
    .data3    ( matrix_p23       ),
    .max_data ( max_data_2       ),
    .mid_data ( mid_data_2       ),
    .min_data ( min_data_2       )
);
sort_three u_sort_three_3(
    .clk      ( clk              ),
    .rst_n    ( rst_n            ),
    .data1    ( matrix_p31       ),
    .data2    ( matrix_p32       ),
    .data3    ( matrix_p33       ),
    .max_data ( max_data_3       ),
    .mid_data ( mid_data_3       ),
    .min_data ( min_data_3       )
);
//step2:every line find min_of_max,med_of_med,max_of_min
sort_three u_sort_three_4(
    .clk      ( clk              ),
    .rst_n    ( rst_n            ),
    .data1    ( max_data_1       ),
    .data2    ( max_data_2       ),
    .data3    ( max_data_3       ),
    .max_data ( max_data_4       ),
    .mid_data ( mid_data_4       ),
    .min_data ( min_data_4       )
);
sort_three u_sort_three_5(
    .clk      ( clk              ),
    .rst_n    ( rst_n            ),
    .data1    ( mid_data_1       ),
    .data2    ( mid_data_2       ),
    .data3    ( mid_data_3       ),
    .max_data ( max_data_5       ),
    .mid_data ( mid_data_5       ),
    .min_data ( min_data_5       )
);
sort_three u_sort_three_6(
    .clk      ( clk              ),
    .rst_n    ( rst_n            ),
    .data1    ( min_data_1       ),
    .data2    ( min_data_2       ),
    .data3    ( min_data_3       ),
    .max_data ( max_data_6       ),
    .mid_data ( mid_data_6       ),
    .min_data ( min_data_6       )
);
//step3:find median value
sort_three u_sort_three_7(
    .clk      ( clk              ),
    .rst_n    ( rst_n            ),
    .data1    ( min_data_4       ),
    .data2    ( mid_data_5       ),
    .data3    ( max_data_6       ),
    .max_data ( max_data_7       ),
    .mid_data ( mid_data_7       ),
    .min_data ( min_data_7       )
);
assign post_data={3{mid_data_7}};

//-----------------------------
//   signal synchronization
//-----------------------------
assign post_clken=post_clken_dy[2];
assign post_href=post_href_dy[2];
assign post_vsync=post_vsync_dy[2];
always @(posedge clk or negedge rst_n) begin
    if(~rst_n)begin
        post_clken_dy3'd0;
        post_href_dyd0;
        post_vsync_dy3'd0;
    end
    else begin
        post_clken_dy{post_clken_dy[1:0],matrix_frame_clken};
        post_href_dy{post_href_dy[1:0],matrix_frame_href};
        post_vsync_dy{post_vsync_dy[1:0],matrix_frame_vsync};
    end
end

endmodule

接下来是中值滤波的仿真结果,我打个比方,比如下面69这个数值是输出的中值。获得中值需要消耗三个时钟周期,因此矩阵数据比中值要快三个时钟周期,所以和这个中值对应的矩阵数据是:36、36、36、d7、bc、36、ff、f5、69。对应中值就是69,这边显示696969是把8位数据拼接成了24位的。

ZYNQ图像处理(6)——均值滤波和中值滤波
最后给出的这三张图片一次是加了椒盐噪声的RGB图片,均值滤波后的图片以及中值滤波后的图片。可以明显看到,中值滤波对椒盐噪声的滤除作用明显。
ZYNQ图像处理(6)——均值滤波和中值滤波
ZYNQ图像处理(6)——均值滤波和中值滤波
ZYNQ图像处理(6)——均值滤波和中值滤波

五、总结

均值滤波和中值滤波是图像处理的基本操作,主要核心都是提取3×3的图像矩阵。因为现在的摄像头含噪声很少,所有没有放到FPGA开发板中进行测试,只是对其进行了matlab和vivado的仿真。后面做完这部分基础的图像处理后我整理下代码并供大家参考。

Original: https://blog.csdn.net/qq_40995480/article/details/126831740
Author: 树叶~
Title: ZYNQ图像处理(6)——均值滤波和中值滤波

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/632962/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球