参考:小梅哥的《FPGA系统设计与验证实战指南》
一、算法介绍
均值滤波是典型的线性滤波算法,它是指在图像上对目标像素给一个模板,该模板包括了其周围的临近像素(以目标像素为中心的周围8个像素,构成一个滤波模板,即去掉目标像素本身),再用模板中的全体像素的平均值来代替原来像素值。
均值滤波也称为线性滤波,其采用的主要方法为邻域平均法。线性滤波的基本原理是用均值代替原图像中的各个像素值,即对待处理的当前像素点(x,y),选择一个模板,该模板由其近邻的若干像素组成,求模板中所有像素的均值,再把该均值赋予当前像素点(x,y),作为处理后图像在该点上的灰度g(x,y),即g(x,y)=1/m ∑f(x,y) m为该模板中包含当前像素在内的像素总个数。
均值滤波本身存在着固有的缺陷,即它不能很好地保护图像细节,在图像去噪的同时也破坏了图像的细节部分,从而使图像变得模糊,不能很好地去除噪声点。
FPGA的均值滤波算法实现步骤:
f(x,y)表示(x,y)点的像素值。
g(x,y)表示(x,y)点经过均值处理后的值。
g(x,y)=1/8*(f(x-1,y-1)+f(x,y-1)+f(x+1,y-1)+f(x-1,y)+f(x+1,y)+f(x-1,y+1)+f(x,y+1)+f(x+1,y+1))
由上式我们看出(x,y)点的3x3像素点的均值等于其周围邻域的八个点的像素值之和除以8。
1>形成3x3矩阵像素
2>求周围邻域八个点的像素值之和
3>将结果右移三位(相当于除以8)得到结果。
二、程序设计
关于如何形成3*3的像素矩阵,参考另一篇文章:FPGA丨设计行缓存(linebuffer)生成像素矩阵
module mean_filter_3x3(
input clk, //pixel clk
input rst_n,
input [15:0]data_in, //16 bit 灰度 pixel
input data_in_en,
output reg [15:0] data_out,
output reg data_out_en
);
//------------------------------------
// 三行像素缓存
//-----------------------------------
wire [15:0] line0;
wire [15:0] line1;
wire [15:0] line2;
//-----------------------------------------
// 3x3 像素矩阵中的像素点
//-----------------------------------------
reg [15:0] line0_data0;
reg [15:0] line0_data1;
reg [15:0] line0_data2;
reg [15:0] line1_data0;
reg [15:0] line1_data1;
reg [15:0] line1_data2;
reg [15:0] line2_data0;
reg [15:0] line2_data1;
reg [15:0] line2_data2;
wire mat_flag;
reg mat_flag_1;
reg mat_flag_2;
reg mat_flag_3;
reg mat_flag_4;
always @(posedge clk)begin
mat_flag_1 <= mat_flag;
mat_flag_2 <= mat_flag_1;
mat_flag_3 <= mat_flag_2;
mat_flag_4 <= mat_flag_3;
end
//---------------------------------------------
// 获取3*3的图像矩阵
//---------------------------------------------
matrix_3x3 matrix_3x3_inst(
.clk (clk),
.rst_n(rst_n),
.din (data_in),
.valid_in(data_in_en),
.dout(),
.dout_r0(line0),
.dout_r1(line1),
.dout_r2(line2),
.mat_flag(mat_flag)
);
//--------------------------------------------------
// Form an image matrix of three multiplied by three
//--------------------------------------------------
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
line0_data0 <= 16'b0;
line0_data1 <= 16'b0;
line0_data2 <= 16'b0;
line1_data0 <= 16'b0;
line1_data1 <= 16'b0;
line1_data2 <= 16'b0;
line2_data0 <= 16'b0;
line2_data1 <= 16'b0;
line2_data2 <= 16'b0;
end
else if(data_in_en) begin //像素有效信号
line0_data0 <= line0;
line0_data1 <= line0_data0;
line0_data2 <= line0_data1;
line1_data0 <= line1;
line1_data1 <= line1_data0;
line1_data2 <= line1_data1;
line2_data0 <= line2;
line2_data1 <= line2_data0;
line2_data2 <= line2_data1;
end
end
//--------------------------------------------------------------------------
// 计算最终结果
//--------------------------------------------------------------------------
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
data_out <= 16'b0;
else if(data_in_en)
data_out <= (line0_data0 + line0_data1 + line0_data2 + line1_data0 +
line1_data2 + line2_data0 + line2_data1 + line2_data2)>>3;
else ;
end
always @(posedge clk or negedge rst_n)
if(rst_n == 1'b0)
data_out_en <= 1'b0;
else if(mat_flag_3 == 1'b1 && mat_flag_4 == 1'b1)
data_out_en <= 1'b1;
else
data_out_en <= 1'b0;
endmodule
三、仿真
`timescale 1ns/1ps
`define CLK_PERIOD 20//50MHZ
module tb ();
reg clk;
reg [15:0] din;
reg rst_n;
reg valid_in;
//wires
wire [15:0] data_out;
wire data_out_en;
mean_filter_3x3 u_mean_filter_3x3 (
.clk ( clk ),
.rst_n ( rst_n ),
.data_in ( din [15:0] ),
.data_in_en ( valid_in ),
.data_out ( data_out [15:0] ),
.data_out_en ( data_out_en )
);
initial begin
clk = 0;
rst_n = 0;
valid_in = 0;
#(`CLK_PERIOD * 10);
rst_n=1;
#(`CLK_PERIOD*10);
valid_in = 1;
#(`CLK_PERIOD*480*5);
valid_in = 0;
#(`CLK_PERIOD*20);
$stop;
end
always #(`CLK_PERIOD/2) clk = ~clk;
always @ (posedge clk or negedge rst_n)begin
if(!rst_n)
din <= 0;
else if(din == 479)
din <= 0;
else if (valid_in == 1'b1)
din <= din + 1'b1;
end
endmodule
可以看出,在mat_flag
拉高的三个clk后第一个有效像素矩阵形成,由于计算逻辑在always中又将计算结果延迟了一个clk,所以一个clk后计算结果才出现,也即输出有效。这里的data_out_en
会在数据有效时拉高,因为我把data_out_en
定义成了reg,所以这里滞后了一个clk。
需要注意的是:
上述代码的输入图像数据默认是灰度图像的数据,数据位宽为16。关于如何得到灰度图,可以参考另一篇文章: