# 图像数据的序列化与反序列化
# 1.Python中输入输出流
Python的io
模块提供了Python
中处理各种类型输入输出的主要功能,Python
中处理的主要有3
种类型的io
,分别是
text I/O
,binary I/O
和raw 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
格式的。下面是通过python
的builtin.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
,LF
3 (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
只解析像素数据,所以只能从像素数据(同png
中IDAT
部分)的字节缓存中恢复图像,不能直接使用png
中读取的数据,因其包含有辅助数据等。可以解析Image.tobytes
或numpy.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个字符。
# 参考资料
- 1.https://docs.python.org/3/library/io.html (opens new window)
- 2.https://zh.wikipedia.org/zh-hk/PNG#%E6%96%87%E4%BB%B6%E7%BB%93%E6%9E%84 (opens new window)
- 3.https://tool.oschina.net/commons?type=4 (opens new window)
- 4.https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.frombuffer (opens new window)
- 5.https://docs.python.org/3/library/base64.html (opens new window)
← 单应矩阵 方向梯度直方图HOG →