说明
已经写过一些相关的内容,现在完整的串一下。
内容
图片可以以文件形态、字符串形态和矩阵形态存在。
1 文件形态
对于计算机,都是二进制
2 读取一个图片并展示
文件形态下,可以使用特定的包(或者软件)代开一张图片并展示。这里,特定的包已经将二进制数据处理成了我们期望的展示方式。
当图片读入内存后,通常都是矩阵形态, 例如Image和numpy之间就可以方便的转来转去。
3 以二进制字符串方式读取一个图片
要进行传输时,我们通常要「序列化」。可以想象,字符串是序列化的,下面读取原始的字符串信息。
some_fpath = 'tem.png'
with open(some_fpath, 'rb') as f:
file_bin_str = f.read()
可以看到,里面有一些文件的元信息。
4 进行编码
理论上,我们读到了文件的二进制字符串,就可以直接传送了,为什么要编码?主要的目的就是「不带误解」的传输。
由于某些系统中只能使用ASCII字符。Base64就是用来将非ASCII字符的数据转换成ASCII字符的一种方法。Base64其实不是安全领域下的加密解密算法,而是一种编码,也就是说,它是可以被翻译回原来的样子。它并不是一种加密过程。所以base64只能算是一个编码算法,对数据内容进行编码来适合传输。虽然base64编码过后原文也变成不能看到的字符格式,但是这种方式很初级,很简单。
pic_str = base64.b64encode(file_bin_str)
看起来是整齐了,也没有特殊的字符。
为啥还要进行utf-8编码?我没有找到直接的答案,不过个人的理解是方便(从规范的角度)。
因为普通的接口请求都是以utf-8的方式传送字符串的,这样就和习惯对接。当然,其实是可以通过base64直接传送文件的(注意base64的encode还是二进制编码)。这里,二进制字符串要「翻译」给人看,所以叫decode
,这个和base64的b64encode编码不是一回事。
pic_str1 = pic_str.decode('utf-8')
5 传送到服务端
很显然,如果将图片读取为字符串,显然是不是给人看的。
客户端使用request发送
import requests as req
input_dict = {'img_str':pic_str1}
test = req.post(host_ip, json = input_dict)
6 服务端接受
服务端(flask)直接这么解就可以了
input_data = request.get_json()
需要注意的点(一个踩过的坑):最好声明一下header字段里的content-type(默认的请求头是application/x-www-form-urlencoded
), 对应的flask是request.get_data
,这个是不一样的。如果这个搞错了, get_json是None, 而get_data是可以看到数据的。
先在服务端看一下刚才成功的提交请求头
print(request.headers)
Host: ...
User-Agent: python-requests/2.23.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Content-Length: 359451
Content-Type: application/json
所以在客户端,一个标准一些的写法应该是
input_dict = {'img_str':pic_str1}
headers = {'Content-Type':"application/json; charset=utf-8"}
test = req.post(host_ip, json = input_dict, headers = headers)
7 服务端处理
以流的形式在内存中处理
服务端收到数据后,当然可以再存为图片。但如果要处理的话又得从硬盘读回来,而很多数据可能就处理一下(也不必保存),所以一般使用流的方式。
如果只是短时间的重复利用,并不希望长期持久化,而且对速度的要求比较高,这时候就可以考虑缓存。说到缓存,很多朋友就想到redis,熟悉python的朋友还会想到装饰器和闭包函数。
不过python已经原生为我们准备好了类文件对象(file-like object),这种对象在内存中创建,可以像文件一样被操作。
(上面那段话具体可参考这篇文章)
from io import BytesIO
b64_bin_str = input_dict['img_str'].encode('utf-8')
bin_str = base64.b64decode(b64_bin_str)
img_handler = BytesIO(bin_str)
img_in_memory = Image.open(img_handler)
img_in_memory
结果和我们直接打开一个本地文件是相同的。流程为:
- 1 将图片字符串按utf-8重新变为二进制字符串(base64)
- 2 将b64二进制字符串反编码,还原客户端发过来的图片二进制文件源字符串
- 3 将二进制字符串处理(相当于开辟一个内存文件
这步相当于是读取某个路径的图片
),并返回文件句柄(handler) - 4 使用对应的程序包处理文件句柄。
为了便于理解文件句柄,举个例子。这个错误应该都碰到过:文件句柄申请太多导致异常 Too many open files
。简单来说每个文件系统都有一个最大的允许打开文件的数量,系统称为句柄(handler
)。通常情况下我们不会碰到(大约是过一段时间无用句柄会被系统关闭),但是当运行程序时就很关键。所以python甚至出现了with xxx as f: xxx
的语法,这样当使用完毕后句柄会立即关闭。
8 结束
到这里就差不多了,剩下的如果要保存啥的都是反过程。