简体   繁体   English

在 FastAPI 中渲染 Z3B7F949B2343F9E5390​​E29F6EF5E1778Z 数组

[英]Render NumPy array in FastAPI

I have found How to return a numpy array as an image using FastAPI?我发现如何使用 FastAPI 将 numpy 数组作为图像返回? , however, I am still struggling to show the image, which appears just as a white square. ,但是,我仍在努力显示图像,它看起来就像一个白色方块。

I read an array into io.BytesIO like so:我将一个数组读入io.BytesIO ,如下所示:

def iterarray(array):
    output = io.BytesIO()
    np.savez(output, array)
    yield output.get_value()

In my endpoint, my return is StreamingResponse(iterarray(), media_type='application/octet-stream')在我的端点中,我的返回是StreamingResponse(iterarray(), media_type='application/octet-stream')

When I leave the media_type blank to be inferred a zipfile is downloaded.当我将media_type留空以推断时,会下载一个 zipfile。

How do I get the array to be displayed as an image?如何让数组显示为图像?

Option 1 - Return image as bytes选项 1 - 以字节形式返回图像

The below examples show how to convert an image loaded from disk, or an in-memory image (numpy array), into bytes (using either PIL or OpenCV libraries) and return them using a customResponse .下面的示例展示了如何将从磁盘加载的图像或内存中的图像(numpy 数组)转换为字节(使用PILOpenCV库)并使用自定义Response返回它们。 For the purposes of this demo, the below code is used to create the in-memory sample image (numpy array), which is based on 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

Using PIL使用 PIL


Server side:服务器端:

You can load an image from disk, or use Image.fromarray to load an in-memory image ( Note : For demo purposes, when the case is loading the image from disk, the below demonstrates that operation inside the route. However, if the same image is going to be served multiple times, one could load the image only once at startup and store it on the app instance , as described in this answer ).您可以从磁盘加载图像,也可以使用Image.fromarray加载内存中的图像(注意:出于演示目的,当案例是从磁盘加载图像时,下面演示了路由内部的操作。但是,如果将多次提供相同的图像,一个人可以在startup时仅加载一次图像并将其存储在app实例中,如本答案中所述)。 Next, write the image to a buffered stream, ie, BytesIO , and use the getvalue() method to get the entire contents of the buffer.接下来,将图像写入缓冲的 stream 即BytesIO ,并使用getvalue()方法获取缓冲区的全部内容。 Even though the buffered stream is garbage collected when goes out of scope, it is generally better to call close() or use the with statement , as shown here .即使缓冲的 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')
Client side:客户端:

The below demonstrates how to send a request to the above endpoint using Python requests module, and write the received bytes to a file, or convert the bytes back to PIL Image , as described here .下面演示如何使用 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") 

Using OpenCV使用 OpenCV


Server side:服务器端:

You can load an image from disk using cv2.imread() function, or use an in-memory image, which - if is in RGB order, as in the example below - needs to be converted, as OpenCV uses BGR as its default colour order for images .您可以使用cv2.imread() function 从磁盘加载图像,或者使用内存中的图像,如果是RGB顺序,如下例所示 - 需要转换,因为OpenCV 使用BGR作为其默认颜色图像顺序 Next, use cv2.imencode() function, which compresses the image data (based on the file extension you pass that defines the output format - ie, .png , .jpg , etc.) and stores it in an in-memory buffer that is used to transfer the data over the network.接下来,使用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')
Client side:客户端:

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 require a file extension, as the correct codec will be deduced from the first bytes of the compressed image in the buffer. 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)

Some more info更多信息

Since you noted that you would like the image displayed - similar to a FileResponse - using a customResponse to return the bytes should be the way to do this, instead of using StreamingResponse .由于您注意到您希望显示图像 - 类似于FileResponse - 使用自定义Response返回字节应该是执行此操作的方法,而不是使用StreamingResponse To indicate to the browser that the image should be viewed in the browser, the HTTP response should include the following header, as described here and as shown in the above examples (the quotes around the filename are required, if the filename contains special characters):为了向浏览器指示应在浏览器中查看图像, HTTP响应应包括以下 header,如此所述和上述示例中所示(如果filename包含特殊字符,则需要filename两边的引号) :

headers = {'Content-Disposition': 'inline; filename="test.png"'}

Whereas, to have the image downloaded rather than viewed (use attachment instead):鉴于,要下载而不是查看图像(改用attachment ):

headers = {'Content-Disposition': 'attachment; filename="test.png"'}

If you would like to display (or download) the image using a Javascript interface, such as Fetch API or Axios, have a look at the answers here and here .如果您想使用 Javascript 接口显示(或下载)图像,例如 Fetch API 或 Axios,请查看此处此处的答案。

As for the StreamingResponse , if the numpy array is fully loaded into memory from the beginning, StreamingResponse is not necessary at all.至于StreamingResponse ,如果从一开始就将 numpy 数组完全加载到 memory 中,则根本不需要StreamingResponse StreamingResponse streams by iterating over the chunks provided by your iter() function (if Content-Length is not set in the headers, which, unlike StreamingResponse , other Response classes set that header for you, so that the browser will know where the data ends). StreamingResponse通过迭代iter() function 提供的块来流式传输(如果Content-Length未在标头中设置,与StreamingResponse不同,其他Response类为您设置 header ,以便浏览器知道数据在哪里结束)。 As described in this answer :本答案所述:

Chunked transfer encoding makes sense when you don't know the size of your output ahead of time, and you don't want to wait to collect it all to find out before you start sending it to the client.当您提前不知道 output 的大小时,分块传输编码是有意义的,并且您不想在开始将其发送到客户端之前等待收集所有内容以找出答案。 That can apply to stuff like serving the results of slow database queries, but it doesn't generally apply to serving images .这可以适用于提供慢速数据库查询的结果之类的东西,但它通常不适用于提供图像

Even if you would like to stream an image file that is saved on disk (which you should rather not, unless it is a rather large file that can't fit into memory. Instead, you should use use FileResponse ), file-like objects, such as those created by open() , are normal iterators;即使您想要 stream 保存在磁盘上的图像文件(您不应该这样做,除非它是一个相当大的文件,无法放入 memory。相反,您应该使用使用FileResponse ), 文件类对象,例如那些由open()创建的,是普通的迭代器; thus, you can return them directly in a StreamingResponse , as described in the documentation and as shown below:因此,您可以直接在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 it directly as well: 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")

Thus, for your case scenario, it is best to return aResponse with your custom content and media_type , as well as setting the Content-Disposition header, as described above, so that the image is viewed in the browser.因此,对于您的情况,最好返回一个带有您的自定义contentmedia_typeResponse ,并如上所述设置Content-Disposition header,以便在浏览器中查看图像。

Option 2 - Return image as JSON-encoded numpy array选项 2 - 将图像作为 JSON 编码的 numpy 数组返回

The below should not be used for displaying the image in the browser, but it is rather added here - for the sake of completeness - showing how to convert an image into a numpy array ( preferably, using asarray() function ), return the numpy array and convert it back to image on client side, as described in this and this answer.下面不应该用于在浏览器中显示图像,而是在此处添加 - 为了完整起见 - 显示如何将图像转换为 numpy 数组(最好使用asarray() function ),返回 Z22919510C75ZF6FC数组并将其转换回客户端的图像,如thisthis answer中所述。

Using PIL使用 PIL


Server side:服务器端:
@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())
Client side:客户端:
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")

Using OpenCV使用 OpenCV


Server side:服务器端:
@app.get("/image")
def get_image():
    arr = cv2.imread('test.png', cv2.IMREAD_UNCHANGED)
    return json.dumps(arr.tolist())
Client side:客户端:
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