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

前言

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

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

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

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

  • 设置亚马逊实例

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

  • 使用 Docker 部署 FastAPI

  • 带 UI 的端到端应用程序

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

1. 设置亚马逊实例

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

  • 使用 https://us-west-2.console.aws.amazon.com/console 转到 AWS 管理控制台。

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

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

Fastapi学习笔记之使用 Amazon ec2 部署图像检测模型-1_第1张图片

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

Fastapi学习笔记之使用 Amazon ec2 部署图像检测模型-1_第2张图片

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

Fastapi学习笔记之使用 Amazon ec2 部署图像检测模型-1_第3张图片

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

Fastapi学习笔记之使用 Amazon ec2 部署图像检测模型-1_第4张图片

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

Fastapi学习笔记之使用 Amazon ec2 部署图像检测模型-1_第5张图片

另外,这里要注意公共 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@
Fastapi学习笔记之使用 Amazon ec2 部署图像检测模型-1_第6张图片

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

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

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

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

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

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

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

Fastapi学习笔记之使用 Amazon ec2 部署图像检测模型-1_第7张图片
import base64

with open("sample_images/dog_with_ball.jpg", "rb") as image_file:
    base64str = base64.b64encode(image_file.read()).decode("utf-8")
Fastapi学习笔记之使用 Amazon ec2 部署图像检测模型-1_第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

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

Fastapi学习笔记之使用 Amazon ec2 部署图像检测模型-1_第9张图片

(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”) 来定义我们的端点。

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}

(3)在本地测试 FastAPI 代码

在我们继续使用 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()
Fastapi学习笔记之使用 Amazon ec2 部署图像检测模型-1_第10张图片

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

(4)让我们可视化一下

我们可以在 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'])

输出如下:

Fastapi学习笔记之使用 Amazon ec2 部署图像检测模型-1_第11张图片

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

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

img = base64str_to_PILImage(base64str)
drawboundingbox(img, data_dict['boxes'], data_dict['classes'])
Fastapi学习笔记之使用 Amazon ec2 部署图像检测模型-1_第12张图片

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

你可能感兴趣的:(Python,Fastapi,python,fastapi)