FPGA的直方图均衡
文章目录
- 一、直方图均衡
- 二、代码实现
- 三、仿真
一、直方图均衡
直方图均衡(Histogram Equalization)是一种用于增强图像对比度的图像处理技术。它通过重新分配图像像素的灰度值,使得图像的灰度直方图在整个灰度范围内均匀分布,从而增强图像的细节和视觉效果。
直方图均衡也称为直方图拉伸,是一种简单有效的图像增强技术,通过改变图像的直方图分布,来改变图像中各像素的灰度,可用于增强动态范围偏小的图像的对比度。原始图像由于其灰度分布可能集中在较窄的区间,结果呈现出曝光不足/过高,使图像灰度集中在低/高亮度范围内,对比度很低。
直方图均衡的基本原理,就是对在图像中像素个数多的灰度值(即对画面起主要作用的灰度值)进行拉伸,而对像素个数少的灰度值(即对画面不起主要作用的灰度值)进行合并,从而提高对比度,使图像清晰,达到增强的目的。如下图所示:
二、代码实现
直方图均衡代码参考的这篇博客:FPGA图像处理仿真实验——直方图均衡化,该代码不需要缓存一张图像,但是需要连续的两帧图像,前一帧用于像素灰度级数统计和灰度级数累积统计,后一帧从 BRAM 中读取对应的灰度级数累积统计结果,并与 136957 相乘得到 mult_result,其中mult_result[34:27为整数部分, mult_result[26:0]为小数部分。完整代码如下:
`timescale 1ns / 1ps
module histogram_equ(
input clk,
input rst_n,
input per_frame_vsync,
input per_frame_href,
input per_frame_clken,
input [7:0] per_img_Y,
output post_frame_vsync,
output post_frame_href,
output post_frame_clken,
output [23:0] post_img_Y
);
localparam IMG_TOTAL_PIXEL=307200; //640*480
localparam IMG_MAX_GRAY=256; //0-255
//reg web;
wire [18:0] cnt,cntplus; //同一灰度级像素个数计数器
wire [18:0] ram_his_rd_data; //从累计直方图中读出的数据
reg vsync_delay;
wire vsync_negedge; //场同步信号下降沿
wire vsync_posedge; //场同步信号上升沿
reg web2; //双端口RAM u2的写使能信号
reg tag; //标志位
reg [7:0] dizhi;
reg [7:0] dizhi1,dizhi2; //双端口RAM u2的写数据地址
wire [7:0] addra; //双端口RAM u1的a端口地址
wire [18:0] dout; //双端口RAM u1的输出数据
wire [18:0] din; //双端口RAM u1的输入数据
reg [7:0] addrb; //双端口RAM u1的b端口地址
wire web; //双端口RAM的写使能
reg [18:0] sum; //为了计算累加直方图,用于存放直方图数据的和
wire [18:0] ram_accu_rddata; //从累加直方图中读出的数据
//统计像素个数时的写使能信号
//always @(posedge clk)
//begin
// web<=per_frame_clken;
// //addrb<=per_img_Y;
//end
assign cntplus=cnt+1;
//将场同步信号延时一拍
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
vsync_delay<=1'b0;
else
vsync_delay<=per_frame_vsync;
end
assign vsync_negedge=vsync_delay&(!per_frame_vsync);
assign vsync_posedge=(!vsync_delay)&per_frame_vsync;
always @(posedge clk)
begin
dizhi1<=dizhi;
dizhi2<=dizhi1;
end
//在一帧图像输入完成后需要对RAM进行数据读出和数据清0,此时的读写数据地址为变量dizhi
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
dizhi<=8'd0;
else
begin
if(tag==0)
begin
if(dizhi==255)
dizhi<=dizhi;
else
dizhi<=dizhi+1;
end
end
end
//在对累计直方图进行写操作时,只有在标志位tag=0时才进行,并且只遍历一次0-255地址
always @(posedge clk)
begin
if(tag==0)
begin
if(dizhi2==255)
web2<=0;
else
web2<=1;
end
end
//当场有效信号为上升沿时,标志位置1,下降沿时,标志位置0。tag=1代表正在输入图像数据流,tag=0代表两帧图像输入数据流之间的间隙
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
tag<=1;
else
begin
if(vsync_posedge)
tag<=1;
if(vsync_negedge)
tag<=0;
end
end
always @(posedge clk)
begin
addrb<=addra;
end
assign addra=tag?per_img_Y:dizhi; //当输入图像数据流时,地址为每个像素点图像灰度值对应的地址,当处于两帧图像输入数据流之间的间隙时,地址为0-255遍历过程的地址
assign cnt=tag?dout:cnt; //当输入图像数据流时,cnt与u1 的输出数据相连,否则保持
assign ram_his_rd_data=web2?dout:ram_his_rd_data; //当web2有效时,从累计直方图中读出的数据与双端口RAM u2的输出相连,否则保持
assign din=tag?cntplus:(19'd0); //当tag=1时,输入为+1后的数据,当tag=0时,需要对RAN进行清0操作,所以输入为0
assign web=tag?per_frame_clken:1; //tag=1时,使能信号为帧时钟使能信号,tag=0时,要对RAM进行清零操作,所以使能信号一直为1
//计算累加直方图要输入的数据
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
sum<=19'd0;
end
else
begin
if(web2==1)
begin
sum<=sum+ram_his_rd_data;
end
end
end
//双端口RAM u1,用于存放直方图的数据,当输入图片数据流有效时,将每个像素点灰度值对应的地址存放的数据读出,再加1,然后再写回去
//在帧与帧之间的间隙,从地址0-255,将累计直方图数据读出,再将RAM清零
blk_mem_gen_0 u1_blk_mem_gen_0(
.clka(clk),
.wea(1'b0),
.addra(addra), // input wire [7 : 0] addra
.dina(0), // input wire [18 : 0] dina
.douta(dout), // output wire [18 : 0] douta
.clkb(clk), // input wire clkb
.web(web), // input wire [0 : 0] web
.addrb(addrb), // input wire [7 : 0] addrb
.dinb(din), // input wire [18 : 0] dinb
.doutb() // output wire [18 : 0] doutb
);
//双端口RAM u2,用于存放累加直方图数据,将累加直方图的数据从a端口写入,b端口读出
blk_mem_gen_0 u2_blk_mem_gen_0(
.clka(clk),
.wea(web2),
.addra(dizhi2), // input wire [7 : 0] addra
.dina(sum), // input wire [18 : 0] dina
.douta(), // output wire [18 : 0] douta
.clkb(per_frame_clken), // input wire clkb
.web(1'b0), // input wire [0 : 0] web
.addrb(per_img_Y), // input wire [7 : 0] addrb
.dinb(), // input wire [18 : 0] dinb
.doutb(ram_accu_rddata) // output wire [18 : 0] doutb
);
//直方图均衡化
reg [34:0] data_mult; //乘法运算结果
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
data_mult<=35'd0;
else
data_mult<=ram_accu_rddata* 18'd136957;
end
reg [7:0] data_div; //除法运算结果
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
data_div<=8'd0;
else
//data_div<=data_mult/IMG_TOTAL_PIXEL;
data_div = data_mult[34:27] + data_mult[26];
end
//------------------------------------------
//lag 3 clocks signal sync
reg [2:0] per_frame_vsync_r;
reg [2:0] per_frame_href_r;
reg [2:0] per_frame_clken_r;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
per_frame_vsync_r <= 0;
per_frame_href_r <= 0;
per_frame_clken_r <= 0;
end
else
begin
per_frame_vsync_r <= {per_frame_vsync_r[1:0], per_frame_vsync};
per_frame_href_r <= {per_frame_href_r[1:0], per_frame_href};
per_frame_clken_r <= {per_frame_clken_r[1:0], per_frame_clken};
end
end
assign post_frame_vsync = per_frame_vsync_r[2];
assign post_frame_href = per_frame_href_r[2];
assign post_frame_clken = per_frame_clken_r[2];
assign post_img_Y = post_frame_href?{data_div,data_div,data_div}:24'd0;
endmodule
代码中例化了两个双端RAM,具体如下
三、仿真
前面提到,由于不需要缓存整张图片用于直方图均衡的计数,所以属于连续的两帧图片,而仿真代码正好是将同一张图片连续读取两次,所以不需要修改仿真代码。仿真代码完整的可以去找之前的博客,这里只放仿真顶层文件,代码如下:
`timescale 1ns / 1ps
module img_sim_tb();
localparam PIC_INPUT_PATH = "gsls_test1.bmp" ;
localparam PIC_OUTPUT_PATH = "output.bmp" ;
localparam PIC_WIDTH = 640 ;
localparam PIC_HEIGHT = 480 ;
reg cmos_clk = 0;
reg cmos_rst_n = 0;
wire cmos_vsync ;
wire cmos_href ;
wire cmos_clken ;
wire [23:0] cmos_data ;
wire pos_vsync ;
wire pos_hsync ;
wire pos_de ;
wire [23:0] pos_data ;
parameter cmos0_period = 6;
always#(cmos0_period/2) cmos_clk = ~cmos_clk;
initial #(20*cmos0_period) cmos_rst_n = 1;
//--------------------------------------------------
//Camera Simulation
sim_cmos #(
.PIC_PATH (PIC_INPUT_PATH )
, .IMG_HDISP (PIC_WIDTH )
, .IMG_VDISP (PIC_HEIGHT )
)u_sim_cmos0(
.clk (cmos_clk )
, .rst_n (cmos_rst_n )
, .CMOS_VSYNC (cmos_vsync )
, .CMOS_HREF (cmos_href )
, .CMOS_CLKEN (cmos_clken )
, .CMOS_DATA (cmos_data )
, .X_POS ()
, .Y_POS ()
);
histogram_equ_top u_histogram_equ_top(
.clk (cmos_clk) ,
.rst_n (cmos_rst_n),
.pre_image_vsync (cmos_vsync), // vsync信号
.pre_image_clken (cmos_clken), // 时钟使能信号
.pre_image_hsync (cmos_href), // 数据有效信号
.pre_image_data (cmos_data), // 输入图像数据RGB
.pos_image_vsync (pos_vsync), // vsync信号
.pos_image_clken (pos_de), // 时钟使能信号
.pos_image_hsync (pos_hsync), // 数据有效信号
.pos_image_data (pos_data) // 输出图像Y数据
);
//--------------------------------------------------
//Video saving
video_to_pic #(
.PIC_PATH (PIC_OUTPUT_PATH )
, .START_FRAME (2 )
, .IMG_HDISP (PIC_WIDTH )
, .IMG_VDISP (PIC_HEIGHT )
)u_video_to_pic0(
.clk (cmos_clk )
, .rst_n (cmos_rst_n )
, .video_vsync (pos_vsync )
, .video_hsync (pos_hsync )
, .video_de (pos_de )
, .video_data (pos_data )
);
endmodule
仿真结果如下,左边的为原始图像,中间是使用python代码处理的直方图均衡化,右边是Verilog仿真的直方图均衡化,可以看到有明显的图像失真,问题出在那暂时没有找留个坑。