JPEG 图片存储格式与元数据解析

news/2024/11/24 21:03:48/

1. .jpg, .png, .gif

说到图片,我们首先会想到,几种常见图片格式,如:.jpg, .png, .gif 等。

但当我门在说图片的格式时,除了在说图片文件的后缀不同,还有什么不同呢?

事实上,图片的格式,在技术上,是指图片所遵循的压缩标准。更准确地说,是数字图像的压缩标准(计算机上的图片都是数字图像,即由 0 和 1 构成的二进制数字图像文件)。

可能会有人不明白,为什么图片的格式是压缩标准? 图片为什么要压缩? 难道存储在我们个人电脑的图片都是压缩的?

没错,不管是存储在我们个人电脑,手机,还是在网络上图片其实都是经过压缩后的图片数据。

那么,压缩前的原始图像数据又是什么样的? 以及为什么要对图像进行压缩?

2. 原始图像数据

不管是什么格式,或采用什么样的压缩标准,原始的图像数据其实都是一样的,而且也符合我门直观的理解。

例如,一张 4 × 4 (宽度和高度都是 4 个像素)的彩色图片,未压缩的的原始图像数据,就是一个 4 × 4 矩形网格,每一个网格代表一个像素。

而彩色图片的每一个像素,又是由 红,绿,蓝 三基色构成,如下图右边所示,红绿蓝,对应于 r g b 三个数值,也就是我常说的 RGB 色彩模式。

4乘4 彩色图片,这里进行了放大处理,右边和左边是同一张图,为像素标注了 RGB 数值

RGB,我们在计算机视觉领域,又称为颜色通道,彩色图像有三个通道值,每个颜色通道,都是一个 0~255 的整数值,占用一个字节(Byte)的存储空间。

因此,我们很容易计算上面这张 4×4 彩色图片占用的存储空间为 4 × 4 × 3 = 48 字节 (Bytes) 。换算成我们熟悉的 KB,就是 48 / 1024 = 0.046875 KB,不到 0.1 KB。

事实上,我们很少见到这么小的图片,甚至在我们的个人电脑和手机上,根本无法正常看到这么小的图片。这里为了方便理解和计算,做了技术上的处理,而不是真实看到的图片大小。

拓展:按照在电脑上常用的分辨率 72 ppi (Pixels Per Inch:像素每英寸),即 每 2.54 厘米 容纳 72 个像素,或者说,一个像素占用的屏幕尺寸是 0.35 毫米,那么上面 4 × 4 图片,在屏幕上 1:1 显示,占用屏幕的物理尺寸只有 1.4 × 1.4 毫米。显然,用肉眼是无法看清的。

在理解一张 4 × 4 的彩色图片占用存储空间大小,我们同样的方式计算如下,320 × 320 的彩色图片,这个大小在我们日常生活,也不算一张大图,相当于我们用作微信头像的大小。

320 × 320

我相信我们可以很快得出结果,320 × 320 × 3 = 300 KB ,相当于上面 4 × 4 图片的 6000 多倍。

可能大家觉得这张图片还不算大。我测算,用自己的 iPhone 8 Plus 正常拍摄一张手机照片,它的大小是 3024 × 4032 ,这样一张图片在未压缩的情况下,所占用的存储空间大小是 3024 × 4032 × 3 = 35 MB 。而实际,如下图,在我的 Mac 上看到的图片, 只有 6.8 M ,也就是说,我们在使用手机拍摄照片后,在保存在相册之前,相机程序已经自动对我们拍摄的照片照片进行了压缩,这里的压缩比是 35 / 6.8 = 5,压缩比并不是一个固定值,也就是说同样大小的不同照片,在经过相同的压缩处理后,占用磁盘的空间也是不一样的。

事实上,图像压缩在数字图像处理领域,是应用最为普遍的和成功的,大部分图片查看器,编辑器,网页浏览器,等与图片相关的应用程序,乃至,开发人员使用图片处理库,底层都使用了图像的压缩和解压缩算法,并且对于用户,或者上层的应用开发人员,是完全透明的,以至于我们觉察不到它的存在。

PS: 图像的压缩和解压缩,也称之为,编码和解码,其实是同一个意思,并且适用于数字视频的编解码

3. 图像压缩

如果,大家对上文,将手机拍摄的一张原始图像是 35 MB 压缩保存后是 6.8MB ,没有太大的概念。

那么,我们不妨再用电影举例,一部宽高为 720 × 480(彩色),帧率为 30 帧/秒,时长为 2 小时的电影,其未压缩前的大小是:

720 × 480 × 3 (字节/像素) × 30 (帧/秒) × 3600 (秒/小时) × 2小时 = 209 GB

不考虑音频,电影的画面,本质就是由一张张连续显示的图像构成,每一副图像,我们称之为一帧)

也就是一个 1TB 的移动硬盘,只能装下不到 5 部这样的清晰度一般的电影。这显然是不能接受,也与我们日常生活对电影存储的认知不符。

因此,我们要感到庆幸,对图像和视频的压缩算法,无时无刻不在为我们的数字生活服务。我们没有觉察到,但一定不能忽视它的存在。

3.1 存储在磁盘上真实图像的二进制数据

事实上,图像的压缩或编码,本质就是为了解决图像在存储和网络传输过程的空间消耗,让有限的磁盘和网络带宽,存储和传送海量的数字图像和视频提供了技术后盾。

那么压缩后的图片数据到底长啥样?

我们依然使用前文用到的那只可爱的 小狗狗 图片,它在我电脑上文件名为 dog.jpeg。

我们知道,不同于普通文本文件,图片在计算机里存储形式,是二进制文件。

在 linux 和 MacOS 系统上,我们可以借助一个命令行工具 hexdump 来查看任何二进制文件,包括图片。

读者,可以将下面这张图片 保存到 自己的电脑上。

320 × 320

在命令行界面,进入 dog.jpeg 文件所在目录,运行如下命令:

hexdump dog.jpeg
# 输出结果如下(中间数据已省略,只显示开头和结尾各两行):
# 0000000 ff d8 ff e0 00 10 4a 46 49 46 00 01 01 00 00 48
# 0000010 00 48 00 00 ff e1 00 8c 45 78 69 66 00 00 4d 4d
# ....
# 0004780 04 12 48 f5 a5 70 0b 82 18 7c 8c 30 39 cf 4e be
# 0004790 f5 11 82 30 c3 f7 47 00 12 39 3c 50 08 ff d9 

Tips: 如果想对显示的格式进行控制,可以尝试增加如下选项,格式化显示输出:

hexdump -e '16/1 "%02X "' -e '"\n"' dog.jpeghexdump -e '16/1 "%02X " "  |  "' -e '16/1 "%_p" "\n"' dog.jpeg

我们看到的输出如下图所示(中间内容省略,这里只截取了开头和结尾各两行):

图中,红线框圈住的部分,是图片数据的字节流编址,可以看作是为了查看方便,添加的行号,红框右边的才是图片的真实存储字节流,并且每行显示 16 个字节。当然不管是“行号”还是图片数据,为了显示的简介性,默认都是用了16进制。

这里我忽略红框中的“行号”,只关注图片字节流数据。

这里要注意的是,图中数据是一行行显示的,并且每行中,字节间都有空格,其实,这里还是为了方便查看才这样显示的,真实存储的数据并非一行一行,字节间也没有空格,所谓字节流,就是图片数据字节都是连续不间断的,串成一条线,在程序里,体现为一个一维的字节数组。

为了验证这点,我们不妨用实践说话。

在一台已经安装了Python(MacOS 内置了 Python 2)机器,启动命令行,输入 python 进入 python 交互式编辑环境。使用如下 python 代码,查看 图片 dog.jpeg 的二进制字节流。

with open("dog.jpeg", "rb") as f:image_bytes_data = f.read()image_bytes_data[:16]
image_bytes_data[-16:]

运行输出如下:

image_bytes_data 以字节为单位,保存着图片二进制数据,可以使用切片,查看前 16 个字节 和最后 16 个字节。通过与前文使用 hexdump 查看的数据对比,可以看出是一致的。细心的读者可能发现,在 hexdump 显示结果的最后一行,只有 15 个字节。因此,这里看到最后 16 个字节,是从倒数第二行最后一个字节开始的。

3.2 图像二进制数据格式

我们已经知道如何通过命令行工具 hexdump 和 python 脚本查看图片的二进制数据,并且我们知道这不是图片原始的二维RGB阵列数据,而是经过压缩后,方便存储和网络传输用的一维二进制字节流。

那么这些字节数据,到底代表什么意思,我们使用的图片应用程序如何根据这些数据,解压缩或解码,还原成,计算机显示器可以显示的二维 RGB 像素阵列呢?

本文仅仅对字节流数据组成格式,各部分代表的含义进行简单介绍,以对图片存储数据解码有个基本认识,对于解码部分的完整实现,超出本文的讨论范围,感兴趣,推荐参考专业书籍或开源图片编解码器。

3.2.1 标记数据

首先,还是引用前文的数据截图:

我们注意到用橙色线框框着的两个部分,ff d8 表示图片数据开始,英文缩写 SOI (Start Of Image),ff d9 表示图片数据的结尾,英文缩写 EOI (End Of Image)。

我们不难发现,两者都是以 ff 开头。事实上,图片存储的数据,大体只包含两类数据,一类是 ff 开头,后跟1个字节, 这个字节既不能等于 0 也不能等于 ff,表示不同类型的标记(Marker)数据,而剩下的就是图片的压缩数据或编码数据。

由于标记数据记录着图片的元数据,同时决定了,图片压缩数据如何解码。因此我们重点介绍标记数据。

为了通过编程实践,更好地理解不同的标记数据,我们根据已经掌握的标记数据特点,即,标记数据都是以 ff 开头,后跟一个, 既不能等于 0 也不能等于 ff 的字节 类型。编写如下 python 脚本来提取图片中的标记数据。

Warm Tips:如下代码,请在 python3 环境下运行

将以下 python 脚本复制,保存到文件 view_dog_marker_data.py

# 从磁盘读取图片二进制字节流数据
with open('dog.jpeg', 'rb') as f:image_data = f.read()image_data = ['%x' % image_data[i] for i in range(len(image_data))]# 解析标记数据,保存到字典 tagmarker
tagmarker = dict()
tag = ''
tag_start = False
data_start = False
for i, b in enumerate(image_data):if len(b) == 1:b = '0' + bif b == 'ff':tag_start = Truecontinueif tag_start:if b != 'ff' and (b != '00'):tag = 'ff' + bif not tag in tagmarker:tagmarker[tag] = list()tag_start = Falsedata_start = Truecontinueelse:tag_start = Falsetagmarker[tag].append('ff')if data_start:tagmarker[tag].append(b)# 查看解析后,有那些标记数据,以及对应数据的长度
for tag, arr in tagmarker.items():s = len(arr)if s == 0:print(tag)continueprint("{}:\t{}\tbytes".format(tag, s))

使用 python3 运行脚本:

python view_dog_marker_data.py

输出结果如下:

ffd8
ffe0:	16	    bytes
ffe1:	140	    bytes
ffed:	56	    bytes
ffc0:	17	    bytes
ffc4:	424	    bytes
ffdb:	134	    bytes
ffdd:	4	    bytes
ffda:	457	    bytes
ffd0:	2391	bytes
ffd1:	2129	bytes
ffd2:	2571	bytes
ffd3:	2008	bytes
ffd4:	1936	bytes
ffd5:	1977	bytes
ffd6:	1951	bytes
ffd7:	2058	bytes
ffd9

3.2.2 标记数据字段含义

我们看到,除了已经知道的 ffd8ffd9 分别表示图片的开头和结尾,并且后面没有内容数据,其他我们还不知道的标记数据,后面都有不同长度的内容数据。

下面直接给出,这些标记数据对应字段:

字节码标记符
ffd8SOI*
ffe0 … ffefAPP0 … APP15
ffdbDQT
ffc0 ffc1 ffc2 ffc3SOF0 SOF1 SOF2 SOF3
ffc5 ffc6 ffc7SOF5 SOF6 SOF7
ffc8 ffc9 ffca ffcbJPG SOF9 SOF10 SOF11
ffcd ffce ffcfSOF13 SOF14 SOF15
ffc4DHT
ffccDAC
ffd0 … ffd7RSTm*
ffdaSOS
ffdbDQT
ffdcDNL
ffddDRI
ffdeDHP
ffdfEXP
fff0 … fffdJPG0 … JPG13
fffeCOM
ff01TEM*
ff02 … ffbfRES
ffd9EOI*

为了在前面的脚本基础上,得到字节码对应的标记符,创建如下字典:

tag_map = {"ffd8": "SOI","ffc4": "DHT",'ffc8': "JPG",'ffcc': "DAC","ffda": "SOS","ffdb": "DQT","ffdc": "DNL","ffdd": "DRI","ffde": "DHP","ffdf": "EXP","fffe": "COM","ff01": "TEM","ffd9": "EOI"
}for i in range(16):tag = 'ffe%x' % itag_map[tag] = 'APP'+str(i)for i in [0, 1, 2, 3, 5, 6, 7, 9, 10, 11, 13, 14, 15]:tag = 'ffc%x' % itag_map[tag] = 'SOF'+str(i)for i in range(8):tag = 'ffd%x' % itag_map[tag] = 'RST'+str(i)for i in range(14):tag = 'fff%x' % itag_map[tag] = 'JPG'+str(i)map_tag = { v: k for k, v in tag_map.items() }

通过字典,将字节码替换成标记字段,查看解析结果:

for tag, arr in tagmarker.items():s = len(arr)if tag in tag_map:tag = tag_map[tag]if s == 0:print(tag)continueprint("{}:\t{}\tbytes".format(tag, s))

输出结果如下:

SOI
APP0:	16	    bytes
APP1:	140	    bytes
APP13:	56	    bytes
SOF0:	17	    bytes
DHT:	424	    bytes
DQT:	134	    bytes
DRI:	4	    bytes
SOS:	457	    bytes
RST0:	2391	bytes
RST1:	2129	bytes
RST2:	2571	bytes
RST3:	2008	bytes
RST4:	1936	bytes
RST5:	1977	bytes
RST6:	1951	bytes
RST7:	2058	bytes
EOI

到此为止,我们完成了对图像存储数据的初步解析,进一步解析出各标记数据的详细信息,以及图片压缩数据,期待后续更新。

附件

下图为 国际电信联盟 (INTERNATIONAL TELECOMMUNICATION UNION) 发布的静态图像数字压缩和编码规范中,关于标记码的分配表,也是本文解析图片标记数据,的参考依据。感兴趣,可以通过此表,了解到本文未详尽的内容,如关于标记数据的描述说明。

参考

  • [1] T.81 page 34
  • [2] JPEG File Interchange Format

坚持写专栏不易,如果觉得本文对你有帮助,记得点个赞。感谢支持!

  • 个人网站: https://kenblog.top
  • github 站点: https://kenblikylee.github.io
  • 掘金: https://juejin.im/user/5bd2b8b25188252a784d19d7
  • 知乎: https://www.zhihu.com/people/kenbliky/posts


微信扫描二维码 获取最新技术原创


http://www.ppmy.cn/news/682398.html

相关文章

我用python给我的可爱小邻居做了一个猫狗识别系统~

嗨害大家好鸭~ 我是小熊猫❤ 时不时有一些猫猫狗狗天使般出现~ 又天使般回到了他们的星球 今天就来为我之前的可爱小邻居写个猫狗识别系统吧 真的是小天使啊👼 这篇文章中我放弃了以往的model.fit()训练方法, 改用model.train_on_batch方法。 两种方…

【python--爬虫】百度图片爬虫

如何快速收集某个关键字的图片呢?有的小伙伴可能会说百度图片,bingo答对了!o( ̄▽ ̄)o,博主本次就讲解下如何爬取百度图片 环境准备 为了优雅的享用这盘python爬虫大餐,请各位读者大大准备要以下的内容哦&a…

可爱的小狗们

我属狗,也非常喜欢狗,这里发几张漂亮的小狗图片,收藏一下,呵呵. 转载于:https://blog.51cto.com/cdmatong/7537

和谐生活图片欣赏:狗狗 猫猫 我

成员介绍: 狗狗: 名字:miki 年龄:11个月 性别:MM 性格:温和,有时候会很淘气,经常不让我上网,我一动鼠标,她就用嘴拱我的手。 猫猫: 名字&#xff…

【知识星球】猫猫狗狗与深度学习那些事儿

欢迎大家来到《知识星球》专栏,这两天有三AI知识星球会更新一些猫狗相关的数据集和任务,可爱的猫猫狗狗对深度学习可是做出了不少的贡献呢。 作者&编辑 | 言有三 有三AI知识星球-数据集 Oxford-IIIT Pet Oxford-IIIT Pet Dataset是一个猫狗数据集&am…

万物皆可AI,狗狗也不例外

文/ 智能相对论(ID:aixdlun) 作者/ 离离 ​ 近年来,科技的进步让富豪们对宠物有了新的选择。除了具有名贵血统的猫狗和赛马,“饲养”带有尖端科技的AI机器狗似乎也正在形成一种风潮。 四月二十九日,梅耶…

【阶段四】Python深度学习06篇:深度学习项目实战:卷积神经网络进行狗狗图像分类项目

本篇的思维导图: 项目背景 应用Keras框架构建卷积神经网络进行狗狗图像分类的预测,以及模型的优化。主要用来熟悉Keras卷积层、池化层网络的使用以及模型的优化方法。 数据获取 本次建模数据来源于网络,数据项统计如下: 数据集为狗狗数据集,来自全国各地的狗狗图像…

数据结构--栈的引用--前中后缀表达式(前部分)

数据结构–栈的引用–前中后缀表达式(前部分) 常见的算数表达式 由三个部分组成: 操作数、运算符、界限符 \color{red}操作数、运算符、界限符 操作数、运算符、界限符 ps:界限符是必不可少的,反映了计算的先后顺序 波兰表达式(让计算机更容易识别的算数表达式) Reverse Po…