当前位置:   article > 正文

Fastapi学习笔记之使用 Amazon ec2 部署图像检测模型-1_fastapi img2img

fastapi img2img

前言

我写了一个关于 FastAPI 的简单教程,它是关于简化和理解 API 的工作原理,以及使用框架创建一个简单的 API。

该帖子得到了很好的反响,但问得最多的问题是如何在 ec2 上部署 FastAPI API,以及如何使用图像数据而不是简单的字符串、整数和浮点数作为 API 的输入。

我为此在网上进行了搜索,但我所能找到的只是一些简单的文档以及人们使用 NGINX 或 ECS 进行部署的许多不同方式。 这些对我来说似乎都不是特别伟大或完整。

因此,我尝试使用 FastAPI 文档中的一些帮助自己完成此操作。 在这篇文章中,我们将主要关注四件事:

  • 设置亚马逊实例

  • 创建用于对象检测的 FastAPI API

  • 使用 Docker 部署 FastAPI

  • 带 UI 的端到端应用程序

所以,事不宜迟,让我们开始吧。由于文档比较长,我们先从上面俩个加黑的标题开始介绍:)

1. 设置亚马逊实例

在我们开始使用 Amazon ec2 实例之前,我们需要设置一个。 您可能需要使用您的电子邮件 ID 注册并在 AWS 网站上设置付款信息。 就像单点登录一样工作。 从这里开始,我假设您有一个 AWS 账户,因此我将解释接下来的重要部分,以便您可以跟进。

  • 在 AWS 管理控制台上,您可以选择“启动虚拟机”。 在这里,我们尝试设置将部署 FastAPI API 的机器。

(1)第一步,您需要为机器选择 AMI 模板。 我从 Ubuntu 开始选择 18.04 Ubuntu Server。

(2)在第二步中,我选择了 t2.xlarge 机器,它有 4 个 CPU 和 16GB RAM,而不是免费层,因为我想使用对象检测模型并且需要一些资源。

继续按下一步,直到到达“6。 配置安全组”选项卡。 这是这里最关键的一步。 您需要添加类型为“HTTP”且端口范围为:80 的规则。

您可以点击“Review and Launch”,最后点击“Launch”按钮来启动实例。 单击启动后,您可能需要创建一个新的密钥对。 在这里,我正在创建一个名为 fastapi 的新密钥对,并使用“下载密钥对”按钮下载它。 请妥善保管此密钥,因为每次您需要登录此特定计算机时都需要用到它。 下载密钥对后点击“Launch Instance”

您现在可以转到您的实例以查看您的实例是否已启动。 提示:查看实例状态; 它应该显示“正在运行”。

另外,这里要注意公共 DNS(IPv4) 地址和 IPv4 公共 IP。 我们将需要它来连接到这台机器。 对我来说,他们是:

  1. Public DNS (IPv4): ec2-18-237-28-174.us-west-2.compute.amazonaws.com
  2. IPv4 Public IP: 18.237.28.174

在文件夹中运行以下命令后,您保存了 fastapi.pem 文件。 如果文件名为 fastapi.txt,您可能需要将其重命名为 fastapi.pem。

  1. # run fist command if fastapi.txt gets downloaded.
  2. # mv fastapi.txt fastapi.pem
  3. chmod 400 fastapi.pem
  4. ssh -i "fastapi.pem" ubuntu@<Your Public DNS(IPv4) Address>

现在我们已经启动并运行了我们的 Amazon 实例。 我们可以继续到帖子的真实部分。

2、创建用于对象检测的 FastAPI API

在我们部署 API 之前,我们需要有一个 API,对吗? 在我最近的一篇文章中,我转载一个简单的教程来理解 FastAPI 和 API 基础知识。 如果您想了解 FastAPI 基础知识,请阅读这篇文章。

因此,在这里我将尝试创建一个图像检测 API。 至于如何将Image数据传递给API? 这个想法是——什么是图像而不是字符串? 图像只是由字节组成的,我们可以将这些字节编码为字符串。 我们将使用 base64 字符串表示,这是一种将二进制数据转换为 ASCII 字符的流行方式。 并且,我们将传递此字符串表示形式以向我们的 API 提供图像。

(1)一些图像基础知识:什么是图像

那么,让我们首先看看如何将图像转换为字符串。 我们使用“rb”标志从图像文件中读取二进制数据,并使用 base64.b64encode 函数将其转换为 base64 编码数据表示。 然后我们使用 decode to utf-8 函数将基本编码数据转换为人类可读的字符。 如果它现在没有多大意义,请不要担心。 只需了解任何数据都是二进制的,我们可以使用一系列步骤将二进制数据转换为其字符串表示形式。

举个简单的例子,如果我有一个像下面这样的简单图像,我们可以使用以下方法将它转换为字符串:

  1. import base64
  2. with open("sample_images/dog_with_ball.jpg", "rb") as image_file:
  3. 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 图像字符串转换为实际图像的方法。***

  1. import base64
  2. import io
  3. from PIL import Image
  4. def base64str_to_PILImage(base64str):
  5. base64_img_bytes = base64str.encode('utf-8')
  6. base64bytes = base64.b64decode(base64_img_bytes)
  7. bytesObj = io.BytesIO(base64bytes)
  8. img = Image.open(bytesObj)
  9. return img
'
运行

那么这个功能有用吗? 让我们自己看看。 我们可以只使用字符串来取回图像。

(2)编写实际的 FastAPI 代码

在这里,我将使用来自 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”) 来定义我们的端点。

  1. from fastapi import FastAPI
  2. from pydantic import BaseModel
  3. import torchvision
  4. from torchvision import transforms
  5. import torch
  6. from torchvision.models.detection.faster_rcnn import FastRCNNPredictor
  7. from PIL import Image
  8. import numpy as np
  9. import cv2
  10. import io, json
  11. import base64
  12. app = FastAPI()
  13. # load a pre-trained Model and convert it to eval mode.
  14. # This model loads just once when we start the API.
  15. model = torchvision.models.detection.fasterrcnn_resnet50_fpn(pretrained=True)
  16. COCO_INSTANCE_CATEGORY_NAMES = [
  17. '__background__', 'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus',
  18. 'train', 'truck', 'boat', 'traffic light', 'fire hydrant', 'N/A', 'stop sign',
  19. 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', 'cow',
  20. 'elephant', 'bear', 'zebra', 'giraffe', 'N/A', 'backpack', 'umbrella', 'N/A', 'N/A',
  21. 'handbag', 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard', 'sports ball',
  22. 'kite', 'baseball bat', 'baseball glove', 'skateboard', 'surfboard', 'tennis racket',
  23. 'bottle', 'N/A', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl',
  24. 'banana', 'apple', 'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza',
  25. 'donut', 'cake', 'chair', 'couch', 'potted plant', 'bed', 'N/A', 'dining table',
  26. 'N/A', 'N/A', 'toilet', 'N/A', 'tv', 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone',
  27. 'microwave', 'oven', 'toaster', 'sink', 'refrigerator', 'N/A', 'book',
  28. 'clock', 'vase', 'scissors', 'teddy bear', 'hair drier', 'toothbrush'
  29. ]
  30. model.eval()
  31. # define the Input class
  32. class Input(BaseModel):
  33. base64str : str
  34. threshold : float
  35. def base64str_to_PILImage(base64str):
  36. base64_img_bytes = base64str.encode('utf-8')
  37. base64bytes = base64.b64decode(base64_img_bytes)
  38. bytesObj = io.BytesIO(base64bytes)
  39. img = Image.open(bytesObj)
  40. return img
  41. @app.put("/predict")
  42. def get_predictionbase64(d:Input):
  43. '''
  44. FastAPI API will take a base 64 image as input and return a json object
  45. '''
  46. # Load the image
  47. img = base64str_to_PILImage(d.base64str)
  48. # Convert image to tensor
  49. transform = transforms.Compose([transforms.ToTensor()])
  50. img = transform(img)
  51. # get prediction on image
  52. pred = model([img])
  53. pred_class = [COCO_INSTANCE_CATEGORY_NAMES[i] for i in list(pred[0]['labels'].numpy())]
  54. pred_boxes = [[(float(i[0]), float(i[1])), (float(i[2]), float(i[3]))] for i in list(pred[0]['boxes'].detach().numpy())]
  55. pred_score = list(pred[0]['scores'].detach().numpy())
  56. pred_t = [pred_score.index(x) for x in pred_score if x > d.threshold][-1]
  57. pred_boxes = pred_boxes[:pred_t+1]
  58. pred_class = pred_class[:pred_t+1]
  59. return {'boxes': pred_boxes,
  60. 'classes' : pred_class}

(3)在本地测试 FastAPI 代码

在我们继续使用 AWS 之前,让我们检查一下代码是否可以在我们的本地机器上运行。 我们可以使用以下命令在笔记本电脑上启动 API:

uvicorn fastapiapp:app --reload

以上意味着您的 API 现在正在本地服务器上运行,并且 –reload 标志表示当您更改 fastapiapp.py 文件时 API 会自动更新。 这在开发和测试时非常有用,但是当您将 API 投入生产时,您应该删除这个 –reload 标志。

你应该看到类似的东西:

您现在可以尝试访问此 API 并使用 requests 模块查看它是否有效:

  1. import requests,json
  2. payload = json.dumps({
  3. "base64str": base64str,
  4. "threshold": 0.5
  5. })
  6. response = requests.put("[http://127.0.0.1:8000/predict](http://127.0.0.1:8000/predict)",data = payload)
  7. data_dict = response.json()

因此,我们使用 API 获得结果。 此图像包含一只狗和一个运动球。 我们还有边界框的角 1 (x1,y1) 和角 2 (x2,y2) 坐标。

(4)让我们可视化一下

我们可以在 Jupyter notebook 中可视化结果的样子:

  1. from PIL import Image
  2. import numpy as np
  3. import cv2
  4. import matplotlib.pyplot as plt
  5. def PILImage_to_cv2(img):
  6. return np.asarray(img)
  7. def drawboundingbox(img, boxes,pred_cls, rect_th=2, text_size=1, text_th=2):
  8. img = PILImage_to_cv2(img)
  9. class_color_dict = {}
  10. #initialize some random colors for each class for better looking bounding boxes
  11. for cat in pred_cls:
  12. class_color_dict[cat] = [random.randint(0, 255) for _ in range(3)]
  13. for i in range(len(boxes)):
  14. cv2.rectangle(img, (int(boxes[i][0][0]), int(boxes[i][0][1])),
  15. (int(boxes[i][1][0]),int(boxes[i][1][1])),
  16. color=class_color_dict[pred_cls[i]], thickness=rect_th)
  17. 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
  18. plt.figure(figsize=(20,30))
  19. plt.imshow(img)
  20. plt.xticks([])
  21. plt.yticks([])
  22. plt.show()
  23. img = Image.open("sample_images/dog_with_ball.jpg")
  24. drawboundingbox(img, data_dict['boxes'], data_dict['classes'])

输出如下:

在这里你会注意到我从本地文件系统获取图像,这种行为可以被视为作弊,因为我们不想保存用户通过 Web UI 发送给我们的每个文件。 我们应该能够使用创建此图像时也必须使用的相同 base64string 对象。 正确的?

不用担心,我们也可以做到。 还记得我们的 base64str_to_PILImage 函数吗? 我们也可以使用它。

  1. img = base64str_to_PILImage(base64str)
  2. drawboundingbox(img, data_dict['boxes'], data_dict['classes'])

看起来不错。 我们有我们的工作 FastAPI,我们也有我们的亚马逊实例。 我们现在可以继续部署,请参考后续博客。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/916759
推荐阅读
相关标签
  

闽ICP备14008679号