手把手 bmp 格式编解码(二)—— 徒手拆解 bmp 图像

1. BMP 图像数据结构图

  下图是从 Wikipedia 上找到的 BITMAPV5HEADER 数据结构图,画的非常全面。

  上篇博客中说过,windows BMP 格式各个版本都只是在前一版本结构末尾追加字段,因此 BITMAPV5HEADER 的结构图完全可以覆盖前面各个版本的 windows BMP 格式数据结构。后面我们可以对照这个结构图来解剖一张 BMP 图片以加深对于其数据结构分布的理解。

  原图传送门:BMP file format

BITMAPV5HEADER
  接下来我们可以找几张图片验证一下上面的结构图。找了一圈,手头的 BMP 图基本都是 Windows V1 BMP 格式的,那就先用两张 Windows V1 BMP 格式的图做一下讲解,掌握基本原理之后,对照上面的数据结构图,理解其他版本的 BMP 数据结构也不难。可以使用 Hex Editor NeoBeyond Compare 的工具,以十六进制的方式打开 BMP 图片来查看其源数据。

2. 徒手拆解 BMP 图像(位深 8 bit,带调色板)

  如下图所示为 图像处理标准测试图集 中找到一张 Masuda1.bmp 图;

  这张图像的比特深度为 8 比特,且正好是一张彩色图像,因此可以查看一下他的调色板;

Masuda1.bmp

2.1 位图文件头(Bitmap file header)

  注意,bmp 图片文件头中数据是以小端序存储的,因此我们阅读时,需要做一下调整。

  例如,文件类型占两个字节,我们将 前两个字节 数据(0x42 和 0x4d)取出,从后往前读,那么文件类型就是 0x4d42

Masuda1_file_header

序号字节数变量名字段名数据(HEX)数据(DEC)描述
12bfType文件类型0x4D4219778字符显示就是‘BM’
24bfSize文件大小0x0003C436246,838‬检查文件信息,验证一致
32bfReserved1保留字段 10x00000保留字段,必须设置为 0
42bfReserved2保留字段 20x00000保留字段,必须设置为 0
54bfOffBits数据偏移量0x000004361,078即偏移 1,078 字节开始为图像数据

2.2 DIB 头(Device Independent Bitmap Header)

Masuda1_file_DIB_header

序号字节数变量名字段名数据(HEX)数据(DEC)描述
64biSizeDIB 头大小0x0000002840即 DIB 头大小为 40 字节
74biWidth图像宽度0x00000200512图像的宽度为 512 像素
84biHeight图像高度0x000001E0480图像的高度为 480 像素
92biPlanes颜色平面数0x00011目标设备说明颜色平面数,总被设置为 1
102biBitCount每个像素的位数0x00088每个像素的比特深度为 8 比特
114biCompression压缩类型0x000000000(BI_RGB)表示不压缩
124biSizeImages图像数据大小0x0003C000245,760图像大小 = 文件大小 - 偏移量
134biXPelsPerMeter水平分辨率0x000000000表示未知或不适用
144biYPelsPerMeter垂直分辨率0x000000000表示未知或不适用
154biClrUsed调色板大小0x00000100256表示调色板中有 256 种颜色
164biClrImportant重要颜色数0x00000100256表示对图像显示有重要影响的颜色数目

  此处注意,除 图像宽度(biWidth)图像高度(biHeight)水平分辨率(biXPelsPerMeter)垂直分辨率(biYPelsPerMeter) 外,其他变量均为无符号整数,而以上四个则是 整型数(int),即可以为负的:

  • 图像宽度(biWidth)
      如果 biWidth 值为 正数,即图像是 从左向右排列 的,那么每行第一个像素就位于最左侧。
      如果 biWidth 值为 负数,即图像是 从右向左排列 的,那么每行第一个像素就位于最右侧。

  • 图像高度(biHeight)
      如果 biHeight 值为 正数,即图像是 从下到上排布 的,那么第一个像素就位于文件的左下角。
      如果 biHeight 值为 负数,即图像是 从上到下排布 的,那么第一个像素就位于文件的左上角。

  • 水平分辨率(biXPelsPerMeter)
      当 biXPelsPerMeter 为 正数 时,表示 水平分辨率为每英寸的像素数
      当 biXPelsPerMeter 为 负数 时,表示 水平分辨率为每米的像素数

  • 垂直分辨率(biYPelsPerMeter)
      当 biYPelsPerMeter 为 正数 时,表示 垂直分辨率为每英寸的像素数
      当 biYPelsPerMeter 为 负数 时,表示 垂直分辨率为每米的像素数

2.3 调色板(Color Table)

  图像的 比特深度(Bit Depth) 取值有这么几种:1、2、4、8、16、24、32;而 调色板(color table) 则是比特深度小于等于 8 的图像文件所特有的,相对应的调色板大小是 2、4、16 和 256,调色板以 4 字节为单位,每 4 个字节存放一个颜色值,图像的数据是指向调色板的索引。

  调色板是颜色的索引,这里使用的是 8 位色图,共有 256 种颜色。每种颜色由 RGB 三原色再加上 Alpha 值(透明度)组成,需要 4 个字节来表示。256 种颜色并不能涵盖所有的颜色,因此需要一个索引,即用 1 个字节的索引指向 4 个字节表示的颜色。

  所以 调色板就像一个二维数组 table[N][4],其中 N 是颜色的数量(这里是 256)。因此,调色板的大小就是 256 * 4 = 1024 字节。在调色板之前,有 14 字节的 bmp 文件头,40 字节的位图信息头,加上 1024 字节的调色板,一共 1078 字节。真正的图像数据前面有1078字节,这和 bmp 文件头中的数据偏移量相符。

Masuda1_color_table
  这张图的调色板从第 0x00003006 比特开始,每 4 个字节为一个颜色,256 个颜色,共计 1024 个字节。每个颜色又分为 B G R A(注意顺序) 四个通道,因此我们可以列一张表直接将调色板解析出来。此处我们可以拿前四个颜色举个例子,解析如下:

序号颜色数据(HEX)B 通道(蓝色)G 通道(绿色)R 通道(红色)A 通道(透明度)
10xB2B5E5000xB2 [178]0xB5 [181]0xE5 [229]0x00 [0]
20x9CB6E6000x9C [156]0xB6 [182]0xE6 [230]0x00 [0]
30xA5B1E6000xA5 [165]0xB1 [177]0xE6 [230]0x00 [0]
40x9CADED000x9C [156]0xAD [173]0xED [237]0x00 [0]

2.4 图像数据(Image Data)

  这是一张带有调色板的 8 bit 图,因此每个像素数据占一个字节,其内容是调色板的色号索引,我们可以举个例子,更直观的去认识调色板的工作原理;我们可以在图像二进制文件中找到第一个像素点和最后一个像素点的数据,手动将其解析出对应的颜色,与图像相应位置的颜色作比对。

  如下图所示,我们找到第一个像素点的数据为 0x8D,即该像素点的颜色应为调色板中第 0x8D 号颜色。可以计算出这个四字节颜色分量相对于文件 0 位置的偏移为 offset = 0x8D * 4 + 0x36 = 0x26A,我们在文件中找到该颜色,根据 B G R A 的顺序,并借助画图软件还原出来,可以得到该颜色如右下角的方块所示。

  由于这张图的 biWidthbiHeight 均为正数,所以 第一个像素点 应该位于文件的 左下角,我们找到该像素点进行比对,结果一致。

Masuda1_FirstPixel

  如下图所示,我们找到最后一个像素点的数据为 0x4D,即该像素点的颜色应为调色板中第 0x4D 号颜色。可以计算出这个四字节颜色分量相对于文件 0 位置的偏移为 offset = 0x4D * 4 + 0x36 = 0x16A,我们在文件中找到该颜色,根据 B G R A 的顺序,并借助画图软件还原出来,可以得到该颜色如右下角的方块所示。

  由于这张图的 biWidthbiHeight 均为正数,所以 最后一个像素点 应该位于文件的 右上角,我们找到该像素点进行比对,结果一致。

Masuda1_LastPixel

2.5 小结

  解析完整张图片后,我们可以再计算一下文件的大小:

数据块BMP 文件头DIB 头调色板图像数据填充字节文件整体(总计)
字节数14401,024245,7600246,838

3. 徒手拆解 BMP 图像(位深 24 bit,不带调色板)

  如下图所示为 图像处理标准测试图集 中找到一张 LenaRGB.bmp 图;

  下面这张图像的比特深度为 24 比特,我们可以看一下不带调色板的图像其像素数据是如何解析的;

LenaRGB.bmp

3.1 位图文件头(Bitmap file header)

LenaRGB_file_header

序号字节数变量名字段名数据(HEX)数据(DEC)描述
12bfType文件类型0x4D4219778字符显示就是‘BM’
24bfSize文件大小0x000C0038786,488检查文件信息,验证一致
32bfReserved1保留字段 10x00000保留字段,必须设置为 0
42bfReserved2保留字段 20x00000保留字段,必须设置为 0
54bfOffBits数据偏移量0x0000003654即偏移 54 字节开始为图像数据

3.2 DIB 头(Device Independent Bitmap Header)

LenaRGB_file_DIB_header

序号字节数变量名字段名数据(HEX)数据(DEC)描述
64biSizeDIB 头大小0x0000002840即 DIB 头大小为 40 字节
74biWidth图像宽度0x00000200512图像的宽度为 512 像素
84biHeight图像高度0x00000200512图像的高度为 512 像素
92biPlanes颜色平面数0x00011目标设备说明颜色平面数,总被设置为 1
102biBitCount每个像素的位数0x001824每个像素的比特深度为 24 比特
114biCompression压缩类型0x000000000(BI_RGB)表示不压缩
124biSizeImages图像数据大小0x0003C000245,760图像大小 = 文件大小 - 偏移量
134biXPelsPerMeter水平分辨率0x0000B88147,233水平方向每英尺像素数为 47,233 个像素
144biYPelsPerMeter垂直分辨率0x0000B88147,233垂直方向每英尺像素数为 47,233 个像素
154biClrUsed调色板大小0x000000000无调色板
164biClrImportant重要颜色数0x000000000无重要颜色

3.3 图像数据(Image Data)

  这张图像的 bit 深度为 24,表示每个像素占 3 字节,由三个 8 bit RGB 分量组成,其排布顺序应该是按照 B G R 顺序排列的。全图共计 512 x 512 = 262144 个像素,因此图像数据占据了 262144 x 3 = 786432 字节。其起始位置位于偏移量为 54 字节的位置。如下图所示,为图像数据的起始部分。

LenaRGB_Image_Data
  以下是头部像素点的数据解析:

序号颜色数据(HEX)B 通道(蓝色)G 通道(绿色)R 通道(红色)
10x3916520x39 [57]0x16 [22]0x52 [82]
20x3916520x39 [57]0x16 [22]0x52 [82]
30x3E20600x3E [62]0x20 [32]0x60 [96]
40x3E1C5D0x3E [62]0x1C [28]0x5D [93]

  为了更直观的理解其原理,我们还是选取第一个和最后一个像素点进行手动解析,然后与原图中的像素点颜色进行对比。
  第一个像素点,在原图中位置位于图像 左下角,其数据偏移为 0x00000036 如下图所示:

LenaRGB_First_Pixel
  最后一个像素点,在原图中位置位于图像 右上角,其数据偏移为 [(512 x 512) - 1] x 3 + 54 = 7864830x00C0033 如下图所示:

在这里插入图片描述
  对比之后,以上两个像素点均与原图中一致。

3.4 填充字节

  为保证文件的兼容性和 CPU 读写效率,BMP 文件格式通常要求像素数据的每一行字节数必须是 4 的倍数,但是某些 BMP 文件并不完全遵循这个规则。这通常是因为它们使用了不同的压缩方式或文件格式。比如,8 位深度的 BMP 图像使用的是索引颜色,而不是直接存储像素的 RGB 值。例如上面提到的 Masuda1.bmp,文件数据总大小为 246,838 字节,并不是 4 的倍数。在这种情况下,每个像素只需要占用一个字节,因此不需要填充字节来调整行的大小。

  另一方面,24 位深度的 BMP 文件中,每个像素需要占用 3 个字节来存储 RGB 值。如果每一行的字节数不是 4 的倍数,则需要添加填充字节来调整行的大小以确保像素数据能够正确存储。填充字节的数量可以通过以下公式计算:

padding = (4 - (width * bpp) % 4) % 4

  其中,width 表示该行像素的宽度(单位为像素),bpp 表示每个像素的位数(单位为 bit)。例如,一张 24 bit 深度的 BMP 图像,宽度为 100 像素,则每行像素数据占用的字节数为:

bytes_per_row = (100 * 24) / 8 = 300

  因为 300 不是 4 的倍数,所以需要添加填充字节。根据公式,填充字节的数量为:

padding = (4 - (300 % 4)) % 4 = 2

  因此,每行像素数据占用的总字节数为 302,其中包括 300 个像素数据字节和 2 个填充字节。

  现在看上面我们解析的 LenaRGB.bmp 图,按照原本的像素数据排列,虽然一行数据有 512 个像素,每个像素 3 字节存储,每行图像占有 1356 个字节是 4 的倍数,不需要填充字节,但是,文件总大小是 786,486 个字节 并非 4 的倍数,显然不符合每行四字节,所以在文件最后,我们会看到有两字节的 0 数据进行填充:

在这里插入图片描述

3.5 小结

  解析完整张图片后,我们可以再计算一下文件的大小:

数据块BMP 文件头DIB 头调色板图像数据填充字节文件整体(总计)
字节数14400768,4322786,488
  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 很高兴听到您对RISC-V处理器的设计感兴趣。以下是一些基本步骤,可以帮助您开始设计自己的RISC-V处理器: 1. 确定处理器的需求和规格:在设计处理器之前,您需要确定处理器的需求和规格。这包括处理器的性能、功耗、指令集等方面。 2. 学习RISC-V指令集架构:RISC-V是一种开放的指令集架构,您需要学习它的指令集和编码方式。您可以在RISC-V官方网站上找到相关的文档和教程。 3. 设计处理器的微架构:微架构是处理器的实现细节,包括指令流水线、寄存器文件、ALU等。您需要设计一个合适的微架构来实现RISC-V指令集。 4. 编写Verilog代码:Verilog是一种硬件描述语言,您需要使用Verilog编写处理器的代码。您可以使用Verilog模拟器来验证您的代码是否正确。 5. 进行综合和布局布线:综合是将Verilog代码转换为门级电路的过程,布局布线是将门级电路布置在芯片上的过程。您可以使用EDA工具来完成这些步骤。 6. 进行仿真和验证:最后,您需要对处理器进行仿真和验证,以确保它能够正确地执行RISC-V指令集。 希望这些步骤可以帮助您开始设计自己的RISC-V处理器。祝您好运! ### 回答2: 随着计算机技术的不断发展,处理器作为计算机的中央处理单元,一直处于不断更新和迭代的状态。在这个过程中,越来越多的人开始将目光投向自己动手设计处理器的领域,以提高对计算机结构的理解和掌握能力。而RISC-V处理器则成为了越来越受欢迎的处理器设计体系结构之一。下面,我们就来手把手教你设计RISC-V处理器。 首先,需要了解RISC-V处理器的体系结构和指令集,掌握其特点,以便更好地进行设计。RISC-V架构采用精简指令集(Reduced Instruction Set Computing,RISC)的思想,指令集清晰简单,易于扩展和实现,同时提供了不同的指令长度和地址宽度,满足多种应用场景的需求。 其次,需要明确设计RISC-V处理器的目的和需求。例如,设计一款高性能处理器,需要考虑运算速度、处理带宽、低功耗等方面的需求,而设计一款嵌入式处理器,则需要考虑尺寸、功耗、集成度等方面的需求。在确定需求后,可以选择适合的设计方法和实现方式。 接着,需要进行设计和仿真。采用硬件描述语言(如Verilog或VHDL)进行设计,利用仿真软件进行仿真调试,逐步完善处理器的各项功能。需要注意的是,设计时需要清晰明确每一阶段的功能和相应的接口,保证设计的可扩展性。 最后,进行硬件实现和验证。将设计好的RTL电路转换为FPGA或ASIC中的物理实现,进行性能测试和功能验证,发布仿真测试结果和设计文档,确保设计能够满足预期的性能和功能要求,并能够进一步优化和升级。 在以上步骤中,需要掌握的知识包括计算机体系结构、数字电路设计、硬件描述语言的使用等。需要长期的学习和实践,才能够熟练掌握处理器设计的各个环节,并能够设计出具备高性能、低功耗、灵活可扩展等特点的处理器。 ### 回答3: RISC-V是一个由加州大学伯克利分校推出的开源指令集架构,它的设计理念是简化指令集,更加注重可扩展性、可定制性和易于实现。设计RISC-V处理器需要了解计算机体系结构以及数字电路原理,下面将手把手教你设计CPU。 第一步,需要确定处理器的架构。RISC-V处理器一般采用五级流水线结构,包括取指、译码、执行、访存和写回。在这个流水线结构中,每个阶段都有对应的功能,可以保证指令的按序执行。 第步,需要确定指令集架构。RISC-V有基础指令集和标准扩展指令集,需要根据使用需求选择相应的扩展指令集并实现相应的操作。 第三步,需要进行处理器的逻辑设计。包括指令寄存器(IR)、程序计数器(PC)、指令存储器(IM)、寄存器堆、ALU(算数逻辑单元)、数据存储器(DM)等,这些模块通过总线相互连接构成处理器的基本结构。 第四步,需要进行数字电路的设计。处理器逻辑的实现需要用到器件和电路,需要根据设计的结构和功能实现相应的数字电路。 第五步,进行验证和调试。在设计完成后,需要进行仿真验证和调试工作,以保证设计的正确性和稳定性。 总的来说,设计RISC-V处理器需要掌握计算机体系结构、数字电路原理和基础编程知识,需要进行详细、全面的规划和设计。设计过程中需要不断地验证和调整,确保设计的正确性和稳定性,最终完成一个高质量且符合需求的处理器设计。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值