使用AWS Fargate为OpenCV项目创建无服务器API

在这个项目中,我们将在带有Fargate的AWS无服务器容器中部署一个使用OpenCV和Flask的应用程序。

无论是因为你有使用OpenCV和Flask处理项目的经验,还是因为你已经有一个应用程序在使用该堆栈运行,并且你只想将其迁移到带有容器的服务中,使用AWS都可能非常方便。

AWS提供了使用云服务器部署容器的选项,或使用AWS Fargate部署无服务器选项。因此,在这篇文章中,我们将在带有AWS Fargate的无服务器容器中部署在OpenCV和Flask中创建的简单图像识别模型。

为此,我们将把工作分为以下步骤,首先部署网络资源和存储库,将容器中的图像加载到其中。随后,我们将创建Flask项目并测试我们的OpenCV应用程序。一旦应用程序整合,我们将把图像上传到存储库。

我们最终将使用AWS Fargate部署该项目。

部署网络资源

对于第一部分,我们将回收我们在以前的CDK项目中部署的网络资源,在Fargate上创建一个Laravel应用程序的帖子中,你可以在这里找到它:https://medium.com/@JugueDev/despliega-laravel-usando-containers-serverless-con-aws-fargate-%EF%B8%8F-%EF%B8%8F-f4fdcb6c170f

这些网络资源除其他外,包括一个VPC、2个子网、一个安全组、ECR中的图像存储库和一个与这些资源相关的集群。

import { Stack, StackProps, RemovalPolicy, CfnOutput } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as ecr from 'aws-cdk-lib/aws-ecr';
import * as iam from 'aws-cdk-lib/aws-iam';



export interface NetworkStackProps extends StackProps {
    env: { region: string; app: string; version: string; environment: string };
}

export class NetworkStack extends Stack {
    public readonly ecs_app: ecs.ICluster;
    public readonly ecr_repository: ecr.Repository;

    constructor(scope: Construct, id: string, props: NetworkStackProps) {
        super(scope, id, props);

        const vpc = new ec2.Vpc(this, "vpc-test", {
            maxAzs: 2,
            vpcName: "vpc-name"
        });

        const appSG = new ec2.SecurityGroup(this, 'app-sg', {
            vpc: vpc,
            securityGroupName: "app-sg",
        });

        const cluster = new ecs.Cluster(this, "cluster-test", {
            clusterName: "cluster-name",
            enableFargateCapacityProviders: true,
            vpc: vpc,
        });

        this.ecr_repository = new ecr.Repository(this, "test-repo", {
            repositoryName: "repository-name",
            autoDeleteImages: true,
            removalPolicy: RemovalPolicy.DESTROY
        });

        this.ecs_app = ecs.Cluster.fromClusterAttributes(this, 'ecs-cluster', {
            clusterName: "cluster-name",
            vpc: vpc,
            securityGroups: [appSG]
        });

        new CfnOutput(this, 'repositoryUri', {
            value: this.ecr_repository.repositoryUri,
          });

    }
}

在 Flask 中创建项目

现在让我们研究 Flask 应用程序,为此,我们将在Flask中创建一个超级基础的应用程序,创建一个hello_app.py文件,其中包含以下内容:

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello_world():
 return 'Hello World!'

if __name__ == "__main__":
 app.run()
90d854196da5a2f9703dcdd2db72141a.jpeg

我们创建了一个文件,在该文件中定义了我们的requirements.txt项目的依赖关系:

Flask==2.0.2

现在我们使用Docker容器应用程序创建一个DockerFile:

# Dockerfile
FROM python:3.9.10-slim
WORKDIR /server
COPY . /server

# Install python dependencies
RUN pip3 install --upgrade pip setuptools wheel
RUN pip3 install --no-cache-dir -r requirements.txt  --no-build-isolation

ENV FLASK_APP=app
CMD ["python","hello_app.py"]
ecb65881ac0bb043a5d02b13a93d4544.jpeg

openCV 集成应用

现在,我们将把我们的OpenCV应用程序连接到我们的Flask项目,为此,我们需要在requirement.txt文件中添加几个依赖项

click==8.0.3
Flask==2.0.2
itsdangerous==2.0.1
Jinja2==3.0.2
MarkupSafe==2.0.1
Werkzeug==2.0.2
numpy
keras
cmake==3.20.5
scikit-build==0.13.1
setuptools==50.3.2
wheel==0.30.0
tensorflow
opencv-python==4.4.0.46

我们还将创建一个文件夹,将以前以M5格式存储的模型保存在一个名为Model的文件夹中:

7290ae50e77a83ef96a0a6b3f4fea769.jpeg

我们将使用Load_Model模块从Flask应用程序代码中引用该模型:

def FireOrNot(image):
 '''Determines if the image contains a Fire or Not'''
 model = construct_v1(224, 224, training=False)
 model.load(os.path.join("model", "firedetection"),weights_only=True)

 image = cv2.resize(image, (224,224), interpolation = cv2.INTER_AREA)
 image = image.reshape(1,224,224,3) 
 res = float(model.predict(image)[0][0])
 K.clear_session()
 return res

Flask API方法的后端:

@app.route('/', methods=['GET', 'POST'])
def upload_file():
 if request.method == 'POST':
  # check if the post request has the file part
  if 'file' not in request.files:
   flash('No file part')
   return redirect(request.url)
  file = request.files['file']
  # if user does not select file, browser also
  # submit an empty part without filename
  if file.filename == '':
   flash('No selected file')
   return redirect(request.url)
  if file and allowed_file(file.filename):
   filename = secure_filename(file.filename)
   file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
   image = cv2.imread(os.path.dirname(os.path.realpath(__file__))+"/uploads/"+filename)
   # color_result = getDominantColor(image)
   res = FireOrNot(image)
   if res >= 0.9:
    NotFireOrFire = "Fire"
   else:
    NotFireOrFire = "NotFire"
   #return redirect(url_for('upload_file',filename=filename)), jsonify({"key":
   return jsonify({"Result": res, "FireOrNot": NotFireOrFire} )
 return '''
 <!doctype html>
 <title>API</title>
 <h1>API Running Successfully</h1>'''

本地环境中的尝试:

357a46d12559d0dbc68cb8c90ecc45a3.jpeg

脚本 cv_app.py 的内容如下:

import os
from flask import Flask, flash, request, redirect, url_for, jsonify
from werkzeug.utils import secure_filename
import cv2
import numpy as np
import keras
from keras.models import load_model
from keras import backend as K
from model import construct_v1
import os



UPLOAD_FOLDER = './uploads/'
ALLOWED_EXTENSIONS = set(['png', 'jpg', 'jpeg'])
DEBUG = True
app = Flask(__name__)
app.config.from_object(__name__)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER

def allowed_file(filename):
 return '.' in filename and \
     filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

@app.route('/', methods=['GET', 'POST'])
def upload_file():
 if request.method == 'POST':
  # check if the post request has the file part
  if 'file' not in request.files:
   flash('No file part')
   return redirect(request.url)
  file = request.files['file']
  # if user does not select file, browser also
  # submit an empty part without filename
  if file.filename == '':
   flash('No selected file')
   return redirect(request.url)
  if file and allowed_file(file.filename):
   filename = secure_filename(file.filename)
   file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
   image = cv2.imread(os.path.dirname(os.path.realpath(__file__))+"/uploads/"+filename)
   # color_result = getDominantColor(image)
   res = FireOrNot(image)
   if res >= 0.9:
    NotFireOrFire = "Fire"
   else:
    NotFireOrFire = "NotFire"
   #return redirect(url_for('upload_file',filename=filename)), jsonify({"key":
   return jsonify({"Result": res, "FireOrNot": NotFireOrFire} )
 return '''
 <!doctype html>
 <title>API</title>
 <h1>API Running Successfully</h1>'''

def FireOrNot(image):
 '''Determines if the image contains a Fire or Not'''
 model = construct_v1(224, 224, training=False)
 model.load(os.path.join("model/SP-InceptionV1-OnFire", "sp-inceptiononv1onfire"),weights_only=True)

 image = cv2.resize(image, (224,224), interpolation = cv2.INTER_AREA)
 image = image.reshape(1,224,224,3) 
 res = float(model.predict(image)[0][0])
 K.clear_session()
 return res

def getDominantColor(image):
 '''returns the dominate color among Blue, Green and Reds in the image '''
 B, G, R = cv2.split(image)
 B, G, R = np.sum(B), np.sum(G), np.sum(R)
 color_sums = [B,G,R]
 color_values = {"0": "Blue", "1":"Green", "2": "Red"}
 return color_values[str(np.argmax(color_sums))]


if __name__ == "__main__":
 app.run(host= '0.0.0.0', debug=True)

Dockerfile 文件的修改:

# Dockerfile
FROM python:3.9.10-slim
WORKDIR /server
COPY . /server

# Install python dependencies
RUN pip3 install --upgrade pip setuptools wheel
RUN pip3 install --no-cache-dir -r requirements.txt  --no-build-isolation

ENV FLASK_APP=app
CMD ["python","cv_app.py"]
bd90e9807b7c1f0476bd925d6a1bff76.jpeg

最终构建图像。

将图像上载到存储库

一旦图像被构建,我们就可以继续将其上传到我们的存储库中,为此,我们可以遵循AWS ECR本身推荐的步骤。

c50602bb3a323a2c0d5bf01e383d70b5.jpeg

执行:

1cd1f8b947e5b19effe8690b959886f2.jpeg
f331676add87b87af8848f8e70464369.jpeg

在AWS Fargate中部署图像

最后,通过存储库中保存的图像,我们可以将我们的图像部署到AWS Fargate的无服务器服务中,为此,我们部署CDK容器堆栈:

import { Construct } from 'constructs';
import * as ecr from 'aws-cdk-lib/aws-ecr';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as iam from 'aws-cdk-lib/aws-iam';
import { RemovalPolicy, Stack, StackProps } from 'aws-cdk-lib';

export interface ContainerStackProps extends StackProps {
    env: { region: string; app: string; version: string; environment: string },
    ecs_app: ecs.ICluster,
    ecr_repository: ecr.Repository
}

export class ContainerStack extends Stack {

    constructor(scope: Construct, id: string, props: ContainerStackProps) {
        super(scope, id, props);

        // Creamos un rol para asignarlo al ALB
        const executionECSRole = new iam.Role(this, "ecs-execution-role-id", {
            assumedBy: new iam.ServicePrincipal("ecs-tasks.amazonaws.com"),
            roleName: "evs-execution-role",
            description: "Rol de IAM para ejecutar Tasks de ECS.",
        });

        executionECSRole.addManagedPolicy(
            iam.ManagedPolicy.fromAwsManagedPolicyName(
                'service-role/AmazonECSTaskExecutionRolePolicy',
            ));

        const first_image = ecs.EcrImage.fromRegistry(props.ecr_repository.repositoryUri + ":latest");
        const alb_fargate = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'alb-fargate', {
            cluster: props.ecs_app,
            taskImageOptions: {
                image: first_image,
                containerName: "container-name",
                executionRole: executionECSRole,
                containerPort: 5000
            },
            memoryLimitMiB: 2048,
            cpu: 1024,
            desiredCount: 2,
            listenerPort: 80,
            serviceName: "my-test-service",
            publicLoadBalancer: true,
        });

        const fargate_service = alb_fargate.service
    }
}

记住用新创建的repo的url替换图像repo的url。

c8c1941bbed4c8e430434d5c38ad46fe.jpeg

现在,我们已经在AWS的无容器服务器上运行了我们的OpenCV应用程序:

6812a6e122918e105a17872329be3fac.jpeg
dc2d489d6749f682c73df201e1c98207.jpeg

参考文献

https://medium.com/@rajeev_ratan/deploying-a-deep-learning-keras-computer-vision-model-to-aws-using-an-aws-ec2-instance-as-a-web-3d00b09da082?source=post_page-----ab1db7aba53a

https://shipyard.build/blog/first-flask-docker-compose-app/?source=post_page-----ab1db7aba53a

☆ END ☆

如果看到这里,说明你喜欢这篇文章,请转发、点赞。微信搜索「uncle_pn」,欢迎添加小编微信「 woshicver」,每日朋友圈更新一篇高质量博文。

扫描二维码添加小编↓

e7737b5dc4dc3de2b90a09005052d926.jpeg

  • 10
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值