基于FPGA的图像卷积(or 滤波?)

图像的卷积和滤波在某种程度上很类似,在实现的细节上存在一些区别。滤波一般需要在图像周围补0,将滤波掩膜划过整副图像,计算每个像素点的滤波结果(可以理解为补零之后图像在stride为0下的卷积操作)。
而卷积操作通常需要对卷积核进行翻转,同时会改变图像大小(除非kernel==1)。

给出的代码分三个部分:
1.测试图像(128*128)的导入;

2.图像的padding(补零操作)。

3.图像的卷积操作。这里不再对卷积和滤波操作的实现进行区分了。

初步介绍;
卷积核:3*3,随便使用了一个高斯滤波算子;【1 2 1 ;2 4 2; 1 2 1】;
pad_size =1; 即图像外围补一圈0,卷积后图像数据大小不变;
测试数据使用的是1 to 16384的递增数,便于验证卷积后的数据;

图像的padding的核心:构建一个状态机,对第一行和尾行补(pad_size + img_size_row)个0,中间行
前后分别补(pad_size)个0.

图像卷积操作的核心是:巧妙使用移位寄存器这个ip核,当然作者在前期实现的时候,也用过循环取数的方法,对图像数据进行卷积取数。

代码中图像外围补充的是55;便于验证而已;

在这里插入代码片
`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2020/05/15 14:53:57
// Design Name: 
// Module Name: conv
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//

//一副图像的基本卷积操作;

module conv(
	
	input				clk_50m        ,                   //基本时钟50M;
    
	output   [15:0]		img_out			                          
	);
	
wire  [17:0]  net_weights[15:0]   ;               			//定义权重系数;


wire 		locked ;
wire        rst_n ;

wire        clk_100m,clk_200m;
	
parameter      img_size_col = 128;                       	//图像的行与列

parameter      img_size_row = 128;

parameter      imag_size = 16'd16384;  							//128*128

parameter    pad_size = 1;


assign            rst_n = locked;                     		//低电平有效;



//wire  [15:0] Q1,Q2,Q3,Q4,Q5,Q6,Q7,Q8,Q9;                       //移位寄存器的输出;
//wire  [15:0] Q1_1,Q2_1,Q3_1,Q4_1,Q5_1,Q6_1,Q7_1,Q8_1,Q9_1;     //移位寄存器的输出;

//wire    Q_en,Q_en1;

reg      [19:0]		    cnt           	;
reg      [13:0]         addra		  	;
reg                     wea           	;

reg						img_over		;
reg						reg1_fram_sys,reg2_fram_sys,reg3_fram_sys;

reg						start_c			;

reg   [15:0]			pixel_cnt		;

reg						data_req1		;
reg						data_req2		;

reg	  [13:0]			data_raddr		;

reg  [8:0]  row_cnt;
reg  [8:0]  cnt_zero1;
reg  [8:0]  cnt_zero2;
reg  [8:0]  cnt_zero3;
reg  [2:0]	state ;
wire         valid_en ;
reg          reg1_valid_en,reg2_valid_en,reg3_valid_en;

reg			img_pen , reg_img_pen ;

reg  [13:0] imag_paddrb ;

wire  [15:0] img_pdata;

reg  [15:0]	pad_out;


parameter    	IDLE=3'd1   ,
				S1  =3'd2	,
				S2  =3'd3	;



//定义卷积核   {	1  2  1	;
//					2  4  2	;
//					1  2  1	            }

assign net_weights[0] = 1;
assign net_weights[1] = 2;
assign net_weights[2] = 1;

assign net_weights[3] = 2;
assign net_weights[4] = 4;
assign net_weights[5] = 2;

assign net_weights[6] = 1;
assign net_weights[7] = 2;
assign net_weights[8] = 1;




clk_gen i1
 (
 // Clock in ports
  .clk_in1(clk_50m),      // input clk_in1
  // Clock out ports
  .clk_100m(clk_100m),     // output clk_100m
  .clk_200m(clk_200m),     // output clk_200m
  // Status and control signals
  .locked(locked));      // output locked   //low to high
	
	
/******************************************************************///图像数据的导入与缓存;标准图像128*128;
//测试数据;
always@(posedge clk_100m)
	begin
		if(!rst_n)                                      //循环计数;
			cnt <= 20'd0;
		else
			begin
				if(cnt==20'd200000)
					cnt <= 20'd0;
				else
					cnt <= cnt + 20'd1;
			end
	end
	
always@(posedge clk_100m)
	begin
		if(!rst_n)
			wea <= 1'b0;
		else if((cnt>=20'd1)&&(cnt<=20'd16384))
			wea <= 1'b1;
		else
			wea <= 1'b0;
	end

always@(posedge clk_100m)
	begin
		if(!rst_n)
			addra <= 14'd0;
		else if((cnt>=20'd2)&&(cnt<=20'd16385))
			addra <= addra + 20'd1;
		else	
			addra <= 14'd0;
	end
	
always @(posedge clk_100m)                               //提供mod通知;
	begin
		if(!rst_n)		
			img_over <= 1'b0;
		else if(addra>=16'd16300)
			img_over <= 1'b1;
		else	
			img_over <= 1'b0;	
	end
	
always@(posedge clk_100m)                             //检测数据开始的rising沿信号;
	begin
		reg1_fram_sys  <= img_over        ;
		reg2_fram_sys  <= reg1_fram_sys   ;
		reg3_fram_sys  <= reg2_fram_sys   ;
	end	

always@(posedge clk_100m)
	begin
		if(!rst_n)
			start_c <= 1'b0;
		else if(reg2_fram_sys&~reg3_fram_sys)     
			start_c <= 1'b1;
		else if(pixel_cnt==16'd16383)              //一副图的像素计数                     
			start_c <= 1'b0;
		else	
			start_c <= start_c;                   //hold register
	end
	
always@(posedge clk_100m)
	begin
		if(!rst_n)
			pixel_cnt <= 16'd0;
		else if(start_c)                            //像素计数;0——16383;
			pixel_cnt <= pixel_cnt + 16'd1;
		else
			pixel_cnt <= 16'd0;
	end
	
always@(posedge clk_100m)
	begin
		if(!rst_n)
			data_req1 <= 1'b0;
		else if(start_c)
			data_req1 <= 1'b1;
		else
			data_req1 <= 1'b0;
	end
		
always@(posedge clk_100m)
	begin
		if(!rst_n)
			data_raddr <= 14'd0;
		else if(data_req1)
			data_raddr <= data_raddr + 14'd1;
		else
			data_raddr <= 14'd0;
	end

always@(posedge clk_100m)
	data_req2 <= data_req1;
	
		
image_in_ram m1 (                                              	  	 //初始化一副标准的测试图像128*128;
  .clka(clk_100m),    					 // input wire clka
  .wea(wea),                            // input wire [0 : 0] wea
  .addra(addra),                              // input wire [13 : 0] addra
  .dina({2'b0,addra}),                               // input wire [15 : 0] dina
                                       
  .clkb(clk_100m),                         // input wire clkb             
  .enb(data_req1||data_req2),                           // input wire enb
  .addrb(data_raddr),                       // input wire [13 : 0] addrb
  .doutb(img_out)                         // output wire [15 : 0] doutb
);














/***************************************************************///取数+图像的算法卷积;


//思路1:采用shift_ram移位寄存器,对图像数据进行缓存读取,按照计算模式来说,卷积核的维度决定移位寄存器的深度,比如3*3的深度,128*128的图像数据,则寄存器深度保持为3*128;

//思路2:采用循环读数模式,循环读取3*128个图像数据;

wire [8:0]   A[8:0] ;

wire [15:0] Q[8:0] ;

assign  A[0] = 9'd0;
assign  A[1] = 9'd1;
assign  A[2] = 9'd2;

assign  A[3] = 9'd128;
assign  A[4] = 9'd129;
assign  A[5] = 9'd130;

assign  A[6] = 9'd256;
assign  A[7] = 9'd257;
assign  A[8] = 9'd258;

genvar i ;                      		  //提高程序的凝练度;
generate 
	for(i=0;i<=8;i=i+1)
		begin
			shift_ram1 s1 (
			  .A(A[i]),     			 // input wire [8 : 0] A
			  .D(img_out),      		// input wire [15 : 0] D
			  .CLK(clk_100m),  			// input wire CLK
			  .Q(Q[i])      				// output wire [15 : 0] Q
			);
		end
endgenerate








//************************************************************//卷积的padding操作; 在外围补零;

//例如128*128的图在padding之后------》》》130*130=16900;



image_in_ram m3 (                                              	  	 //初始化一副标准的测试图像128*128;
  .clka(clk_100m),    					 // input wire clka
  .wea(wea),                            // input wire [0 : 0] wea
  .addra(addra),                              // input wire [13 : 0] addra
  .dina({2'b0,addra}),                               // input wire [15 : 0] dina
                                       
  .clkb(clk_100m),                         // input wire clkb             
  .enb(img_pen),                           // input wire enb
  .addrb(imag_paddrb),                       // input wire [13 : 0] addrb
  .doutb(img_pdata)                         // output wire [15 : 0] doutb
);


			

always@(posedge clk_100m)                      //使用状态机padding;
	begin
		if(!rst_n)
			begin
				row_cnt <= 0;
				state <= IDLE;
				cnt_zero1 <= 0;
				cnt_zero2 <= 0;
				cnt_zero3 <= 0;
			end
		else
			case(state)
				IDLE:
					begin
						row_cnt <= 0;
						if(reg2_fram_sys&~reg3_fram_sys)  //
							state <= S1;
						else
							state <= state;
					end				
				S1:	
					begin
						if(row_cnt==0)  						//如果是第一行;
							begin
								state <= state;
								begin
									//if(cnt_zero1==129)
									if(cnt_zero1==(img_size_row + 2*pad_size -1))             //      0  0      0  	 0 	   0 	    .......  0      0      0      0    0; 
										begin												  //      0  0      1 	 2 	   3 	    ....... 124    125    126    127   0; 
											row_cnt <= row_cnt + 1;							  //      0 128    129	130	 131 	    ....... 252    253    254    255   0;	
											cnt_zero1 <= 0;                                   //      0 256    257	258	 259 	    ....... 380    381    382    383   0;
										end													 //											.
									else													//											.
										cnt_zero1 <= cnt_zero1 + 1;							  //      0 16256 16257 16258 16259 	.......	16380  16381  16382  16383 0;	
								end                                                           //      0    0    0     0     0       .......  0      0      0      0    0; 				
							end
						else if(row_cnt==(img_size_row + 2*pad_size -1))				 //最后一行;129
							begin
								if(cnt_zero2==(img_size_row + 2*pad_size -1))      		//129  
									begin
										row_cnt <= 0;
										cnt_zero2 <= 0;
										state <= S2;
									end
								else
									cnt_zero2 <= cnt_zero2 + 1;
							end
						else                   //(0,130)以内;
							begin
								state <= state;
								if(cnt_zero3==(img_size_row + 2*pad_size -1))         //129
									begin
										cnt_zero3 <= 0;
										row_cnt <= row_cnt + 1;
									end
								else
									cnt_zero3 <= cnt_zero3 + 1;         //在这个期间给出图像数据的读使能;
							end
					end
					
				S2: state <= IDLE;
					
				default;
				
			endcase
	end

always@(posedge clk_100m) 
	begin
		if(!rst_n)
			img_pen <= 1'b0;
		else if((cnt_zero3 <= img_size_row)&&(cnt_zero3>=pad_size))
			img_pen <= 1'b1;
		else
			img_pen <= 1'b0;
	end

always@(posedge clk_100m) 
	begin
		if(!rst_n)
			imag_paddrb <= 14'd0;
		else if(img_pen)
			imag_paddrb <= imag_paddrb + 1;
		else if(state == S2)
			imag_paddrb <= 14'd0;
		else	
			imag_paddrb <= imag_paddrb;
	end
	
always@(posedge clk_100m)
	reg_img_pen <= img_pen ;
	
always@(posedge clk_100m)
	begin
		reg1_valid_en <= valid_en;
		reg2_valid_en <= reg1_valid_en;
		reg3_valid_en <= reg2_valid_en;
	end
	
assign  valid_en = (state==S1)? 1'b1:1'b0;
	
always@(posedge clk_100m)                   //padding的主要过程;
	begin
		if(!rst_n)
			pad_out <= 16'd0;
		else if(reg2_valid_en)
			if(reg_img_pen)
				pad_out <= img_pdata;
			else
				pad_out <= 16'd55; 
		else
			pad_out <= 16'd0;
	
	end

wire [8:0]   A1[8:0] ;

wire [15:0]  Q1[8:0] ;

wire [15:0]  M[8:0] ;

assign  A1[0] = 9'd0;
assign  A1[1] = 9'd1;
assign  A1[2] = 9'd2;
		 
assign  A1[3] = 9'd130;
assign  A1[4] = 9'd131;
assign  A1[5] = 9'd132;
		 
assign  A1[6] = 9'd260;
assign  A1[7] = 9'd261;
assign  A1[8] = 9'd262;	


genvar j;                      		     //提高程序的凝练度;
generate 
	for(j=0;j<=8;j=j+1)
		begin
			shift_ram1 s2 (
			  .A(A1[j]),     			 // input wire [8 : 0] A
			  .D(pad_out),      		// input wire [15 : 0] D
			  .CLK(clk_100m),  			// input wire CLK
			  .Q(Q1[j])      				// output wire [15 : 0] Q
			);
		end
endgenerate


//MULT 乘法器操作;
genvar k;
generate
	for(k=0;k<=8;k=k+1)
		begin
			assign M[k] = Q1[k]*net_weights[k];            
		end
endgenerate

integer n;
reg  [15:0] M_accumulation[9:0];

always@(*)       //累加;     这样累加操作会不会造成时序拥挤?    M_accumulation1与M_accumulation是一样的,都在组合逻辑下完成数值的运算;
	begin
		if(!rst_n)
			begin
				for(n=0;n<=8;n=n+1)
					M_accumulation[n] = 16'd0;
			end
		else
			begin
				for(n=0;n<=8;n=n+1)
					begin
						M_accumulation[n+1] =  M_accumulation[n] + M[n];
					end
			end
	end
	
wire [15:0]  	M_accumulation1;
reg [15:0]  	M_accumulation2;
reg [15:0]     M_accumulation2_part1,M_accumulation2_part2,M_accumulation2_part3;

assign  M_accumulation1 = M[0]+M[1]+M[2]+M[3]+M[4]+M[5]+M[6]+M[7]+M[8];

always@(posedge clk_100m)                   //优化FPGA时序
	begin
		M_accumulation2_part1 <= (M[0]+M[1])+M[2];
		M_accumulation2_part2 <= (M[3]+M[4])+M[5];
		M_accumulation2_part3 <= (M[6]+M[7])+M[8];
		M_accumulation2       <= (M_accumulation2_part1+M_accumulation2_part2+M_accumulation2_part3);
	end
	
endmodule









Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐