基于FPGA实现图像直方图设计

描述

直方图统计的原理

直方图统计从数学上来说,是对图像中的像素点进行统计。图像直方图统计常用于统计灰度图像,表示图像中各个灰度级出现的次数或者概率。统计直方图的实现采用C/C++或者其他高级语言实现十分简单,单采用FPGA来实现直方图的统计就稍显麻烦。若使用Xilinx和Altera的FPGA芯片,可以使用HLS来进行图像的加速处理。但这暂时不是我的重点。

用C语言实现直方图统计:unsigned int histoBuffer[256];

for(int idxCol = 0; idxCol < imageWidth; idxCol ++)

{

for(int idxRow = 0; idxRow < imageHeight; idxRow ++)

{

histoBuffer[image[idxRow * imageWidth + idxCol]] ++;

}

}

基于FPGA实现图像直方图

在前面可以看到基于C/C++或者其他高级语言实现直方图统计十分简单。但是在FPGA中,需要设计具体的时序和电路才能正确地将直方图进行统计。

直方图统计的注意点

使用FPGA来完成直方图的统计需要注意以下几点:

对一幅图像进行统计,必须等到当前的图像“流过”后才能完成。这使得采用FPGA相较于其他方式并没有太大的优势。

在统计的过程中,需要对已经统计的像素的统计值进行缓存。

在下一帧图像来临的时候,需要将RAM中的数据清空。

设计统计模块

根据上面的要点,在设计直方图统计电路的时候可以按照如下思路来进行:

新的一帧图像来临是,需要将上一帧图像的直方图统计结果清零。

在新一帧图像数据有效时,进行统计

一帧图像数据统计完成后,将统计结果读出,并将统计结果输出到外部。

状态机设计:

在复位或空闲状态下,系统处于IDLE状态,当检测到新一帧图像(vsync信号的上升沿)时,状态跳转到CLEAR状态,清空RAM中保存的上一帧图像的数据。当RAM中的图像数据清空完成后,进入到直方图统计状态CALCULATE,在该状态下进行直方图的统计当一帧图像统计完成后,将本帧图像的统计结果输出,也即GET_HISTO状态。

直方图

CLEAR状态:

下图是CLEAR状态下的时序设计图,拉高一个clear_flag信号,向RAM中写入0,将上一帧图像的统计结果清零。

直方图

CALCULATE状态:

统计状态下完成的任务是最复杂的,由于在图像数据流来领的时候,常常会遇到相邻几个像素点的灰度值是相同的,因此可以将这些点进行统计,然后在将统计值写入到RAM中,将小对RAM的读写操作。

下面的时序图是一个典型的统计时序设计图,基本包括了图像流入时的像素状态。

在统计时,主要是来比较当前的像素点和上一个像素点的值是否相同,若相同则像素统计值cal_pixel就会加一,直到相邻两像素值不同或者一行图像结束时,停止加一,并且将当前统计结果cal_pixel和RAM中已经缓存的统计值进行累加,重新写入到RAM中(也即wr_ram_data),同时需要使能RAM的写操作。wr_ram_en。写入RAM的地址,其实就是当前的灰度值。在这之中,需要注意从RAM中读出数据具有1或者2个时钟周期的Latency(根据IP核设置有关。

在FPGA的直方图统计中,该部分是最重要的。完成了该时序图,也就基本上完成了统计电路。

直方图

GET_HISTO状态:

直方图

该状态下,就是完成对直方图的统计结果的读出。

程序设计

FPGA完成直方图的设计,其实就是上面的三个时序图的设计,完成了上述三个时序图后,就能够直方图统计模块。下面的这个模块完成的是一个256*256大小的灰度图的直方图统计,若需要对其他大小的图像进行直方图统计,只需修改其中的参数即可。其实对于我的设计,每一行的像素个数是由上游模块确定的,在本模块中,只需指定图像的高度即可,指定高度,也仅仅是为了将直方图从RAM中读出。

parameter IMG_WIDTH = 256 ;

parameter IMG_HEIGHT = 256 ;

`timescale 1ns / 1ps

module calculate_histogram(

input wire clk ,

inputwire rst ,

input wire pi_hsync,

inputwire pi_vsync,

inputwire pi_data_vld,

input wire [7:0]pi_data ,

output wire po_histo_vld,

output wire [31:0]po_histo_data

);

//==========================================

//parameter define

//==========================================

parameter IMG_WIDTH = 256 ;

parameter IMG_HEIGHT =256 ;

parameter GRAY_LEVEL= 256;//灰度级

parameter IDLE = 4'b0001;//空闲状态

parameter CLEAR= 4'b0010;//清空RAM中数据状态

parameter CALCULATE = 4'b0100;//统计图像直方图状态

parameter GET_HISTO = 4'b1000;//输出直方图

//==========================================

//internal siganls

//==========================================

reg [3:0]state ;//状态寄存器

reg [1:0]vsync_dd;//场同步信号寄存

//==========================================

//清空RAM阶段

//==========================================

reg [8:0]cnt_clear ;

wireadd_cnt_clear;

wire end_cnt_clear;

reg clear_flag;//清空RAM指示信号

//==========================================

//统计直方图阶段

//==========================================

reg [12:0]cnt_row ;

wire add_cnt_row ;

wire end_cnt_row;

reg data_vld_dd0;//数据有效延时信号

reg data_vld_dd1;//数据有效延时信号

reg [7:0]pi_data_dd0;//有效数据延时

reg [7:0]pi_data_dd1;//有效数据延时

reg [31:0]cal_pixle;//相同的像素统计值

reg [31:0]cal_value ;//写入RAM的统计值

reg cal_value_vld;//写入RAM数据有效信号

reg cal_one_row_done;//统计一行图像数据结束

wire [7:0]cal_wr_ram_addr;//统计状态下写RAM的地址

wire [7:0]cal_rd_ram_addr;//统计状态下读RAM的地址

//==========================================

//读出数据阶段

//==========================================

reg get_data_flag ;

reg [8:0]cnt_get ;

wire add_cnt_get ;

wire end_cnt_get ;

reg histo_data_vld ;

wire [31:0]histo_data ;

//==========================================

//Block RAM Related Signals

//==========================================

reg wr_ram_en ;//写RAM使能信号

reg [7:0]wr_ram_addr;//写RAM地址

reg [31:0]wr_ram_data ;//写入RAM的数据

reg  [7:0]rd_ram_addr ;//读RAM的地址

wire[31:0]rd_ram_data;//从RAM中读出的数据

assign po_histo_data = (histo_data_vld) ? histo_data : 32'd0;

assign po_histo_vld = histo_data_vld;

//----------------state machine describe------------------

always @(posedge clk) begin

if (rst==1'b1) begin

state <= IDLE ;

end

else begin

case(state)

IDLE : begin

//检测到新的一帧图像

if (vsync_dd[0] == 1'b1 && vsync_dd[1] == 1'b0) begin

state <= CLEAR;

end

else begin

state <= IDLE;

end

end

CLEAR : begin

//当前RAM中的数据已经清空

if (end_cnt_clear == 1'b1) begin

state <= CALCULATE;

end

else begin

state <= CLEAR;

end

end

CALCULATE : begin

//当前一幅图像数据的灰度直方图已经统计完成

if (end_cnt_row == 1'b1) begin

state <= GET_HISTO;

end

else begin

state <= CALCULATE;

end

end

GET_HISTO : begin

//将RAM中的直方图数据全部读出

if (end_cnt_get == 1'b1) begin

state <= IDLE;

end

else begin

state <= GET_HISTO;

end

end

default : begin

state <= IDLE;

end

endcase

end

end

//----------------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

//==========================================

//during the clear state

//==========================================

//----------------cnt_clear------------------

//用于清空RAM的计数器

always @(posedge clk) begin

if (rst == 1'b1) begin

cnt_clear <= 'd0;

end

else if (add_cnt_clear) begin

if(end_cnt_clear)

cnt_clear <= 'd0;

else

cnt_clear <= cnt_clear + 1'b1;

end

else begin

cnt_clear <= 'd0;

end

end

assign add_cnt_clear = state == CLEAR && wr_ram_en == 1'b1;

assign end_cnt_clear = add_cnt_clear &&cnt_clear == GRAY_LEVEL - 1;

//----------------clear_flag------------------

always @(posedge clk) begin

if (rst==1'b1) begin

clear_flag <= 1'b0;

end

else if (state == CLEAR ) begin

if (end_cnt_clear == 1'b1) begin

clear_flag <= 1'b0;

end

else begin

clear_flag <= 1'b1;

end

end

else begin

clear_flag <= 1'b0;

end

end

//==========================================

//during the calculate state

//==========================================

//----------------delay------------------

always @(posedge clk) begin

if (rst==1'b1) begin

data_vld_dd0 <= 'd0;

data_vld_dd1 <= 'd0;

pi_data_dd0 <= 'd0;

pi_data_dd1 <= 'd0;

end

else begin

data_vld_dd0 <= pi_data_vld;

data_vld_dd1 <= data_vld_dd0;

pi_data_dd0 <= pi_data;

pi_data_dd1 <= pi_data_dd0;

end

end

//----------------cal_pixle------------------

always @(posedge clk) begin

if (rst==1'b1) begin

cal_pixle <= 'd1;

end

else if (state == CALCULATE && data_vld_dd0 == 1'b1 ) begin

//相邻两个像素点的值不同,统计值回到1

if (pi_data != pi_data_dd0 ) begin

cal_pixle <= 'd1;

end

//一行图形数据统计结束

else if (pi_data_vld == 1'b0 ) begin

cal_pixle <= 'd1;

end

//相邻两个像素点的值相同

else if (pi_data == pi_data_dd0) begin

cal_pixle <= cal_pixle + 1'b1;

end

end

else begin

cal_pixle <= 'd1;

end

end

//----------------cal_value------------------

//写入RAM的数据

always @(posedge clk) begin

if (rst==1'b1) begin

cal_value <= 'd0;

cal_value_vld <= 1'b0;

end

else if (state == CALCULATE ) begin

//相邻两个像素值不同,将当前统计结果写入

if (pi_data != pi_data_dd0 && data_vld_dd0 == 1'b1) begin

//从RAM中读出的数据,有一拍的延时,这里保证了数据对齐

cal_value <= rd_ram_data + cal_pixle;

cal_value_vld <= 1'b1;

end

//一行图像统计结束,将当前结果写入

else if(pi_data_vld == 1'b0 && data_vld_dd0 == 1'b1)begin

cal_value <= rd_ram_data + cal_pixle;

cal_value_vld <= 1'b1;

end

else begin

cal_value <= 'd0;

cal_value_vld <= 1'b0;

end

end

else begin

cal_value <= 'd0;

cal_value_vld <= 1'b0;

end

end

//----------------cal_wr_ram_addr/cal_rd_ram_addr------------------

assign cal_wr_ram_addr = pi_data_dd1; //写入数据RAM的地址

assign cal_rd_ram_addr = pi_data;//读出数据RAM的地址

//----------------cal_one_row_done------------------

always @(posedge clk) begin

if (rst==1'b1) begin

cal_one_row_done <= 1'b0;

end

//一行图像统计完成

else if (state == CALCULATE && pi_data_vld == 1'b0 && data_vld_dd0 == 1'b1) begin

cal_one_row_done <= 1'b1;

end

else begin

cal_one_row_done <= 1'b0;

end

end

//----------------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 = cal_one_row_done == 1'b1;

assign end_cnt_row = add_cnt_row &&cnt_row == IMG_HEIGHT - 1;

//==========================================

//during get histogram data state

//==========================================

//----------------get_data_flag------------------

always @(posedge clk) begin

if (rst==1'b1) begin

get_data_flag <= 1'b0;

end

else if (state == GET_HISTO) begin

if (end_cnt_get == 1'b1) begin

get_data_flag <= 1'b0;

end

else begin

get_data_flag <= 1'b1;

end

end

else begin

get_data_flag <= 1'b0;

end

end

//----------------cnt_get------------------

always @(posedge clk) begin

if (rst == 1'b1) begin

cnt_get <= 'd0;

end

else if (add_cnt_get) begin

if(end_cnt_get)

cnt_get <= 'd0;

else

cnt_get <= cnt_get + 1'b1;

end

else begin

cnt_get <= 'd0;

end

end

assign add_cnt_get = get_data_flag == 1'b1;

assign end_cnt_get = add_cnt_get &&cnt_get == GRAY_LEVEL - 1;

//----------------histo_data_vld------------------

always @(posedge clk) begin

if (rst==1'b1) begin

histo_data_vld <= 1'b0;

end

else begin

histo_data_vld <= get_data_flag;

end

end

assign histo_data = (histo_data_vld) ? rd_ram_data : 'd0 ;

//==========================================

//signals that related to Block RAM

//==========================================

histogram_ram inst_bram_histo (

.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

);

//----------------wr_ram_addr,wr_ram_data,wr_ram_en------------------

always @(*) begin

if (state == CLEAR) begin

wr_ram_addr = cnt_clear;

wr_ram_en = clear_flag;

wr_ram_data = 'd0;

end

else if (state == CALCULATE) begin

wr_ram_addr = cal_wr_ram_addr;

wr_ram_en = cal_value_vld;

wr_ram_data = cal_value;

end

else begin

wr_ram_addr = 'd0;

wr_ram_en = 1'b0;

wr_ram_data = 'd0;

end

end

//----------------rd_ram_addr------------------

always @(*) begin

if (state == CALCULATE) begin

rd_ram_addr = cal_rd_ram_addr;

end

else if (state == GET_HISTO) begin

rd_ram_addr = cnt_get;

end

else begin

rd_ram_addr = 'd0;

end

end

endmodule

仿真验证

由于只是算法的一个验证,我并不想使用太多的外部资源,使用片上的存储资源即可。一个2562568bit大小的图像并不会占用多少资源。图像大小是256*256的灰度图,在matlab中完成直方图的统计,是十分简单的,只需使用imhist这个函数即可。

直方图

直方图部分统计结果如下:

直方图

在modelsim中,对前面所设计的模块进行仿真。仿真的结果如下:

直方图

可以看到仿真的直方图统计结果与matlab中的仿真结果相比一致。在Modelsim的Memory List中,也可以看到一帧图像统计完成后,RAM中的结果,从结果中可以看到统计结果和matlab一致。

直方图

 

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分