项目目标
用户上传一个图片,接收到之后进行数组操作,然后返回处理好的图片,要求全部在内存中完成
项目分析
微信本身给的上传接口的curl写的命令
curl -F media=@{image_name} "https://api.weixin.qq.com/cgi-bin/media/upload?access_token={access_token}&type=image"
如果要在命令行里输入图片名字就必须经过文件系统,不能满足全内存操作的要求,所以必须curl命令转requests请求
https://curlconverter.com/
通过这个网站可以轻松把curl转化成requests,然后一看结果:
import requests
files = {
'media': open('{image_name}', 'rb'),
}
response = requests.post('https://api.weixin.qq.com/cgi-bin/media/upload?access_token={access_token}&type=image', files=files)
然后我就笑了,这还是得经过文件系统,没办法,得自己做一个二进制流。
files = {'media': BytesIO(image_bytes)}
response = requests.post(url, files=files)
这样是不行的,返回错误信息
{'errcode': 40005, 'errmsg': 'invalid file type hint: [vIQHea01094248] rid: 66193184-3f53f282-7178c05f'}
为了检测到底是curl转化requests的有问题,还是BytesIO有问题,我试了一下经过文件系统的例子,是可以的。于是想办法找区别,首先是open函数返回值是一个BufferedReader,我就想着包一层
f1 = BufferedReader(BytesIO(image_name, image_bytes), image_bytes.__sizeof__())
很明显这样也是不行的,返回值还是错误,我又print了一下两种句柄的区别
f1 = BytesIO(image_bytes)
f2 = open(f'{image_name}', 'rb')
print(f1, '\n', f2)
<_io.BufferedReader>
<_io.BufferedReader name='./tmp/6777248.png'>
哦,原来是少了name这个property,那我加上。
嘿,你猜怎么着,没法加,BytesIO的name的readonly,无法修改,那我不管,重新派生一个类。
class NamedBytesIO(io.BytesIO):
def __init__(self, name, *args, **kwargs):
super().__init__(*args, **kwargs)
self.name = name
<_io.BufferedReader name='./tmp/4365267.png'>
<_io.BufferedReader name='./tmp/4365267.png'>
这下打印出来名字了,但还是报错啊,没上传成功,然后我就气得跳脚了。
我就想啊,为什么保存到文件系统里就行,内存里直接发比特流就不行?
有没有可能保存的那一下有情况?
image_name = './tmp/' + str(random.randint(1000000, 9999999)) + img_suffix
# cv2.imwrite(image_name, image)
image.save(image_name)
image.save是不是把图片给变化了?
猛然回想,看他报错是什么?
{'errcode': 40005, 'errmsg': 'invalid file type hint: [vIQHea01094248] rid: 66193184-3f53f282-7178c05f'}
文件不合法?难道我bytes不是图片形式?
然后我突然想到我处理图片的逻辑里,是先要把jpeg类型转化为向量类型,操作像素的的时候是np.ndarray,但是这玩意肯定不是jpeg类型,压缩算法之后的数据肯定不一样。
def cut_white_border(image_bytes: bytes):
image_pil = Image.open(BytesIO(image_bytes))
if image_pil.format == 'PNG':
img_suffix = '.png'
elif image_pil.format == 'JPG':
img_suffix = '.jpg'
else:
return 'error'
# image_mode = image.mode
image_array = np.array(bytearray(image_bytes), dtype=np.uint8)
image = cv2.imdecode(image_array, cv2.IMREAD_UNCHANGED)
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
所以最后我还得把像素信息压缩回jpeg格式
yes, image_tobytes = cv2.imencode('.jpg', image.astype(np.uint8))
好了,解决了,花了我俩小时,我真是蠢啊。
{'type': 'image', 'media_id': '6Pym7GRK0gDsNikc6iUlsLcFD3I1cQuyEAAJZz9y6CUwh5-znSw7biPXbqOhLmEv', 'created_at': 1712927076, 'item': []}
最后解决的时候是这样的,要加上一个名字,否则还是会有问题,name这个property是有用的。
files = {'media': (image_name, BytesIO(image_bytes))}
response = requests.post(url, files=files)
我没试自己派生的那个类是否可以通过,我感觉是可以的,不过这就是requests库内部的处理了,传入tuple可以是2,3,4三种情况,细节就请进去看注释吧。