直方图均衡化,通过一种灰度映射使输入图像经过转换后,在每一灰度级上都有相近似的输出图像,输出的灰度值是均匀的。经过这样处理的图像具有较高的对比对和较大的动态范围。
对于离散的灰度级,直方图均衡化有如下关系:
其中Db是转换过后的像素的灰度值,Dmax是输入图像经过直方图统计后得到的最大的灰度值,A0是一幅图像面积,也就是所有的像素值H(i)是灰度级的各级的统计结果。Da是当前像素的灰度级。
直方图均衡化就的作用就是,对于当前的输入像素,需要首先求出小于当前像素灰度值的所有像素统计值的和,然后与该图像中的最大灰度值进行相乘,最后除以图像的面积。
基于FPGA也是能够完成图像的直方图的处理的。具体的计算步骤如下:
在本人进行算法验证的时候,并没有对输入图像进行帧缓存,正常的操作应该是,将本帧的直方图统计完成后,在将缓存好的图像从内存中取出,并进行直方图均衡化。在进行算法验证时,未进行帧缓存,相当于将上一帧图像数据的统计结果作为下一帧图像的变换因子。这在变化速率不太快的情况下是可以采取的一种方式。
在上一篇博客中介绍了图像直方图的统计方法,并且最终得到的输出的直方图统计值和有效信号。因此上游模块输出直方图统计信息时,可以进行计算直方图对应灰度级下的累加和,并且确定灰度的最大值。
采用一个BRAM来存储一幅图像的灰度累加和。于此同时确定当前图形中的灰度最大值。
在上一步求得灰度累加和之后,当前帧图像像素已经来临,因此可以将该像素对应的灰度级和最大灰度值相乘。由于FPGA并不擅长乘法运算,尤其不擅长除法运算。因此在进行乘除法时,会调用内部的专用的DSP资源,因此可以调用Xilinx的乘法器或者除法器IP来进行乘除法的运算。
在进行乘法运算时,需要根据仿真的结果来确定乘法的Latency。
在上一步的乘法计算完成后,可以进行除法的运算,在除法IP的配置时,可以设置除法的运算的潜伏期。本人所设置的Latency为7。
/*============================================
#
# Author: Wcc - [email protected]
#
# QQ : 1530604142
#
# Last modified: 2020-07-08 20:02
#
# Filename: histogram_equalization.v
#
# Description:
#
============================================*/
`timescale 1ns / 1ps
module histogram_equalization(
input wire clk ,
input wire rst ,
input wire pi_hsync ,//输入的视频流信号
input wire pi_vsync ,
input wire pi_de ,
input wire pi_data_vld ,
input wire [7:0] pi_data ,
input wire pi_histo_vld ,//输入的直方图统计信息
input wire [31:0] pi_histo_data ,
output wire po_hsync ,//输出的视频流信号
output wire po_vsync ,
output wire po_de ,
output wire po_data_vld ,
output wire [7:0] po_data
);
//==========================================
//parameter define
//==========================================
parameter IMG_WIDTH = 128 ;
parameter IMG_HEIGHT = 128 ;
localparam TOTAL_PIXEL = IMG_WIDTH * IMG_HEIGHT;
//==========================================
//internal signal
//==========================================
reg [1:0] vsync_dd ;//场同步信号延时
reg [7:0] pi_data_dd ;//输入数据延时
reg data_vld_dd ;//输入数据有效延时
reg [2:0] stream_vld_dd ;
//==========================================
//求累加和与找寻灰度最大最小值
//==========================================
reg [31:0] gray_sum ;//灰度累加和
reg [7:0] gray_max ;//灰度值最大值
reg [7:0] gray_min ;//灰度值最小值
reg [7:0] gray_idx ;//灰度值索引
//==========================================
//确定当前像素在图像中的位置
//==========================================
reg [12:0] cnt_col ;
wire add_cnt_col ;
wire end_cnt_col ;
reg [12:0] cnt_row ;
wire end_cnt_row ;
wire add_cnt_row ;
//==========================================
//ram 相关
//==========================================
reg wr_ram_en ;//存储灰度累加和的RAM
wire [31:0] wr_ram_data ;//写入RAM的数据
reg [7:0] wr_ram_addr ;//写RAM时的地址
wire [7:0] rd_ram_addr ;
wire [31:0] rd_ram_data ;
//==========================================
//multiplier
//==========================================
wire [39:0] mult_value ;//乘积
wire mult_vld ;//乘积有效信号
//==========================================
//divider
//==========================================
wire div_tvalid ;
wire [63 : 0] div_tdata ;
//----------------vsync_dd------------------
always @(posedge clk) begin
if (rst==1'b1) begin
vsync_dd <= 'd0;
end
else begin
vsync_dd <= {vsync_dd[0], pi_vsync};
end
end
//==========================================
//将累加和写入到RAM中
//==========================================
//----------------gray_sum------------------
always @(posedge clk) begin
if (rst==1'b1) begin
gray_sum <= 'd0;
end
else if (pi_histo_vld == 1'b1) begin
gray_sum <= gray_sum + pi_histo_data;
end
else begin
gray_sum <= 'd0;
end
end
//----------------wr_ram_data------------------
assign wr_ram_data = gray_sum;
//----------------wr_ram_en------------------
always @(posedge clk) begin
if (rst==1'b1) begin
wr_ram_en <= 1'b0;
end
else begin
wr_ram_en <= pi_histo_vld;
end
end
//----------------wr_ram_addr------------------
always @(posedge clk) begin
if (rst==1'b1) begin
wr_ram_addr <= 'd0;
end
else if (wr_ram_en == 1'b1) begin
wr_ram_addr <= wr_ram_addr + 1'b1;
end
else begin
wr_ram_addr <= 'd0;
end
end
//==========================================
//找寻最大最小值
//==========================================
//----------------gray_indx------------------
always @(posedge clk) begin
if (rst==1'b1) begin
gray_idx <= 'd0;
end
else if (pi_histo_vld == 1'b1) begin
gray_idx <= gray_idx + 1'b1;
end
else begin
gray_idx <= 'd0;
end
end
//----------------gray_max------------------
always @(posedge clk) begin
if (rst==1'b1) begin
gray_max <= 'd0;
gray_min <= 'd255;
end
//检测到一帧图像结束
else if (end_cnt_row == 1'b1) begin
gray_max <= 'd0;
gray_min <= 'd255;
end
else if (pi_histo_data != 0 && pi_histo_vld == 1'b1 ) begin
if (gray_max <= gray_idx) begin
gray_max <= gray_idx;
end
if (gray_min >= gray_idx) begin
gray_min <= gray_idx;
end
end
end
//----------------pi_data_dd, data_vld_dd------------------
always @(posedge clk) begin
if (rst==1'b1) begin
pi_data_dd <= 'd0;
data_vld_dd <= 'd0;
end
else begin
pi_data_dd <= pi_data;
data_vld_dd <= pi_data_vld;
end
end
//----------------stream_vld_dd------------------
always @(posedge clk) begin
if (rst==1'b1) begin
stream_vld_dd <= 'd0;
end
else begin
stream_vld_dd <= {stream_vld_dd[1:0], data_vld_dd};
end
end
assign mult_vld = stream_vld_dd[2];
//----------------rd_ram_addr------------------
assign rd_ram_addr = (pi_data_vld) ? pi_data : 'd0;
sum_ram inst_sum_ram (
.clka(clk), // input wire clka
.wea(wr_ram_en), // input wire [0 : 0] wea
.addra(wr_ram_addr), // input wire [7 : 0] addra
.dina(wr_ram_data), // input wire [31 : 0] dina
.clkb(clk), // input wire clkb
.addrb(rd_ram_addr), // input wire [7 : 0] addrb
.doutb(rd_ram_data) // output wire [31 : 0] doutb
);
//==========================================
//乘法器 3个时钟周期的潜伏期
//==========================================
mul_graylevel int_multiplier (
.CLK(clk), // input wire CLK
.A(gray_max), // input wire [7 : 0] A
.B(rd_ram_data), // input wire [31 : 0] B
.P(mult_value) // output wire [39 : 0] P
);
//==========================================
//除法器 7个时钟周期的Latency
//==========================================
// m_axis_dout_tdata[63 : 0 ]
//[63:24] 商
//[21:0] 余数
div_gray inst_divider (
.aclk(clk), // input wire aclk
.s_axis_divisor_tvalid(1'b1), // input wire s_axis_divisor_tvalid
.s_axis_divisor_tdata(TOTAL_PIXEL[23:0]), // input wire [23 : 0] s_axis_divisor_tdata
.s_axis_dividend_tvalid(mult_vld), // input wire s_axis_dividend_tvalid
.s_axis_dividend_tdata(mult_value), // input wire [39 : 0] s_axis_dividend_tdata
.m_axis_dout_tvalid(div_tvalid), // output wire m_axis_dout_tvalid
.m_axis_dout_tdata(div_tdata) // output wire [63 : 0] m_axis_dout_tdata
);
assign po_data_vld = div_tvalid;
//==========================================
//确定当前像素在图像中的位置
//==========================================
//----------------cnt_col------------------
always @(posedge clk) begin
if (rst == 1'b1) begin
cnt_col <= 'd0;
end
else if (add_cnt_col) begin
if(end_cnt_col)
cnt_col <= 'd0;
else
cnt_col <= cnt_col + 1'b1;
end
else begin
cnt_col <= 'd0;
end
end
assign add_cnt_col = div_tvalid == 1'b1;
assign end_cnt_col = add_cnt_col && cnt_col == IMG_WIDTH - 1;
//----------------cnt_row------------------
always @(posedge clk) begin
if (rst == 1'b1) begin
cnt_row <= 'd0;
end
else if (add_cnt_row) begin
if(end_cnt_row)
cnt_row <= 'd0;
else
cnt_row <= cnt_row + 1'b1;
end
end
assign add_cnt_row = end_cnt_col;
assign end_cnt_row = add_cnt_row && cnt_row == IMG_HEIGHT - 1;
//----------------pi_hsync_dd/pi_vsync_dd,pi_de_dd------------------
reg [10:0] pi_hsync_dd;
reg [10:0] pi_vsync_dd;
reg [10:0] pi_de_dd;
reg [87:0] data_dd ;
//从数据输出到输出共有11个时钟周期的latency
always @(posedge clk) begin
if (rst==1'b1) begin
pi_hsync_dd <= 'd0;
pi_vsync_dd <= 'd0;
pi_de_dd <= 'd0;
data_dd <= 'd0;
end
else begin
pi_hsync_dd <= {pi_hsync_dd[9:0], pi_hsync};
pi_vsync_dd <= {pi_vsync_dd[9:0], pi_vsync};
pi_de_dd <= {pi_de_dd[9:0], pi_de};
data_dd <= {data_dd[79:0], pi_data};
end
end
assign po_hsync = pi_hsync_dd[10];
assign po_vsync = pi_vsync_dd[10];
assign po_de = pi_de_dd[10];
assign po_data = (po_data_vld) ? div_tdata[31:24] : data_dd[87:80];
endmodule
在片内存储中,存储有一幅灰度图像的信息,下面的两幅图像中,第一幅图像是经过直方图均衡化过后的结果,第一幅图像的对比度相较于第二幅要高。
参考:《基于FPGA的数字图像处理》牟新刚