【Zedboard】FPGA图像处理 基于ZYNQ完成图像 二值化 Verilog代码实现
目前的成像状态:
(一)、配置摄像头传感器并完成完成图像实时采集:
(a)先介绍摄像头配置的模块:
本项目中使用的摄像头传感器芯片是OV5620,硬件接口上:支持标准的SCCB接口,兼容I2C接口,支持PMOD双排接口,本项目中使用的即为PMOD_5M双排接口。帧率:7.5—120fps。输出信号是10位的RGB RAW格式的图像数据。
OV5620的系统框图如图1-1所示:
其Block Design模块如下图1-2所示:
按照OV5620数据手册,在配置摄像头模块中,采用的是I2C协议对OV5620的初始化寄存器进行配置。主要完成的配置信息如下:
该配置信息,完成了对OV5620数据手册所描述的对RGB三色数据的增益、AWE增益、图像对比度增幅、输出图像分辨率大小的配置。I2C时序控制图入下图1-4所示。
I2C_OV5620_Config模块的具体实现:
//
// Company:
// Engineer: Tan junfu
//
// Create Date: 2020/12/06 01:23:35
// Design Name:
module I2C_OV5620_Config ( // Host Side
iCLK,
iRST_N,
// I2C Side
I2C_SCLK,
I2C_SDAT );
// Host Side
input iCLK;
input iRST_N;
// I2C Side
output I2C_SCLK;
inout I2C_SDAT;
// Internal Registers/Wires
reg [15:0] mI2C_CLK_DIV;
reg [23:0] mI2C_DATA;
reg mI2C_CTRL_CLK;
reg mI2C_GO;
wire mI2C_END;
wire mI2C_ACK;
reg [15:0] LUT_DATA;
reg [5:0] LUT_INDEX;
reg [3:0] mSetup_ST;
// Clock Setting
parameter CLK_Freq = 50000000; // 50 MHz
parameter I2C_Freq = 50000; // 20 KHz
// LUT Data Number
parameter LUT_SIZE = 18;
/ I2C Control Clock
always@(posedge iCLK or negedge iRST_N)
begin
if(!iRST_N)
begin
mI2C_CTRL_CLK <= 0;
mI2C_CLK_DIV <= 0;
end
else
begin
if( mI2C_CLK_DIV < (CLK_Freq/I2C_Freq) )
mI2C_CLK_DIV <= mI2C_CLK_DIV+1;
else
begin
mI2C_CLK_DIV <= 0;
mI2C_CTRL_CLK <= ~mI2C_CTRL_CLK;
end
end
end
I2C_Controller u0 ( .CLOCK(mI2C_CTRL_CLK), // Controller Work Clock
.I2C_SCLK(I2C_SCLK), // I2C CLOCK
.I2C_SDAT(I2C_SDAT), // I2C DATA
.I2C_DATA(mI2C_DATA), // DATA:[SLAVE_ADDR,SUB_ADDR,DATA]
.GO(mI2C_GO), // GO transfor
.END(mI2C_END), // END transfor
.ACK(mI2C_ACK), // ACK
.RESET(iRST_N) );
// Config Control
always@(posedge mI2C_CTRL_CLK or negedge iRST_N)
begin
if(!iRST_N)
begin
LUT_INDEX <= 0;
mSetup_ST <= 0;
mI2C_GO <= 0;
end
else
begin
if(LUT_INDEX<LUT_SIZE)
begin
case(mSetup_ST)
0: begin
mI2C_DATA <= {8'h60,LUT_DATA};
mI2C_GO <= 1;
mSetup_ST <= 1;
end
1: begin
if(mI2C_END)
/*begin
if(!mI2C_ACK)
mSetup_ST <= 2;
else
mSetup_ST <= 0;
mI2C_GO <= 0;*/
begin
// mI2C_WR <= 0;
mI2C_GO <= 0;
if(~mI2C_ACK) //ACK ACTIVE
mSetup_ST <= 2; //INDEX ++
else
mSetup_ST <= 0; //Repeat Transfer
end
end
2: begin
LUT_INDEX <= LUT_INDEX+1;
mSetup_ST <= 0;
end
endcase
end
else
begin
// Config_Done <= 1'b1;
LUT_INDEX <= LUT_INDEX;
mSetup_ST <= 0;
mI2C_GO <= 0;
// mI2C_WR <= 0;
end
end
end
/ Config Data LUT //
always
begin
case(LUT_INDEX)
0 : LUT_DATA <= 16'h0000;
1 : LUT_DATA <= 16'h1140;
2 : LUT_DATA <= 16'h8107;//dsp
3 : LUT_DATA <= 16'hB8F0;//red Gain limit
4 : LUT_DATA <= 16'hB9F0;//Green Gain limit
5 : LUT_DATA <= 16'hBAF0;//blue Gain limit
6 : LUT_DATA <= 16'h1620;//Green Gain
7 : LUT_DATA <= 16'h0220;//Red Gain
8 : LUT_DATA <= 16'h0120;//Blue Gain
9 : LUT_DATA <= 16'h1240;//1280*960
10 : LUT_DATA <= 16'h0cc8;//1280*960
// 11 : LUT_DATA <= 16'h1712;//H Start
// 12 : LUT_DATA <= 16'h18b4;//H End
// 13 : LUT_DATA <= 16'h1901;//V Start
// 14 : LUT_DATA <= 16'h1af4;//V End
//9 : LUT_DATA <= 16'h1240;//1.3MEGA
default: LUT_DATA <= 16'dx ;
endcase
end
endmodule
(二)、采集模块的设计
Capture_prepare模块是图像预处理模块,主要是由于寄存器配置好后或系统复位时会有1ms的延时(来自OV5620数据手册),并且前10帧数据也是不能使用的。因此该模块完成了采集延时处理与数据整合输出有效并且能够正确使用的图像数据。
constant模块是一些实际可调参数,比如画面分辨率,图像采集区域的坐标配置等。
CCD_Capture模块则是数据采集的主模块,完成下OV5620传感器像素时序下对原数据的采集与帧转化,并且标记每一帧的地址,与之后的缓存模块进行交互。
//
// Company:
// Engineer: Tan junfu
//
// Create Date: 2020/12/06 00:23:35
// Design Name:
module CCD_Capture( oDATA,
oDVAL,
oX_Cont,
oY_Cont,
oTX_Cont,
oTY_Cont,
oFrame_Cont,
oSYNC,
iX_POS,
iY_POS,
iDATA,
iFVAL,
iLVAL,
iSTART,
iEND,
iCLK,
iRST_N,
ADDR
);
//--------------------------------------------------//
parameter DATA_SIZE = 10;
//--------------------------------------------------//
input [DATA_SIZE-1:0] iDATA;
input iFVAL;
input iLVAL;
input iSTART;
input iEND;
input iCLK;
input iRST_N;
input [15:0] iX_POS;
input [15:0] iY_POS;
output [DATA_SIZE-1:0] oDATA;
output [15:0] oTX_Cont;
output [15:0] oTY_Cont;
output [15:0] oX_Cont;
output [15:0] oY_Cont;
output [31:0] oFrame_Cont;
output oDVAL;
output oSYNC;
output reg [18:0] ADDR;
reg [15:0] mX_POS;
reg [15:0] mY_POS;
reg rSYNC;
reg [15:0] TX_Cont;
reg [15:0] TY_Cont;
reg [15:0] now_TX_Cont;
reg [15:0] now_TY_Cont;
reg Pre_FVAL;
reg Pre_LVAL;
reg mCCD_FVAL;
reg mCCD_LVAL;
reg mPOS_VAL;
reg [DATA_SIZE-1:0] mCCD_DATA;
reg [15:0] X_Cont;
reg [15:0] Y_Cont;
reg [31:0] Frame_Cont;
reg mSTART;
assign oX_Cont = X_Cont;
assign oY_Cont = Y_Cont;
assign oTX_Cont = TX_Cont;
assign oTY_Cont = TY_Cont;
assign oFrame_Cont = Frame_Cont;
assign oDATA = mCCD_DATA;
assign oDVAL = mCCD_FVAL&mCCD_LVAL&mPOS_VAL;
assign oSYNC = rSYNC;
always@(posedge iCLK or negedge iRST_N)
begin
if(!iRST_N)
mSTART <= 0;
else
begin
if(iSTART)
mSTART <= 1;
if(iEND)
mSTART <= 0;
end
end
reg [15:0] cnt;
always@(posedge iCLK or negedge iRST_N)
begin
if(!iRST_N)
begin
Pre_FVAL <= 0;
mCCD_FVAL <= 0;
mCCD_LVAL <= 0;
mCCD_DATA <= 0;
X_Cont <= 0;
Y_Cont <= 0;
TX_Cont <= 0;
TY_Cont <= 0;
//mX_POS <=iX_POS;
//mY_POS <=iY_POS;
end
else
begin
Pre_FVAL <= iFVAL;
Pre_LVAL <= iLVAL;
if( ({Pre_FVAL,iFVAL}==2'b01) && mSTART )
//if( ({Pre_FVAL,iFVAL}==2'b01))
begin
mCCD_FVAL <= 1;
rSYNC <= 1;
mX_POS <= iX_POS;
mY_POS <= iY_POS;
ADDR <= 0;
end
else if({Pre_FVAL,iFVAL}==2'b10)
begin
mCCD_FVAL <= 0;
rSYNC <= 0;
if(cnt>10)
begin
cnt<=0;
TX_Cont<= now_TX_Cont;
TY_Cont<= now_TY_Cont;
end
else cnt<=cnt+1;
end
else rSYNC <= 0;
mCCD_LVAL <= iLVAL;
mCCD_DATA <= iDATA;
if((X_Cont >= mX_POS) &&(X_Cont < mX_POS+640)&& (Y_Cont >= mY_POS) && (Y_Cont < mY_POS+480))
begin
mPOS_VAL <= 1;
ADDR <= ADDR +1;
end
else
mPOS_VAL <= 0;
if(Y_Cont >= mY_POS+480)
ADDR<=0;
if(mCCD_FVAL)
begin
rSYNC <= 0;
if(mCCD_LVAL)
begin
X_Cont <= X_Cont+1;
now_TX_Cont<=X_Cont;
// if(ADDR<307199)
//
if(TX_Cont<X_Cont)TX_Cont<=X_Cont;
end
else
begin
X_Cont <= 0;
end
if({Pre_LVAL,iLVAL}==2'b01)
begin
Y_Cont <= Y_Cont+1;
now_TY_Cont<=Y_Cont;
if(TY_Cont<Y_Cont)TY_Cont<=Y_Cont;
end
end
else
begin
Y_Cont<=0;
end
end
end
always@(posedge iCLK or negedge iRST_N)
begin
if(!iRST_N)
Frame_Cont <= 0;
else
begin
//if( ({Pre_FVAL,iFVAL}==2'b01) && mSTART )
if( ({Pre_FVAL,iFVAL}==2'b01) )
Frame_Cont <= Frame_Cont+1;
end
end
endmodule
(三)、图像数据在DDR和BRAM中的两种缓存方案
本项目中两种技术方案均已实现,现在以BRAM为例说明:
Block Design图如下所示:
该模块完成了图像的帧缓存,采用双通道与流水线的设计,加快数据读写速度,提高了图像缓存的效率。由于Zedboard的VGA显示采用的是RGB444的格式,因此该RAM的深度也是12位。
使用多端口存储器并不能“真正意义”上地提高存储器的带宽,实际上,存储器的位宽和大小均没有发生变化。但是多端口存储器提供了多个存取接口,也就是多个数据总线和地址总线。多个端口可以同时提供读写功能,理论上将带宽提升了一倍。在本项目中使用多端口比不使用多端口快了1.56倍的读写速度。
为了提高时钟的利用效率,将存储器以一个与系统其他部分更高的速度运行,可以允许在一个系统时钟内对存储器进行两次或多次访问。存储器应该和时钟同步,这样能避免同步问题。双倍数据速率(DDR)存储器就是这样的一种存储器。它能在时钟的上升沿和下降沿各传送一次数据,即每个时钟周期传送两次数据。对于时钟速度较低或者具有高速存储器的系统来说,使用一个较高频率的RAM时钟才是可行的。
此外,当数据每隔几个时钟进入到流水线处理时,整个设计都能以较高的时钟速率运行。这种“多阶段”设计,时钟周期的数目或阶段与像素时钟有关。多阶段设计不仅增加了带宽,而且在不同阶段重复使用硬件而减少了计算硬件。
BRAM IP核配置过程:
模块例化:
frame_buffer u_fb (
.clka(CMOS_PIXCLK),
.wea(value_1),
.addra(wr_address),
.dina({mCMOS_R[9:6],mCMOS_G[9:6],mCMOS_B[9:6]}),
.clkb(VGA_CTRL_CLK),
.addrb(rd_address),
.doutb({Image_Read_R[9:6],Image_Read_G[9:6],Image_Read_B[9:6]})
);
(四)、图像数据在VGA上成像显示
因为涉及到二值化,所以还需要编写一个算法模块再到VGA显示模块。
最终经过RGB565转化到RGB444格式:
关于VGA_display模块的代码如下:
`timescale 1ns / 1ps
//
// Company:
// Engineer: Tan junfu
//
// Create Date: 2020/12/06 01:23:35
// Design Name:
module vga_display(
input clk25 ,
output [3:0] vga_red ,
output [3:0] vga_green ,
output [3:0] vga_blue ,
output vga_hsync ,
output vga_vsync ,
output [9:0] HCnt ,
output [9:0] VCnt ,
output [18:0] frame_addr ,
input [11:0] frame_pixel
);
//parameters define
parameter hRez = 640;
parameter hStartSync = 640+16;
parameter hEndSync = 640+16+96;
parameter hMaxCount = 800;
parameter vRez = 480;
parameter vStartSync = 480+10;
parameter vEndSync = 480+10+2;
parameter vMaxCount = 480+10+2+3;
parameter hsync_active = 0;
parameter vsync_active = 0;
reg [9:0] hCounter=0 ;
reg [9:0] vCounter=0 ;
reg [18:0] address =0;
reg blank =1;
reg vga_hsync_reg ;
reg vga_vsync_reg ;
assign frame_addr = address;
assign HCnt = hCounter;
assign VCnt = vCounter;
always@(posedge clk25)begin
if ( hCounter == hMaxCount-1) begin
hCounter <= 0;
if (vCounter == vMaxCount-1)
vCounter <= 0;
else
vCounter <= vCounter+1;
end
else
hCounter <= hCounter+1;
end
reg [3:0] vga_red_reg;
reg [3:0] vga_green_reg;
reg [3:0] vga_blue_reg;
assign vga_red = vga_red_reg ;
assign vga_green = vga_green_reg;
assign vga_blue = vga_blue_reg ;
always@(posedge clk25)begin
if(blank == 0 )begin
vga_red_reg <= frame_pixel[11:8];
vga_green_reg <= frame_pixel[7:4];
vga_blue_reg <= frame_pixel[3:0];
end
else begin
vga_red_reg <= 4'b0000;
vga_green_reg<= 4'b0000;
vga_blue_reg <= 4'b0000;
end
end
always@(posedge clk25)begin
if (vCounter >= vRez) begin
address <= 0;
blank <= 1;
end
else begin
if (hCounter < 640) begin
blank <=0;
address <= address+1;
end
else
blank <= 1;
end
end
always@(posedge clk25)begin
if (hCounter > hStartSync && hCounter <= hEndSync)
vga_hsync_reg <= hsync_active;
else
vga_hsync_reg <= ~hsync_active;
end
always@(posedge clk25)begin
if (vCounter >= vStartSync && vCounter < vEndSync)
vga_vsync_reg <= vsync_active;
else
vga_vsync_reg <= ~vsync_active;
end
assign vga_vsync = vga_vsync_reg;
assign vga_hsync = vga_hsync_reg;
endmodule
(五)、最终图像显示效果
(六)、项目中遇到的问题与解决方法
难题一:VGA成像失败,显示器呈花屏。
解决方法:认真检查数据位,最终发现是RGB处理模块映射后高低位需要转换。并且需要从RGB565取高位后到RGB444格式再传入VGA接口。转换模块如下:
难题二:采集失败,显示器呈黑屏,无数据。
解决方法:通过仿真得知,摄像头在初始化和复位后的前10~20帧数据是无效的,输出的数据并不是采集的真实图像数据。故做了延时模块,添加了帧计数器,并成功解决。
难题三:加入了算法模块即——二值化转换后画面混乱,无法正常观测图像。
解决方法:使用锁相环PLL时钟分频IP核,重新分配VGA显示模块与算法模块的时钟,因为从RAM中取出图像数据成像时,要使用独立时钟源。
//end
以上。需要完整工程代码的朋友请私信我。