FPGA图像处理之直方图均衡实现
1. 直方图均衡的原理
直方图均衡的基本原理,就是对在图像中像素个数多的灰度值(即对画面起主要作用的灰度值)进行拉伸,而对像素个数较少的灰度值(即对画面不起主要作用的灰度值)进行合并,从而提高对比度,使图像清晰,达到增强的目的。
为了将原图像的亮度范围进行扩展, 需要一个映射函数, 将原图像的像素值均衡映射到新直方图中, 这个映射函数有两个条件:
- 不能打乱原有的像素值大小顺序, 映射后亮、 暗的大小关系不能改变;
- 映射后必须在原有的范围内,即像素映射函数的值域应在0和255之间;
综合以上两个条件,累积分布函数是个好的选择,因为累积分布函数是单调增函数(控制大小关系),并且值域是0到1(控制越界问题),所以直方图均衡化中使用的是累积分布函数。
2. 直方图均衡的FPGA实现
根据直方图均衡的功能将其划分为两个子模块,分别为hist_stat模块和histEQ_proc模块,直方图均衡FPGA设计框架,如图1所示。hist_stat模块的功能是进行像素的灰度级数统计和灰度级数累积统计;histEQ_proc模块的功能是对原始图像进行直方图均衡。
直方图均衡的业务处理流程如下。
(1)将原始图像输入到hist_stat模块进行像素灰度级数统计和灰度级数累计统计,同时原始图像存储于外部存储器。
(2)在(1)完成后,histEQ_proc模块从外部存储器读取原始图像进行直方图均衡,输出增强后的图像。
3. FPGA 代码实现
1.外部存储器模块
module bram_ture_dual_port
#(
parameter C_ADDR_WIDTH = 8,
parameter C_DATA_WIDTH = 8
)(
input wire clka ,
input wire wea ,
input wire [C_ADDR_WIDTH-1:0] addra ,
input wire [C_DATA_WIDTH-1:0] dina ,
output reg [C_DATA_WIDTH-1:0] douta ,
input wire clkb ,
input wire web ,
input wire [C_ADDR_WIDTH-1:0] addrb ,
input wire [C_DATA_WIDTH-1:0] dinb ,
output reg [C_DATA_WIDTH-1:0] doutb
);
//----------------------------------------------------------------------
localparam C_MEM_DEPTH = {C_ADDR_WIDTH{1'b1}};
reg [C_DATA_WIDTH-1:0] mem [C_MEM_DEPTH:0];
integer i;
initial
begin
for(i = 0;i <= C_MEM_DEPTH;i = i+1)
mem[i] = 0;
end
always @(posedge clka)
begin
if(wea == 1'b1)
mem[addra] <= dina;
else
douta <= mem[addra];
end
always @(posedge clkb)
begin
if(web == 1'b1)
mem[addrb] <= dinb;
else
doutb <= mem[addrb];
end
endmodule
2.hist_stat模块
module hist_stat
(
input wire clk ,
input wire rst_n ,
input wire img_vsync ,
input wire img_href ,
input wire [ 7:0] img_gray ,
output reg [ 7:0] pixel_level ,
output reg [19:0] pixel_level_acc_num ,
output reg pixel_level_valid
);
//----------------------------------------------------------------------
// bram signals define
wire bram_a_wenb;
wire [ 7:0] bram_a_addr;
wire [19:0] bram_a_rdata;
wire bram_b_wenb;
wire [ 7:0] bram_b_addr;
wire [19:0] bram_b_wdata;
wire [19:0] bram_b_rdata;
//----------------------------------------------------------------------
// preprocess
reg [7:0] pixel_data;
always @(posedge clk)
begin
pixel_data <= img_gray;
end
reg pixel_data_valid;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
pixel_data_valid <= 1'b0;
else
pixel_data_valid <= img_href;
end
reg img_vsync_dly;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
img_vsync_dly <= 1'b0;
else
img_vsync_dly <= img_vsync;
end
wire pixel_data_eop;
assign pixel_data_eop = img_vsync_dly & ~img_vsync;
//----------------------------------------------------------------------
// preprocess
reg [7:0] pixel_data_tmp;
always @(posedge clk)
begin
pixel_data_tmp <= pixel_data;
end
reg pixel_data_valid_tmp1;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
pixel_data_valid_tmp1 <= 1'b0;
else
begin
if(pixel_data_valid == 1'b1)
begin
if(pixel_data_valid_tmp1 == 1'b0)
pixel_data_valid_tmp1 <= 1'b1;
else
begin
if(pixel_data_tmp == pixel_data)
pixel_data_valid_tmp1 <= 1'b0;
else
pixel_data_valid_tmp1 <= 1'b1;
end
end
else
pixel_data_valid_tmp1 <= 1'b0;
end
end
reg [1:0] pixel_data_cnt_tmp;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
pixel_data_cnt_tmp <= 2'd1;
else
begin
if((pixel_data_valid == 1'b1)&&(pixel_data_valid_tmp1 == 1'b1)&&(pixel_data_tmp == pixel_data))
pixel_data_cnt_tmp <= 2'd2;
else
pixel_data_cnt_tmp <= 2'd1;
end
end
reg pixel_data_eop_tmp;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
pixel_data_eop_tmp <= 1'b0;
else
pixel_data_eop_tmp <= pixel_data_eop;
end
//----------------------------------------------------------------------
// c1
reg [7:0] pixel_data_c1;
always @(posedge clk)
begin
pixel_data_c1 <= pixel_data;
end
reg pixel_data_eop_c1;
reg pixel_data_valid_tmp_c1;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
pixel_data_eop_c1 <= 1'b0;
pixel_data_valid_tmp_c1 <= 1'b0;
end
else
begin
pixel_data_eop_c1 <= pixel_data_eop_tmp;
pixel_data_valid_tmp_c1 <= pixel_data_valid_tmp1;
end
end
//----------------------------------------------------------------------
// c2 : delay 3 clock
reg [7:0] pixel_data_c2;
always @(posedge clk)
begin
pixel_data_c2 <= pixel_data_c1;
end
reg pixel_data_eop_tmp1_c2;
reg pixel_data_eop_tmp2_c2;
reg pixel_data_eop_c2;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
pixel_data_eop_tmp1_c2 <= 1'b0;
pixel_data_eop_tmp2_c2 <= 1'b0;
pixel_data_eop_c2 <= 1'b0;
end
else
begin
pixel_data_eop_tmp1_c2 <= pixel_data_eop_c1;
pixel_data_eop_tmp2_c2 <= pixel_data_eop_tmp1_c2;
pixel_data_eop_c2 <= pixel_data_eop_tmp2_c2;
end
end
//----------------------------------------------------------------------
// c3
reg bram_rw_ctrl_flag_c3;
reg [8:0] bram_rw_ctrl_cnt;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
bram_rw_ctrl_flag_c3 <= 1'b0;
else
begin
if(pixel_data_eop_c2 == 1'b1)
bram_rw_ctrl_flag_c3 <= 1'b1;
else if(bram_rw_ctrl_cnt == 9'h100)
bram_rw_ctrl_flag_c3 <= 1'b0;
else
bram_rw_ctrl_flag_c3 <= bram_rw_ctrl_flag_c3;
end
end
reg [8:0] bram_rw_ctrl_cnt_dly;
always @(posedge clk)
begin
bram_rw_ctrl_cnt_dly <= bram_rw_ctrl_cnt;
end
always @(*)
begin
if(bram_rw_ctrl_flag_c3 == 1'b1)
bram_rw_ctrl_cnt <= bram_rw_ctrl_cnt_dly + 1'b1;
else
bram_rw_ctrl_cnt <= 9'b0;
end
wire [7:0] bram_addr_c3;
assign bram_addr_c3 = bram_rw_ctrl_cnt - 1'b1;
//----------------------------------------------------------------------
// c4
reg bram_rw_ctrl_flag_c4;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
bram_rw_ctrl_flag_c4 <= 1'b0;
else
bram_rw_ctrl_flag_c4 <= bram_rw_ctrl_flag_c3;
end
reg [7:0] bram_addr_c4;
always @(posedge clk)
begin
bram_addr_c4 <= bram_addr_c3;
end
//----------------------------------------------------------------------
// c5
reg [7:0] pixel_level_c5;
always @(posedge clk)
begin
pixel_level_c5 <= bram_addr_c4;
end
reg [19:0] pixel_level_num_c5;
always @(posedge clk)
begin
if(bram_rw_ctrl_flag_c4 == 1'b1)
pixel_level_num_c5 <= bram_b_rdata;
else
pixel_level_num_c5 <= 20'b0;
end
reg pixel_level_valid_c5;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
pixel_level_valid_c5 <= 1'b0;
else
pixel_level_valid_c5 <= bram_rw_ctrl_flag_c4;
end
//----------------------------------------------------------------------
// c6
reg [7:0] pixel_level_c6;
always @(posedge clk)
begin
pixel_level_c6 <= pixel_level_c5;
end
reg [19:0] pixel_level_acc_num_c6;
always @(posedge clk)
begin
if(pixel_level_valid_c5 == 1'b1)
begin
if(pixel_level_c5 == 8'b0)
pixel_level_acc_num_c6 <= pixel_level_num_c5;
else
pixel_level_acc_num_c6 <= pixel_level_acc_num_c6 + pixel_level_num_c5;
end
else
pixel_level_acc_num_c6 <= pixel_level_acc_num_c6;
end
reg pixel_level_valid_c6;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
pixel_level_valid_c6 <= 1'b0;
else
pixel_level_valid_c6 <= pixel_level_valid_c5;
end
//----------------------------------------------------------------------
// signal output
always @(posedge clk)
begin
pixel_level <= pixel_level_c6;
pixel_level_acc_num <= pixel_level_acc_num_c6;
end
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
pixel_level_valid <= 1'b0;
else
pixel_level_valid <= pixel_level_valid_c6;
end
//----------------------------------------------------------------------
// bram_a20d256_b20d256 module initialization
assign bram_a_wenb = bram_rw_ctrl_flag_c4;
assign bram_a_addr = (bram_rw_ctrl_flag_c4 == 1'b1) ? bram_addr_c4 : pixel_data_tmp;
assign bram_b_wenb = pixel_data_valid_tmp_c1;
assign bram_b_addr = (bram_rw_ctrl_flag_c3 == 1'b1) ? bram_addr_c3 : pixel_data_c2;
assign bram_b_wdata = bram_a_rdata + pixel_data_cnt_tmp;
bram_ture_dual_port
#(
.C_ADDR_WIDTH (8 ),
.C_DATA_WIDTH (20)
)
u_bram_ture_dual_port
(
.clka (clk ),
.wea (bram_a_wenb ),
.addra (bram_a_addr ),
.dina (20'b0 ),
.douta (bram_a_rdata ),
.clkb (clk ),
.web (bram_b_wenb ),
.addrb (bram_b_addr ),
.dinb (bram_b_wdata ),
.doutb (bram_b_rdata )
);
endmodule
3.histEQ_proc模块
module histEQ_proc
(
input wire clk ,
input wire rst_n ,
input wire [ 7:0] pixel_level ,
input wire [19:0] pixel_level_acc_num ,
input wire pixel_level_valid ,
output reg histEQ_start_flag ,
input wire per_img_vsync ,
input wire per_img_href ,
input wire [ 7:0] per_img_gray ,
output wire post_img_vsync ,
output wire post_img_href ,
output wire [ 7:0] post_img_gray
);
//----------------------------------------------------------------------
// delay 1 clock
wire bram_a_wenb;
wire [ 7:0] bram_a_addr;
wire [19:0] bram_a_wdata;
wire [ 7:0] bram_b_addr;
wire [19:0] bram_b_rdata;
assign bram_a_wenb = pixel_level_valid;
assign bram_a_addr = pixel_level;
assign bram_a_wdata = pixel_level_acc_num;
assign bram_b_addr = per_img_gray;
bram_ture_dual_port
#(
.C_ADDR_WIDTH (8 ),
.C_DATA_WIDTH (20)
)
u_bram_ture_dual_port
(
.clka (clk ),
.wea (bram_a_wenb ),
.addra (bram_a_addr ),
.dina (bram_a_wdata ),
.douta ( ),
.clkb (clk ),
.web (1'b0 ),
.addrb (bram_b_addr ),
.dinb (20'b0 ),
.doutb (bram_b_rdata )
);
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
histEQ_start_flag <= 1'b0;
else
begin
if((pixel_level_valid == 1'b1)&&(pixel_level == 8'd255))
histEQ_start_flag <= 1'b1;
else
histEQ_start_flag <= 1'b0;
end
end
//----------------------------------------------------------------------
// uint8(CumPixel/980) = round(CumPixel * 136957/2^27) , CumPixel <= 500*500
reg [34:0] mult_result;
always @(posedge clk)
begin
mult_result <= bram_b_rdata * 18'd136957;
end
reg [7:0] pixel_data;
always @(posedge clk)
begin
pixel_data <= mult_result[34:27] + mult_result[26];
end
//----------------------------------------------------------------------
// lag 3 clocks signal sync
reg [2:0] per_img_vsync_r;
reg [2:0] per_img_href_r;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
per_img_vsync_r <= 3'b0;
per_img_href_r <= 3'b0;
end
else
begin
per_img_vsync_r <= {per_img_vsync_r[1:0],per_img_vsync};
per_img_href_r <= {per_img_href_r[1:0],per_img_href};
end
end
//----------------------------------------------------------------------
assign post_img_vsync = per_img_vsync_r[2];
assign post_img_href = per_img_href_r[2];
assign post_img_gray = pixel_data;
endmodule
4.参考资料
1.基于MATLAB与FPGA的图像处理教程(韩彬)
2.https://blog.csdn.net/weixin_40163266/article/details/113802909