簡體   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