【FPGA】中值滤波处理BMP图片

时间:2023-01-09 15:54:57

一、中值滤波

中值滤波法是一种非线性平滑技术,它将每一像素点的灰度值设置为该点某邻域窗口内的所有像素点灰度值的中值。
中值滤波是基于排序统计理论的一种能有效抑制噪声的非线性信号处理技术,中值滤波的基本原理是把数字图像或数字序列中一点的值用该点的一个邻域中各点值的中值代替,让周围的像素值接近真实值,从而消除孤立的噪声点。

二、BMP图片格式

bmp文件的存储格式是Windows系统中广泛使用的图像文件格式,对图像不做任何程度的压缩处理,主要分为位图头文件,位图信息头,调色板信息,像素数据四大部分,由于通常是处理RBG图像,因此仅讨论RGB的情况

位图头文件 :文件的格式、大小等信息,前14Byte;主要关心的是图像尺寸,像素数据开始位置。
位图信息头:提供图像数据的尺寸、位平面数、压缩方式、颜色索引等信息,前40Byte;主要关心的是图像宽度,图像高度。
调色板信息:保留字,不关心
像素数据 :像素数据,8bit一个像素数据,即1Byte。

三、功能实现

1.代码设计思路

【FPGA】中值滤波处理BMP图片

第一步:对3x3窗口的每一行进行排序,得到每行max,mid,min数
第二步:对第一步排序得到的第一列3个max排序取min,第二列3个mid排序取mid,第三列 3个min排序取max
第三步:对第二步产生的三个数进行排序,取mid得到中值

2.shift IP核

altera提供了矩阵的IP核,我们设置好位宽8、两路输出、每行640个深度(图像长度),这样就能生成一个3x3的矩阵
【FPGA】中值滤波处理BMP图片shift ip核仿真图:

3.代码实现

【FPGA】中值滤波处理BMP图片
我们首先把输入的数据放进filter_3x3模块,然后通过shift IP核生成一个3x3的窗口,然后再通过sort_3模块对窗口里9个数进行排序得到中值并输出。

median_filter顶层模块

module median_filter(
    input               clk     ,
    input               rst_n   ,
    
    input               iValid  ,
    input       [7:0]   iData   ,

    output      [7:0]   oData
);

//参数定义
wire    [7:0]filter_11;
wire    [7:0]filter_12;
wire    [7:0]filter_13;

wire    [7:0]filter_21;
wire    [7:0]filter_22;
wire    [7:0]filter_23;

wire    [7:0]filter_31;
wire    [7:0]filter_32;
wire    [7:0]filter_33;
																																															
wire    [7:0]max1;
wire    [7:0]max2;
wire    [7:0]max3;

wire    [7:0]mid1;
wire    [7:0]mid2;
wire    [7:0]mid3;

wire    [7:0]min1;
wire    [7:0]min2;
wire    [7:0]min3;

wire    [7:0]min_of_max;
wire    [7:0]max_of_min;
wire    [7:0]mid_of_mid;

filter_3x3 inst_filter_3x3(
    .clk      (clk),
    .rst_n    (rst_n),
    .iValid   (iValid),
    .iData    (iData),

    .oData_11 (filter_11), 
    .oData_12 (filter_12), 
    .oData_13 (filter_13),

    .oData_21 (filter_21), 
    .oData_22 (filter_22), 
    .oData_23 (filter_23),

    .oData_31 (filter_31), 
    .oData_32 (filter_32), 
    .oData_33 (filter_33)
);

//第一步:对3x3窗口的每一行进行排序,得到每行max,mid,min数
sort_3 inst_sort_3_11(
    .clk   (clk),
    .rst_n (rst_n),
    .data1 (filter_11),
    .data2 (filter_12),
    .data3 (filter_13),
    .max   (max1),
    .mid   (mid1),
    .min   (min1)
);
sort_3 inst_sort_3_12(
    .clk   (clk),
    .rst_n (rst_n),
    .data1 (filter_21),
    .data2 (filter_22),
    .data3 (filter_23),
    .max   (max2),
    .mid   (mid2),
    .min   (min2)
);
sort_3 inst_sort_3_13(
    .clk   (clk),
    .rst_n (rst_n),
    .data1 (filter_31),
    .data2 (filter_32),
    .data3 (filter_33),
    .max   (max3),
    .mid   (mid3),
    .min   (min3)
);

//第二步:对第一步排序得到的第一列3个max排序取min,第二列3个mid排序取mid,第三列 3个min排序取max
sort_3 inst_sort_3_21(
    .clk   (clk),
    .rst_n (rst_n),
    .data1 (max1),
    .data2 (max2),
    .data3 (max3),
    .max   (),
    .mid   (),
    .min   (min_of_max)
);
sort_3 inst_sort_3_22(
    .clk   (clk),
    .rst_n (rst_n),
    .data1 (mid1),
    .data2 (mid2),
    .data3 (mid3),
    .max   (),
    .mid   (mid_of_mid),
    .min   ()
);
sort_3 inst_sort_3_23(
    .clk   (clk),
    .rst_n (rst_n),
    .data1 (min1),
    .data2 (min2),
    .data3 (min3),
    .max   (max_of_min),
    .mid   (),
    .min   ()
);

//第三步:对第二步产生的三个数进行排序,取mid得到中值
sort_3 inst_sort_3_3(
    .clk   (clk),
    .rst_n (rst_n),
    .data1 (min_of_max),
    .data2 (mid_of_mid),
    .data3 (max_of_min),
    .max   (),
    .mid   (oData),
    .min   ()
);
endmodule

fitlet_3x3模块

module filter_3x3(
    input               clk         ,
    input               rst_n       ,


    input       [7:0]   iData       ,
    input               iValid      ,

    output  reg [7:0]   oData_11    ,
    output  reg [7:0]   oData_12    ,
    output  reg [7:0]   oData_13    ,

    output  reg [7:0]   oData_21    ,
    output  reg [7:0]   oData_22    ,
    output  reg [7:0]   oData_23    ,

    output  reg [7:0]   oData_31    ,
    output  reg [7:0]   oData_32    ,
    output  reg [7:0]   oData_33    
);

    reg     [7:0]   row3_data   ;
    wire    [7:0]   row2_data   ;
    wire    [7:0]   row1_data   ;

     // shift_ram实现2行缓存
    line_shift_ram inst_line_shift_ram(
        .clock    (clk),
        .clken    (iValid),
        .shiftin  (row3_data),
        .taps0x   (row2_data),
        .taps1x   (row1_data),
        .shiftout ()
    );

    // 将输入数据寄存作为窗口第三行起始数据,同时也作为行缓存的输入
    always @(posedge clk, negedge rst_n) begin
        if(!rst_n)
            row3_data <= 0;
        else if(iValid)
            row3_data <= iData;
        else 
            row3_data <= 0;
    end

    // 获取3x3模板
    always @(posedge clk, negedge rst_n) begin
        if(!rst_n) begin
            {oData_11, oData_12, oData_13} <= 3'b000;
            {oData_21, oData_22, oData_23} <= 3'b000;
            {oData_31, oData_32, oData_33} <= 3'b000;
        end else if(iValid) begin
            {oData_11, oData_12, oData_13} <= {oData_12, oData_13, row1_data};
            {oData_21, oData_22, oData_23} <= {oData_22, oData_23, row2_data};
            {oData_31, oData_32, oData_33} <= {oData_32, oData_33, row3_data};
        end
    end

endmodule

sort_3模块

//排序模块,对三个数进行排序
module sort_3(
    input               clk     ,
    input               rst_n   ,
    input       [7:0]   data1   ,
    input       [7:0]   data2   ,
    input       [7:0]   data3   ,
    output reg  [7:0]   max     ,
    output reg  [7:0]   mid     ,
    output reg  [7:0]   min
);

//max最大数
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)
        max <= 1'b0 ;
    else if(data1 >= data2 && data1 >= data3)
        max <= data1;
    else if(data2 >= data1 && data2 >= data3)
        max <= data2;
    else
        max <= data3;
end

//mid中间数
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)
        mid <= 1'b0 ;
    else 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;
end

//min最小数
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)
        min <= 1'b0 ;
    else if(data1 <= data2 && data1 <= data3)
        min <= data1;
    else if(data2 <= data1 && data2 <= data3)
        min <= data2;
    else
        min <= data3;
end
endmodule

tb文件

`timescale 1ns / 1ns

module bmp_tb;
reg				clk		;
reg				rst_n	;
reg				iValid	;
reg		[7:0]	iData	;
wire	[7:0]	oData	;


//图像属性:图像宽度 图像高度 图像尺寸 图像像素点起始位
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;
//文件bmp文件数据
reg		[7:0]	rd_data  [0:307200];//根据自己图片大小
reg		[7:0]	dout_data  [0:307200];

//写操作
reg		[23:0]	wr_data;
integer i = 0;
integer index,index0;



median_filter median_filter_inst(
    .clk	(clk	),
    .rst_n	(rst_n	),

    .iValid	(iValid	),
    .iData	(iData	),

    .oData	(oData	)
);


//时钟周期设置
parameter CYCLE =20;
initial begin
	clk = 1;
	forever
	#(CYCLE/2)
	clk=~clk;
end
//复位设置
initial begin
	rst_n = 1;
	#(CYCLE*2);
	rst_n = 0;
	#(CYCLE*3);
	rst_n = 1;
end

initial
begin
	iValid = 0;
	#(6*CYCLE)
	//打开原始图像
	bmp_file_id = $fopen("D:\\desktop\\in_img.bmp","rb");
	//打开输出图像
	bmp_dout_id = $fopen("D:\\desktop\\out_img.bmp","wb");
	//打开输出数据
	dout_txt_id = $fopen("D:\\desktop\\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 = 307200;
	$fclose(bmp_file_id);

	index = start_index;
	repeat(646)begin
	#(CYCLE);
	iData = rd_data[index];
	iValid = 1;
	index = index + 1;
	end
	repeat(bmp_size-646)begin
	#(CYCLE);
	dout_data[index-646] = oData;
	iData = rd_data[index];
	index = index + 1;
	end
	iValid = 0;
	repeat(646)begin
	#(CYCLE);
	dout_data[index-646] = oData;
	iData = 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\n", wr_data[23:16]);
    end
    $fclose(dout_txt_id);
   
end

endmodule

四、结果测试

代码完成后,在tb文件里的对应路径创建好所需要输出图片的bmp和txt文件,然后进行仿真
【FPGA】中值滤波处理BMP图片

【FPGA】中值滤波处理BMP图片
仿真完成后可以看到得到了滤波后的图片【FPGA】中值滤波处理BMP图片

原图与滤波后图对比
【FPGA】中值滤波处理BMP图片【FPGA】中值滤波处理BMP图片

参考博客

中值滤波代码参考:
http://t.csdn.cn/XnTAc
http://t.csdn.cn/JWYsX
bmp图片格式:
http://t.csdn.cn/enoq8
shift IP核
http://t.csdn.cn/zbWdU