选项 1 - 以字节形式返回图像
以下示例显示如何将从磁盘加载的图像或内存中的图像(numpy 数组)转换为字节(使用 PIL 或 OpenCV 库)并使用自定义 Response 返回它们.出于本演示的目的,以下代码用于创建基于 this answer 的内存示例图像(numpy 数组)。
# Function to create a sample RGB image
def create_img():
w, h = 512, 512
arr = np.zeros((h, w, 3), dtype=np.uint8)
arr[0:256, 0:256] = [255, 0, 0] # red patch in upper left
return arr
使用 PIL
服务器端:
您可以从磁盘加载图片,也可以使用Image.fromarray来加载内存中的图片(注意:出于演示目的,当案例是从磁盘加载图片时,以下演示该操作在路由内部进行。但是,如果要多次提供同一图像,则可以在startup 和store it on the app instance 仅加载一次图像,如this answer 所述)。接下来,将图像写入缓冲流,即BytesIO,并使用getvalue() 方法获取缓冲区的全部内容。即使缓冲的流在超出范围时被垃圾回收,通常也是better to call close() or use the with statement,如图here。
from fastapi import Response
from PIL import Image
import numpy as np
import io
@app.get("/image", response_class=Response)
def get_image():
# loading image from disk
# im = Image.open('test.png')
# using an in-memory image
arr = create_img()
im = Image.fromarray(arr)
# save image to an in-memory bytes buffer
with io.BytesIO() as buf:
im.save(buf, format='PNG')
im_bytes = buf.getvalue()
headers = {'Content-Disposition': 'inline; filename="test.png"'}
return Response(im_bytes, headers=headers, media_type='image/png')
客户端:
下面演示了如何使用 Python requests 模块向上述端点发送请求,并将接收到的字节写入文件,或将字节转换回 PIL Image,如 here 所述。
import requests
from PIL import Image
url = 'http://127.0.0.1:8000/image'
r = requests.get(url=url)
# write raw bytes to file
with open("test.png", 'wb') as f:
f.write(r.content)
# or, convert back to PIL Image
# im = Image.open(io.BytesIO(r.content))
# im.save("test.png")
使用 OpenCV
服务器端:
您可以使用cv2.imread() 函数从磁盘加载图像,或者使用内存中的图像,如果它是RGB 的顺序,如下例所示 - 需要转换为OpenCV uses BGR as its default colour order for images。接下来,使用cv2.imencode() 函数压缩图像数据(基于您传递的定义输出格式的文件扩展名 - 即.png、.jpg 等)并将其存储在内存缓冲区中用于通过网络传输数据。
import cv2
@app.get("/image", response_class=Response)
def get_image():
# loading image from disk
# arr = cv2.imread('test.png', cv2.IMREAD_UNCHANGED)
# using an in-memory image
arr = create_img()
arr = cv2.cvtColor(arr, cv2.COLOR_RGB2BGR)
# arr = cv2.cvtColor(arr, cv2.COLOR_RGBA2BGRA) # if dealing with 4-channel RGBA (transparent) image
success, im = cv2.imencode('.png', arr)
headers = {'Content-Disposition': 'inline; filename="test.png"'}
return Response(im.tobytes() , headers=headers, media_type='image/png')
客户端:
在客户端,您可以将原始字节写入文件,或使用numpy.frombuffer() 函数和cv2.imdecode() 函数将缓冲区解压缩为图像格式(类似于this) - cv2.imdecode() 不需要一个文件扩展名,因为正确的编解码器将从缓冲区中压缩图像的第一个字节推导出来。
url = 'http://127.0.0.1:8000/image'
r = requests.get(url=url)
# write raw bytes to file
with open("test.png", 'wb') as f:
f.write(r.content)
# or, convert back to image format
# arr = np.frombuffer(r.content, np.uint8)
# img_np = cv2.imdecode(arr, cv2.IMREAD_UNCHANGED)
# cv2.imwrite('test.png', img_np)
更多信息
由于您注意到您希望显示图像 - 类似于 FileResponse - 使用自定义 Response 返回字节应该是执行此操作的方法,而不是使用 StreamingResponse。为了向浏览器指示应在浏览器中查看图像,HTTP 响应应包含以下标头,如 here 所述和上述示例中所示(filename 周围的引号是必需的,如果filename 包含特殊字符):
headers = {'Content-Disposition': 'inline; filename="test.png"'}
然而,下载而不是查看图像(使用 attachment 代替):
headers = {'Content-Disposition': 'attachment; filename="test.png"'}
如果您想使用 Javascript 接口(例如 Fetch API 或 Axios)显示(或下载)图像,请查看答案 here 和 here。
至于StreamingResponse,如果numpy数组从一开始就完全加载到内存中,StreamingResponse就完全没有必要了。 StreamingResponse 通过迭代 iter() 函数提供的块来流式传输(如果 Content-Length 未在标头中设置,与 StreamingResponse 不同,其他 Response 类为您设置该标头,以便浏览器会知道数据在哪里结束)。如this answer中所述:
当您不知道数据的大小时,分块传输编码是有意义的
你的输出提前,你不想等待收集它
在您开始将其发送给客户之前找出所有内容。这样可以
适用于提供慢速数据库查询结果之类的东西,但是
它通常不适用于提供图片。
即使您想流式传输保存在磁盘上的图像文件(您不应该这样做,除非它是一个无法放入内存的相当大的文件。相反,您应该使用 FileResponse) , file-like 对象,如open() 创建的对象,是普通的迭代器;因此,您可以直接在StreamingResponse 中返回它们,如documentation 中所述,如下所示:
@app.get("/image")
def get_image():
def iterfile():
with open("test.png", mode="rb") as f:
yield from f
return StreamingResponse(iterfile(), media_type="image/png")
或者,如果图像被加载到内存中,然后被保存到BytesIO 缓冲流中以返回字节,BytesIO,就像io module 的所有具体类一样,是@987654350 @object,表示也可以直接返回:
@app.get("/image")
def get_image():
arr = create_img()
im = Image.fromarray(arr)
buf = BytesIO()
im.save(buf, format='PNG')
buf.seek(0)
return StreamingResponse(buf, media_type="image/png")
因此,对于您的情况,最好使用您的自定义content 和media_type 返回一个Response,并设置Content-Disposition 标头,如上所述,以便查看图像在浏览器中。
选项 2 - 将图像返回为 JSON 编码的 numpy 数组
下面不应该用于在浏览器中显示图像,而是在这里添加 - 为了完整起见 - 显示如何将图像转换为 numpy 数组(preferably, using asarray() function),返回 numpy 数组并将其转换回客户端的图像,如this 和this 回答中所述。
使用 PIL
服务器端:
@app.get("/image")
def get_image():
im = Image.open('test.png')
# im = Image.open("test.png").convert("RGBA") # if dealing with 4-channel RGBA (transparent) image
arr = np.asarray(im)
return json.dumps(arr.tolist())
客户端:
url = 'http://127.0.0.1:8000/image'
r = requests.get(url=url)
arr = np.asarray(json.loads(r.json())).astype(np.uint8)
im = Image.fromarray(arr)
im.save("test.png")
使用 OpenCV
服务器端:
@app.get("/image")
def get_image():
arr = cv2.imread('test.png', cv2.IMREAD_UNCHANGED)
return json.dumps(arr.tolist())
客户端:
url = 'http://127.0.0.1:8000/image'
r = requests.get(url=url)
arr = np.asarray(json.loads(r.json())).astype(np.uint8)
cv2.imwrite('test.png', arr)