如何在 fastAPI 中返回图像?

Posted

技术标签:

【中文标题】如何在 fastAPI 中返回图像?【英文标题】:How do I return an image in fastAPI? 【发布时间】:2019-09-16 07:06:35 【问题描述】:

使用 python 模块fastAPI,我不知道如何返回图像。在烧瓶中我会做这样的事情:

@app.route("/vector_image", methods=["POST"])
def image_endpoint():
    # img = ... # Create the image here
    return Response(img, mimetype="image/png")

这个模块中对应的调用是什么?

【问题讨论】:

【参考方案1】:

我遇到了类似的问题,但使用的是 cv2 图像。这可能对其他人有用。使用StreamingResponse

import io
from starlette.responses import StreamingResponse

app = FastAPI()

@app.post("/vector_image")
def image_endpoint(*, vector):
    # Returns a cv2 image array from the document vector
    cv2img = my_function(vector)
    res, im_png = cv2.imencode(".png", cv2img)
    return StreamingResponse(io.BytesIO(im_png.tobytes()), media_type="image/png")

【讨论】:

谢谢!我认为这比我需要临时文件的破解要好得多。 如果您正在使用BytesIO,尤其是与 PIL/skimage 一起使用,请确保在返回之前也使用img.seek(0) 这对于返回 GridFS 对象也很有效,例如:val = grid_fs_file.read()return StreamingResponse(io.BytesIO(val), media_type="application/pdf") 非常感谢! 自编写此答案以来情况可能已发生变化,但今天在此答案中使用 StreamingResponse 似乎是错误的。见my answer。【参考方案2】:

尚未正确记录,但您可以使用 Starlette 中的任何内容。

因此,如果它是磁盘中具有路径的文件,则可以使用FileResponse:https://www.starlette.io/responses/#fileresponse

如果它是在您的路径操作中创建的类似文件的对象,在 Starlette 的下一个稳定版本(FastAPI 内部使用)中,您还可以在StreamingResponse 中返回它。

【讨论】:

感谢您的回复!我让它与你的建议一起工作,但这并不容易(而且可能有点矫枉过正!)。请参阅下面的解决方案。除了这个问题,fastAPI 很高兴与一个非常好的文档一起工作,感谢提供它! 我还在问题中为您的库创建了一个标签。随意编辑它,并“观看”,这样您就可以看到其他用户提出的问题。【参考方案3】:

所有其他答案都很重要,但现在返回图片非常容易

from fastapi.responses import FileResponse

@app.get("/")
async def main():
    return FileResponse("your_image.jpeg")

【讨论】:

你还需要为此安装aiofiles 谢谢。我正在使用它来返回使用 fastAPI 保存的文件。还要感谢 @Igor 指出我也需要 aiofiles!【参考方案4】:

如果内存中已经有图像的字节数

使用您的自定义 contentmedia_type 返回 fastapi.responses.Response

您还需要使用端点装饰器来让 FastAPI 将正确的媒体类型放入 OpenAPI 规范中。

@app.get(
    "/image",

    # Set what the media type will be in the autogenerated OpenAPI specification.
    # fastapi.tiangolo.com/advanced/additional-responses/#additional-media-types-for-the-main-response
    responses = 
        200: 
            "content": "image/png": 
        
    

    # Prevent FastAPI from adding "application/json" as an additional
    # response media type in the autogenerated OpenAPI specification.
    # https://github.com/tiangolo/fastapi/issues/3258
    response_class=Response,
)
def get_image()
    image_bytes: bytes = generate_cat_picture()
    # media_type here sets the media type of the actual response sent to the client.
    return Response(content=image_bytes, media_type="image/png")

请参阅Response documentation。

如果你的图片只存在于文件系统中

返回fastapi.responses.FileResponse

请参阅FileResponse documentation。


小心StreamingResponse

其他答案建议StreamingResponseStreamingResponse 更难正确使用,所以我不推荐它,除非你确定不能使用 ResponseFileResponse

尤其是这样的代码毫无意义。它不会以任何有用的方式“流式传输”图像。

@app.get("/image")
def get_image()
    image_bytes: bytes = generate_cat_picture()
    # ❌ Don't do this.
    image_stream = io.BytesIO(image_bytes)
    return StreamingResponse(content=image_stream, media_type="image/png")

首先,StreamingResponse(content=my_iterable) 通过迭代my_iterable 提供的块进行流式传输。但是当该可迭代对象是 BytesIO、the chunks will be \n-terminated lines 时,这对于二值图像就没有意义了。

即使分块是有意义的,分块在这里毫无意义,因为我们从一开始就拥有整个 image_bytes bytes 对象。我们不妨从一开始就将整个事情传递给Response。从 FastAPI 保留数据,我们不会获得任何好处。

第二,StreamingResponse对应HTTP chunked transfer encoding。 (这可能取决于您的 ASGI 服务器,但至少 Uvicorn 是这种情况。)这不是分块传输编码的好用例。

如果您事先不知道输出的大小,并且您不想在开始将其发送到客户端之前等待收集所有内容以找出答案,则分块传输编码是有意义的。这可以应用于提供慢速数据库查询的结果之类的东西,但它通常不适用于提供图像。

不必要的分块传输编码可能是有害的。例如,这意味着客户端在下载文件时无法显示进度条。见:

Content-Length header versus chunked encoding Is it a good idea to use Transfer-Encoding: chunked on static files?

【讨论】:

很好的答案,但是,除了image/png,OpenAPI 文档仍会将application/json 列为可能的200 响应。它甚至首先列出了这一点,因此它是生成的文档中显示的第一个可能的响应。你知道如何让它只列出image/png吗?另请参阅我在github.com/tiangolo/fastapi/issues/3258 中关于此的问题 @estan 很好。看起来您已经在该 GitHub 问题中找到了解决方案。我有另一种方法;我已经用它回复了那个 GitHub 问题,并将其添加到我的答案中。 No StreamingResponse 不对应分块编码。根据 WSGI 规范 (see "Handling the Content-Length Header"),FastAPI/starlette 不受此控制。其他响应类为您设置 Content-Length 标头。 StreamingResponse 没有。 StreamingResponse(content, headers='Content-Length': str(content_length)) 不太可能被分块。对于服务器 (uvicorn),这看起来与任何其他静态响应相同。 @PhilipCouling "Corresponds" 可能是错误的词,是的。像“StreamingResponse() 很可能由服务器使用分块传输编码处理”这样的内容会更好吗? @Maxpm no 我实际上会在答案中明确表示您需要手动设置内容长度标头(否则它可能会被分块)。这是你提到的基本问题。接受的答案还有另一个问题。传回文件对象存在很大的绊倒危险。 api不会关闭它。所以 BytesIO 没问题,但不是任何真正的文件对象。【参考方案5】:

感谢@biophetik 的回答,还有一个让我感到困惑的重要提醒:如果您正在使用BytesIO,尤其是与 PIL/skimage 一起使用,请确保在返回之前也使用img.seek(0)

@app.get("/generate")
def generate(data: str):
  img = generate_image(data)
  print('img=%s' % (img.shape,))
  buf = BytesIO()
  imsave(buf, img, format='JPEG', quality=100)
  buf.seek(0) # important here!
  return StreamingResponse(buf, media_type="image/jpeg",
    headers='Content-Disposition': 'inline; filename="%s.jpg"' %(data,))

【讨论】:

【参考方案6】:

来自@SebastiánRamírez 的answer 为我指明了正确的方向,但对于那些希望解决问题的人来说,我需要几行代码才能让它工作。我需要从 starlette(不是 fastAPI?)导入 FileResponse,添加 CORS 支持,然后从临时文件返回。也许有更好的方法,但我无法让流媒体工作:

from starlette.responses import FileResponse
from starlette.middleware.cors import CORSMiddleware
import tempfile

app = FastAPI()
app.add_middleware(
    CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"]
)

@app.post("/vector_image")
def image_endpoint(*, vector):
    # Returns a raw PNG from the document vector (define here)
    img = my_function(vector)

    with tempfile.NamedTemporaryFile(mode="w+b", suffix=".png", delete=False) as FOUT:
        FOUT.write(img)
        return FileResponse(FOUT.name, media_type="image/png")

【讨论】:

您能说得更具体些吗?比如文件名在哪里?什么是Item,路径在哪里? @PekoChan 你说得对,我遗漏了一些部分。我试图将我实际使用的代码调整为一个最小的示例。我把它弄得太小了,希望我已经修好了。【参考方案7】:

你可以在 FastAPI 中做一些非常相似的事情

from fastapi import FastAPI, Response

app = FastAPI()

@app.post("/vector_image/")
async def image_endpoint():
    # img = ... # Create the image here
    return Response(content=img, media_type="image/png")

【讨论】:

图片的类型是什么?如何创建图像? 此处为png图片,根据应用需求制作图片【参考方案8】:

如果它是磁盘中带有path 的文件,您可以使用FileResponse

import os

from fastapi import FastAPI 
from fastapi.responses import FileResponse

app = FastAPI()

path = "/path/to/files"

@app.get("/")
def index():
    return "Hello": "World"

@app.get("/vector_image", responses=200: "description": "A picture of a vector image.", "content" : "image/jpeg" : "example" : "No example available. Just imagine a picture of a vector image.")
def image_endpoint():
    file_path = os.path.join(path, "files/vector_image.jpg")
    if os.path.exists(file_path):
        return FileResponse(file_path, media_type="image/jpeg", filename="vector_image_for_you.jpg")
    return "error" : "File not found!"

【讨论】:

【参考方案9】:

由于我的图像是使用 PIL 构建的,因此我的需求并没有完全满足。我的 fastapi 端点获取一个图像文件名,将其作为 PIL 图像读取,并在内存中生成一个缩略图 jpeg,可以在 html 中使用,例如:

<img src="http://localhost:8000/images/thumbnail/bigimage.jpg">

import io
from PIL import Image
from fastapi.responses import StreamingResponse
@app.get('/images/thumbnail/filename',
  response_description="Returns a thumbnail image from a larger image",
  response_class="StreamingResponse",
  responses= 200: "description": "an image", "content": "image/jpeg": )
def thumbnail_image (filename: str):
  # read the high-res image file
  image = Image.open(filename)
  # create a thumbnail image
  image.thumbnail((100, 100))
  imgio = io.BytesIO()
  image.save(imgio, 'JPEG')
  imgio.seek(0)
  return StreamingResponse(content=imgio, media_type="image/jpeg")

【讨论】:

以上是关于如何在 fastAPI 中返回图像?的主要内容,如果未能解决你的问题,请参考以下文章

如何测试使用图像的 FastAPI api 端点?

如何从fastapi中的另一个api调用一个api?

如何从 fastapi websocket 答案中获取“lastEventId”?

您将如何使用带有 FastAPI 的 asyncpg 将选择查询的返回值映射到 pydantic 模型以进行输出和验证?

FastAPI 依赖项(yield):如何手动调用它们?

如何在 FastAPI 中启用 CORS?