# 图像数据的序列化与反序列化

# 1.Python中输入输出流

Python的io模块提供了Python中处理各种类型输入输出的主要功能,Python中处理的主要有3种类型的io,分别是
text I/O,binary I/Oraw I/O。这三种类型的具体对象都是文件对象,有时也被称为类文件对象。各种流对象处理不同的文件类型。例如,往binary流对像中写入str类型的对象会报类型错误TypeError,同样往text流对象中写入binary类型的内容时也会报错。

text流对象主要处理str类型文件的读取和写入,当文件是以字节为单位存储时,通过text流对象可以使得文件的编解码和如换行符等的跨平台支持变的无感。最简单的创建text流对象的方式是通过open函数,如

import io
f = open('text.txt', 'r', encoding='utf8')
# 在内存中的字节流可以通过io.StringIO来创建
f = io.StringIO("some text")

binary I/O用来处理类字节对象,并返回一个字节对象bytes object,Python中的bytes类和str差不多,只是在定义时引号前面加b,如bytes_data = b'data',bytes对象只支持ASCII码字符,任何二进制码超过127的字符在bytes对象中必须以转移序列来表示。类字节对象bytes-like object是支持缓存协议Buffer Protocol (opens new window)且能输出一个C语言连续缓存的对象,binary I/O不会自动对文件进行编解码和换行符转换。binary I/O用来处理非文本数据和或者想要手动控制文本数据的处理时。创建binary I/O对象的最简单方式是:

f = open("myfile.jpg", "rb")
# 从内存中读取
f = io.BytesIO(b"some initial binary data: \x00\x01")

# 2.png文件格式简介

png图片格式是可携式网络图形P ortable N etwork G raphics,PNG)是一种支援无损压缩的点阵图图形格式,支援索引、灰度、RGB三种颜色方案以及Alpha通道等特性。PNG的开发目标是改善并取代GIF作为适合网络传输的格式而不需专利许可,PNG于1997年3月作为知识性RFC 2083发布,于2004年作为ISO/IEC标准发布2 (opens new window)

png文件中,按字节读取后可以看到,开头的8个字节码总是十六进制数89 50 4E 47 0D 0A 1A 0A,这正是png格式文件的署名,是用来标识png格式的,所以只是修改了文件的后缀名,软件还是能够知道图片是png格式的。下面是通过pythonbuiltin.open函数按bytes对象读取png文件后得到的输出部分,

p = "sample.png"
with open(p,'rb') as f:
    img_data = f.read()
    print(img_data)
# b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x0...'

可以看到前8个字节分别是\x89PNG\r\n\x1a\n,十六进制数0x50,0x4e,0x47,0x0d,oxoa分别对应的ASCII码正是P,N,G,CR,LF3 (opens new window)

png中定义了两种数据块,一种是PNG档案必须包含、读写软件也都必须要支援的关键块(critical chunk);另一种叫做辅助块(ancillary chunks),PNG允许软件忽略它不认识的附加块。

关键数据块中有4个标准数据块:

  • 档案头数据块IHDR(header chunk):包含有图像基本信息,如宽高等,作为第一个数据块出现并只出现一次,其长度为固定的13个字节:

    描述

    长度

    图片宽度

    4字节

    图片高度

    4字节

    图像深度

    1字节

    颜色类型

    1字节

    压缩方法

    1字节

    过滤方式

    1字节

    扫描方式

    1字节

    如上面打开的图像的IHDR\x00\x00\x00\xb6\x00\x00\x00\x86可知图像宽高为\x00\x00\x00\xb6即182和\x00\x00\x00\x86即134。

  • 调色盘数据块PLTE(palette chunk):必须放在图像数据块之前。

  • 图像数据块IDAT(image data chunk):储存实际图像数据。PNG数据允许包含多个连续的图像数据块。

  • 图像结束数据IEND(image trailer chunk):放在档案尾部,表示PNG数据流结束。

辅助数据块:常见的有:

数据块识别符号 数据块含意 数据块位置限制
cHRM 基色和白色点数据块 放在PLTE和IDAT之前
gAMA 图像γ数据块 放在PLTE和IDAT之前
pHYs 物理像素尺寸数据块 放在IDAT之前
tIME 图像最后修改时间数据块 无限制
tEXt 档案基本讯息数据块 无限制

# 3.PIL库解析图像数据4 (opens new window)

  • PIL.Image.open(fp, mode='r', formats=None)

fp参数支持图像文件路径,如string/Path/文件对象。对于普通的png文件,可以通过PIL.Image.open(filename)函数来直接读取,

from PIL import Image
p = "sample.png"
# 1.read from data path
img = Image.open(p)
# 获取图像的exif信息
print(img.getexif())

# 2.read from file object
with open(p) as f:
     img_b = io.BytesIO(f.read())
     img = Image.open(img_b)
  • PIL.Image.frombuffer(mode, size, data, decoder_name='raw', *args)

基于字节缓存来创建一幅图像,值得注意的是frombuffer只解析像素数据,所以只能从像素数据(同pngIDAT部分)的字节缓存中恢复图像,不能直接使用png中读取的数据,因其包含有辅助数据等。可以解析Image.tobytesnumpy.tobytes(order='c'))生成的结果来构造图像。 同样还有Image.frombytes函数。

import numpy as np

with open(p) as f:
     img_b = io.BytesIO(f.read())
     img = Image.open(img_b)

# 1.from Image.tobytes
img_bytes = img.tobytes()
img = Image.frombuffer('RGB', (182, 134), img_bytes)

# 2.from numpy.tobytes
fb = io.BytesIO()
fb.write(img_bytes)
img_bytes_buffer = fb.getbuffer()
img_bytes = np.frombuffer(img_bytes_buffer,dtype=np.uint8).reshape(182, 134, -1)
img_bytes = img_bytes.tobytes(order='C')img = Image.frombuffer('RGB', (182, 134), img_bytes)

# 4.opencv解析图像

同样,使用OpenCV读取图像,可通过imread,也可以使用decode方法从内存数据中解析,譬如png格式的文件,可以先通过builtin.open读取成字节流,再使用np.frombuffer(bytes, dtype=uint8)构造图片的缓存数据后使用imdecode方法得到图像数据。

from PIL import Image
p = "sample.png"
with open(p, 'rb') as f:
     img_bytes_data = f.read()
     img_buffer = np.frombuffer(img_bytes_data, dtype=np.uint8)
     img = cv2.imdecode(img_buffer, cv2.IMREAD_COLOR)
     cv2.cvtColor(img, cv2.COLOR_RGB2BGR, img)
     plt.imshow(img)

当然这里只是一个示例,正常cv2.imdecode肯定不是通过这种方式使用,通常与cv2.imencode结合使用,用于图像数据的传递。

# 5.labelme标注文件中的img_data数据解析

import base64
import cv2
import numpy as np
from PIL import Image
from matplotlib import pyplot as plt

json_path = "sample.json"
data = json.load(open(json_path))
data = data["imageData"]
bytes_data = base64.b64decode(data)
# 1.use Image to reconstruct image
bd = io.BytesIO(bytes_data)
img = Image.open(bd)

# 2.use cv2.imdecode to reconstruct image
img_data = np.frombuffer(bytes_data, dtype=np.uint8)
img = cv2.imdecode(img_data, cv2.IMREAD_COLOR)
cv2.cvtColor(img, cv2.COLOR_RGB2BGR, img)
plt.imshow(img)

图像数据保存到json文件中时,为了可视化的方便,通常对图像数据使用base64进行了编码,因此解析json中的img_data数据时要先使用base64解码得到图像数据的字节码,再使用numpy.frombuffer(bytes, dtype=uint8)方法创建cv2.imdecode能够解析的内存块数据,cv2.imdecode解析的是完整的图像数据而非只有像素数据,包括图像的宽高创建日期等信息,所以不需要额外的信息即可恢复图像。base64模块支持Base16/Base32/Base64/Base85编解码方法5 (opens new window),其作用主要是将二进制数据编码成可打印的ASCII码,以Base16算法为例,Base16编码会将字节切为4个一组,所以此编码后会用到2个字符,数据会变为原来的2倍。类推,Base32编码会将字节切为 5 个一组,每 5 个字节可以重组为8个字符。

(adsbygoogle = window.adsbygoogle || []).push({});

# 参考资料