如何在 fastAPI 中返回图像?

Hoo*_*ked 37 python api fastapi

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

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

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

小智 46

我有一个类似的问题,但有一个 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")
Run Code Online (Sandbox Code Playgroud)

  • 如果您使用“BytesIO”,特别是与 PIL/skimage 一起使用,请确保在返回之前也执行“img.seek(0)”! (12认同)
  • 自从编写这个答案以来,事情可能已经发生了变化,但是今天在这个答案中使用“StreamingResponse”似乎是错误的。请参阅[我的答案](/sf/answers/4724797241/)。 (5认同)
  • 这对于返回 GridFS 对象也非常有效,例如: `val = grid_fs_file.read()` `return StreamingResponse(io.BytesIO(val), media_type="application/pdf")` 非常感谢! (2认同)

Seb*_*rez 26

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

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

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

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

Yag*_*nci 22

所有其他答案都在点上,但现在返回图像很容易

from fastapi.responses import FileResponse

@app.get("/")
async def main():
    return FileResponse("your_image.jpeg")
Run Code Online (Sandbox Code Playgroud)

  • 您还需要为此安装 `aiofiles` 库 (7认同)

Hoo*_*ked 11

@SebastiánRamírez的回答为我指明了正确的方向,但对于那些希望解决问题的人,我需要几行代码才能使其工作。我需要FileResponse从 starlette(不是 fastAPI?)导入,添加 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")
Run Code Online (Sandbox Code Playgroud)

  • 你能说得更具体一点吗?比如文件名在哪里?物品是什么,路线在哪里? (2认同)
  • @PekoChan 你是对的,我遗漏了一些部分。我试图将我实际使用的代码改编为一个最小的示例。我把它做得有点太小了,希望我已经修复了它。 (2认同)

Hen*_*wan 10

感谢@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,)})
Run Code Online (Sandbox Code Playgroud)

  • 哇!!!`buf.seek(0)` 救了我 (2认同)
  • @EvgenyKolyakov 很高兴知道两年后它很有用:)我因此掉了一些头发哈哈哈 (2认同)

Max*_*xpm 7

如果您已经在内存中拥有图像的字节

返回fastapi.responses.Response带有您的自定义contentmedia_type.

您还需要使用端点装饰器来让 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")
Run Code Online (Sandbox Code Playgroud)

请参阅Response文档

如果您的图像仅存在于文件系统中

返回一个fastapi.responses.FileResponse.

请参阅FileResponse文档


小心 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")
Run Code Online (Sandbox Code Playgroud)

首先,StreamingResponse(content=my_iterable)通过迭代由my_iterable. 但是当那个可迭代对象是 a 时BytesIO块将被\n终止 lines,这对二进制图像没有意义。

即使块划分是有道理的,这里的块划分也毫无意义,因为我们image_bytes bytes从一开始就可以使用整个对象。我们不妨Response从一开始就将整个事情传递到 a 中。通过阻止来自 FastAPI 的数据,我们不会获得任何好处。

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

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

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

  • 没有 StreamingResponse 不对应于分块编码。根据 WSGI 规范,FastAPI/starlette 无法控制这一点([请参阅“处理内容长度标头”](https://www.python.org/dev/peps/pep-0333/#handling-the-内容长度标题))。其他响应类为您设置“Content-Length”标头。StreamingResponse 没有。`StreamingResponse(content, headers={'Content-Length': str(content_length)})` 不太可能被分块。对于服务器(uvicorn)来说,这看起来与任何其他静态响应相同。 (3认同)
  • 很好的答案,但是这样一来,除了“image/png”之外,OpenAPI 文档仍然会将“application/json”列为可能的 200 响应。它甚至首先列出了这一点,因此这是生成的文档中显示的第一个可能的响应。你知道如何让它只列出“image/png”吗?另请参阅我关于此问题的问题 https://github.com/tiangolo/fastapi/issues/3258 (2认同)
  • @estan 很好。您似乎已经在该 GitHub 问题中找到了解决方案。我有一个替代方法;我已经用它回复了 GitHub 问题,并将其添加到我的答案中。 (2认同)

dam*_*dam 6

上面的内容并不能完全满足我的需求,因为我的镜像是用 PIL 构建的。我的 fastapi 端点采用图像文件名,将其读取为 PIL 图像,并在内存中生成可在 HTML 中使用的缩略图 jpeg,如下所示:

<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")
Run Code Online (Sandbox Code Playgroud)