基于FPGA的图像卷积(滤波)
基于FPGA的图像卷积(or 滤波?)图像的卷积和滤波在某种程度上很类似,在实现的细节上存在一些区别。滤波一般需要在图像周围补0,将滤波掩膜划过整副图像,计算每个像素点的滤波结果(可以理解为补零之后图像在stride为0下的卷积操作)。而卷积操作通常需要对卷积核进行翻转,同时会改变图像大小(除非kernel==1)。给出的代码分三个部分:1.测试图像(128*128)的导入;2.图像的paddin
基于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
更多推荐
所有评论(0)