前言
在平时利用Modelsim仿真时,一般是利用它查看某些信号的波形和取值是否正常,但是在利用FPGA进行图像处理的时候,通常我们想直观的看到处理后的图片是否达到了预期效果,因此产生了一个思考,能否利用Modelsim对图片进行仿真。在网上查找资料后,发现大磊出过一个视频教学,在经过学习后,成功实现,故写一篇帖子和大家分享学习。
原理
解析BMP图片+模拟摄像头时序
说通俗一点,就是在tb文件中对BMP格式的图片进行解析,用数组统计其长度宽度像素值等参数;然后写一个摄像头时序代码,利用模拟摄像头将这个模拟的图片输入进行处理,最后将输出的数组转换为BMP格式的图片输出
代码
BMP图片解析为数组
integer iBmpFileId;
integer oTxtFileId;
integer iIndex = 0;
integer iCode;
integer iBmpWidth; //输入BMP 宽度
integer iBmpHight; //输入BMP 高度
integer iBmpSize; //输入BMP 字节数
integer iDataStartIndex; //输入BMP 像素数据偏移量
localparam BMP_SIZE = 54 + IMG_HDISP * IMG_VDISP * 3 - 1; //输出BMP 字节数
reg [ 7:0] rBmpData [0:BMP_SIZE];
integer i,j;
//---------------------------------------------
initial begin
iBmpFileId = $fopen(PIC_PATH,"rb");
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);
end
模拟摄像头时序(OV7725/5640)
//产生摄像头时序
wire cmos_vsync ;
reg cmos_href;
wire cmos_clken;
reg [23:0] cmos_data;
reg cmos_clken_r;
reg [31:0] cmos_index;
localparam H_SYNC = 11'd10;
localparam H_BACK = 11'd10;
localparam H_DISP = IMG_HDISP;
localparam H_FRONT = 11'd10;
localparam H_TOTAL = H_SYNC + H_BACK + H_DISP + H_FRONT;
localparam V_SYNC = 11'd10;
localparam V_BACK = 11'd10;
localparam V_DISP = IMG_VDISP;
localparam V_FRONT = 11'd10;
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 * IMG_HDISP * 3 + x_pos * 3 + 54; // 3*(y*640 + x) + 54
cmos_data <= {rBmpData[cmos_index], rBmpData[cmos_index+1] , rBmpData[cmos_index+2]};
end
end
reg [10:0] x_pos_d [0 : 10];
reg [10:0] y_pos_d [0 : 10];
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
for(i = 0; i < 11; i = i + 1)begin
x_pos_d[i] <= 0;
y_pos_d[i] <= 0;
end
end
else begin
x_pos_d[0] <= x_pos;
y_pos_d[0] <= y_pos;
for(i = 1; i < 11; i = i + 1)begin
x_pos_d[i] <= x_pos_d[i-1];
y_pos_d[i] <= y_pos_d[i-1];
end
end
end
assign CMOS_VSYNC = cmos_vsync;
assign CMOS_HREF = cmos_href;
assign CMOS_CLKEN = cmos_clken;
assign CMOS_DATA = cmos_data;
assign X_POS = x_pos;
assign Y_POS = y_pos;
图片恢复及存储处理
integer iCode;
integer iBmpFileId;
integer iBmpWidth;
integer iBmpHight;
integer iBmpSize;
integer iDataStartIndex;
integer iIndex = 0;
localparam BMP_SIZE = 54 + IMG_HDISP * IMG_VDISP * 3 - 1;
reg [ 7:0] BmpHead [0:53];
reg [ 7:0] Vip_BmpData [0:BMP_SIZE];
reg [ 7:0] vip_pixel_data [0:BMP_SIZE-54];
reg [31:0] rBmpWord;
reg video_vsync_d1 = 0;
reg [11:0] frame_cnt = 0;
reg [31:0] PIC_cnt = 0;
wire [7:0] PIC_img_R;
wire [7:0] PIC_img_G;
wire [7:0] PIC_img_B;
assign PIC_img_R = video_data[16+:8];
assign PIC_img_G = video_data[ 8+:8];
assign PIC_img_B = video_data[ 0+:8];
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
video_vsync_d1 <= 0;
end
else begin
video_vsync_d1 <= video_vsync;
end
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
frame_cnt <= 0;
end
else if(video_vsync_d1 & !video_vsync)begin
frame_cnt <= frame_cnt + 1;
end
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n) begin
PIC_cnt <= 32'd0;
end
else if(video_de) begin
if(frame_cnt == START_FRAME - 1) begin
PIC_cnt <= PIC_cnt + 3;
vip_pixel_data[PIC_cnt+0] <= PIC_img_R;
vip_pixel_data[PIC_cnt+1] <= PIC_img_G;
vip_pixel_data[PIC_cnt+2] <= PIC_img_B;
end
end
end
//---------------------------------------------
//initial the BMP file header
//The detail BMP file header is inpart from https://blog.csdn.net/Meteor_s/article/details/82414155 and inpart from my test.
initial begin
for(iIndex = 0; iIndex < 54; iIndex = iIndex + 1) begin
BmpHead[iIndex] = 0;
end
#2
{BmpHead[1],BmpHead[0]} = {8'h4D,8'h42};
{BmpHead[5],BmpHead[4],BmpHead[3],BmpHead[2]} = BMP_SIZE + 1;//File Size (Bytes)
BmpHead[10] = 8'd54;//Bitmap Data Offset
BmpHead[14] = 8'h28;//Bitmap Header Size
{BmpHead[21],BmpHead[20],BmpHead[19],BmpHead[18]} = IMG_HDISP;//Width
{BmpHead[25],BmpHead[24],BmpHead[23],BmpHead[22]} = IMG_VDISP;//Height
BmpHead[26] = 8'd1;//Number of Color Planes
BmpHead[28] = 8'd24;//Bits per Pixel
end
//---------------------------------------------
//write the data to the output bmp file
initial begin
//---------------------------------------------
//waiting for the start frame
wait(frame_cnt == START_FRAME);
iBmpFileId = $fopen(PIC_PATH,"wb+");
for (iIndex = 0; iIndex < BMP_SIZE + 1; iIndex = iIndex + 1) begin
if(iIndex < 54) begin
Vip_BmpData[iIndex] = BmpHead[iIndex];
end
else begin
Vip_BmpData[iIndex] = vip_pixel_data[iIndex-54];
end
end
for (iIndex = 0; iIndex < BMP_SIZE + 1; iIndex = iIndex + 1) begin
$fwrite(iBmpFileId,"%c",Vip_BmpData[iIndex]);
end
$fclose(iBmpFileId);
$display("The picture is saved in %s",PIC_PATH);
$stop;
end
处理调用
`ifdef Modelsim_Sim
localparam PIC_INPUT_PATH = "C:\\Users\\saoge\\Desktop\\FPGA\\FPGA3\\defog\\PIC\\ceshi1.bmp" ;
localparam PIC_OUTPUT_PATH = "C:\\Users\\saoge\\Desktop\\FPGA\\FPGA3\\defog\\PIC\\outcom2.bmp" ;
`endif
//--------------------------------------------------------------------------------
`ifdef Vivado_Sim
localparam PIC_INPUT_PATH = "../../../../../PIC/duck.bmp" ;
localparam PIC_OUTPUT_PATH = "../../../../../PIC/outcom.bmp" ;
`endif
localparam PIC_WIDTH = 640 ;
localparam PIC_HEIGHT = 480 ;
reg cmos_clk = 0;
reg cmos_rst_n = 0;
wire cmos_vsync ;
wire cmos_href ;
wire cmos_clken ;
wire [23:0] cmos_data ;
wire haze_removal_vsync ;
wire haze_removal_hsync ;
wire haze_removal_de ;
wire [23:0] haze_removal_data ;
parameter cmos0_period = 6;
always#(cmos0_period/2) cmos_clk = ~cmos_clk;
initial #(20*cmos0_period) cmos_rst_n = 1;
//--------------------------------------------------
//Camera Simulation
sim_cmos #(
.PIC_PATH (PIC_INPUT_PATH )
, .IMG_HDISP (PIC_WIDTH )
, .IMG_VDISP (PIC_HEIGHT )
)u_sim_cmos0(
.clk (cmos_clk )
, .rst_n (cmos_rst_n )
, .CMOS_VSYNC (cmos_vsync )
, .CMOS_HREF (cmos_href )
, .CMOS_CLKEN (cmos_clken )
, .CMOS_DATA (cmos_data )
, .X_POS ()
, .Y_POS ()
);
//--------------------------------------------------
//Image Processing
haze_removal_top #(
.Y_ENHANCE_ENABLE (0 )
, .PIC_WIDTH (PIC_WIDTH )
)u_haze_removal_top(
.clk (cmos_clk )
, .rst_n (cmos_rst_n )
//处理前数据
, .pre_frame_vsync (cmos_vsync )
, .pre_frame_href (cmos_href )
, .pre_frame_clken (cmos_clken )
, .pre_img (cmos_data )
//处理后的数据
, .post_frame_vsync (haze_removal_vsync )
, .post_frame_href (haze_removal_hsync )
, .post_frame_clken (haze_removal_de )
, .post_img (haze_removal_data )
);
//--------------------------------------------------
//Video saving
video_to_pic #(
.PIC_PATH (PIC_OUTPUT_PATH )
, .START_FRAME (2 )
, .IMG_HDISP (PIC_WIDTH )
, .IMG_VDISP (PIC_HEIGHT )
)u_video_to_pic0(
.clk (cmos_clk )
, .rst_n (cmos_rst_n )
, .video_vsync (haze_removal_vsync )
, .video_hsync (haze_removal_hsync )
, .video_de (haze_removal_de )
, .video_data (haze_removal_data )
)
在处理调用时,其实就是测试文件的TOP文件,除了进行解析模拟输出外,还需要对图片进行处理,这里的代码是对图片进行了去雾操作(关于去雾算法可以去我前面的博客看)
结果
输入一张图片,输出仿真后的图片,该代码可用