flask部署过程中受制于网络传输,和存读图片时间,检测存在较高延迟,通过多线程部署以达到降低延迟的目的。gunicorn可实现多线程和生产环境部署。
gunicorn目前支持linux,不支持windows,所以在linux里面安装。
在linux建立虚拟环境,正常环境可能出问题。之后
pip install gunicorn gevent
安装好之后,找不到gunicorn,就为gunicorn添加软链接。
server.py
from flask import Flask, request
import torch
import io
from io import BytesIO
import base64
import json
from PIL import Image
#加载模型
model = torch.hub.load('ultralytics/yolov5','custom',path = 'weight/face.pt',force_reload=False,source='local')
# 设置允许上传的文件格式
ALLOW_EXTENSIONS = ['png', 'jpg', 'jpeg', 'gif']
# 判断文件后缀是否存在
def allowed_file(filename):
return '.' in filename and filename.rsplit('.', 1)[-1] in ALLOW_EXTENSIONS
# 定义路由
app = Flask(__name__)
@app.route("/photo", methods=['POST','GET'])
def uploads():
if not request.method == "POST":
return
if request.files.get("image"):
image_file = request.files["image"]
file_name = image_file.filename
if allowed_file(file_name)==True:
image_bytes = image_file.read()
img = Image.open(io.BytesIO(image_bytes))
results = model(img, size=640)
#str = results.pandas().xyxy[0].to_json(orient="records")
#删除检测框坐标,看着比较烦人
sss = results.pandas().xyxy[0]
sss.drop(columns=['xmin', 'ymin', 'xmax', 'ymax'], inplace=True)
str = sss.to_json(orient="records")
im = request.values.to_dict()
value = im['download_image']
if value == 'True':
# results.imgs # array of original images (as np array) passed to model for inference
results.render() # updates results.imgs with boxes and labels
image_str = []
for img in results.imgs:
buffered = BytesIO()
img_base64 = Image.fromarray(img)
img_base64.save(buffered, format="JPEG")
image = base64.b64encode(buffered.getvalue()).decode('utf-8') # base64 encoded image with results
image_str.append(image)
s = {'result': str, 'image': image_str[0]}
else:
s = {'result': str}
else:
s = {file_name: "格式错误,无法正常打开",'error': 'error',}
s = json.dumps(s)
return s
if __name__ == "__main__":
from werkzeug.middleware.proxy_fix import ProxyFix
app.wsgi_app = ProxyFix(app.wsgi_app)
app.run()
使用gunicorn多线程部署
创建一个配置文件
gunicorn_config.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# 监听本机的端口
bind = "127.0.0.1:5000"
# 未决连接的最大数量,即等待服务的客户的数量
backlog = 2048
# 进程数
workers = 2
# 线程数
threads= 4
# 工作模式为gevent
worker_class = 'gevent'
# 最大客户端并发数量,默认情况下这个值为1000。
worker_connections = 1000
# 超时 默认30秒
timeout = 120
# 连接上等待请求的秒数,默认情况下值为2
keepalive = 50
# 根目录,server.py所在目录
chdir = '/opt/examine-face'
#记录PID
pidfile='gunicorn.pid'
启动服务:
server是服务端的文件名
app是server的部署方式
gunicorn -c gunicorn_config.py server:app
客户端多线程访问
client.py
import requests
import os
import time
from threading import Thread
from PIL import Image
from io import BytesIO
import base64
#设置一下图片的后缀
ALLOW_EXTENSIONS = ['png', 'jpg', 'jpeg', 'gif','pic','bmp']
def allowed_file(filename):
return '.' in filename and filename.rsplit('.', 1)[-1] in ALLOW_EXTENSIONS
aa = []
def make_txt(path):
#这个脚本是基于一个文件夹下面有很多文件夹,图片存在文件夹里面,找出设置后缀格式的文件,添加到表格
if os.path.isdir(path):
for i in os.listdir(path):
new_path = os.path.join(path+'/',i)
make_txt(new_path)
else:
if allowed_file(path) == True:
aa.append(path)
return aa
def jpg_compress(filelist,url,download_image):
#图片传输获取结果的代码
for i in filelist:
#获取每张图片的路径,以二进制格式打开
img_path = os.path.join(path+'/',i)
file_name = img_path.split('/')[-1]
file = open(img_path, 'rb')
files = {'image': file}
#设置一个参数,服务端读取参数判断是否返回图片 True或者False。
data = {'download_image': download_image}
# 设置请求头,不保持每个线程的连接,防止线程太多,报错
headers = {
'Connection': 'close',
}
r = requests.post(url, headers=headers,files=files,data = data)
file.close()
result = r.json()
# result.pop('name')
result['path'] = img_path
try:
# 对图片进行解码并打开
img = Image.open(BytesIO(base64.b64decode(result['image'])))
# 结果删除图片编码进行打印
result.pop('image')
# if result['result'] == '[]':
print(result)
# 储存图片,在本项目目录,需要建立一个名字为3的文件夹
if result['result'] != '[]':
img.save('3/'+file_name)
# img.show()#展示图片
img.close()
except KeyError:
print(result)
def multi_thread_process(list,num_threads,url,download_image):
#写线程代码
if num_threads == 1:
jpg_compress(list,url,download_image)
return
filelist_total = list
filenum = len(filelist_total)
filenum_each_thread = int(filenum/num_threads)
thread_list = []
for i in range(num_threads-1):
time.sleep(0.1)
thread_list.append(Thread(target=jpg_compress, args=(filelist_total[i*filenum_each_thread : (i+1)*filenum_each_thread],url,download_image)))
thread_list.append(Thread(target=jpg_compress, args=(filelist_total[(num_threads-1)*filenum_each_thread :],url,download_image)))
for th in thread_list:
th.start()
for th in thread_list:
th.join()
if __name__ == '__main__':
#设置一下需要的参数
#路径,这个路径下面的所有图片,包括下属文件夹的所有图片都会被读取。
path = 'C:/Users/Administrator/Desktop/test/2'
#设置一个目标服务器的地址
url = 'http://127.0.0.1:5000/photo'
#是否下载图片,True False
download_image = False
#设置线程数量,线程数量基于部署的线程数,不要太大,不然会报错
num_threads = 8
#计时,开始
a = time.time()
#调用路径函数,读取该路径及下属文件夹所有的图片存入表格
list = make_txt(path)
#调用线程函数,线程函数里面会调用传输图片的函数,输入所需要的参数,进行端口访问,获取结果
multi_thread_process(list,num_threads,url,download_image)
#计时结束
b = time.time()
num = len(list)
cost = b - a
#计算耗时情况
print('耗时', cost)
print('图片总数:', num)
print('平均耗时:',cost/num)