因为硕士毕业论文的实验需要使用ZYNQ实现图像的处理,所以一直在研究如何使用FPGA处理图像。本实验打算首先从灰度转化中值滤波等实验,但老是卡在图像仿真测试上,不知道该如何写vivado的测试激励。主要有以下几点:
1、不知道如何将图像的信息导入到测试文件中的那些例化接口,有使用.bat 文件的,有直接使用BMP图像格式的,但是不清楚这些导入的图像信息如何配置到例化的模块接口里的。并且其中的行场信号该如何配置?是这个图像信息中本身就有的,还是需要在测试激励文件中单独配置?
2、不清楚处理后的测试文件是如何写到新的图像文件信息中的。
经过一段时间的学习,我终于知道如何写测试激励了,在这里我是使用vivado工具自带的那个仿真,使用的输入源为bmp格式的图像,不需要将图像转为.bat格式了,这样能够省去繁琐的MATLAB转换步骤。测试代码只要是参照B站大磊FPGA的视频。
`timescale 1ns / 1ns
//使用BMP格式照片仿真VIP视频图像处理算法
module vivado_sim();
integer iBmpFileId ; //承接输入的BMP文件
integer oBmpFileId_1 ; //输出BMP图像1,
integer iIndex = 0 ; //输出bmp数据索引
integer pixel_index = 0 ; //输出像素索引
integer iCode;
integer iBmpWidth ; //输入图像宽度
integer iBmpHight ; //输入图像高度
integer iBmpSize ; //输入图像尺寸
integer iDataStartIndex;//输入图像偏移量
reg [ 7:0] rBmpData [0:2000000]; //用于寄存输入BMP图片中的字节数据(包括54字节的文件头)
reg [ 7:0] Vip_BmpData_1 [0:2000000]; //用于寄存视频图像处理之后 的BMP图片 数据
reg [31:0] rBmpWord; //输出BMP图片时用于寄存数据(以word为单位,即4byte)
reg [ 7:0] pixel_data; //输出视频流时的像素数据
reg clk;
reg rst_n;
reg [ 7:0] vip_pixel_data_1 [0:921600]; //640x480x3
wire PIC1_vip_out_frame_vsync;
wire PIC1_vip_out_frame_href ;
wire PIC1_vip_out_frame_clken;
wire [7:0] PIC1_vip_out_img_R ;
wire [7:0] PIC1_vip_out_img_G ;
wire [7:0] PIC1_vip_out_img_B ;
initial begin
iBmpFileId = $fopen("1_Jing_J66819.bmp","rb") ;
//将输入BMP图片加载到数组中 21_Su_A65NF7
iCode = $fread(rBmpData,iBmpFileId);
//根据BMP图片文件头的格式,分别计算出图片的 宽度 /高度 /像素数据偏移量 /图片字节数
iBmpWidth = {rBmpData[21],rBmpData[20],rBmpData[19],rBmpData[18]};
iBmpHight = {rBmpData[25],rBmpData[24],rBmpData[23],rBmpData[22]};
iBmpSize = {rBmpData[ 5],rBmpData[ 4],rBmpData[ 3],rBmpData[ 2]};
iDataStartIndex = {rBmpData[13],rBmpData[12],rBmpData[11],rBmpData[10]};
//关闭输入BMP图片
$fclose(iBmpFileId);
//延迟13ms,等待第一帧VIP处理结束
#13000000
//输出第一张
oBmpFileId_1 = $fopen("output_file_1.bmp","wb+");
//加载图像处理后,BMP图片的文件头和像素数据
//---------------------------------------------
//输出第一张
for (iIndex = 0; iIndex < iBmpSize; iIndex = iIndex + 1) begin
if(iIndex < 54)
Vip_BmpData_1[iIndex] = rBmpData[iIndex];
else
Vip_BmpData_1[iIndex] = vip_pixel_data_1[iIndex-54];
end
//将数组中的数据写到输出BMP图片中
for (iIndex = 0; iIndex < iBmpSize; iIndex = iIndex + 1) begin
rBmpWord = Vip_BmpData_1[iIndex];
$fwrite(oBmpFileId_1,"%c",rBmpWord);
end
//关闭输出BMP图片
$fclose(oBmpFileId_1);
end
//initial end
//---------------------------------------------
//---------------------------------------------
//初始化时钟和复位信号
reg inita_done ;
initial begin
clk = 1;
rst_n = 0;
inita_done = 0 ;
#110
rst_n = 1;
#22_00
inita_done = 1 ;
end
//产生50MHz时钟
always #10 clk = ~clk;
//---------------------------------------------
//---------------------------------------------
//在时钟驱动下,从数组中读出像素数据,用于在Modelsim中查看BMP中的数据
always@(posedge clk or negedge rst_n)begin
if(!rst_n) begin
pixel_data <= 8'd0;
pixel_index <= 0;
end
else begin
pixel_data <= rBmpData[pixel_index];
pixel_index <= pixel_index+1;
end
end
//---------------------------------------------
//---------------------------------------------
//产生摄像头时序
wire cmos_vsync ;
reg cmos_href;
wire cmos_clken;
reg [23:0] cmos_data;
reg cmos_clken_r;
reg [31:0] cmos_index;
parameter [10:0] IMG_HDISP = 11'd640;
parameter [10:0] IMG_VDISP = 11'd480;
localparam H_SYNC = 11'd5;
localparam H_BACK = 11'd5;
localparam H_DISP = IMG_HDISP;
localparam H_FRONT = 11'd5;
localparam H_TOTAL = H_SYNC + H_BACK + H_DISP + H_FRONT;
localparam V_SYNC = 11'd1;
localparam V_BACK = 11'd0;
localparam V_DISP = IMG_VDISP;
localparam V_FRONT = 11'd1;
localparam V_TOTAL = V_SYNC + V_BACK + V_DISP + V_FRONT;
//---------------------------------------------
//模拟 OV7725/OV5640 驱动模块输出的时钟使能
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
cmos_clken_r <= 0;
else
cmos_clken_r <= ~cmos_clken_r;
end
//---------------------------------------------
//水平计数器
reg [10:0] hcnt;
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
hcnt <= 11'd0;
else if(cmos_clken_r)
hcnt <= (hcnt < H_TOTAL - 1'b1) ? hcnt + 1'b1 : 11'd0;
end
//---------------------------------------------
//竖直计数器
reg [10:0] vcnt;
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
vcnt <= 11'd0;
else if(cmos_clken_r) begin
if(hcnt == H_TOTAL - 1'b1)
vcnt <= (vcnt < V_TOTAL - 1'b1) ? vcnt + 1'b1 : 11'd0;
else
vcnt <= vcnt;
end
end
//---------------------------------------------
//场同步
reg cmos_vsync_r;
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
cmos_vsync_r <= 1'b0; //H: Vaild, L: inVaild
else begin
if(vcnt <= V_SYNC - 1'b1)
cmos_vsync_r <= 1'b0; //H: Vaild, L: inVaild
else
cmos_vsync_r <= 1'b1; //H: Vaild, L: inVaild
end
end
assign cmos_vsync = cmos_vsync_r;
//---------------------------------------------
//行有效
wire frame_valid_ahead = ( vcnt >= V_SYNC + V_BACK && vcnt < V_SYNC + V_BACK + V_DISP
&& hcnt >= H_SYNC + H_BACK && hcnt < H_SYNC + H_BACK + H_DISP )
? 1'b1 : 1'b0;
reg cmos_href_r;
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
cmos_href_r <= 0;
else begin
if(frame_valid_ahead)
cmos_href_r <= 1;
else
cmos_href_r <= 0;
end
end
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
cmos_href <= 0;
else
cmos_href <= cmos_href_r;
end
assign cmos_clken = cmos_href & cmos_clken_r;
//-------------------------------------
//从数组中以视频格式输出像素数据
wire [10:0] x_pos;
wire [10:0] y_pos;
assign x_pos = frame_valid_ahead ? (hcnt - (H_SYNC + H_BACK )) : 0;
assign y_pos = frame_valid_ahead ? (vcnt - (V_SYNC + V_BACK )) : 0;
always@(posedge clk or negedge rst_n)begin
if(!rst_n) begin
cmos_index <= 0;
cmos_data <= 24'd0;
end
else begin
//cmos_index <= y_pos * 1920 + x_pos*3 + 54; // 3*(y*640 + x) + 54
//读取图片时上下翻转
cmos_index <= (479- y_pos) * 1920 + x_pos*3 + 54; // 3*((479-y)*640 + x) + 54
cmos_data <= {rBmpData[cmos_index], rBmpData[cmos_index+1] , rBmpData[cmos_index+2]};
end
end
wire per_frame_vsync = cmos_vsync ;
wire per_frame_href = cmos_href;
wire per_frame_clken = cmos_clken;
wire [7:0] per_img_red = cmos_data[ 7: 0];
wire [7:0] per_img_green = cmos_data[15: 8];
wire [7:0] per_img_blue = cmos_data[23:16];
wire per0_frame_vsync ;
wire per0_frame_href ;
wire per0_frame_clken ;
wire [7:0] per0_img_Y ;
wire [7:0] per0_img_Cb ;
wire [7:0] per0_img_Cr ;
VIP_RGB888_YCbCr444 u_VIP_RGB888_YCbCr444
(
//global clock
.clk(clk), //cmos video pixel clock
.rst_n(rst_n), //global reset
//Image data prepred to be processd
.per_frame_vsync (per_frame_vsync), //Prepared Image data vsync valid signal
.per_frame_href (per_frame_href), //Prepared Image data href vaild signal
.per_frame_clken (per_frame_clken), //Prepared Image data output/capture enable clock
.per_img_red (per_img_red), //Prepared Image red data to be processed
.per_img_green (per_img_green), //Prepared Image green data to be processed
.per_img_blue (per_img_blue), //Prepared Image blue data to be processed
//Image data has been processd
.post_frame_vsync (PIC1_vip_out_frame_vsync), //Processed Image data vsync valid signal
.post_frame_href (PIC1_vip_out_frame_href), //Processed Image data href vaild signal
.post_frame_clken (PIC1_vip_out_frame_clken), //Processed Image data output/capture enable clock
.post_img_Y (PIC1_vip_out_img_R), //Processed Image brightness output
.post_img_Cb (PIC1_vip_out_img_G), //Processed Image blue shading output
.post_img_Cr (PIC1_vip_out_img_B) //Processed Image red shading output
);
//寄存图像处理之后的像素数据
//-------------------------------------
//第一张图
reg [7:0] frame_cnt_1;
reg [31:0] vip_cnt_1;
reg PIC1_out_frame_vsync_r; //寄存VIP输出的场同步
//------------------------------------------------------------------
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
PIC1_out_frame_vsync_r <= 1'b0;
else
PIC1_out_frame_vsync_r <= PIC1_vip_out_frame_vsync;
end
//------------------------------------------------------------------
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
frame_cnt_1 <= 8'b0;
else if(inita_done &&((!PIC1_out_frame_vsync_r )& (PIC1_vip_out_frame_vsync)))
if(frame_cnt_1 < 8'd12)
frame_cnt_1 <= frame_cnt_1 + 1'b1;
else
frame_cnt_1 <= frame_cnt_1;
else
frame_cnt_1 <= frame_cnt_1 ;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n) begin
vip_cnt_1 <= 32'd0;
end
//输出第一帧数据
else if(frame_cnt_1 == 8'd1) begin
if(PIC1_vip_out_frame_href & PIC1_vip_out_frame_clken) begin
vip_cnt_1 <= vip_cnt_1 + 3;
vip_pixel_data_1[(479-(vip_cnt_1/1920))*1920 + (vip_cnt_1%1920)+0] <= PIC1_vip_out_img_B ;
vip_pixel_data_1[(479-(vip_cnt_1/1920))*1920 + (vip_cnt_1%1920)+1] <= PIC1_vip_out_img_G ;
vip_pixel_data_1[(479-(vip_cnt_1/1920))*1920 + (vip_cnt_1%1920)+2] <= PIC1_vip_out_img_R ;
end
end
end
endmodule
1、仿真图像的传入和输出需要实现用到fopen函数,在传入仿真图像时需要使用fopen函数将图像导入到一个数组中,在输入处理完的图像时将处理后的数组写入到照片中。
2、在测试文件中配置时钟信号、复位信号、场信号、行信号、数据信号等,在复位信号使能后,每当有一个到来时,都会有有一个图像数据通过之前的输入图像数组写入到待处理的数据信号中。然后这些数据就会传入到自己写的图像处理模块中进行处理。
最然说只有几行字,但是这个问题困扰了我好久,如果要是认认真真的去琢磨加上有人帮忙的话,估计几天就可以解决这个问题,可惜的事身边搞这个的人比较少,网上的一些教程也不是完全针对我这个痛点的,并且在一些相关的帖子底下请教也是迟迟得不到回复。虽然解决这个问题在一些大佬眼里比较简单,但是当我自己独立完成后还是有不小的成就感。
还有一点就是在查阅问题的时候老是分心,比如这次查如何写测试激励的问题,老是查着查着去看别什么神经网络加速,二值化等知识去了,以后一定要专心。