直方圖統(tǒng)計(jì)的原理
直方圖統(tǒng)計(jì)從數(shù)學(xué)上來說,是對(duì)圖像中的像素點(diǎn)進(jìn)行統(tǒng)計(jì)。圖像直方圖統(tǒng)計(jì)常用于統(tǒng)計(jì)灰度圖像,表示圖像中各個(gè)灰度級(jí)出現(xiàn)的次數(shù)或者概率。統(tǒng)計(jì)直方圖的實(shí)現(xiàn)采用C/C++或者其他高級(jí)語言實(shí)現(xiàn)十分簡單,單采用FPGA來實(shí)現(xiàn)直方圖的統(tǒng)計(jì)就稍顯麻煩。若使用Xilinx和Altera的FPGA芯片,可以使用HLS來進(jìn)行圖像的加速處理。但這暫時(shí)不是我的重點(diǎn)。
用C語言實(shí)現(xiàn)直方圖統(tǒng)計(jì):unsigned int histoBuffer[256];
for(int idxCol = 0; idxCol < imageWidth; idxCol ++)
{
for(int idxRow = 0; idxRow < imageHeight; idxRow ++)
{
histoBuffer[image[idxRow * imageWidth + idxCol]] ++;
}
}
基于FPGA實(shí)現(xiàn)圖像直方圖
在前面可以看到基于C/C++或者其他高級(jí)語言實(shí)現(xiàn)直方圖統(tǒng)計(jì)十分簡單。但是在FPGA中,需要設(shè)計(jì)具體的時(shí)序和電路才能正確地將直方圖進(jìn)行統(tǒng)計(jì)。
直方圖統(tǒng)計(jì)的注意點(diǎn)
使用FPGA來完成直方圖的統(tǒng)計(jì)需要注意以下幾點(diǎn):
對(duì)一幅圖像進(jìn)行統(tǒng)計(jì),必須等到當(dāng)前的圖像“流過”后才能完成。這使得采用FPGA相較于其他方式并沒有太大的優(yōu)勢(shì)。
在統(tǒng)計(jì)的過程中,需要對(duì)已經(jīng)統(tǒng)計(jì)的像素的統(tǒng)計(jì)值進(jìn)行緩存。
在下一幀圖像來臨的時(shí)候,需要將RAM中的數(shù)據(jù)清空。
設(shè)計(jì)統(tǒng)計(jì)模塊
根據(jù)上面的要點(diǎn),在設(shè)計(jì)直方圖統(tǒng)計(jì)電路的時(shí)候可以按照如下思路來進(jìn)行:
新的一幀圖像來臨是,需要將上一幀圖像的直方圖統(tǒng)計(jì)結(jié)果清零。
在新一幀圖像數(shù)據(jù)有效時(shí),進(jìn)行統(tǒng)計(jì)
一幀圖像數(shù)據(jù)統(tǒng)計(jì)完成后,將統(tǒng)計(jì)結(jié)果讀出,并將統(tǒng)計(jì)結(jié)果輸出到外部。
狀態(tài)機(jī)設(shè)計(jì):
在復(fù)位或空閑狀態(tài)下,系統(tǒng)處于IDLE狀態(tài),當(dāng)檢測(cè)到新一幀圖像(vsync信號(hào)的上升沿)時(shí),狀態(tài)跳轉(zhuǎn)到CLEAR狀態(tài),清空RAM中保存的上一幀圖像的數(shù)據(jù)。當(dāng)RAM中的圖像數(shù)據(jù)清空完成后,進(jìn)入到直方圖統(tǒng)計(jì)狀態(tài)CALCULATE,在該狀態(tài)下進(jìn)行直方圖的統(tǒng)計(jì)當(dāng)一幀圖像統(tǒng)計(jì)完成后,將本幀圖像的統(tǒng)計(jì)結(jié)果輸出,也即GET_HISTO狀態(tài)。
CLEAR狀態(tài):
下圖是CLEAR狀態(tài)下的時(shí)序設(shè)計(jì)圖,拉高一個(gè)clear_flag信號(hào),向RAM中寫入0,將上一幀圖像的統(tǒng)計(jì)結(jié)果清零。
CALCULATE狀態(tài):
統(tǒng)計(jì)狀態(tài)下完成的任務(wù)是最復(fù)雜的,由于在圖像數(shù)據(jù)流來領(lǐng)的時(shí)候,常常會(huì)遇到相鄰幾個(gè)像素點(diǎn)的灰度值是相同的,因此可以將這些點(diǎn)進(jìn)行統(tǒng)計(jì),然后在將統(tǒng)計(jì)值寫入到RAM中,將小對(duì)RAM的讀寫操作。
下面的時(shí)序圖是一個(gè)典型的統(tǒng)計(jì)時(shí)序設(shè)計(jì)圖,基本包括了圖像流入時(shí)的像素狀態(tài)。
在統(tǒng)計(jì)時(shí),主要是來比較當(dāng)前的像素點(diǎn)和上一個(gè)像素點(diǎn)的值是否相同,若相同則像素統(tǒng)計(jì)值cal_pixel就會(huì)加一,直到相鄰兩像素值不同或者一行圖像結(jié)束時(shí),停止加一,并且將當(dāng)前統(tǒng)計(jì)結(jié)果cal_pixel和RAM中已經(jīng)緩存的統(tǒng)計(jì)值進(jìn)行累加,重新寫入到RAM中(也即wr_ram_data),同時(shí)需要使能RAM的寫操作。wr_ram_en。寫入RAM的地址,其實(shí)就是當(dāng)前的灰度值。在這之中,需要注意從RAM中讀出數(shù)據(jù)具有1或者2個(gè)時(shí)鐘周期的Latency(根據(jù)IP核設(shè)置有關(guān)。
在FPGA的直方圖統(tǒng)計(jì)中,該部分是最重要的。完成了該時(shí)序圖,也就基本上完成了統(tǒng)計(jì)電路。
GET_HISTO狀態(tài):
該狀態(tài)下,就是完成對(duì)直方圖的統(tǒng)計(jì)結(jié)果的讀出。
程序設(shè)計(jì)
FPGA完成直方圖的設(shè)計(jì),其實(shí)就是上面的三個(gè)時(shí)序圖的設(shè)計(jì),完成了上述三個(gè)時(shí)序圖后,就能夠直方圖統(tǒng)計(jì)模塊。下面的這個(gè)模塊完成的是一個(gè)256*256大小的灰度圖的直方圖統(tǒng)計(jì),若需要對(duì)其他大小的圖像進(jìn)行直方圖統(tǒng)計(jì),只需修改其中的參數(shù)即可。其實(shí)對(duì)于我的設(shè)計(jì),每一行的像素個(gè)數(shù)是由上游模塊確定的,在本模塊中,只需指定圖像的高度即可,指定高度,也僅僅是為了將直方圖從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;//灰度級(jí)
parameter IDLE = 4'b0001;//空閑狀態(tài)
parameter CLEAR= 4'b0010;//清空RAM中數(shù)據(jù)狀態(tài)
parameter CALCULATE = 4'b0100;//統(tǒng)計(jì)圖像直方圖狀態(tài)
parameter GET_HISTO = 4'b1000;//輸出直方圖
//==========================================
//internal siganls
//==========================================
reg [3:0]state ;//狀態(tài)寄存器
reg [1:0]vsync_dd;//場(chǎng)同步信號(hào)寄存
//==========================================
//清空RAM階段
//==========================================
reg [8:0]cnt_clear ;
wireadd_cnt_clear;
wire end_cnt_clear;
reg clear_flag;//清空RAM指示信號(hào)
//==========================================
//統(tǒng)計(jì)直方圖階段
//==========================================
reg [12:0]cnt_row ;
wire add_cnt_row ;
wire end_cnt_row;
reg data_vld_dd0;//數(shù)據(jù)有效延時(shí)信號(hào)
reg data_vld_dd1;//數(shù)據(jù)有效延時(shí)信號(hào)
reg [7:0]pi_data_dd0;//有效數(shù)據(jù)延時(shí)
reg [7:0]pi_data_dd1;//有效數(shù)據(jù)延時(shí)
reg [31:0]cal_pixle;//相同的像素統(tǒng)計(jì)值
reg [31:0]cal_value ;//寫入RAM的統(tǒng)計(jì)值
reg cal_value_vld;//寫入RAM數(shù)據(jù)有效信號(hào)
reg cal_one_row_done;//統(tǒng)計(jì)一行圖像數(shù)據(jù)結(jié)束
wire [7:0]cal_wr_ram_addr;//統(tǒng)計(jì)狀態(tài)下寫RAM的地址
wire [7:0]cal_rd_ram_addr;//統(tǒng)計(jì)狀態(tài)下讀RAM的地址
//==========================================
//讀出數(shù)據(jù)階段
//==========================================
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使能信號(hào)
reg [7:0]wr_ram_addr;//寫RAM地址
reg [31:0]wr_ram_data ;//寫入RAM的數(shù)據(jù)
reg [7:0]rd_ram_addr ;//讀RAM的地址
wire[31:0]rd_ram_data;//從RAM中讀出的數(shù)據(jù)
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
//檢測(cè)到新的一幀圖像
if (vsync_dd[0] == 1'b1 && vsync_dd[1] == 1'b0) begin
state <= CLEAR;
end
else begin
state <= IDLE;
end
end
CLEAR : begin
//當(dāng)前RAM中的數(shù)據(jù)已經(jīng)清空
if (end_cnt_clear == 1'b1) begin
state <= CALCULATE;
end
else begin
state <= CLEAR;
end
end
CALCULATE : begin
//當(dāng)前一幅圖像數(shù)據(jù)的灰度直方圖已經(jīng)統(tǒng)計(jì)完成
if (end_cnt_row == 1'b1) begin
state <= GET_HISTO;
end
else begin
state <= CALCULATE;
end
end
GET_HISTO : begin
//將RAM中的直方圖數(shù)據(jù)全部讀出
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------------------
//檢測(cè)一幀圖像
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的計(jì)數(shù)器
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
//相鄰兩個(gè)像素點(diǎn)的值不同,統(tǒng)計(jì)值回到1
if (pi_data != pi_data_dd0 ) begin
cal_pixle <= 'd1;
end
//一行圖形數(shù)據(jù)統(tǒng)計(jì)結(jié)束
else if (pi_data_vld == 1'b0 ) begin
cal_pixle <= 'd1;
end
//相鄰兩個(gè)像素點(diǎn)的值相同
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的數(shù)據(jù)
always @(posedge clk) begin
if (rst==1'b1) begin
cal_value <= 'd0;
cal_value_vld <= 1'b0;
end
else if (state == CALCULATE ) begin
//相鄰兩個(gè)像素值不同,將當(dāng)前統(tǒng)計(jì)結(jié)果寫入
if (pi_data != pi_data_dd0 && data_vld_dd0 == 1'b1) begin
//從RAM中讀出的數(shù)據(jù),有一拍的延時(shí),這里保證了數(shù)據(jù)對(duì)齊
cal_value <= rd_ram_data + cal_pixle;
cal_value_vld <= 1'b1;
end
//一行圖像統(tǒng)計(jì)結(jié)束,將當(dāng)前結(jié)果寫入
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; //寫入數(shù)據(jù)RAM的地址
assign cal_rd_ram_addr = pi_data;//讀出數(shù)據(jù)RAM的地址
//----------------cal_one_row_done------------------
always @(posedge clk) begin
if (rst==1'b1) begin
cal_one_row_done <= 1'b0;
end
//一行圖像統(tǒng)計(jì)完成
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
仿真驗(yàn)證
由于只是算法的一個(gè)驗(yàn)證,我并不想使用太多的外部資源,使用片上的存儲(chǔ)資源即可。一個(gè)2562568bit大小的圖像并不會(huì)占用多少資源。圖像大小是256*256的灰度圖,在matlab中完成直方圖的統(tǒng)計(jì),是十分簡單的,只需使用imhist這個(gè)函數(shù)即可。
直方圖部分統(tǒng)計(jì)結(jié)果如下:
在modelsim中,對(duì)前面所設(shè)計(jì)的模塊進(jìn)行仿真。仿真的結(jié)果如下:
可以看到仿真的直方圖統(tǒng)計(jì)結(jié)果與matlab中的仿真結(jié)果相比一致。在Modelsim的Memory List中,也可以看到一幀圖像統(tǒng)計(jì)完成后,RAM中的結(jié)果,從結(jié)果中可以看到統(tǒng)計(jì)結(jié)果和matlab一致。
-
FPGA
+關(guān)注
關(guān)注
1629文章
21734瀏覽量
603117 -
直方圖
+關(guān)注
關(guān)注
0文章
21瀏覽量
7883
原文標(biāo)題:FPGA圖像處理基礎(chǔ)----直方圖統(tǒng)計(jì)
文章出處:【微信號(hào):gh_9d70b445f494,微信公眾號(hào):FPGA設(shè)計(jì)論壇】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論