一、均值和中值滤波基本原理
首先要做的是最简单的均值滤波算法。均值滤波是典型的线性滤波算法,它是指在图像上对目标像素给一个模板,该模板包括了其周围的临近像素(以目标象素为中心的周围 8 个像素,构成一个滤波模板,即去掉目标像素本身),再用模板中的全体像素的平均值来代替原来像素值。中值滤波算法可以形象的用上述表格来描述,即对于每个 33 的阵列而言,中间像素的值,等于边缘 8 个像素的平均值。
无论是直接获取的灰度图像,还是由彩色图像转换得到的灰度图像,里面都有噪声的存在,噪声对图像质量有很大的影响。进行中值滤波不仅可以去除孤点噪声,而且可以保持图像的边缘特性,不会使图像产生显著的模糊,比较适合于实验中的人脸图像。中值滤波算法与均值滤波非常的相似,但滤波的效果却有很大的差别,区别如下:(1) 均值滤波相当于低通滤波,有将图像模糊化的趋势,对椒盐噪声基本无能力。(2) 中值滤波的有点事可以很好的过滤椒盐噪声,缺点是容易造成图像的不连续。中值滤波的算法非常简单,只要求得 33 像素阵列的中间值即可,这样就有效的移植了最大值与最小值,图像会变得均匀,对椒盐噪声有很好的滤除效果!
《基于FPGA的实时中值滤波器的硬件实现》这篇文章中介绍了一种适合FPGA实现的快速寻找中值的算法,算法过程是这样的:首先计算每一行的最大值、中值以及最小值;之后计算第一列最大值中的最小值,第二列中间值中的中间值,第三列最小值中的最大值;最后再次计算这三个值的中间值就可以得到中值了。
二、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位,优先写操作。详细的可以看配置的图。
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了,所以读速度是比写速度快了三个时钟周期,因为下一个行同步来时才可以得到上一行数据。
可以看到,下面这张图就是下一个行同步时间内,下一行数据从taps0x中读了出来。taps1x也是同样的道理,这边不在叙述。
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} <= 24'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} <= {matrix_p12, matrix_p13, row1_data};
{matrix_p21, matrix_p22, matrix_p23} <= {matrix_p22, matrix_p23, row2_data};
{matrix_p31, matrix_p32, matrix_p33} <= {matrix_p32, matrix_p33, row3_data};
end
else begin
{matrix_p11, matrix_p12, matrix_p13} <= {matrix_p11, matrix_p12, matrix_p13};
{matrix_p21, matrix_p22, matrix_p23} <= {matrix_p21, matrix_p22, matrix_p23};
{matrix_p31, matrix_p32, matrix_p33} <= {matrix_p31, matrix_p32, matrix_p33};
end
end
else begin
{matrix_p11, matrix_p12, matrix_p13} <= 24'h0;
{matrix_p21, matrix_p22, matrix_p23} <= 24'h0;
{matrix_p31, matrix_p32, matrix_p33} <= 24'h0;
end
end
endmodule
仿真的结果如下面三张图所示,当pei_img_Y数据来到的时候,延迟两个时钟周期进入到row3_data中,之后在延迟一个周期数据送到p31、p32以及p33中。row2_data和row1_data的得到也是同样的原理。
三、均值滤波的实现
前面已经清楚描述了均值滤波的实现过程,主要就是将图像像素点周围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_p2<=12'd0;
add_p3<=12'd0;
end
else begin
add_p1<=matrix_p11+matrix_p12+matrix_p13;
add_p2<=matrix_p21+matrix_p23;
add_p3<=matrix_p31+matrix_p32+matrix_p33;
end
end
//step2:add all the data
always @(posedge clk or negedge rst_n) begin
if(~rst_n)begin
add_all<=12'd0;
end
else begin
add_all<=add_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_dy<=2'd0;
post_href_dy<=2'd0;
post_vsync_dy<=2'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。
四、中值滤波的实现
中值滤波的实现和均值滤波相似,也是需要先提取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_data<=8'd0;
mid_data<=8'd0;
min_data<=8'd0;
end
else if (data1>=data2 && data2>=data3)begin
max_data<=data1;
mid_data<=data2;
min_data<=data3;
end
else if (data1>=data3 && data3>=data2)begin
max_data<=data1;
mid_data<=data3;
min_data<=data2;
end
else if (data2>=data1 && data1>=data3)begin
max_data<=data2;
mid_data<=data1;
min_data<=data3;
end
else if (data2>=data3 && data3>=data1)begin
max_data<=data2;
mid_data<=data3;
min_data<=data1;
end
else if (data3>=data1 && data1>=data2)begin
max_data<=data3;
mid_data<=data1;
min_data<=data2;
end
else if (data3>=data2 && data2>=data1)begin
max_data<=data3;
mid_data<=data2;
min_data<=data1;
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_dy<=3'd0;
post_href_dy<=3'd0;
post_vsync_dy<=3'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位的。
最后给出的这三张图片一次是加了椒盐噪声的RGB图片,均值滤波后的图片以及中值滤波后的图片。可以明显看到,中值滤波对椒盐噪声的滤除作用明显。
文章来源:https://www.toymoban.com/news/detail-472595.html
五、总结
均值滤波和中值滤波是图像处理的基本操作,主要核心都是提取3×3的图像矩阵。因为现在的摄像头含噪声很少,所有没有放到FPGA开发板中进行测试,只是对其进行了matlab和vivado的仿真。后面做完这部分基础的图像处理后我整理下代码并供大家参考。文章来源地址https://www.toymoban.com/news/detail-472595.html
到了这里,关于ZYNQ图像处理(6)——均值滤波和中值滤波的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!