1. 问题
阿里云PAI平台官方Stable Diffusion(以下简称sd)镜像EAS服务上传PNG图片调用/sdapi/v1/png-info接口返回乱码。
镜像版本:eas-registry-vpc.cn-shanghai.cr.aliyuncs.com/pai-eas/stable-diffusion-webui:4.2
./webui.sh --listen --port 8000 --skip-version-check --no-hashing --skip-prepare-environment --api-log --time-log --nowebui --xformers --enable-extensions stable-diffusion-webui-wd14-tagger multidiffusion-upscaler-for-automatic1111 adetailer sd-webui-3d-open-pose-editor --embeddings-dir /code/stable-diffusion-webui/data-oss-api/embeddings
请求异常返回截图:
测试图片用例:
2. 系列文档传送门
https://developer.aliyun.com/article/1574263
3. 环境搭建与问题复现
3.1. webui测试用例图片生成
同是sd问题,我们通过sd的webui界面配合中英文的promt和相关参数来生成测试用例的png图片尝试调用API
promt:a cute dog
Negative prompt: 不要屋内的景色
其余参数保持默认(见下图)
生成测试用例PNG图片:
3.2. webui png图片信息测试
3.3. api请求本地测试与问题复现
3.3.1. 测试代码
这里用相同的镜像拉了一个EAS的服务,测试代码由于涉及多个path的请求所以自己通过requests构建了自定义http请求,相关代码说明见注释。
import json import requests import io import base64 from pprint import pprint from PIL import Image, PngImagePlugin from PIL.PngImagePlugin import PngInfo # eas服务请求的endpoint url = "http://1623027246845796.cn-hangzhou.pai-eas.aliyuncs.com/api/predict/sunyf_sd_test02.sunyf_sd_test02_9245177a" if __name__ == '__main__': # 打开测试用例图片,二进制读 with open('/Users/adamsun/Downloads/cute_dog.png', 'rb') as image_file: # 采用base64编码 encoded_string = base64.b64encode(image_file.read()) # 根据编码后的string构建请求的png信息 png_payload = { "image": "data:image/png;base64," + encoded_string.decode('utf-8') } # headers中包含认证的token headers = {"Authorization": 'xxx=='} # api请求 response2 = requests.post(url=f'{url}/sdapi/v1/png-info' ,json=png_payload ,headers=headers ) pprint(response2.json())
3.3.2. 本地测试用例(正常)
3.3.3. 客户部分代码节选
可以看到客户代码中对于http请求的header以及entity以及png图像的都通过charset的方式指定了utf-8(与eas底层所在系统保持一致)
(image客户采用的是
data:image/png;base64,<编码图像数据>
的方式进行数据传输和接口请求)
注:base64编码后decode的charset也是utf-8
3.3.4. 本地测试客户图例(乱码)
4. 源码浅析
通过登录eas的容器我们可以找到当前eas内置的sd服务4.2版本的Dockerfile
对应源码地址:https://gitee.com/stable_diffusion/stable-diffusion-webui/tree/v1.6.0/。我们通过git clone的方式将源码下载到本地。
书接上文,api的请求都是通过fastapi做的封装,如下是入口类
from modules.api.api import Api
通过调用的api path我们可以看到
self.add_api_route("/sdapi/v1/png-info", self.pnginfoapi, methods=["POST"], response_model=models.PNGInfoResponse)
/sdapi/v1/png-info对应的handler方法是pnginfoapi,对应的返回体是models.PNGInfoResponse
对应方法pnginfoapi中,比较关键的是如下三个方法的调用:
- decode_base64_to_image(req.image.strip())
- images.read_info_from_image(image)
- infotext_utils.parse_generation_parameters(geninfo)
decode_base64_to_image方法的输入是api请求的request对象,将json string中的image作为入参。根据入参的样式分成两部分:
- https和http开头的连接会通过请求的方式获取图片并转换成BytesIO
- data:image/开头的会取data:image/png;base64,<编码图像数据>后面编码后的数据转换成BytesIO
随后通过sd项目中实现的./stable-diffusion-webui/modules/images.py 的modules.images.read方法封装成pillow库的Image.Image对象
images.read_info_from_image(image)方法对读入的Image对象做信息提取
items取的就是Image对象的info属性
geninfo取info属性字段中parameters的信息
后续代码还涉及了EXIF、GIF、pop掉忽略的kv以及NovelAI类型图像的信息处理
infotext_utils.parse_generation_parameters(geninfo)方法以前面提取出的图像中的info作为输入,返回的是api返回体中parameters的部分,这里主要是解析与图像生成相关参数的部分,包含promt、negative prompt、steps、sampler等,没有字符集相关的特殊处理。
return models.PNGInfoResponse(info=geninfo, items=items, parameters=params)
后续pnginfoapi方法返回的response继承自BaseModel的基础数据类,提供数据验证以及序列化支持,charset也都是utf-8。
可以判断的是从接收到请求到api接口处理完成对于png图片info均没有不合预期的编码行为。
5. 本地调试与问题原因
请求客户端、服务端处理逻辑以及服务端所在机器默认charset均没有异常的情况下尝试本地调试。
根据源码读取请求图片数据以及转换成Image对象的逻辑,本地直接采用
Image.open
的方法读取客户提供的图片,根据read_info_from_image的逻辑来看图片本身的info信息中就已经含有乱码,与客户侧验证后确认也确实仅有这一张第三方图片解析乱码异常,猜测应该是图片保存时环境或其他因素存在编码异常,非PAI平台侧问题