根据任务内容,我开始用wxMEdit查看图片文件,尝试研究计算机眼里的图片

我利用Windows画图制作了这么一张简单的3*4的图片。
里面的颜色都是由0或255组成,便于研究
1.PNG

果然十六进制不是给人类读的
但在上网一番学习后,我也对其有了一定的理解

00行:00-07这8个字节是代表png格式的Header,或者说是png文件的固定开头,这样子电脑才能知道这是个png文件(01-03的ASCII转换过来就是PNG,有点意思)
现在再来了解一个东西:数据块
因为除开png头部分其他都可视作数据块的组成
数据块由下面四个部分组成
名称 | 字节数 | 解释 |
---|---|---|
Length(长度) | 4 | 指明Chunk Data的长度,单位:字节 |
Chunk Type Code(数据块类型码) | 4 | 规定数据块类型,其名称即由ASCII对应字符确定 |
Chunk Data(数据块实际内容) | 可变 | 最重要的内容部分,其长度由Length决定 |
CRC(循环冗余检测) | 4 | 存储用来检测是否有错误的循环冗余码 |
其中,数据块还分为关键数据块和辅助数据块
关键数据块必须存在,辅助数据块可有可无。通常以大写字母开头的为关键数据块,以小写字母开头的为辅助数据块
那么现在我们就跟着上面我做了注释的图来初步了解一些数据块吧
1.IHDR
第00行的08-0B说明了有个数据实际内容长0x0D=13字节的数据块,是什么数据块呢?从接下去的0C-0F可以看出是IHDR数据块
这个数据块中的实际内容储存着这个png文件的一些重要信息
名称 | 字节数 | 解释 | 实例说明 |
---|---|---|---|
Width | 4 | 图片宽度,单位:像素 | 就如原图一样宽度只有3个像素 |
Height | 4 | 图片高度,单位:像素 | 同理,高度是4个像素 |
Bit depth | 1 | 图像深度: 索引彩色图像:1,2,4或8 灰度图像:1,2,4,8或16 真彩色图像:8或16 |
如第一个像素是(255,0,0)纯红,255需要8个bit表示 |
ColorType | 1 | 颜色类型: 0:灰度图像, 1,2,4,8或16 2:真彩色图像,8或16 3:索引彩色图像,1,2,4或8 4:带透明度数据(或称α通道数据)的灰度图像,8或16 6:带透明度数据(或称α通道数据)的真彩色图像,8或16 |
文件中为06 |
Compression method | 1 | 表示压缩算法。目前只支持 0,表示 Deflate/Inflate。Deflate/inflate 是一种结合了 LZ77 和霍夫曼编码的无损压缩算法,被广泛运用于 7-zip,zlib,gzip 等场景。 | 00 |
Filter method | 1 | 代表在压缩前应用的过滤函数类型,目前只支持0 。过滤函数类型 0 里面包括了 5 种过滤函数。 | 00 |
Interlace method | 1 | 代表图片数据是否经过交错,0代表没有交错,1代表交错。 | 00即没有交错 |
这个数据块的实际内容到此结束,共13字节正好,接下来4个字节是CRC校验,CRC校验暂且不是研究重点,知道有这个东西得占4个字节就好。
然后这个IHDR数据块就到此结束,让我们来看下一个数据块
2.sRGB
同理,从20行的01-04可以看出有一个数据实际内容长1个字节的数据块,再往后读四个字节05-08可以看出是sRGB数据块
这个数据块用于表示图片的色彩空间
对于这1个字节的数据实际内容,有
值 | 表达 |
---|---|
00 | 表示感性的,用于展示照片等 |
01 | 表示相对色彩,用于展示图标等 |
02 | 表示饱和的,用于展示图表等 |
03 | 表示绝对色彩,用于展示图片原本的色彩 |
文件中的数据实际内容为00
之后4字节的0A-0D为CRC校验,这个数据块就到这里结束了
3.gAMA
可知它的实际内容长4个字节。它的功能与亮度调整,色彩管理,图像处理等方面有关。这个我们不细究。
CRC校验后数据块结束
4.pHYS
它的实际内容长9个字节,它规定的是图像的物理大小
前四个字节规定X轴上每个单位长度的像素数,后四个字节规定Y轴上每个单位长度的像素数,最后一个字节规定单位是什么
例图中,x轴上每个单位长度有0x1274=4724个像素
y轴上每个单位长度有0x1274=4724个像素
单位是什么呢?,下一个字节是0x01,它对应的单位是米。
也就是一米里塞4724个像素
随后CRC检验,数据块结束
5.IDAT
这个数据块是整个PNG最重要的部分,因为它储存着每个像素块的信息。毕竟没有像素块让计算机把图从何渲染起?
但PNG没有那么单纯,里面的信息是经过算法压缩的,所以储存的不是原始数据。不然里面就应该有一堆0x00和0xFF了
但事实上没有
通过一番折腾,我通过AI弄到了能够解码这个数据块的python程序,跑了一下

抄出来
0x | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 0A | 0B | 0C | 0D | 0E | 0F |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
00 | 00 | FF | 00 | 00 | FF | 00 | FF | 00 | FF | 00 | 00 | FF | FF | 02 | 00 | FF |
01 | 00 | 00 | FF | 01 | FF | 00 | 00 | FF | 00 | 00 | 01 | 00 | 00 | 00 | FF | FF |
02 | FF | FF | 00 | 00 | 00 | 00 | 00 | 02 | FF | FF | FF | 00 | 00 | 00 | 00 | 00 |
03 | 00 | 00 | 00 | 00 |
在前面我们知道,这个图片的色深是8bit,每个像素的组成是RGBA
每个行的起头是一个字节的过滤器字节,其代表过滤器的类型
过滤器字节 | 过滤器类型 | 过滤方式 |
---|---|---|
0x00 | 无 | 保留原始数据 |
0x01 | 减 | 减去A |
0x02 | 上 | 减去B |
0x03 | 平均 | 根据A,B取平均,并向下取整 |
0x04 | Paeth | 使用最接近于 p = A + B − C 的 A、B 或 C 的数值 |

对于每一行像素,格式都是
1 | (过滤器)[(R,G,B,A),(R,G,B,A),(R,G,B,A)...(R,G,B,A)] |
了解了这些前置内容,我们就可以开始正式分析图片像素了
第一行像素
1 | (00)[(FF,00,00,FF),(00,FF,00,FF),(00,00,FF,FF)] //其中A的FF表示完全不透明 |
可以知道,这一行没有过滤器,
像素从左到右依次为红,黄,蓝,均不透明。与原图相符
第二行像素
1 | (02)[(00,FF,00,00),(FF,01,FF,00),(00,FF,00,00)] |
过滤器0x02表示与上面相减,那么我们通过把这一行与上一行相加就可以得到原始数据
1 | (xx)[(FF,FF,00,FF),(FF,00,FF,FF),(00,FF,FF,FF)] //其中FF+01溢出变为00 |
就可以得到这一行的像素是
黄,紫,青,均不透明。与原图相符
第三行像素
1 | (01)[(00,00,00,FF),(FF,FF,FF,00),(00,00,00,00)] |
过滤器0x01表示与左侧相减,其中第一个像素是原始数据(因为左侧没有像素)
那么从第二个像素开始,将每个像素的现数据与左侧像素(原数据)相加即可得到原始数据
1 | (xx)[(00,00,00,FF),(FF,FF,FF,FF),(FF,FF,FF,FF)] |
显然是黑,白,白,均不透明。与原图相符
第四行像素
1 | (02)[(FF,FF,FF,00),(00,00,00,00),(00,00,00,00)] |
过滤器0x02表示与上面相减,通过把这一行与上一行(原始)相加即可得到原始数据
1 | (xx)[(FF,FF,FF,FF),(FF,FF,FF,FF),(FF,FF,FF,FF)] |
很明显,这一行是白,白,白,均不透明。与原图相符
至此,四行像素我们都解析完毕了,均与原图相符。之后4个字节的CRC校验,数据块结束
(这一部分真的折腾了我好几天QAQ)
6.IEND
PNG文件的最后12个字节一定是0x[00 00 00 00 49 45 4E 44 AE 42 60 82]
我们仍可以用数据块的角度去看待它
0x[00 00 00 00]说明这个数据块实际内容长度为0。这个数据块的作用是标志文件结束,自然不需要什么内容
0x[49 45 4E 44]说明这个数据块是IEND
那既然实际内容长度为0,那就是没有实际内容
所以接下去的4个字节是CRC校验。因为前面的8个字节固定,所以CRC校验的4个字节也是固定的
就能知道PNG一定是以这12个字节结束
至此,PNG部分就算结束
2.BMP
同样一张图片,用Windows画图将其转换为BMP格式,用wxMEdit打开它。同样的,研究其数据结构

对其做上标记,使其更好阅读

我用方括号括出不同的数据块,在其中用横线或是尖括号标出其中的子块(触摸板画图不好操控,能看就行)
BMP的阅读方式比较怪,但总结一下就是
字节外从右往左,字节内从左往右
例如[AB CD]在BMP中要读作是[CD AB]
(这玩意我瞪了半天才明白,因为我发现像素一直对不上)
但不同的数据块我们仍然以从左往右的顺序进行分析
我们先将这个BMP分成三个大块
1.文件头(bit map file header)
2.位图信息头(bitmap information)
3.位图数据(pixel data)
(有些BMP文件还会出现调色板(color palette)数据块,但这里没有,我们先略过)
1.文件头
这一部分是BMP的固有开头,占14字节,其中记录了这些信息(蓝色方括)
字段名 | 大小(字节) | 描述 |
---|---|---|
FileType(文件类型) | 2 | 描述其文件类型,固定为[42 4D],对应ASCII中的BM |
FileSize(文件大小) | 4 | 记录文件大小,单位为KB,文件中为[66 00 00 00]即0x66=102KB(查看文件属性,这个文件确实大小为102KB) |
Reserved(保留位) | 2 | 2位保留位预留给图片渲染应用,初始化必须为0(说实话我也不懂这是什么意思,但查到的资料就是这么说的) |
Reserved(保留位) | 2 | 同上 |
PixelDataOffset(从头到位图数据的偏移) | 4 | 例图中的值为0x36=54,即记录在位图数据开始前一共有多少个字节(你可以看一下,位图数据的第一个字节是第3行第7个(紫色方括号),也就是在位图数据开始前有0x36个字节) |
文件头数据块就到此结束
2.位图信息头
(橙色方括)
字段名 | 大小(字节) | 描述 |
---|---|---|
HeaderSize | 4 | 信息头大小,单位是字节,例图中为0x28=40字节 |
ImageWidth | 4 | 描述图片宽度,单位是像素,例图中为0x03=3符合事实 |
ImageHeight | 4 | 描述图片高度,单位是像素,例图中为0x04=4符合事实 |
Planes | 2 | 目标设备说明颜色平面数,总被设置为1 |
BitsPerPixel | 2 | 定义每个像素需要多少个bit储存,一般有1、2、4、8、16、24、32这几种。例图中为0x18=24,因为三原色中每种颜色是0-255占8bit,一个像素就需要3*8=24bit。(这里没有透明度了) |
Compression | 4 | 图像的压缩类型,最常用的就是0(BI_RGB),表示不压缩(再压缩我又得折腾半年) |
ImageSize | 4 | 位图数据的大小,当用BI_RGB格式时,可以设置为0。例图中是0x30=48符合其大小 |
XpixelsPerMeter | 4 | 水平分辨率,单位是像素/米,有符号整数。例图为0x1274=4724 |
YpixelsPerMeter | 4 | 垂直分辨率,单位是像素/米,有符号整数.例图为0x1274=4724 |
TotalColors | 4 | 位图使用的调色板中的颜色索引数,为0说明使用所有 |
ImportantColors | 4 | 对图像显示有重要影响的颜色索引数,为0说明都重要 |
到此,位图信息头结束
3.位图数据
我用紫色方括号括了起来
为什么里面会有尖括号和划线之分呢?难道像素数据不都一个样吗?
因为BMP存在在一个机制
比特填充(bit padding)
BMP扫描引擎的最小单位是4字节,所以每一行的字节数必须是4的倍数,若有不足的部分会用0x00填充
例图中每一行有三个像素,即3*3=9个字节,不是4的倍数,所以每一行会有三个0x00填充进去以达到一行12个字节,即使它不表达任何东西
如果我们从左往右看像素块(绿尖括)的话,那么它在图像内对应的顺序是从左到右,从下到上。
在一个像素内(绿尖括内),我们可以理解为字节外从右往左,字节内从左往右的RGB排列
也可以理解为正常从左往右的BGR
毕竟这两者一模一样,怎么理解是个人主观的问题
总之,通过位图数据的内容,我们可以轻松还原出图像的内容
红色 | 绿色 | 蓝色 |
---|---|---|
黄色(R+G) | 紫色(R+B) | 青色(G+B) |
黑色 | 白色 | 白色 |
白色 | 白色 | 白色 |
至此,我们成功解读完了这个BMP文件的内容
3.JPEG
同样用Windows画图将例图保存为JPEG

呜哇,看起来比前面两种都长都复杂
先让我们了解数据段的结构。其实跟PNG的数据块蛮类似的
以下是数据段的一般结构
名称 | 长度(字节) | 说明 |
---|---|---|
段标识 | 1 | 这一个字节固定为0xFF,它标志的数据段的起始 |
段类型 | 1 | 用这一个字节说明这是什么类型的数据段 |
段长度 | 2 | 这两个字节会记录'段长度'+'段内容'的长度,单位是字节 |
段内容 | '段长度'-2 | 记录数据段的实际内容,但是它的长度一定会≤65533字节 |
有些数据段会只有段标识和段类型这两部分,如SOI和EOI
由于JPEG的压缩方式过于复杂,我在历经几天的折腾之后,仍不理解它的压缩原理以及解码方式。所以这里我只简单地标记数据块,并只作简单的讲解,无法涉及算法内容

1.SOI
这个数据段只有段标识和段类型两块组成,固定为0x[FF D8]。这也是JPEG文件的固定开头
2.APP0
这个数据段一个分成如下几个部分
名称 | 长度(字节) | 说明 |
---|---|---|
段标识 | 1 | 固定为0xFF |
段类型 | 1 | E0即为APP0 |
段长度 | 2 | 例图中为0x0010=16,如果n不为0的话则是(16+3*n)D |
以下为段内容 | ||
交换格式 | 5 | 例图为'JFIF',即例图中0x[4A 46 49 46 00]的ASCII对应字符(其中0x00是空字符) |
主版本号 | 1 | 例图中为0x01,暂且不深究版本之间的区别 |
次版本号 | 1 | 例图中为0x01,暂且不深究版本之间的区别 |
密度单位 | 1 | 0=无单位 1=像素/英寸 2=像素/厘米 例图中为0x02 |
X像素密度 | 2 | 水平方向的密度 ,例图中为0x[00 2F],代表47像素/厘米(?我也不是很理解) |
Y像素密度 | 2 | 垂直方向的密度 |
缩略图X像素 | 1 | 缩略图水平像素数目 ,例图中为0x00(应该就是没有缩略图) |
缩略图Y像素 | 1 | 缩略图垂直像素数目 ,例图中为0x00(应该就是没有缩略图) |
RGB缩略图 | 3*n | 例图中不存在这一段,所以n=0。当前两段数据均大于0时,这段才会存在,这时n=缩略图像素总数='缩略图X像素'*'缩略图Y像素' |
JFIF是JPEG File Interchange Forma的缩写,即JPEG文件交换格式。另外还有TIFF等格式,但很少用
像素密度的含义我也不是很理解
因为我把这张图放大64倍后用尺子在屏幕上量宽是3.4-3.5厘米之间,与像素密度不符(相符的话应该在4cm出头)。可能是指在打印机上的密度?
关于缩略图这东西,我查到的资料说是大部分JPEG都没这东西,也就是n一般都为0,所以就先不深究了
此外,一些JPEG图片可能包含APPn数据段(这里n不是上面那个n,是在1-E之间取值的一个数),它的段类型字节是0xEn。手机照片通常包含APP1,会记录时间,地点等信息。
3.DQT
这个图片文件中有两个DQT,我们都放在这里一起研究
名称 | 长度(字节) | 说明 |
---|---|---|
段标识 | 1 | 固定为0xFF |
段类型 | 1 | DQT为0xDB |
段长度 | 2 | 其值为3+n,例图两处DQT中n=1,所以两处段长度均为0x43 |
以下为段内容 | ||
QT信息 | 1 | 以两位十六进制数的视角去看这一个字节,其中高位代表QT精度,低位代表QT编号。当高位是0H时代表精度是一个字节,如果不是0H的话精度就是两个字节。例图中第一个DQT数据段中这一字节是0x00,即QT精度为1字节,QT编号为0。第二个DQT数据段中这一字节是0x01,即QT精度为1字节,QT编号为1 |
QT内容 | n | 其中n=64*‘QT精度’=(0x40)*‘QT精度’,其中QT精度的单位是字节。例图中两处DQT数据段QT精度均为1字节,所以两处n=64=0x40 |
这个数据段应该是与压缩和解码有关,但我目前还不理解
4.SOF
名称 | 长度(字节) | 说明 |
---|---|---|
段标识 | 1 | 固定为0xFF |
段类型 | 1 | 0xC0 |
段长度 | 2 | 例图中为0x0011即17字节 |
以下为段内容 | ||
样本精度 | 1 | 单位为bit,例图中为8,大多数软件不支持12和16。样本是单个像素的颜色分量,或者说一个样本就是一个组件。所以下面的组件ID,采样系数,量化表号等每个都占1个字节 |
图片高度 | 2 | 例图中为4,符合实际 |
图片宽度 | 2 | 例图中为3,符合实际 |
组件数量 | 1 | 1代表灰度图,3代表YCbCr/YIQ彩色图,4代表CMYK彩色图 我想这个组件数量是指色彩的组件数量,如灰度图只有亮度一个组件,所以只有黑白。也可以说RGB,YCbCr,YIQ是三个组件,CMYK是四个组件 |
以下部分会重复'组件数量'次 | ||
组件ID | 1 | 1代表Y, 2代表Cb, 3代表Cr, 4代表I, 5代表Q |
采样系数 | 1 | 把这一字节当成两位十六进制数来看,高位代表垂直采样系数,低位代表水平采样系数 |
量化表号 | 1 | 这个不是很理解有什么用 |
5.DHT
这玩意我到处查到的信息说是定义huffman表。但要我说出具体是干什么用的。。。有生之年吧,反正肯定跟压缩算法有关
名称 | 长度(字节) | 说明 |
---|---|---|
段标识 | 1 | 固定为0xFF |
段类型 | 1 | 0xC4 |
段长度 | 2 | 其值为19+n |
以下为段内容 | ||
HT信息 | 1 | JPEG文件里有2类Haffman
表:一类用于DC(直流量),一类用于AC(交流量)。一般有4个表:亮度的DC和AC,色度的DC和AC。最多可有6个。 其中高位代表HT类型,0为DC表,1为AC表。低位代表HT号 |
HT位表 | 16 | 这16个数相加应≤256 |
HT值表 | n | n是HT位表里16个数的和 |
6.SOS
名称 | 长度(字节) | 说明 |
---|---|---|
段标识 | 1 | 0xFF |
段类型 | 1 | 0xDA |
段长度 | 2 | 其值=6+2×'扫描行内组件数量' |
以下为段内容 | ||
扫描行内组件数量 | 1 | 必须属于[1,4],通常为3,例图中也为3 |
以下部分会重复'扫描行内组件数量'次 | ||
组件ID | 1 | 1代表Y, 2代表Cb, 3代表Cr, 4代表I, 5代表Q |
Huffman表号 | 1 | 看作两位十六进制 高位表示AC表号属于[0,3] 低位表示DC表号属于[0,3] |
循环体结束 | ||
剩余部分 | 3 | 查到的资料表示作用未知 |
7.图片压缩数据
它确实不是常规数据块的格式。但暂且我也无法理解JPEG的压缩及解码方式,所以先略过
8.EOI
JPEG文件以0x[FF D9]这两个字节标志其结束
JPEG我暂且还只能写出这么多。如果我真的能理解它的解码方式的话我会补充
我尝试询问GPT,它一直说不出具体的解码方法。但它能在使用pillow库的情况下用Python写出解码程序
1 | from PIL import Image |
我尝试运行,得到输出
1 | Pixel at (0, 0): R=254, G=0, B=9 |
很明显,JPEG的压缩是有失真的