使用 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 元素,一个用于从本地上传文件,一个用于提交。
对 upload.html 进行预览:选择 upload.html 文件,右键,选择 Open With,然后选择使用 Preview 打开
创建结果 HTML 页面
在 templates 文件夹下创建 HTML 文件 result.html,结果显示页面将在此文件实现。为了界面的反复使用,我们可以把 upload.html 中的元素也包含到里面,同时在下方显示结果。
{{predict}}
创建 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