赞
踩
我写了一个关于 FastAPI 的简单教程,它是关于简化和理解 API 的工作原理,以及使用框架创建一个简单的 API。
该帖子得到了很好的反响,但问得最多的问题是如何在 ec2 上部署 FastAPI API,以及如何使用图像数据而不是简单的字符串、整数和浮点数作为 API 的输入。
我为此在网上进行了搜索,但我所能找到的只是一些简单的文档以及人们使用 NGINX 或 ECS 进行部署的许多不同方式。 这些对我来说似乎都不是特别伟大或完整。
因此,我尝试使用 FastAPI 文档中的一些帮助自己完成此操作。 在这篇文章中,我们将主要关注四件事:
设置亚马逊实例
创建用于对象检测的 FastAPI API
使用 Docker 部署 FastAPI
带 UI 的端到端应用程序
所以,事不宜迟,让我们开始吧。由于文档比较长,我们先从上面俩个加黑的标题开始介绍:)
在我们开始使用 Amazon ec2 实例之前,我们需要设置一个。 您可能需要使用您的电子邮件 ID 注册并在 AWS 网站上设置付款信息。 就像单点登录一样工作。 从这里开始,我假设您有一个 AWS 账户,因此我将解释接下来的重要部分,以便您可以跟进。
使用 https://us-west-2.console.aws.amazon.com/console 转到 AWS 管理控制台。
在 AWS 管理控制台上,您可以选择“启动虚拟机”。 在这里,我们尝试设置将部署 FastAPI API 的机器。
继续按下一步,直到到达“6。 配置安全组”选项卡。 这是这里最关键的一步。 您需要添加类型为“HTTP”且端口范围为:80 的规则。
您可以点击“Review and Launch”,最后点击“Launch”按钮来启动实例。 单击启动后,您可能需要创建一个新的密钥对。 在这里,我正在创建一个名为 fastapi 的新密钥对,并使用“下载密钥对”按钮下载它。 请妥善保管此密钥,因为每次您需要登录此特定计算机时都需要用到它。 下载密钥对后点击“Launch Instance”
您现在可以转到您的实例以查看您的实例是否已启动。 提示:查看实例状态; 它应该显示“正在运行”。
另外,这里要注意公共 DNS(IPv4) 地址和 IPv4 公共 IP。 我们将需要它来连接到这台机器。 对我来说,他们是:
- Public DNS (IPv4): ec2-18-237-28-174.us-west-2.compute.amazonaws.com
-
- IPv4 Public IP: 18.237.28.174
在文件夹中运行以下命令后,您保存了 fastapi.pem 文件。 如果文件名为 fastapi.txt,您可能需要将其重命名为 fastapi.pem。
- # run fist command if fastapi.txt gets downloaded.
- # mv fastapi.txt fastapi.pem
-
- chmod 400 fastapi.pem
- ssh -i "fastapi.pem" ubuntu@<Your Public DNS(IPv4) Address>
现在我们已经启动并运行了我们的 Amazon 实例。 我们可以继续到帖子的真实部分。
在我们部署 API 之前,我们需要有一个 API,对吗? 在我最近的一篇文章中,我转载一个简单的教程来理解 FastAPI 和 API 基础知识。 如果您想了解 FastAPI 基础知识,请阅读这篇文章。
因此,在这里我将尝试创建一个图像检测 API。 至于如何将Image数据传递给API? 这个想法是——什么是图像而不是字符串? 图像只是由字节组成的,我们可以将这些字节编码为字符串。 我们将使用 base64 字符串表示,这是一种将二进制数据转换为 ASCII 字符的流行方式。 并且,我们将传递此字符串表示形式以向我们的 API 提供图像。
那么,让我们首先看看如何将图像转换为字符串。 我们使用“rb”标志从图像文件中读取二进制数据,并使用 base64.b64encode 函数将其转换为 base64 编码数据表示。 然后我们使用 decode to utf-8 函数将基本编码数据转换为人类可读的字符。 如果它现在没有多大意义,请不要担心。 只需了解任何数据都是二进制的,我们可以使用一系列步骤将二进制数据转换为其字符串表示形式。
举个简单的例子,如果我有一个像下面这样的简单图像,我们可以使用以下方法将它转换为字符串:
- import base64
-
- with open("sample_images/dog_with_ball.jpg", "rb") as image_file:
- base64str = base64.b64encode(image_file.read()).decode("utf-8")
在这里,我的笔记本电脑上有一个名为 dog_with_ball.png 的文件的字符串表示形式。
太好了,我们现在有了图像的字符串表示。 而且,我们可以将此字符串表示形式发送到我们的 FastAPI。 但我们还需要有一种方法从图像的字符串表示中读回图像。 毕竟,我们使用 PyTorch 和任何其他包的图像检测 API 需要有一个可以预测的图像对象,而这些方法不适用于字符串。
所以这是一种从图像的 base64 字符串创建 PIL 图像的方法。 大多数情况下,我们只是按照相同的顺序执行相反的步骤。 我们使用 .encode 编码为‘utf-8’。 然后我们使用 base64.b64decode 解码为字节。 我们使用这些字节通过 io.BytesIO 创建一个字节对象,并使用 Image.open 打开这个字节 IO 对象作为 PIL 图像,它可以很容易地用作我的 PyTorch 预测代码的输入。*** 再次简单地说,它 只是一种将 base64 图像字符串转换为实际图像的方法。***
- import base64
- import io
- from PIL import Image
-
- def base64str_to_PILImage(base64str):
- base64_img_bytes = base64str.encode('utf-8')
- base64bytes = base64.b64decode(base64_img_bytes)
- bytesObj = io.BytesIO(base64bytes)
- img = Image.open(bytesObj)
- return img
'运行
那么这个功能有用吗? 让我们自己看看。 我们可以只使用字符串来取回图像。
在这里,我将使用来自 torchvision.models 的 Pytorch 预训练 fasterrcnn_resnet50_fpn 检测模型进行对象检测,该模型在 COCO 数据集上进行训练以保持代码简单,但可以使用任何模型。 如果您想使用 Pytorch 训练您的自定义图像分类或图像检测模型,您可以查看这些帖子。
下面是 FastAPI 的完整代码。 虽然看起来很长,但我们已经知道了所有的部分。 在这段代码中,我们主要执行以下步骤:
使用 FastAPI() 构造函数创建我们的快速 API 应用程序。
加载我们的模型和训练它的类。 我从 PyTorch 文档中获得了类列表。
我们还定义了一个新类 Input ,它使用一个名为 pydantic 的库来验证我们将从 API 最终用户获得的输入数据类型。 在这里,最终用户为对象检测预测提供了 base64str 和一些分数阈值。
我们添加了一个名为 base64str_to_PILImage 的函数,它按照它的名字进行操作。
我们编写了一个名为 get_predictionbase64 的预测函数,它使用图像的 base64 字符串表示和阈值作为输入返回边界框和类的字典。 我们还在这个函数之上添加 @app .put(“/predict”) 来定义我们的端点。
- from fastapi import FastAPI
- from pydantic import BaseModel
- import torchvision
- from torchvision import transforms
- import torch
- from torchvision.models.detection.faster_rcnn import FastRCNNPredictor
- from PIL import Image
- import numpy as np
- import cv2
- import io, json
- import base64
-
-
- app = FastAPI()
-
- # load a pre-trained Model and convert it to eval mode.
- # This model loads just once when we start the API.
- model = torchvision.models.detection.fasterrcnn_resnet50_fpn(pretrained=True)
- COCO_INSTANCE_CATEGORY_NAMES = [
- '__background__', 'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus',
- 'train', 'truck', 'boat', 'traffic light', 'fire hydrant', 'N/A', 'stop sign',
- 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', 'cow',
- 'elephant', 'bear', 'zebra', 'giraffe', 'N/A', 'backpack', 'umbrella', 'N/A', 'N/A',
- 'handbag', 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard', 'sports ball',
- 'kite', 'baseball bat', 'baseball glove', 'skateboard', 'surfboard', 'tennis racket',
- 'bottle', 'N/A', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl',
- 'banana', 'apple', 'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza',
- 'donut', 'cake', 'chair', 'couch', 'potted plant', 'bed', 'N/A', 'dining table',
- 'N/A', 'N/A', 'toilet', 'N/A', 'tv', 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone',
- 'microwave', 'oven', 'toaster', 'sink', 'refrigerator', 'N/A', 'book',
- 'clock', 'vase', 'scissors', 'teddy bear', 'hair drier', 'toothbrush'
- ]
- model.eval()
-
- # define the Input class
- class Input(BaseModel):
- base64str : str
- threshold : float
-
- def base64str_to_PILImage(base64str):
- base64_img_bytes = base64str.encode('utf-8')
- base64bytes = base64.b64decode(base64_img_bytes)
- bytesObj = io.BytesIO(base64bytes)
- img = Image.open(bytesObj)
- return img
-
- @app.put("/predict")
- def get_predictionbase64(d:Input):
- '''
- FastAPI API will take a base 64 image as input and return a json object
- '''
- # Load the image
- img = base64str_to_PILImage(d.base64str)
- # Convert image to tensor
- transform = transforms.Compose([transforms.ToTensor()])
- img = transform(img)
- # get prediction on image
- pred = model([img])
- pred_class = [COCO_INSTANCE_CATEGORY_NAMES[i] for i in list(pred[0]['labels'].numpy())]
- pred_boxes = [[(float(i[0]), float(i[1])), (float(i[2]), float(i[3]))] for i in list(pred[0]['boxes'].detach().numpy())]
- pred_score = list(pred[0]['scores'].detach().numpy())
- pred_t = [pred_score.index(x) for x in pred_score if x > d.threshold][-1]
- pred_boxes = pred_boxes[:pred_t+1]
- pred_class = pred_class[:pred_t+1]
- return {'boxes': pred_boxes,
- 'classes' : pred_class}
在我们继续使用 AWS 之前,让我们检查一下代码是否可以在我们的本地机器上运行。 我们可以使用以下命令在笔记本电脑上启动 API:
uvicorn fastapiapp:app --reload
以上意味着您的 API 现在正在本地服务器上运行,并且 –reload 标志表示当您更改 fastapiapp.py 文件时 API 会自动更新。 这在开发和测试时非常有用,但是当您将 API 投入生产时,您应该删除这个 –reload 标志。
你应该看到类似的东西:
您现在可以尝试访问此 API 并使用 requests 模块查看它是否有效:
- import requests,json
-
- payload = json.dumps({
- "base64str": base64str,
- "threshold": 0.5
- })
-
- response = requests.put("[http://127.0.0.1:8000/predict](http://127.0.0.1:8000/predict)",data = payload)
- data_dict = response.json()
因此,我们使用 API 获得结果。 此图像包含一只狗和一个运动球。 我们还有边界框的角 1 (x1,y1) 和角 2 (x2,y2) 坐标。
我们可以在 Jupyter notebook 中可视化结果的样子:
- from PIL import Image
- import numpy as np
- import cv2
- import matplotlib.pyplot as plt
-
- def PILImage_to_cv2(img):
- return np.asarray(img)
-
- def drawboundingbox(img, boxes,pred_cls, rect_th=2, text_size=1, text_th=2):
- img = PILImage_to_cv2(img)
- class_color_dict = {}
-
- #initialize some random colors for each class for better looking bounding boxes
- for cat in pred_cls:
- class_color_dict[cat] = [random.randint(0, 255) for _ in range(3)]
-
- for i in range(len(boxes)):
- cv2.rectangle(img, (int(boxes[i][0][0]), int(boxes[i][0][1])),
- (int(boxes[i][1][0]),int(boxes[i][1][1])),
- color=class_color_dict[pred_cls[i]], thickness=rect_th)
- cv2.putText(img,pred_cls[i], (int(boxes[i][0][0]), int(boxes[i][0][1])), cv2.FONT_HERSHEY_SIMPLEX, text_size, class_color_dict[pred_cls[i]],thickness=text_th) # Write the prediction class
- plt.figure(figsize=(20,30))
- plt.imshow(img)
- plt.xticks([])
- plt.yticks([])
- plt.show()
-
- img = Image.open("sample_images/dog_with_ball.jpg")
- drawboundingbox(img, data_dict['boxes'], data_dict['classes'])
输出如下:
在这里你会注意到我从本地文件系统获取图像,这种行为可以被视为作弊,因为我们不想保存用户通过 Web UI 发送给我们的每个文件。 我们应该能够使用创建此图像时也必须使用的相同 base64string 对象。 正确的?
不用担心,我们也可以做到。 还记得我们的 base64str_to_PILImage 函数吗? 我们也可以使用它。
- img = base64str_to_PILImage(base64str)
- drawboundingbox(img, data_dict['boxes'], data_dict['classes'])
看起来不错。 我们有我们的工作 FastAPI,我们也有我们的亚马逊实例。 我们现在可以继续部署,请参考后续博客。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。