之前学习了许多和Verilog文件操作相关的函数,比如$fdisplay,$fwrite,$fseek,$ftell,$feof,$fgetc,$fgets,$fread等,本节课将结合BMP图像的特点,练习使用其中几个函数操作BMP文件。
1. BMP格式
在开始使用Verilog文件操作函数之前,先简单介绍一下BMP文件的格式。BMP(bitmap,位图),也通常被称为DIB(device independent bitmap,与设备无关的位图)。BMP文件是独立于显示器的位图数字图像文件格式(关于图像色彩的内容,比如RGB24,详见文章 图像色彩空间转换 。)。BMP是Windows系统常用的图像存储格式,通常以扩展名 “ *.bmp ” 的方式出现。因为BMP文件格式与设备无关,所以BMP文件经常在设备之间传送。
BMP文件格式如下表所示:
表1:
| 结构名称 | 可选/必选 | 尺寸 | 目的 | 描述 |
|---|---|---|---|---|
| 位图文件头(Bitmap file header) | 必选 | 14 个字节 | 存储有关位图图像文件的一般信息 | 文件加载到内存后不需要 |
| DIB 文件头 | 必选 | 固定大小 (存在 7 个不同的版本) |
存储有关位图图像的详细信息并定义像素格式 | 紧跟在位图文件头之后,最通用的DIB文件头为BITMAPINFOHEADER,长度为40个字节 |
| 额外的位掩码(Extra bit masks) | 可选 | 3 或 4个 DWORD (12 或 16 字节) |
定义像素格式 | 仅在 DIB 标头是BITMAPINFOHEADER且 Compression Method 成员设置为 BI_BITFIELDS 或 BI_ALPHABITFIELDS 的情况下出现 |
| 颜色表(Color table) | 可选 | 可变 | 定义位图图像数据(像素数组)使用的颜色 | 强制颜色深度( color depth)≤ 8 位 |
| 间隙1(Gap 1) | 可选 | 可变 | 结构对齐 | 位图文件头中的文件偏移到像素阵列的工件 |
| 像素阵列(Pixel array) | 必选 | 可变 | 定义像素的实际值 | 像素格式由 DIB 标头或额外位掩码定义。Pixel 数组中的每一行都被填充为 4 字节大小的倍数 |
| 间隙2(Gap 2) | 可选 | 可变 | 结构对齐 | DIB 头中的 ICC 配置文件数据偏移字段的工件 |
| ICC 颜色配置文件(ICC color profile) | 可选 | 可变 | 定义颜色管理的颜色配置文件 | 还可以包含指向包含颜色配置文件的外部文件的路径。当作为“非压缩 DIB”加载到内存中时,它位于颜色表和 Gap1 之间。 |
- BMP文件头应该是应用程序首先读取的,来确保该文件确实为BMP文件并且没有被损坏,可以正常读出。BMP文件的总长度从BMP文件头的偏移量0x2开始,共两个字节。
- DIB文件头告诉应用程序有关图像的详细信息,用来在屏幕上显示图像,一般常用的BITMAPINFOHEADER格式下,DIB文件头的长度为40个字节。
- 颜色表是一个字节表,通常以RGB24格式指定,用来列出位图使用的颜色,以便在颜色显示能力有限的设备上进行优化,便于将来转换为不同的像素格式和颜色表。
- 像素阵列是以32-bit为单位排列,逐个像素的描述图像(宽度不足4个字节的BMP图像需要加上padding)。图像像素描述的规律一般是从下到上,从左到右,逐行从图像的底部到顶部。
以一个8*8(没有padding)的BMP图像为例来说明BMP格式:
图1 BMP图像
使用HEX 格式打开该BMP文件,如下所示:

图2 HEX格式打开BMP文件
分析该文件如下表所示:
表2:
| 偏移量 | 大小(字节) | HEX值(16进制表示) | 描述 |
|---|---|---|---|
| 位图文件头(Bitmap file header) | |||
| 0h | 2 | 42 4D | ID |
| 2h | 4 | F6 00 00 00 | BMP文件的大小(54 字节的文件头 + 192字节的像素色彩),54 = 14(位图文件头) + 40(DIB文件头),192 = 8 * 8 * 3(8*8个像素,每个像素使用RGB24描述,3个字节) |
| 6h | 2 | 00 00 | 保留 |
| 8h | 2 | 00 00 | 保留 |
| Ah | 4 | 36 00 00 00 | 可以找到像素阵列(位图数据)的偏移量 |
| DIB 文件头 | |||
| Eh | 4 | 28 00 00 00 | DIB 标头中的字节数(从这一点开始),为40个字节 |
| 12h | 4 | 08 00 00 00 | 位图的宽度(以像素为单位) |
| 16h | 4 | 08 00 00 00 | 位图的高度(以像素为单位)。像素顺序是从下到上的。 |
| 1Ah | 2 | 01 00 | 使用的颜色平面数量 |
| 1Ch | 2 | 18 00 | 每个像素使用的位数,这里因为是RGB24,所以是0x18 |
| 1Eh | 4 | 00 00 00 00 | 不适用像素阵列压缩 |
| 22h | 4 | C0 00 00 00 | 位图数据(像素阵列)的大小,为192个字节 |
| 26h | 4 | C4 E0 00 00 | 图像的打印分辨率,每米3780(0xEC4) 个像素(水平/垂直) |
| 2Ah | 4 | C4 E0 00 00 | |
| 2Eh | 4 | 00 00 00 00 | 调色盘中的颜色数量 |
| 32h | 4 | 00 00 00 00 | 0表示所有的颜色都是重要的 |
| 像素阵列(Pixel array) | |||
| 36h | 3 | 00 00 FF | 红色 |
| 39h | 3 | FF 00 00 | 蓝色 |
| 3Ch | 3 | 13 13 FF | 浅一点的蓝色 |
| 3Fh | 3 | FF 68 68 | 粉红色 |
| … | … | … | … |
| F3h | 3 | 00 FF 00 | 绿色 |
2. BMP实验
实验目的:使用Verilog文件操作函数将BMP文件(假设DIB文件头格式为BITMAPINFOHEADER,图像宽度是4的倍数,不考虑padding,可参见表2)中所有的蓝色成分和红色成分交换,例如,某个像素的RGB值为0xff0000,处理后输出该像素为0x0000ff。如下图所示为RGB24的格式中蓝色成分和红色成分的位置:

图3 RGB24格式
- 实验代码(仿真Testbench)如下:
`timescale 1ns / 1ps
module bmp_test(
);
//original bmp file
localparam FILE_TXT = "D:/Files_import/2022/0207/test3.bmp";
//new bmp file with color switched
localparam FILE_NEW = "D:/Files_import/2022/0207/pixel_new.bmp";
integer fpre,fnew; // two bmp files
integer i;//debuging purpose only
//parameter to adjust the endian, color and etc.
reg [7:0] fbyte1, fbyte2, fbyte3, fbyte4;
reg [7:0] fbyte [3:0];
integer num;//param used for $fread
reg [31:0] image_size;//param for counting image size
reg [31:0] image_width;//param for counting image width
reg [31:0] image_height;//param for counting image height
initial
begin
i = 0;
fpre = $fopen(FILE_TXT, "rb"); //binary read
fnew = $fopen(FILE_NEW,"wb"); //binary write
if(fpre == 0)
begin
$display("$open file failed") ;
$stop;
end
$display("\n ============= file opened... ============= ") ;
//================= BMP HEADER + DIB HEADER ================
//byte 0-1
num = $fread(fbyte,fpre,0,2);
i = i + 2;
#5;
$fwrite(fnew,"%c",fbyte[0]);
$fwrite(fnew,"%c",fbyte[1]);
//BYTE 0x2-0x5 image size
num = $fread(fbyte,fpre,0,4);
i = i + 4;
$fwrite(fnew,"%c",fbyte[0]);
$fwrite(fnew,"%c",fbyte[1]);
$fwrite(fnew,"%c",fbyte[2]);
$fwrite(fnew,"%c",fbyte[3]);
image_size = {fbyte[3],fbyte[2],fbyte[1],fbyte[0]};
#10;
$display("The total image size is %d\n",image_size) ;
#10;
//Byte 0x6-0x11
while(i < 32'h12)
begin
num = $fread(fbyte1,fpre,0,1);
i = i + 1;
$fwrite(fnew,"%c",fbyte1);
end
//BYTE 0x12-0x15 image width
num = $fread(fbyte,fpre,0,4);
i = i + 4;
$fwrite(fnew,"%c",fbyte[0]);
$fwrite(fnew,"%c",fbyte[1]);
$fwrite(fnew,"%c",fbyte[2]);
$fwrite(fnew,"%c",fbyte[3]);
image_width = {fbyte[3],fbyte[2],fbyte[1],fbyte[0]};
$display("The total image width is %d\n",image_width) ;
#10;
//BYTE 0x16-0x19 image height
num = $fread(fbyte,fpre,0,4);
i = i + 4;
$fwrite(fnew,"%c",fbyte[0]);
$fwrite(fnew,"%c",fbyte[1]);
$fwrite(fnew,"%c",fbyte[2]);
$fwrite(fnew,"%c",fbyte[3]);
image_height = {fbyte[3],fbyte[2],fbyte[1],fbyte[0]};
#10;
$display("The total image width is %d\n",image_height) ;
#10;
//byte 0x20-0x36
while(i < 32'h36)
begin
num = $fread(fbyte1,fpre,0,1);
i = i + 1;
$fwrite(fnew,"%c",fbyte1);
end
//====================== PIXCEL ARRAY ======================
////BYTE 0x36 image height
while($feof(fpre) != 1)
begin
num = $fscanf(fpre,"%c",fbyte1);
num = $fscanf(fpre,"%c",fbyte2);
num = $fscanf(fpre,"%c",fbyte3);
$fwrite(fnew,"%c",fbyte3);// red
$fwrite(fnew,"%c",fbyte2);// green
$fwrite(fnew,"%c",fbyte1);// blue
end
$fclose(fpre);
$fclose(fnew);
$display("\n ============= file closed... ============= ") ;
#100;
$stop;
end
endmodule
- 图像变换演示:
- 例1:8*8 像素的BMP图像
[content_control]
[/content_control]
原图:
图4 8*8 BMP图像
交换红,蓝元素后的图像:
图5 处理后的8*8 BMP图像
-
- 例2:512*286风景图
[content_control]
[/content_control]
原图:
图6 512*286风景图
交换红,蓝元素后的图像:
![]()
图7 处理后的512*286风景图
-
- 例3:316*313雷娜图
[content_control]
[/content_control]
原图:
图8 316*313雷娜图
交换红,蓝元素后的图像:
图9 处理后的316*313雷娜图
3. 总结
本文通过一个简单的例子,使用Verilog文件操作对BMP文件中像素的红,蓝元素进行了替换,学习了BMP文件格式和简单的图像处理。在此基础上,感兴趣的同学可以进行扩展学习,例如对于图像宽度不是4字节对齐,需要考虑padding的情况如何进行处理;例如对于不是RGB24而是RGBA32(使用4个字节而不是3个字节来表示每个像素的色彩模型)的BMP文件如何进行相应的变换等。

