[英]Render NumPy array in FastAPI
我发现如何使用 FastAPI 将 numpy 数组作为图像返回? ,但是,我仍在努力显示图像,它看起来就像一个白色方块。
我将一个数组读入io.BytesIO
,如下所示:
def iterarray(array):
output = io.BytesIO()
np.savez(output, array)
yield output.get_value()
在我的端点中,我的返回是StreamingResponse(iterarray(), media_type='application/octet-stream')
当我将media_type
留空以推断时,会下载一个 zipfile。
如何让数组显示为图像?
下面的示例展示了如何将从磁盘加载的图像或内存中的图像(numpy 数组)转换为字节(使用PIL
或OpenCV
库)并使用自定义Response
返回它们。 出于本演示的目的,以下代码用于创建基于此答案的内存示例图像(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
您可以从磁盘加载图像,也可以使用Image.fromarray
加载内存中的图像(注意:出于演示目的,当案例是从磁盘加载图像时,下面演示了路由内部的操作。但是,如果将多次提供相同的图像,一个人可以在startup
时仅加载一次图像并将其存储在app
实例中,如本答案中所述)。 接下来,将图像写入缓冲的 stream 即BytesIO
,并使用getvalue()
方法获取缓冲区的全部内容。 即使缓冲的 stream 在离开 scope 时被垃圾收集,通常最好调用close()
或使用with
语句,如下所示。
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
,如此处所述。
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")
您可以使用cv2.imread()
function 从磁盘加载图像,或者使用内存中的图像,如果是RGB
顺序,如下例所示 - 需要转换,因为OpenCV 使用BGR
作为其默认颜色图像顺序。 接下来,使用cv2.imencode()
function 压缩图像数据(基于您传递的定义 output 格式的文件扩展名 - 即.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')
On client side, you can write the raw bytes to a file, or use numpy.frombuffer()
function and cv2.imdecode()
function to decompress the buffer into an image format (similar to this ) - cv2.imdecode()
does not需要文件扩展名,因为将从缓冲区中压缩图像的第一个字节推导出正确的编解码器。
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
响应应包括以下 header,如此处所述和上述示例中所示(如果filename
包含特殊字符,则需要filename
两边的引号) :
headers = {'Content-Disposition': 'inline; filename="test.png"'}
鉴于,要下载而不是查看图像(改用attachment
):
headers = {'Content-Disposition': 'attachment; filename="test.png"'}
如果您想使用 Javascript 接口显示(或下载)图像,例如 Fetch API 或 Axios,请查看此处和此处的答案。
至于StreamingResponse
,如果从一开始就将 numpy 数组完全加载到 memory 中,则根本不需要StreamingResponse
。 StreamingResponse
通过迭代iter()
function 提供的块来流式传输(如果Content-Length
未在标头中设置,与StreamingResponse
不同,其他Response
类为您设置 header ,以便浏览器知道数据在哪里结束)。 如本答案所述:
当您提前不知道 output 的大小时,分块传输编码是有意义的,并且您不想在开始将其发送到客户端之前等待收集所有内容以找出答案。 这可以适用于提供慢速数据库查询的结果之类的东西,但它通常不适用于提供图像。
即使您想要 stream 保存在磁盘上的图像文件(您不应该这样做,除非它是一个相当大的文件,无法放入 memory。相反,您应该使用使用FileResponse
), 文件类对象,例如那些由open()
创建的,是普通的迭代器; 因此,您可以直接在StreamingResponse
中返回它们,如文档中所述,如下所示:
@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")
or, if the image was loaded into memory instead, and was then saved into a BytesIO
buffered stream in order to return the bytes, BytesIO
, like all the concrete classes of io module , is a file-like object, which means you can return它也直接:
@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
header,以便在浏览器中查看图像。
下面不应该用于在浏览器中显示图像,而是在此处添加 - 为了完整起见 - 显示如何将图像转换为 numpy 数组(最好使用asarray()
function ),返回 Z22919510C75ZF6FC数组并将其转换回客户端的图像,如this和this answer中所述。
@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")
@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)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.