直方圖統計的原理
直方圖統計從數學上來說,是對圖像中的像素點進行統計。圖像直方圖統計常用于統計灰度圖像,表示圖像中各個灰度級出現的次數或者概率。統計直方圖的實現采用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一致。
-
FPGA
+關注
關注
1629文章
21753瀏覽量
604159 -
直方圖
+關注
關注
0文章
21瀏覽量
7903
原文標題:FPGA圖像處理基礎----直方圖統計
文章出處:【微信號:gh_9d70b445f494,微信公眾號:FPGA設計論壇】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論