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