82TensorFlow 2 模型部署方法实践--使用 Flask 框架部署模型

使用 Flask 框架部署模型

环境配置

首先创建 Python3.6 虚拟环境,虚拟环境方便我们更好地管理依赖。
同时为了留出足够的空间以保证第三方库正常安装,我们需要临时删除线上环境中的一些非必要文件。

$sudo rm -rf ../../opt
$virtualenv -p /usr/bin/python3.6 pyenv

进入虚拟环境,安装本节课程需要的库:Flask,OpenCV,TensorFlow 和 skimage。

$ . pyenv/bin/activate
$ pip install flask==1.1.1 opencv-python==4.1.2.30 tensorflow scikit-image==0.16.2 --no-cache-dir -i https://mirrors.aliyun.com/pypi/simple/

下载预训练模型,并将其放于 ~/.keras/models 目录下。

$ wget https://labfile.oss.aliyuncs.com/courses/1435/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_1.0_224.h5
$ mkdir -p ~/.keras/models
$ cp mobilenet_v2_weights_tf_dim_ordering_tf_kernels_1.0_224.h5 ~/.keras/models

通过客户端发送 POST 请求

如目前的互联网公司为开发者提供的人工智能开放平台,允许开发者申请账号后调用他们的 API 接口,接口文档举例描述如下:
描述:调用者提供图片文件或者图片 URL,进行图片分析,识别图片中的物体。
调用 URL:XXX.com/image_recognition。
调用方法:POST。

请求参数:
必选:
api_key: 调用此 API 的凭证。
三选一
image_url: 图片的 URL。
image_file: 一个图片,二进制文件,需要用 POST multipart/form-data 的方式上传。
image_base64: Base64 编码的二进制图片数据。

创建客户端脚本

我们先模仿开发者的行为,创建客户端来调用 API 接口。在桌面创建文件脚本 client.py,首先导入需要的库。

import base64
from skimage import data
import requests
import cv2

接下来对导入 NumPy 格式的图片,OpenCV 提供了 cv2.imencode 来把 NumPy 格式的图片编码成流数据,放到内存缓存中,函数 cv2.imdecode 可以从编码的数据流恢复到 NumPy 数组。

# 从 skimage 中获取图片
image = data.chelsea()

# OpenCV 图像的数据类型也是 NumPy 2 维数组
_, content = cv2.imencode('.jpg', image)
# 将流数据用 base64 编码
image_base64 = base64.b64encode(content)

最后封装数据,发送到服务端 http://0.0.0.0:8080/mobilenet ,并打印结果。

# 制作指定的数据格式
data = {'image_base64': image_base64}
# 向服务端地址 http://0.0.0.0:8080/mobilenet 发送数据
r = requests.post('http://0.0.0.0:8080/mobilenet', data=data)
# 打印从服务端获得的结果
print(r.text)

创建服务端

客户端脚本创建完成后,我们接下来实现服务端的功能。在桌面创建文件脚本 service.py,首先导入需要的库。

import base64
import cv2
import numpy as np
from flask import Flask, request
from tensorflow.keras.applications.mobilenet_v2 import MobileNetV2, preprocess_input, decode_predictions

对服务和模型进行初始化,并导入预训练模型。

app = Flask(__name__)
model = MobileNetV2(weights='imagenet')

实现路由 mobilenet 中图像识别的功能。

@app.route('/mobilenet', methods=("POST",))
def mobilenet():
    # 获取从客户端传来的数据中 image_base64 字段的值
    image_base64 = request.form['image_base64']
    # 将 Base64 解码
    image_str = base64.b64decode(image_base64)
    # 将解码后的字符串转成 uint8 的 NumPy
    image_np = np.fromstring(image_str, np.uint8)
    # 用函数 cv2.imdecode 将编码的数据流恢复到 NumPy 数组
    image = cv2.imdecode(image_np, cv2.IMREAD_COLOR)
    # 调整图片大小为模型输入的大小
    image = cv2.resize(image, (224, 224))
    # 制作输入数据
    x = np.expand_dims(image, 0)
    x = preprocess_input(x)
    # 获得输出向量
    output = model.predict(x)
    # 解码输出向量,这里只取第一个结果
    preds = decode_predictions(output,top=1)
    pred = np.squeeze(preds)
    # 返回预测结果
    return pred[1]

最后在地址 0.0.0.0:8080 启动服务。host= 若不填,服务则会在 127.0.0.1 启动,这样的话是无法访问 Web 服务的。

if __name__ == '__main__':
    app.run(host='0.0.0.0',port=8080)

至此,服务端脚本构建完毕。

启动程序

在终端输入 python service.py 来启动服务端。
新建一个终端,进入虚拟环境并运行客户端。

$ . pyenv/bin/activate
$ python client.py

我们即可得到相应的请求结果。
本小节实验完成后在服务端终端窗口按下 Ctrl + C 终止服务,继续下面的实验。

通过 HTML 发送 POST 请求

在某些情况下会有非开发者使用服务,这时候可以提供一个网页界面,让用户通过网页上传图片的方式进行识别,即有一个前端的 HTML 服务给用户上传图片,图片上传后提交到后端的 Flask 服务,由 Flask 服务进行图像识别,并返回结果到 HTML 页面上。
首先在桌面创建两个文件夹 static 和 templates,前者用于存放 HTML 上传的图片,后者用于存放 HTML 文件。

创建上传 HTML 页面

在 templates 文件夹下创建 HTML 文件 upload.html,上传功能页面将在此文件实现。上传页面需要创建一个表单元素,然后包含两个 input 元素,一个用于从本地上传文件,一个用于提交。

<!DOCTYPE html>
<html lang="en">
  <body>
    <!--创建 HTML 表单 -->
    <form action="" enctype="multipart/form-data" method="POST">
      <!--创建本地上传文件按钮  -->
      <input type="file" name="file" style="margin-top:20px;" />
      <!--创建提交文件按钮  -->
      <input
        type="submit"
        value="上传"
        class="button-new"
        style="margin-top:15px;"
      />
    </form>
  </body>
</html>

对 upload.html 进行预览:选择 upload.html 文件,右键,选择 Open With,然后选择使用 Preview 打开


image.png

创建结果 HTML 页面

在 templates 文件夹下创建 HTML 文件 result.html,结果显示页面将在此文件实现。为了界面的反复使用,我们可以把 upload.html 中的元素也包含到里面,同时在下方显示结果。

<!DOCTYPE html>
<html lang="en">
  <body>
    <form action="" enctype="multipart/form-data" method="POST">
      <input type="file" name="file" style="margin-top:20px;" />
      <input
        type="submit"
        value="上传"
        class="button-new"
        style="margin-top:15px;"
      />
    </form>
    <!--显示图片-->
    <img src="{{ url_for('static', filename= filename) }}" width="500px" />
    <!--显示结果-->
    <div>{{predict}}</div>
  </body>
</html>

创建 Flask 应用

在桌面创建文件脚本 web.py,该脚本用于接收 HTML 传过来的图像数据,进行图像识别后返回结果。
首先导入需要的库。

from flask import Flask, render_template, request
import numpy as np
import cv2
from tensorflow.keras.applications.mobilenet_v2 import MobileNetV2, preprocess_input, decode_predictions

对服务和模型进行初始化,并导入预训练模型。

app = Flask(__name__)
model = MobileNetV2(weights='imagenet')

实现图像识别的功能,当获取到的是 POST 传过来的数据,则开始识别,不然就返回 upload.html 界面。 在这里我们使用 render_template 模板,其功能是先引入 HTML 文件,然后根据后面传入的参数,对 HTML 进行修改渲染。

@app.route('/', methods=['POST', 'GET'])
def main_page():
    if request.method == 'POST':
        file = request.files['file']
        # 将图片存在 static 文件夹中
        file.save('static/'+file.filename)
        # 读取图片
        image = cv2.imread('static/'+file.filename)
        # OpenCV 读取图片是 BGR 格式,需要转换为 RGB
        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        # 放缩图片到 224 * 224
        image = cv2.resize(image, (224, 224))
        x = np.expand_dims(image, 0)
        # 图片预处理
        x = preprocess_input(x)
        # 进行预测
        output = model.predict(x)
        # 取 top1 的预测结果
        preds = decode_predictions(output, top=1)
        predict = np.squeeze(preds)
        # 返回数据
        return render_template('result.html', filename=file.filename, predict=predict)
    # GET 方法返回 upload.html
    return render_template('upload.html')

最后在地址 0.0.0.0:8080 启动服务。同样,必须指定 host= ,否则服务默认会在 127.0.0.1 启动,这样的话是无法访问 Web 服务的。

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080)

启动程序

在终端输入 python web.py 启动程序。
点击右侧工具栏中的 Web 服务 按钮,访问 Web 服务进行提交图片。
https://labfile.oss.aliyuncs.com/courses/1435/3-1.mp4
最后,你可以通过终端下载本次实验的完整代码进行练习:

wget https://labfile.oss.aliyuncs.com/courses/1435/full_code_3.zip
unzip full_code_3.zip
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容