繁体   English   中英

在 FastAPI 中渲染 Z3B7F949B2343F9E5390​​E29F6EF5E1778Z 数组

[英]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。

如何让数组显示为图像?

选项 1 - 以字节形式返回图像

下面的示例展示了如何将从磁盘加载的图像或内存中的图像(numpy 数组)转换为字节(使用PILOpenCV库)并使用自定义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

使用 PIL


服务器端:

您可以从磁盘加载图像,也可以使用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") 

使用 OpenCV


服务器端:

您可以使用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")

因此,对于您的情况,最好返回一个带有您的自定义contentmedia_typeResponse ,并如上所述设置Content-Disposition header,以便在浏览器中查看图像。

选项 2 - 将图像作为 JSON 编码的 numpy 数组返回

下面不应该用于在浏览器中显示图像,而是在此处添加 - 为了完整起见 - 显示如何将图像转换为 numpy 数组(最好使用asarray() function ),返回 Z22919510C75ZF6FC数组并将其转换回客户端的图像,如thisthis answer中所述。

使用 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)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM