之前学习了许多和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)如下:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
`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文件如何进行相应的变换等。

