1.BMP介绍
BMP(Bitmap)是一种用于存储位图图像的文件格式,广泛应用于 Windows 操作系统中。BMP 文件可以存储高质量的图像数据,包括颜色深度较高的图片,同时支持无压缩或可选的简单压缩方式。
BMP格式
:
文件头(14 字节):
- 文件类型:2 字节,通常为
BM
。 - 文件大小:4 字节,整个 BMP 文件的大小。
- 保留字段:4 字节,保留字段,值为 0。
- 像素数据偏移量:4 字节,图像数据开始的位置。
信息头(40 字节,常见的 BITMAPINFOHEADER):
- 头大小:4 字节,信息头的大小。
- 图像宽度:4 字节,以像素为单位。
- 图像高度:4 字节,以像素为单位(正数表示自下而上,负数表示自上而下)。
- 颜色平面数:2 字节,通常为 1。
- 每像素位数:2 字节,表示颜色深度,常见值为 24。
- 压缩方式:4 字节,通常为 0 表示无压缩。
- 图像大小:4 字节,图像数据的大小,压缩时有效。
- 水平分辨率:4 字节,水平方向像素每米数。
- 垂直分辨率:4 字节,垂直方向像素每米数。
- 使用颜色数:4 字节,调色板中使用的颜色数。
- 重要颜色数:4 字节,调色板中重要的颜色数。
图像数据:包含每个像素的颜色值,对于 24 位 BMP,每个像素由 3 个字节组成,分别表示红色、绿色和蓝色。
示例
:
Tip
:
- BMP的54个字节的文件头都是小端模式,即36_00_03_00实际的十六进制是(30036)h = (196,662)d = (256(宽)* 256 (高)*3+对齐字节+信息头)
- BMP图像的每行需要是4的倍数(即对齐),如果不够需要填充0,如上图256%4 = 0,所以196,662 = (256(宽)* 256 (高)*3(字节)+0+54)
- BMP中图像数据的存储是从左下开始的,即坐标(width:0,height:height)开始存贮的,而matlab中imread函数或者软件打开bmp查看数值时(如上图)是从左下读的,所以不用考虑存储的位置
2.BMP转VGA时序
首先,在verilog中是无法直接读取bmp图片的,需要将bmp通过脚本将其转为.dat文件,然后使用$readmemh函数去读取到数组中后,根据模拟的时钟信号将数据发出,下面是转bmp图片转dat文件的matlab代码:
注意:以下所有代码都是针对仿真的时候使用,且都需要替换文件名和路径!!!
clear;%从工作区中删除项目释放内存
clc;
close all;%关闭所有窗口
%main
% 1.读取图片数据
img = imread('1.bmp');
% imread函数两个参数:文件路径和读取格式
imtool(img,[]);
[h, w, ~] = size(img); % 获取图像高度、宽度
fid = fopen('image_data.dat', 'w');
% 遍历图像数据,将其保存为十六进制形式
for i = h:-1:1 % 从底部开始for j = 1:wpixel_val = img(i,j,:); % 获取每个像素的RGB值r = pixel_val(1); % 红色通道g = pixel_val(2); % 绿色通道b = pixel_val(3); % 蓝色通道fprintf(fid, '%02X%02X%02X\n', r, g, b); % 输出为 6 位十六进制,RGB 顺序end
end
当生成.dat文件之后,就可以直接将时序数据转换成VGA/HDMI 时序,如下所示:
module SIM_Generate_Data
#(parameter P_CLK_PERIOD = 5 , // 5nsparameter P_RST_DURATION = 50 , // 100nsparameter P_H_MAX_CNT = 800 , // 8bitparameter P_H_SYNC = 96 , parameter P_H_FRONT = 144 , //96 40 8parameter P_V_MAX_CNT = 525 , // 8bitparameter P_V_SYNC = 35 , //2 25 8parameter P_V_FRONT = 144 , parameter P_SCREEN_WIDTH = 640 , //96 40 8 640 8 8 = 800parameter P_SCREEN_HEIGHT = 480 , //2 25 8 480 8 2 = 525parameter P_IMAGE_WIDTH = 256 ,parameter P_IMAGE_HEIGHT = 256 ,parameter P_BACKFROUND_COLOR = 24'h000000
)
( output wire o_clk ,output wire o_rst_n ,output wire [7:0] o_r_channel ,output wire [7:0] o_g_channel ,output wire [7:0] o_b_channel ,output reg o_hsync ,output reg o_vsync
);localparam P_H_IMG_START = P_H_FRONT+P_SCREEN_WIDTH/2-P_IMAGE_WIDTH/2-1;
localparam P_H_IMG_END = P_H_FRONT+P_SCREEN_WIDTH/2+P_IMAGE_WIDTH/2-1;
localparam P_V_IMG_START = P_V_FRONT+P_SCREEN_HEIGHT/2-P_IMAGE_HEIGHT/2-1;
localparam P_V_IMG_END = P_V_FRONT+P_SCREEN_HEIGHT/2+P_IMAGE_HEIGHT/2-1;initial beginr_clk = 1'b0;r_rst_n = 1'b0;#(P_RST_DURATION)r_rst_n = 1'b1;
endinitial begin$readmemh("../../../../../sim_data/image_data.dat", r_image_data);$display("Tip\:last image data read from file: %h",r_image_data[P_IMAGE_WIDTH*P_IMAGE_HEIGHT-1]);
endalways #(P_CLK_PERIOD/2) r_clk = ~r_clk;reg r_clk ;
reg r_rst_n ;
reg [23:0] r_image_data [P_IMAGE_WIDTH*P_IMAGE_HEIGHT-1:0];
reg [15:0] r_width_cnt ;
reg [15:0] r_height_cnt ;
reg [7:0] r_r_channel ;
reg [7:0] r_g_channel ;
reg [7:0] r_b_channel ;
reg r_img_hsync ;
reg r_img_vsync ;
reg [15:0] r_h_cnt ;
reg [15:0] r_v_cnt ;
reg [15:0] r_total_cnt ;
reg [7:0] or_r_channel ;
reg [7:0] or_g_channel ;
reg [7:0] or_b_channel ;
reg [7:0] or_r_channel_1d ;
reg [7:0] or_g_channel_1d ;
reg [7:0] or_b_channel_1d ;assign o_clk = r_clk ;
assign o_rst_n = r_rst_n ;
assign o_r_channel = or_r_channel_1d;
assign o_g_channel = or_g_channel_1d;
assign o_b_channel = or_b_channel_1d;always @(posedge r_clk) beginif (!r_rst_n) begino_hsync <= 1'b0;o_vsync <= 1'b0;end else begino_hsync <= r_hsync;o_vsync <= r_vsync;end
end//gererate r_hsync,r_vsync
always @(posedge r_clk,negedge r_rst_n) beginif(!r_rst_n)r_h_cnt <= 16'd0;else if(r_h_cnt == P_H_MAX_CNT-1) r_h_cnt <= 16'd0;elser_h_cnt <= r_h_cnt + 16'd1;
endalways @(posedge r_clk,negedge r_rst_n) beginif(!r_rst_n)r_v_cnt <= 16'd0;else if(r_v_cnt == P_V_MAX_CNT-1 && r_h_cnt == P_H_MAX_CNT-1) r_v_cnt <= 16'd0;else if(r_h_cnt == P_H_MAX_CNT-1)r_v_cnt <= r_v_cnt + 16'd1;elser_v_cnt <= r_v_cnt;
endalways @(posedge r_clk,negedge r_rst_n) beginif(!r_rst_n)r_img_hsync <= 1'b0;else if(r_h_cnt < P_H_SYNC)r_img_hsync <= 1'b1;elser_img_hsync <= 1'b0;
end
always @(posedge r_clk , negedge r_rst_n) beginif(!r_rst_n)beginr_vsync <= 1'b0;end else if(r_v_cnt >P_V_IMG_START && r_v_cnt <= P_V_IMG_END)beginr_vsync <= 1'b1;end else beginr_vsync <= 1'b0;end
endalways @(posedge r_clk,negedge r_rst_n) beginif(!r_rst_n)r_img_vsync <= 1'b0;else if(r_v_cnt < P_V_SYNC)r_img_vsync <= 1'b1;elser_img_vsync <= 1'b0;
endalways @(posedge r_clk,negedge r_rst_n) beginif(!r_rst_n)beginr_width_cnt <= 16'd0;r_height_cnt <= 16'd0;r_r_channel <= 8'd0;r_g_channel <= 8'd0;r_b_channel <= 8'd0;end else if(r_h_cnt > P_H_IMG_START && r_h_cnt <= P_H_IMG_END && r_v_cnt > P_V_IMG_START && r_v_cnt <= P_V_IMG_END)begin //show image datar_r_channel <= r_image_data[r_width_cnt+r_height_cnt*P_IMAGE_WIDTH][23:16];r_g_channel <= r_image_data[r_width_cnt+r_height_cnt*P_IMAGE_WIDTH][15:8];r_b_channel <= r_image_data[r_width_cnt+r_height_cnt*P_IMAGE_WIDTH][7:0];r_width_cnt <= r_width_cnt + 16'd1;if(r_h_cnt == P_H_IMG_END )r_height_cnt = r_height_cnt + 16'd1;elser_height_cnt = r_height_cnt;end else if(r_h_cnt > P_H_FRONT-1 && r_h_cnt < (P_H_FRONT+P_SCREEN_WIDTH) && r_v_cnt > P_V_FRONT-1 && r_v_cnt < (P_V_FRONT+P_SCREEN_HEIGHT))begin //show background color)beginr_r_channel <= P_BACKFROUND_COLOR[23:16];r_g_channel <= P_BACKFROUND_COLOR[15:8];r_b_channel <= P_BACKFROUND_COLOR[7:0];endelse beginr_width_cnt <= 16'd0;r_r_channel <= 8'd0;r_g_channel <= 8'd0;r_b_channel <= 8'd0;end
end//actully generated image data output
always @(posedge r_clk,negedge r_rst_n) beginif(!r_rst_n)beginor_r_channel <= 8'd0;or_g_channel <= 8'd0;or_b_channel <= 8'd0;r_hsync <= 1'b0;end else if(r_h_cnt > P_H_IMG_START && r_h_cnt <= P_H_IMG_END && r_v_cnt > P_V_IMG_START && r_v_cnt <= P_V_IMG_END)begin //show image datar_hsync <= 1'b1;or_r_channel <= r_image_data[r_width_cnt+r_height_cnt*P_IMAGE_WIDTH][23:16];or_g_channel <= r_image_data[r_width_cnt+r_height_cnt*P_IMAGE_WIDTH][15:8];or_b_channel <= r_image_data[r_width_cnt+r_height_cnt*P_IMAGE_WIDTH][7:0];end else beginr_hsync <= 1'b0;or_r_channel <= 8'd0;or_g_channel <= 8'd0;or_b_channel <= 8'd0;end
endalways @(posedge r_clk,negedge r_rst_n) beginif(!r_rst_n)beginor_r_channel_1d <= 8'd0;or_g_channel_1d <= 8'd0;or_b_channel_1d <= 8'd0;end else beginor_r_channel_1d <= or_r_channel;or_g_channel_1d <= or_g_channel;or_b_channel_1d <= or_b_channel;end
endalways @(posedge r_clk,negedge r_rst_n) beginif(!r_rst_n)beginr_total_cnt <= 16'd0;end else if(!r_vsync)r_total_cnt <= 16'd0;else if(r_hsync && r_vsync)r_total_cnt <= r_total_cnt + 16'd1;elser_total_cnt <= r_total_cnt;
end
endmodule
3.将VGA时序转成BMP 图片
在转的时候,同时生成了.dat文件,如果生成的bmp不对,可以查看对应的.dat与原.dat文件的差异,也可以直接比较生成的BMP文件差异。
module SIM_output_image_TB
#(parameter P_DATA_WIDTH = 8 ,parameter P_IMG_WIDTH = 256 ,parameter P_IMG_HEIGHT = 256
)
(input i_clk ,input i_rst_n ,input i_h_sync ,input i_v_sync ,input [P_DATA_WIDTH-1:0] i_data
);// 定义输入信号
reg ri_h_sync ,ri_v_sync ;
reg [P_DATA_WIDTH-1:0] ri_data ;
wire w_valid_signal ;assign w_valid_signal = ri_h_sync && ri_v_sync ;always @(posedge i_clk or negedge i_rst_n) beginif (!i_rst_n) beginri_h_sync <= 'd0;ri_v_sync <= 'd0;ri_data <= 'd0;end else beginri_h_sync <= i_h_sync;ri_v_sync <= i_v_sync;ri_data <= i_data ;end
end/*--------------大端模式转小端模式----------------*/
//bmp采取的是小端模式
function [31:0] big_convert_little_endian(input [31:0] fi_data
);
beginbig_convert_little_endian[7:0] = fi_data[31:24] ;big_convert_little_endian[15:8] = fi_data[23:16] ;big_convert_little_endian[23:16] = fi_data[15:8] ;big_convert_little_endian[31:24] = fi_data[7:0] ;
end
endfunction
/*--------------宽度4字节对齐----------------*/
/*
在 BMP 文件中,图像数据的每一行必须是 4 字节对齐的,这意味着每行的数据大小必须是 4 的倍数。如果图像的每行像素数据没有达到 4 字节的倍数,就需要在每行的末尾添加一些填充字节,使其达到 4 字节的倍数
*/
function [31:0] f_image_width_align(input [31:0] fi_image_width
);if(fi_image_width>0)beginf_image_width_align = fi_image_width + (4-((fi_image_width * 3) % 4))%4;end else$display("image_width is less than or equal to zero!");
endfunction/*--------------bmp文件写入----------------*/
task bmp_file_write(input integer ti_fp,input [23:0] ti_image_data
);begin$fwrite(ti_fp, "%c%c%c", ti_image_data[7:0],ti_image_data[15:8],ti_image_data[23:16]);end
endtask
/*--------------bmp文件关闭----------------*/
task bmp_file_close(input integer fp
);begin$fclose(fp);$display("BMP file already closed!");end
endtask/*--------------bmp文件末尾补0----------------*/
task bmp_paddings_zero(input integer ti_fp ,input integer ti_zero_num
);integer i;
beginif(ti_zero_num>0)beginfor (i=0;i<ti_zero_num;i=i+1) begin$fwrite(ti_fp,"%c",0); // 填充字节endend
end
endtask
/*--------------建立bmp文件----------------*/
task bmp_file_create(input integer ti_image_width ,input integer ti_image_height ,output reg [31:0] to_real_row_width ,output integer o_fp
);reg [7:0] bmp_header [53:0]; // bmp文件头reg [31:0] binary_file_size ; // 文件大小reg [31:0] little_endian_width,little_endian_height; // 宽度转为小端模式integer i;beginto_real_row_width = f_image_width_align(ti_image_width); // 真实行宽$display("image_width = %03d,real_image_width = %03d",ti_image_width,to_real_row_width);little_endian_width = big_convert_little_endian(to_real_row_width); // 宽度转为小端模式little_endian_height = big_convert_little_endian(ti_image_height); // 宽度转为小端模式binary_file_size = big_convert_little_endian(to_real_row_width*3*ti_image_height + 54);/*--------------bmp文件头(14bytes)---------------*///标识符:2 字节 ("BM")bmp_header[0] = 8'h42;bmp_header[1] = 8'h4d;// 文件大小:4 字节bmp_header[2] = binary_file_size[31:24]; //因为前面大端转小端的已经转了,所以这里直接赋值bmp_header[3] = binary_file_size[23:16];bmp_header[4] = binary_file_size[15:8] ;bmp_header[5] = binary_file_size[7:0] ;// 保留位:4 字节(通常为 0)bmp_header[6] = 8'h00;bmp_header[7] = 8'h00;bmp_header[8] = 8'h00;bmp_header[9] = 8'h00;// 像素数据的偏移量:4 字节bmp_header[10] = 8'h36;bmp_header[11] = 8'h00;bmp_header[12] = 8'h00;bmp_header[13] = 8'h00;/*--------------bmp信息头(40bytes)---------------*///信息头大小:4 字节(通常为 40)bmp_header[14] = 8'h28;bmp_header[15] = 8'h00;bmp_header[16] = 8'h00;bmp_header[17] = 8'h00;// 图像宽度:4 字节bmp_header[18] = little_endian_width[31:24];bmp_header[19] = little_endian_width[23:16];bmp_header[20] = little_endian_width[15:8] ;bmp_header[21] = little_endian_width[7:0] ;// 图像高度:4 字节bmp_header[22] = little_endian_height[31:24];bmp_header[23] = little_endian_height[23:16];bmp_header[24] = little_endian_height[15:8] ;bmp_header[25] = little_endian_height[7:0] ;// 颜色平面数:2 字节(必须为 1)bmp_header[26] = 8'h01;bmp_header[27] = 8'h0;// 每像素位数:2 字节(例如 24 位表示 RGB)bmp_header[28] = 8'h18;bmp_header[29] = 8'h0;// 压缩方式:4 字节(0 表示不压缩)bmp_header[30] = 8'h0;bmp_header[31] = 8'h0;bmp_header[32] = 8'h0;bmp_header[33] = 8'h0;// 图像数据大小:4 字节bmp_header[34] = 8'h0;bmp_header[35] = 8'h0;bmp_header[36] = 8'h0;bmp_header[37] = 8'h0;// 水平方向分辨率:4 字节bmp_header[38] = 8'h0;bmp_header[39] = 8'h0;bmp_header[40] = 8'h0;bmp_header[41] = 8'h0;// 垂直方向分辨率:4 字节bmp_header[42] = 8'h0;bmp_header[43] = 8'h0;bmp_header[44] = 8'h0;bmp_header[45] = 8'h0;// 使用的颜色数:4 字节(0 表示使用所有颜色)bmp_header[46] = 8'h0;bmp_header[47] = 8'h0;bmp_header[48] = 8'h0;bmp_header[49] = 8'h0;// 重要的颜色数:4 字节(0 表示所有颜色都重要)bmp_header[50] = 8'h0; bmp_header[51] = 8'h0;bmp_header[52] = 8'h0;bmp_header[53] = 8'h0;//打开文件o_fp = $fopen("../../../../../sim_data/result.bmp", "w");if(o_fp!= 0)begin$display("width:%d,height:%d",ti_image_width,ti_image_height);for (i = 0;i <= 53 ;i = i + 1) begin$fwrite(o_fp,"%c",bmp_header[i]);endend else$display("Error opening result.bmp file!\n");end
endtask/*--------------建立输出dot文件指针,方便后续写入数据----------------*/
task output_create_dot_file(output integer to_fp
);
beginto_fp = $fopen("../../../../../sim_data/output.dat", "w"); if (to_fp == 0)$display("Error creating dot file!\n");else$display("Dot file created successfully!\n");
end
endtask/*--------------根据.dat文件指针,持续写入数据----------------*/
task output_write_dot_file(input integer ti_fp,input [23:0] ti_data
);
beginif(ti_fp!= 0) begin$fwrite(ti_fp,"%04X\n",ti_data);$display("Expected uppercase hex output: %04X", ti_data);endelse$display("Error writing dot file!\n");
end
endtask/*--------------根据.dat文件指针,关闭----------------*/
task output_close_dot_file(input integer ti_fp
);
beginif (ti_fp != 0) begin$display("Closing dot file: %d", ti_fp);$fclose(ti_fp);end else begin$display("Error closing dot file: Invalid file pointer");end
end
endtask// 定义输出信号
integer bmp_file ; // 文件指针
integer dot_file ; // 文件指针
integer real_width ;
integer w, h ; // 当前像素坐标
integer i, j ; // 循环变量
// 存储像素数据
reg [23:0] image_data [0:P_IMG_WIDTH*P_IMG_HEIGHT-1]; // 24位色,每个像素3字节// 初始化仿真信号
initial begini = 0;j = 0;bmp_file_create(P_IMG_WIDTH, P_IMG_HEIGHT, real_width, bmp_file); // 创建BMP文件,写入bmp文件头output_create_dot_file(dot_file); // 创建输出文件
end
// 仿真结束时写入图像数据
always @(posedge i_clk or negedge i_rst_n) beginif (!i_rst_n) beginw <= 0;h <= 0; // 从左下角开始填充end else if (w_valid_signal) begin// 存储像素数据,8位输入拼接为24位RGB数据image_data[h * P_IMG_WIDTH + w] <= {ri_data, ri_data, ri_data};$display("h:%04d, w:%04d, data:%04X", h, w, {ri_data, ri_data, ri_data});output_write_dot_file(dot_file,{ri_data, ri_data, ri_data});// 坐标递增if (w == P_IMG_WIDTH - 1) beginw <= 0;if (h == P_IMG_HEIGHT - 1) beginh <= 0;output_close_dot_file(dot_file); // 关闭输出的对比文件文件// 当图像数据全部写入时,输出 BMP 文件for (i = 0; i < P_IMG_HEIGHT; i = i + 1) beginfor (j = 0; j < P_IMG_WIDTH; j = j + 1) beginbmp_file_write(bmp_file, image_data[i * P_IMG_WIDTH + j]);// 行填充部分 0bmp_paddings_zero(bmp_file, real_width - P_IMG_WIDTH);endendbmp_file_close(bmp_file);#10; $finish; // 仿真结束end elseh <= h + 1; // 填充下一行end else w <= w + 1; // 填充下一列end
end
endmodule