Preface
之前做了一些涉及对后缀为.JPEG格式的图片的解析工作,虽然最终证明是努力错了方向,但是对.JPEG图片解析也学到一些,整理一下,以备不时之需,同样也希望给有这个需求的人一定帮助。
XMP&Exif
这两种一般是我们常见的所谓的图片的“信息”,一般情况下,对与原始图片,我们单击右键属性,弹出来的相关信息即是储存在这两个文件中间。
Exif(Exchangeable image file format)与XMP(Extensible Metadata Platform)可以简单的理解XMP存储比Exif更多的信息,很多信息可以以XMP的形式存储于图片中,使用python3,两者分别可以使用exifread,以及libxmp进行相应信息读取:
#get the exif info from the image
def exif(infile):import sysimport exifreadimage_content = open(infile, 'rb')tags = exifread.process_file(image_content)if tags is None:print('Could not open ' + infile)sys.exit(1)image_content.close()return tags
#read the relevant xmp informationdef xmp_info(infile):from libxmp import XMPFiles, conststry:xmpfile = XMPFiles(file_path=infile, open_forupdate=True)xmp = xmpfile.get_xmp()return xmpexcept:print('Unable to get xmp information')
对于Exif信息,可以使用piexif进行相应编辑,例如可以将exif信息进行转移:
import piexif
piexif.transplant(src, dst)
可以参考包的相关描述:
piexif.load(filename) - Get exif data as dict.
piexif.dump(exif_dict) - Get exif as bytes.
piexif.insert(exif_bytes, filename) - Insert exif into JPEG, or WebP.
piexif.remove(filename) - Remove exif from JPEG, or WebP.
piexif.transplant(filename, filename) - Transplant exif from JPEG to JPEG.
Marker
大部分的时候,对图像的一些简单信息提取,或者相应编辑,使用exif以及xmp中的metadata足以应付。对于图像更深度信息提取,需要对图像本身的结构做一定了解。所有的文件,不管是图片还是其他,都是数据,对JPEG文件而言,其使用了一些特定的字符来进行其数据的标记,即所谓的markers,一副图片的完整数据结构如下所示:
SOI(0xFFD8)代表Start Of Image,因此,所有的图片数据开始的字符均是FFD8。
EOI(0xFFD9)代表End Of Image,一般图片的结尾应该是FFD9,但可能存在需要将一些额外信息写入图片,这种情况下,在FFD9之后还会存在其他markers。
对于不同markers代表意思。详见此文章:List of JPEG Markers。
狭义上,我们所关注的图片为图中红色部分,而广义部分则是包括header+payload,而我们所谓的exif或者xmp其实就是其头信息的一部分。而不同的markers(即图片中括号内FFD8等)是由2个字节组成。读取图片并以16进制显示:
#read images by bytes and return as in Hexadecimal Systemdef read_bytes(image): with open(image, 'rb') as f:data = f.read()return data.hex()
在传统一般的ffd8-ffd9外还有ffe2—ffe5四个marker段,这四个markers即常被称为APP2-APP5(application segment),这四段一般为自定义段,一般的都是用户写入一些自定义的信息或者扩展信息。而我么熟悉的exif或者xmp一般即写在ffe1内,关于头文件内部的详细一些介绍,可以查看这篇文章:JPEG文件格式解析(一)Exif与JFIF。
JPEG图片解析代码:
def image_parse(image):# read image and save as bytes in a listwith open(image, 'rb') as f:image_data = f.read()image_data = ['%x' % image_data[i] for i in range(len(image_data))]# index = ['d8', 'e1', 'db', 'c4', 'c0', 'da', 'd9', 'e2', 'e3', 'e4', 'e5']# parse the image markers and save in dictionarytagmarker = dict()tag = ''tag_start = Falsedata_start = Falsefor 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'):#if b in index: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)# list the markersfor tag, arr in tagmarker.items():s = len(arr)if s == 0:print(tag)continueprint("{}:\t{}\tbytes".format(tag, s))# pdb.set_trace()return tagmarker
上述代码需要一定的优化,并非所有ff代表的就是marker开始,使用该方法粗略找到之后,可以再进行一次筛选(即自定义index即可以)。或者进行相应的优化即可。(由于这个也不是工作重点,所以代码也没进行普遍性优化),另:这段代码是在哪里copy来的,也不太记得了,所以就没参考了。
下图即为实际图片解析后结构:
ffd8
ffe1: 9364 bytes
ffdb: 432 bytes
ffc4: 1382 bytes
ffc0: 69 bytes
ffda: 228997 bytes
ffd9
ffe2: 180 bytes
ffe3: 655382 bytes
ffe4: 2561 bytes
ffe5: 32540 bytes
Refer
- 图像元数据(Metadata) 及 Exif信息分析
- piexif · PyPI
- python修改图片exif信息
- JPEG头部解析
- JPEG文件格式解析(一) Exif 与 JFIF
- List of JPEG Markers
- Corrupt JPEG Files and how to fix them