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来完成直方图的统计需要注意以下几点:

  1. 对一幅图像进行统计,必须等到当前的图像“流过”后才能完成。这使得采用FPGA相较于其他方式并没有太大的优势。
  2. 在统计的过程中,需要对已经统计的像素的统计值进行缓存。
  3. 在下一帧图像来临的时候,需要将RAM中的数据清空。

设计统计模块

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

  1. 新的一帧图像来临是,需要将上一帧图像的直方图统计结果清零。
  2. 在新一帧图像数据有效时,进行统计
  3. 一帧图像数据统计完成后,将统计结果读出,并将统计结果输出到外部。

状态机设计:

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

CLEAR状态:

  下图是CLEAR状态下的时序设计图,拉高一个clear_flag信号,向RAM中写入0,将上一帧图像的统计结果清零。
FPGA图像处理基础----直方图统计_第2张图片

CALCULATE状态:

  统计状态下完成的任务是最复杂的,由于在图像数据流来领的时候,常常会遇到相邻几个像素点的灰度值是相同的,因此可以将这些点进行统计,然后在将统计值写入到RAM中,将小对RAM的读写操作。
  下面的时序图是一个典型的统计时序设计图,基本包括了图像流入时的像素状态。
  在统计时,主要是来比较当前的像素点和上一个像素点的值是否相同,若相同则像素统计值cal_pixel就会加一,直到相邻两像素值不同或者一行图像结束时,停止加一,并且将当前统计结果cal_pixel和RAM中已经缓存的统计值进行累加,重新写入到RAM中(也即wr_ram_data),同时需要使能RAM的写操作。wr_ram_en。写入RAM的地址,其实就是当前的灰度值。在这之中,需要注意从RAM中读出数据具有1或者2个时钟周期的Latency(根据IP核设置有关。
  在FPGA的直方图统计中,该部分是最重要的。完成了该时序图,也就基本上完成了统计电路。
FPGA图像处理基础----直方图统计_第3张图片

GET_HISTO状态:

FPGA图像处理基础----直方图统计_第4张图片
  该状态下,就是完成对直方图的统计结果的读出。

程序设计

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


/*============================================
#
# Author: Wcc - [email protected]
#
# QQ : 1530604142
#
# Last modified: 2020-07-06 19:48
#
# Filename: calculate_histogram.v
#
# Description: 
#
============================================*/
`timescale 1ns / 1ps
module calculate_histogram(
	input 	wire 			clk 		,
	input	wire 			rst 		,
	input 	wire 			pi_hsync	,
	input	wire 			pi_vsync	,
	input	wire 			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 	;
wire			add_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这个函数即可。
FPGA图像处理基础----直方图统计_第5张图片
直方图部分统计结果如下:

FPGA图像处理基础----直方图统计_第6张图片
  在modelsim中,对前面所设计的模块进行仿真。仿真的结果如下:
在这里插入图片描述
  可以看到仿真的直方图统计结果与matlab中的仿真结果相比一致。在Modelsim的Memory List中,也可以看到一帧图像统计完成后,RAM中的结果,从结果中可以看到统计结果和matlab一致。
FPGA图像处理基础----直方图统计_第7张图片


参考:《基于FPGA的数字图像处理》牟新刚

你可能感兴趣的:(FPGA图像处理,FPGA,直方图统计)