FPGA实现图像的非线性滤波:中值滤波
在图像预处理中,最基础也最重要的处理方法是图像滤波与增强。图像滤波可以很好地消除测量成像或者环境带来的随机噪声、高斯噪声和椒盐噪声等。图像增强可以增强图像细节,提高图像对比度。
滤波器的种类有很多种。按照输出和输入之间是否有唯一且确定的传递函数,我们可以把滤波器分为线性滤波器和非线性滤波器两种。
非线性滤波器在通常情况下没有特定的转移函数。一类比较重要的非线性滤波就是统计排序滤波器,如中值滤波、最大/最小值滤波等。
中值滤波对于某些类型的随机噪声具有非常理想的降噪能力,对于线性平滑滤波而言,在处理像素邻域之内的噪声点时,噪声的存在总会或多或少影响该点的像素值的计算(高斯平滑影响的程度与噪声点到中心点的距离成正比),但在中值滤波中噪声点则常常直接被忽略掉的;而且与线性平滑滤波器相比,中值滤波在降噪同时引起的模糊效应较低,进行中值滤波不仅可以去除孤点噪声,而且可以保持图像的边缘特性,不会使图像产生显著的模糊,比较适合于实验中的人脸图像。中值滤波的一种典型应用是消除椒盐噪声(即黑白噪声)。
一、理论分析
中值滤波方法是:对待处理的当前像素,选择一个3x3、5x5 或其他模板,该模板为其邻近的若千个像素组成,对模板的像素由小到大进行排序,再用模板的中值来替代原像素的值。3x3 模板下的排序算法如图所示:

二、MATLAB实现
clc; clear all; close all; RGB = imread('flower.bmp'); %读取图片 imgn = imnoise(RGB,'salt & pepper',0.05); %椒盐密度0.05 gray = im2double(rgb2gray(imgn)); %灰度图 [ROW,COL, DIM] = size(gray); %得到图像行列数 %-------------------------------------------------------------------------- % Mean Filter 均值滤波 %-------------------------------------------------------------------------- Mean_Img = zeros(ROW,COL); for r = 2:1:ROW-1 for c = 2:1:COL-1 Mean_Img(r,c) = (gray(r-1, c-1) + gray(r-1, c) + gray(r-1, c+1) + gray(r, c-1) + gray(r, c) + gray(r, c+1) + gray(r+1, c-1) + gray(r+1, c) + gray(r+1, c+1)) / 9; end end %-------------------------------------------------------------------------- % Median Filter 中值滤波 %-------------------------------------------------------------------------- Median_Img = zeros(ROW,COL); for r = 2:ROW-1 for c = 2:COL-1 median3x3 =[gray(r-1,c-1) gray(r-1,c) gray(r-1,c+1) gray(r,c-1) gray(r,c) gray(r,c+1) gray(r+1,c-1) gray(r+1,c) gray(r+1,c+1)]; sort1 = sort(median3x3, 2, 'descend'); sort2 = sort([sort1(1), sort1(4), sort1(7)], 'descend'); sort3 = sort([sort1(2), sort1(5), sort1(8)], 'descend'); sort4 = sort([sort1(3), sort1(6), sort1(9)], 'descend'); mid_num = sort([sort2(3), sort3(2), sort4(1)], 'descend'); Median_Img(r,c) = mid_num(2); end end %-------------------------------------------------------------------------- % Show Image %-------------------------------------------------------------------------- subplot(2,2,1); imshow(imgn); title('椒盐噪声'); subplot(2,2,2); imshow(gray); title('灰度图'); subplot(2,2,3); imshow(Mean_Img); title('均值滤波'); subplot(2,2,4); imshow(Median_Img); title('中值滤波'); % 由实验可知: % 1、椒盐噪声就是黑白噪声,均值滤波对椒盐噪声基本无作用。 % 2、中值滤波对椒盐噪声的处理非常好。
当我们使用3x3窗口后获取领域中的9个像素,就需要对9个像素值进行排序,为了提高排序效率,排序算法思想如下所示。
(1)对窗内的每行像素按降序排序,得到最大值、中间值和最小值。
(2)把三行的最小值即第三列相比较,取其中的最大值。
(3)把三行的最大值即第- -列相比较,取其中的最小值。
(4)把三行的中间值即第二列相比较,再取一次中间值。
(5)把前面的到的三个值再做一次排序,获得的中值即该窗口的中值。
MATLAB代码中采用了 sort 排序函数,该函数使用方法如下所示:
sort(A)若A可以使矩阵或行列向量,默认都是对A进行升序排列。 sort (A)是默认的升序,而sort (A,' descend' )是降序排序。 sort(A)若A是矩阵,默认对A的各列进行升序排列 sort (A, dim) dim=1时相当于sort (A) dim=2时表示对矩阵A中的各行元素升序排列 sort(A,dim, ’descend' )则对矩阵的每行进行降序排列
点击运行,得到如下结果:

结果可以看出:均值滤波对椒盐噪声无效,中值滤波则过滤了绝大部分椒盐噪声,如果还不够可以再用一次中值滤波,达到满意效果。
三、FPGA实现

1、形成3x3矩阵
这个在前面的博客花了3篇来解释,就不多说了,我把3x3矩阵的代码用一个专门的 .v 文件写好,这里直接调用即可。输入是灰度数据,即 YCbCr格式中的 8bit Y分量,输出是矩阵数据。
//**************************************************************************
// *** 名称 : matrix_3x3_8bit.v
// *** 作者 : xianyu_FPGA
// *** 博客 : https://www.cnblogs.com/xianyufpga/
// *** 日期 : 2020年3月
// *** 描述 : 3x3矩阵,边界采用像素复制,最大支持1024x1024,耗费1clk
//**************************************************************************
module matrix_3x3_8bit
//========================< 参数 >==========================================
#(
parameter H_DISP = 12'd480 , //图像宽度
parameter V_DISP = 12'd272 //图像高度
)
//========================< 端口 >==========================================
(
input wire clk ,
input wire rst_n ,
//input ---------------------------------------------
input wire din_vld ,
input wire [ 7:0] din ,
//output --------------------------------------------
output reg [ 7:0] matrix_11 ,
output reg [ 7:0] matrix_12 ,
output reg [ 7:0] matrix_13 ,
output reg [ 7:0] matrix_21 ,
output reg [ 7:0] matrix_22 ,
output reg [ 7:0] matrix_23 ,
output reg [ 7:0] matrix_31 ,
output reg [ 7:0] matrix_32 ,
output reg [ 7:0] matrix_33
);
//========================< 信号 >==========================================
reg [11:0] cnt_col ;
wire add_cnt_col ;
wire end_cnt_col ;
reg [11:0] cnt_row ;
wire add_cnt_row ;
wire end_cnt_row ;
wire wr_en_1 ;
wire wr_en_2 ;
wire rd_en_1 ;
wire rd_en_2 ;
wire [ 7:0] q_1 ;
wire [ 7:0] q_2 ;
wire [ 7:0] row_1 ;
wire [ 7:0] row_2 ;
wire [ 7:0] row_3 ;
//==========================================================================
//== FIFO例化,show模式,深度为大于两行数据个数
//==========================================================================
fifo_show_2048x8 u1
(
.clock (clk ),
.data (din ),
.wrreq (wr_en_1 ),
.rdreq (rd_en_1 ),
.q (q_1 )
);
fifo_show_2048x8 u2
(
.clock (clk ),
.data (din ),
.wrreq (wr_en_2 ),
.rdreq (rd_en_2 ),
.q (q_2 )
);
//==========================================================================
//== 行列划分
//==========================================================================
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
cnt_col <= 12'd0;
else if(add_cnt_col) begin
if(end_cnt_col)
cnt_col <= 12'd0;
else
cnt_col <= cnt_col + 12'd1;
end
end
assign add_cnt_col = din_vld;
assign end_cnt_col = add_cnt_col && cnt_col== H_DISP-12'd1;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
cnt_row <= 12'd0;
else if(add_cnt_row) begin
if(end_cnt_row)
cnt_row <= 12'd0;
else
cnt_row <= cnt_row + 12'd1;
end
end
assign add_cnt_row = end_cnt_col;
assign end_cnt_row = add_cnt_row && cnt_row== V_DISP-12'd1;
//==========================================================================
//== fifo 读写信号
//==========================================================================
assign wr_en_1 = (cnt_row < V_DISP - 12'd1) ? din_vld : 1'd0; //不写最后1行
assign rd_en_1 = (cnt_row > 12'd0 ) ? din_vld : 1'd0; //从第1行开始读
assign wr_en_2 = (cnt_row < V_DISP - 12'd2) ? din_vld : 1'd0; //不写最后2行
assign rd_en_2 = (cnt_row > 12'd1 ) ? din_vld : 1'd0; //从第2行开始读
//==========================================================================
//== 形成 3x3 矩阵,边界采用像素复制
//==========================================================================
//矩阵数据选取
//---------------------------------------------------
assign row_1 = q_2;
assign row_2 = q_1;
assign row_3 = din;
//打拍形成矩阵,1clk
//---------------------------------------------------
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
{matrix_11, matrix_12, matrix_13} <= {8'd0, 8'd0, 8'd0};
{matrix_21, matrix_22, matrix_23} <= {8'd0, 8'd0, 8'd0};
{matrix_31, matrix_32, matrix_33} <= {8'd0, 8'd0, 8'd0};
end
//------------------------------------------------------------------------- 第1排矩阵
else if(cnt_row == 12'd0) begin
if(cnt_col == 12'd0) begin //第1个矩阵
{matrix_11, matrix_12, matrix_13} <= {row_3, row_3, row_3};
{matrix_21, matrix_22, matrix_23} <= {row_3, row_3, row_3};
{matrix_31, matrix_32, matrix_33} <= {row_3, row_3, row_3};
end
else begin //剩余矩阵
{matrix_11, matrix_12, matrix_13} <= {matrix_12, matrix_13, row_3};
{matrix_21, matrix_22, matrix_23} <= {matrix_22, matrix_23, row_3};
{matrix_31, matrix_32, matrix_33} <= {matrix_32, matrix_33, row_3};
end
end
//------------------------------------------------------------------------- 第2排矩阵
else if(cnt_row == 12'd1) begin
if(cnt_col == 12'd0) begin //第1个矩阵
{matrix_11, matrix_12, matrix_13} <= {row_2, row_2, row_2};
{matrix_21, matrix_22, matrix_23} <= {row_2, row_2, row_2};
{matrix_31, matrix_32, matrix_33} <= {row_3, row_3, row_3};
end
else begin //剩余矩阵
{matrix_11, matrix_12, matrix_13} <= {matrix_12, matrix_13, row_2};
{matrix_21, matrix_22, matrix_23} <= {matrix_22, matrix_23, row_2};
{matrix_31, matrix_32, matrix_33} <= {matrix_32, matrix_33, row_3};
end
end
//------------------------------------------------------------------------- 剩余矩阵
else begin
if(cnt_col == 12'd0) begin //第1个矩阵
{matrix_11, matrix_12, matrix_13} <= {row_1, row_1, row_1};
{matrix_21, matrix_22, matrix_23} <= {row_2, row_2, row_2};
{matrix_31, matrix_32, matrix_33} <= {row_3, row_3, row_3};
end
else begin //剩余矩阵
{matrix_11, matrix_12, matrix_13} <= {matrix_12, matrix_13, row_1};
{matrix_21, matrix_22, matrix_23} <= {matrix_22, matrix_23, row_2};
{matrix_31, matrix_32, matrix_33} <= {matrix_32, matrix_33, row_3};
end
end
end
endmodule
2、sort排序
MATLAB有sort函数,FPGA里可没有,我们得自己写一个,由于要用到多次,因此用一个.v文件来写,用的时候调用即可。sort代码如下所示:
1 module sort 2 //========================< 端口 >========================================== 3 ( 4 //system -------------------------------------------- 5 input wire clk , 6 input wire rst_n , 7 //input --------------------------------------------- 8 input wire [7:0] data1 , 9 input wire [7:0] data2 , 10 input wire [7:0] data3 , 11 //output -------------------------------------------- 12 output reg [7:0] max_data , //最大值 13 output reg [7:0] mid_data , //中间值 14 output reg [7:0] min_data //最小值 15 ); 16 //========================================================================== 17 //== 最大值 18 //========================================================================== 19 always @(posedge clk or negedge rst_n) begin 20 if(!rst_n) 21 max_data <= 8'd0; 22 else if(data1 >= data2 && data1 >= data3) 23 max_data <= data1; 24 else if(data2 >= data1 && data2 >= data3) 25 max_data <= data2; 26 else if(data3 >= data1 && data3 >= data2) 27 max_data <= data3; 28 end 29 //========================================================================== 30 //== 中间值 31 //========================================================================== 32 always @(posedge clk or negedge rst_n) begin 33 if(!rst_n) 34 mid_data <= 8'd0; 35 else if((data2 >= data1 && data1 >= data3) || (data3 >= data1 && data1 >= data2)) 36 mid_data <= data1; 37 else if((data1 >= data2 && data2 >= data3) || (data3 >= data2 && data2 >= data1)) 38 mid_data <= data2; 39 else if((data1 >= data3 && data3 >= data2) || (data1 >= data3 && data3 >= data2)) 40 mid_data <= data3; 41 end 42 //========================================================================== 43 //== 最小值 44 //========================================================================== 45 always @(posedge clk or negedge rst_n) begin 46 if(!rst_n) 47 min_data <= 8'd0; 48 else if(data3 >= data1 && data2 >= data1) 49 min_data <= data1; 50 else if(data3 >= data2 && data1 >= data2) 51 min_data <= data2; 52 else if(data1 >= data3 && data2 >= data3) 53 min_data <= data3; 54 end 55 56 57 58 endmodule
3、调用sort,完成中值滤波
调用上面写好的 sort 就行了,注意一下整个的逻辑,消耗了3个时钟周期。
(1)对窗内的每行像素按降序排序,得到最大值、中间值和最小值。
(2)把三行的最小值即第三列相比较,取其中的最大值。
(3)把三行的最大值即第- -列相比较,取其中的最小值。
(4)把三行的中间值即第二列相比较,再取一次中间值。
(5)把前面的到的三个值再做一次排序,获得的中值即该窗口的中值。
第1个周期实现(1),第2个周期实现(2)(3)(4),第3个周期实现(5)。
形成3x3矩阵耗费1clk,中值滤波耗费3clk,因此行场和使能信号都需要延迟4拍。
//**************************************************************************
// *** 名称 : median.v
// *** 作者 : xianyu_FPGA
// *** 博客 : https://www.cnblogs.com/xianyufpga/
// *** 日期 : 2020年3月
// *** 描述 : Y分量进行median中值滤波
//**************************************************************************
module median
//========================< 参数 >==========================================
#(
parameter H_DISP = 12'd480 , //图像宽度
parameter V_DISP = 12'd272 //图像高度
)
//========================< 端口 >==========================================
(
input wire clk , //时钟
input wire rst_n , //复位
//input ---------------------------------------------
input wire Y_hsync , //Y分量行同步
input wire Y_vsync , //Y分量场同步
input wire [ 7:0] Y_data , //Y分量数据
input wire Y_de , //Y分量数据使能
//output --------------------------------------------
output wire median_hsync , //median行同步
output wire median_vsync , //median场同步
output wire [ 7:0] median_data , //median数据
output wire median_de //median数据使能
);
//========================< 信号 >==========================================
//matrix_3x3 ----------------------------------------
wire [ 7:0] matrix_11 ;
wire [ 7:0] matrix_12 ;
wire [ 7:0] matrix_13 ;
wire [ 7:0] matrix_21 ;
wire [ 7:0] matrix_22 ;
wire [ 7:0] matrix_23 ;
wire [ 7:0] matrix_31 ;
wire [ 7:0] matrix_32 ;
wire [ 7:0] matrix_33 ;
//median --------------------------------------------
wire [ 7:0] max_data1 ;
wire [ 7:0] mid_data1 ;
wire [ 7:0] min_data1 ;
wire [ 7:0] max_data2 ;
wire [ 7:0] mid_data2 ;
wire [ 7:0] min_data2 ;
wire [ 7:0] max_data3 ;
wire [ 7:0] mid_data3 ;
wire [ 7:0] min_data3 ;
wire [ 7:0] max_min_data ;
wire [ 7:0] mid_mid_data ;
wire [ 7:0] min_max_data ;
//同步 ----------------------------------------------
reg [ 3:0] Y_de_r ;
reg [ 3:0] Y_hsync_r ;
reg [ 3:0] Y_vsync_r ;
//==========================================================================
//== matrix_3x3_8bit,生成3x3矩阵,输入和使能需对齐,耗费1clk
//==========================================================================
//--------------------------------------------------- 矩阵顺序
// {matrix_11, matrix_12, matrix_13}
// {matrix_21, matrix_22, matrix_23}
// {matrix_31, matrix_32, matrix_33}
//--------------------------------------------------- 模块例化
matrix_3x3_8bit
#(
.H_DISP (H_DISP ),
.V_DISP (V_DISP )
)
u_matrix_3x3_8bit
(
.clk (clk ),
.rst_n (rst_n ),
.din_vld (Y_de ),
.din (Y_data ),
.matrix_11 (matrix_11 ),
.matrix_12 (matrix_12 ),
.matrix_13 (matrix_13 ),
.matrix_21 (matrix_21 ),
.matrix_22 (matrix_22 ),
.matrix_23 (matrix_23 ),
.matrix_31 (matrix_31 ),
.matrix_32 (matrix_32 ),
.matrix_33 (matrix_33 )
);
//==========================================================================
//== 中值滤波,耗费3clk
//==========================================================================
//每行像素降序排列,clk1
//---------------------------------------------------
//第1行
sort u1
(
.clk (clk ),
.rst_n (rst_n ),
.data1 (matrix_11 ),
.data2 (matrix_12 ),
.data3 (matrix_13 ),
.max_data (max_data1 ),
.mid_data (mid_data1 ),
.min_data (min_data1 )
);
//第2行
sort u2
(
.clk (clk ),
.rst_n (rst_n ),
.data1 (matrix_21 ),
.data2 (matrix_22 ),
.data3 (matrix_23 ),
.max_data (max_data2 ),
.mid_data (mid_data2 ),
.min_data (min_data2 )
);
//第3行
sort u3
(
.clk (clk ),
.rst_n (rst_n ),
.data1 (matrix_31 ),
.data2 (matrix_32 ),
.data3 (matrix_33 ),
.max_data (max_data3 ),
.mid_data (mid_data3 ),
.min_data (min_data3 )
);
//三行的最小值取最大值
//三行的中间值取中间值
//三行的最大值取最小值,clk2
//---------------------------------------------------
//min-max
sort u4
(
.clk (clk ),
.rst_n (rst_n ),
.data1 (min_data1 ),
.data2 (min_data2 ),
.data3 (min_data3 ),
.max_data (min_max_data ),
.mid_data ( ),
.min_data ( )
);
//mid-mid
sort u5
(
.clk (clk ),
.rst_n (rst_n ),
.data1 (mid_data1 ),
.data2 (mid_data2 ),
.data3 (mid_data3 ),
.max_data ( ),
.mid_data (mid_mid_data ),
.min_data ( )
);
//max-min
sort u6
(
.clk (clk ),
.rst_n (rst_n ),
.data1 (max_data1 ),
.data2 (max_data2 ),
.data3 (max_data3 ),
.max_data ( ),
.mid_data ( ),
.min_data (max_min_data )
);
//前面的三个值再取中间值,clk3
//---------------------------------------------------
sort u7
(
.clk (clk ),
.rst_n (rst_n ),
.data1 (max_min_data ),
.data2 (mid_mid_data ),
.data3 (min_max_data ),
.max_data ( ),
.mid_data (median_data ),
.min_data ( )
);
//==========================================================================
//== 信号同步
//==========================================================================
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
Y_de_r <= 4'b0;
Y_hsync_r <= 4'b0;
Y_vsync_r <= 4'b0;
end
else begin
Y_de_r <= {Y_de_r[2:0], Y_de};
Y_hsync_r <= {Y_hsync_r[2:0], Y_hsync};
Y_vsync_r <= {Y_vsync_r[2:0], Y_vsync};
end
end
assign median_de = Y_de_r[3];
assign median_hsync = Y_hsync_r[3];
assign median_vsync = Y_vsync_r[3];
endmodule
四、上板验证
同样取一张含有椒盐噪声的图片来看现象。
原图:

中值滤波后:

从实验结果看出,椒盐噪声都被过滤了,实验成功。
我的板子坏了,出现一些不该有的横条,如果是好的板子则没有这些鬼东西。
参考资料:
[1] OpenS Lee:FPGA开源工作室(公众号)
[2] CrazyBingo:基于VIP_Board Mini的FPGA视频图像算法(HDL-VIP)开发教程-V1.6
[3] NingHechuan:FPGA图像处理教程

浙公网安备 33010602011771号