一、中值滤波算法原理
中值滤波算法简单来说就是:通过对3x3窗口中的数据进行排序,最终获得中值。
对于待处理的像素,我们选择一个3x3的窗口模板,该窗口内的像素为待处理像素的邻近像素,对窗口内的像素分别按行列排序,最终计算出中值,用该中值代替原像素值,实现中值滤波。

二、基于FPGA设计中值滤波
1.3x3图像窗口生成
卷积是图像处理 中很常见的一种操作,3x3是最常见的窗口大小。

如果像素是一个个来的,要想实现3x3卷积,就得同时获取一个像素和它周围的8个像素,将输入像素缓存2行,这样就能同时获取3行的像素输入,此时再将这3个并行输入的像素移位进3x3窗口,就获得了3x3卷积模板,如图:

这里要注意,输入像素此时作为第三行数据输入3x3窗口,最下面的行缓存输出的才是第一行像素,上图窗口的右下角是3x3卷积模板的左上角,窗口的左上角是3x3卷积模板的右下角。
实现两行缓存并获取3x3卷积窗口,用shift-ram是最简单的实现方法。
1.2 shift-ram
1.2.1shift_ram简介
shift-ram是一个ip核,quartus13.0中叫做Shift register(RAM based)
普通shift-ram如图:

带taps的shift-ram:

其实带taps的shift-ram就是多个普通shift-ram组合,使用带taps的shift-ram可以轻松实现行缓存,设定taps数量为2,taps间隔为一行的像素数(此处为640),即可缓存两行。之后将这两个taps和输入像素移位进3x3窗口即可获得3x3卷积模板。
1.2.2 shift_ram配置方法

三、程序设计
module median_filter (
input clk ,
input rst_n ,
input din_vld ,
input [7:0] din , //输入的图像数据
output [7:0] dout , //输出的图像数据
output dout_vld
);
//打拍
reg [3:0] din_vld_r;
wire [7:0] taps0 ;
wire [7:0] taps1 ;
wire [7:0] taps2 ;
//行同步
reg [7:0] row0_0;
reg [7:0] row0_1;
reg [7:0] row0_2;
reg [7:0] row1_0;
reg [7:0] row1_1;
reg [7:0] row1_2;
reg [7:0] row2_0;
reg [7:0] row2_1;
reg [7:0] row2_2;
//行排列
reg [7:0] row0_min;
reg [7:0] row0_med;
reg [7:0] row0_max;
reg [7:0] row1_min;
reg [7:0] row1_med;
reg [7:0] row1_max;
reg [7:0] row2_min;
reg [7:0] row2_med;
reg [7:0] row2_max;
//列排列
reg [7:0] col0_min;
reg [7:0] col0_med;
reg [7:0] col0_max;
reg [7:0] col1_min;
reg [7:0] col1_med;
reg [7:0] col1_max;
reg [7:0] col2_min;
reg [7:0] col2_med;
reg [7:0] col2_max;
//取出max列的min,med列的med,min列的max
reg [7:0] data_max;
reg [7:0] data_med;
reg [7:0] data_min;
/**************************************************************
四级流水(打四拍)
**************************************************************/
always@(posedge clk or negedge rst_n)
if(!rst_n)
din_vld_r <= 4'd0;
else
din_vld_r <= {din_vld_r[2:0],din_vld};
/**************************************************************
shift_ram(3x3)模块
**************************************************************/
shift_ramshift_ram_inst (
.aclr ( !rst_n ),
.clken ( din_vld ),
.clock ( clk ),
.shiftin ( din ),
.shiftout ( ),
.taps0x ( taps0 ),
.taps1x ( taps1 ),
.taps2x ( taps2 )
);
/**************************************************************
第一级流水
**************************************************************/
//缓存3行数据
always@(posedge clk or negedge rst_n)
if(!rst_n) begin
row0_0 <= 'd0; row0_1 <= 'd0; row0_2 <= 'd0;
row1_0 <= 'd0; row1_1 <= 'd0; row1_2 <= 'd0;
row2_0 <= 'd0; row2_1 <= 'd0; row2_2 <= 'd0;
end
else if(din_vld_r[0]) begin
row0_0 <= taps0; row0_1 <= row0_0; row0_2 <= row0_1;
row1_0 <= taps1; row1_1 <= row1_0; row1_2 <= row1_1;
row2_0 <= taps2; row2_1 <= row2_0; row2_2 <= row2_1;
end
/**************************************************************
第二级流水
**************************************************************/
//三行分别排序
always@(posedge clk or negedge rst_n)
if(!rst_n) begin
row0_min <= 'd0;row0_med <= 'd0;row0_max <= 'd0;
row1_min <= 'd0;row1_med <= 'd0;row1_max <= 'd0;
row2_min <= 'd0;row2_med <= 'd0;row2_max <= 'd0;
end
else if(din_vld_r[1]) begin
COMPARE(row0_0,row0_1,row0_2,row0_max,row0_med,row0_min);
COMPARE(row1_0,row1_1,row1_2,row1_max,row1_med,row1_min);
COMPARE(row2_0,row2_1,row2_2,row2_max,row2_med,row2_min);
end
/**************************************************************
第三级流水
**************************************************************/
//每一行排完后,取出
//第一列的最小值
//第二列的中间值
//第三列的最大值
always@(posedge clk or negedge rst_n)
if(!rst_n) begin
col0_min <= 'd0;
col1_med <= 'd0;
col0_max <= 'd0;
end
else if(din_vld_r[2]) begin
COMPARE(row0_max,row1_max,row2_max,col0_max,col0_med,col0_min);
COMPARE(row0_med,row1_med,row2_med,col1_max,col1_med,col1_min);
COMPARE(row0_min,row1_min,row2_min,col2_max,col2_med,col2_min);
end
/**************************************************************
第四级流水
**************************************************************/
//得到最终的中值
always@(posedge clk or negedge rst_n)
if(!rst_n) begin
data_max <= 'd0;
data_med <= 'd0;
data_min <= 'd0;
end
else if(din_vld_r[3])begin
COMPARE(col0_min,col1_med,col2_max,data_max,data_med,data_min);
end
//输出端口
assign dout = data_med;
assign dout_vld = din_vld_r[3];
/**************************************************************
COMPARE任务
**************************************************************/
//用于比较三个数的大小并排列
task COMPARE;
input [7:0] data1 ;
input [7:0] data2 ;
input [7:0] data3 ;
output [7:0] max ;
output [7:0] mid ;
output [7:0] min ;
begin
//max
if(data1 >= data2 && data1 >= data3)
max = data1;
else if(data2 >= data1 && data2 >= data3)
max = data2;
else
max = data3;
//med
if((data1 >= data2 && data1 <= data3) || (data1 >= data3 && data1 <= data2))
mid = data1;
else if((data2 >= data1 && data2 <= data3) || (data2 >= data3 && data2 <= data1))
mid = data2;
else
mid = data3;
//min
if(data1 <= data2 && data1 <= data3)
min = data1;
else if(data2 <= data1 && data2 <= data3)
min = data2;
else
min = data3;
end
endtask
endmodule
四、仿真测试


4.1 代码逻辑功能仿真
首先对设计的代码单独进行仿真观察波形,看中值滤波功能是否正常执行
4.1.1 median_filter_tb
4.1.1.1 测试代码
`timescale 1ns/1ps
module median_filter_tb();
parameter CLK_CYCLE = 20;
regsys_clk,sys_rst_n;
reg din_vld;
reg [7:0] din;
always #(CLK_CYCLE/2) sys_clk = ~sys_clk;
initial begin
sys_clk = 1'b1;
sys_rst_n = 1'b0;
#(CLK_CYCLE*2);
sys_rst_n = 1'b1;
end
median_filter median_filter_tb(
/* input */ .clk (sys_clk),
/* input */ .rst_n (sys_rst_n),
/* input */ .din_vld (din_vld),
/* input [7:0] */ .din (din),
/* output [7:0] */ .dout (),
/* output */ .dout_vld()
);
integer i;
initial begin
din = 8'b0;
din_vld =1'b0;
#(CLK_CYCLE*20);
for(i=0;i<500;i=i+1)begin
din_vld = 1'b1;
din = {$random}%100;
@(posedge sys_clk);
end
din_vld = 1'b0;
#(CLK_CYCLE*200)
$stop(2);
end
endmodule
一键获取完整项目代码
4.1.1.1 仿真波形
观察每级流水波形,看每级流水功能是否正常。若哪级流水错误,则去修改该级流水代码逻辑,直到功能正常。
4.1.2 test_image
4.1.2.1 测试代码
仿真代码中涉及的系统函数可参考:Verilog 系统函数
`timescale 1ns/1ps
module test_image();
parameter CLK_CYCLE = 20;
regclk,rst_n;
always #(CLK_CYCLE/2) clk = ~clk;
initial begin
clk = 1'b1;
rst_n = 1'b0;
#(CLK_CYCLE*2);
rst_n = 1'b1;
end
reg [7:0] din;
reg din_vld;
wire [7:0] dout;
wire dout_vld;
median_filter med_filter_inst(
/* input */ .clk (clk),
/* input */ .rst_n (rst_n),
/* input */ .din_vld (din_vld),
/* input [7:0] */ .din (din ),
/* output [7:0] */ .dout (dout ),
/* output */ .dout_vld(dout_vld)
);
integer bmp_width;//图像宽度
integer bmp_high;//图像高度
integer bmp_size;//图像尺寸
integer start_index;//图像像素点起始位
//bmp file id
integer bmp_file_id;
integer bmp_dout_id;
integer dout_txt_id;
integer h;//文件句柄
reg [7:0] rd_data [0:921600];//bmp文件图片大小
reg [7:0] dout_data [0:921600];
//写操作
reg[23:0]wr_data;
integer i = 0;
integer index,index0;
initial begin
din_vld = 0;
#(CLK_CYCLE*10)
//打开原始图像$fopen("原始图像地址,文件夹间用\隔开","命令码")
bmp_file_id = $fopen("E:\Material\IntelFPGA\test\test6\median_filter\sim\in_bmp.bmp","rb");
//打开输出图像
bmp_dout_id = $fopen("E:\Material\IntelFPGA\test\test6\median_filter\sim\out_bmp.bmp","wb");
//打开输出数据
dout_txt_id = $fopen("E:\Material\IntelFPGA\test\test6\median_filter\sim\out.img.txt","w+");
//读取bmp文件
h = $fread(rd_data,bmp_file_id);
// 图像宽度
bmp_width = {rd_data[21], rd_data[20], rd_data[19], rd_data[18]};
// 图像高度
bmp_high = {rd_data[25], rd_data[24], rd_data[23], rd_data[22]};
// 像素起始位置
start_index = {rd_data[13], rd_data[12], rd_data[11], rd_data[10]};
// 图像尺寸
bmp_size = {rd_data[5], rd_data[4], rd_data[3], rd_data[2]};
// bmp_size = 921600;
$fclose(bmp_file_id);
index = start_index;
//重复1280次,1280个像素点
repeat(1280)begin
#(CLK_CYCLE*1);
din = rd_data[index];
din_vld = 1;
index = index + 1;
end
repeat(bmp_size-1280)begin
#(CLK_CYCLE*1);
dout_data[index-1280] = dout;
din = rd_data[index];
index = index + 1;
end
din_vld = 0;
// repeat(1280)begin
// #(CLK_CYCLE*1);
// dout_data[index-1280] = dout;
// din = rd_data[index];
// index = index + 1;
// end
//输出BMP
for(i = 0; i < bmp_size; i = i + 1)begin
if(i < start_index)
$fwrite(bmp_dout_id, "%c", rd_data[i]);//注意参数%c
else
$fwrite(bmp_dout_id, "%c", dout_data[i]);
end
$fclose(bmp_dout_id);
//输出txt,只存像素点
for(index0 = start_index; index0 < bmp_size-2; index0 = index0 + 3)begin
wr_data = {dout_data[index0 + 2], dout_data[index0 + 1], dout_data[index0]};
$fwrite(dout_txt_id, "%d,", wr_data[7:0]);
$fwrite(dout_txt_id, "%d,", wr_data[15:8]);
$fwrite(dout_txt_id, "%d ", wr_data[23:16]);
end
$fclose(dout_txt_id);
$stop;
end
endmodule
-
FPGA
+关注
关注
1665文章
22577浏览量
640950 -
滤波算法
+关注
关注
2文章
98浏览量
14503
原文标题:基于FPGA的图像中值滤波
文章出处:【微信号:gh_9d70b445f494,微信公众号:FPGA设计论坛】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
基于FPGA的中值滤波算法实现
基于医学图像的有效中值滤波算法研究
快速中值滤波的FPGA实现
测井图像的多级中值滤波算法及其FPGA实现
基于FPGA的图像调焦算法的实现方案
基于FPGA的图像中值滤波算法实现方案
评论