欢迎关注『youcans动手学模型』系列
本专栏内容和资源同步到 GitHub/youcans
【YOLO5 项目实战】(9)将 YOLO5 部署到 Web 端
本节详细讲解使用 Flask 框架构建 YOLOv5 模型的 Web 应用程序,将 YOLOv5 模型部署到Web端,实现基于 Web 的图像处理和目标检测系统。
限于篇幅,本节只介绍上传图片进行目标检测。关于视频流的处理,将在后文介绍。
1. Flask 框架的安装与使用
Flask 是一个Python 编写的Web微框架,让我们可以用Python语言快速实现Web服务。只提供Web服务器支持,不提供全栈开发支持。Flask 非常轻量、学习简单,适合小微原型系统的开发。
1.1 Flask 框架的安装与使用
为了使用 Flask 构建计算机视觉 Web 应用程序,首先进行安装:
$ pip3 install flask
安装完成后,通过控制台命令cmd或 Anaconda Prompt (miniconda)进入命令行进行测试。
$ python
>>>import flask
>>>print(flask.__version__)2.3.1
1.2 Flask 框架例程
使用Flask框架编写一个显示“Hello Flask!”的web程序,介绍如何配置、调试Flask框架。
(1)新建一个Flask项目,项目的文件树如下。
---项目文件名\
|---static\
|---templates\
|--- server.py
项目默认配置建立static和templates目录,static目录用来存放静态资源,例如图片、js、css文件等,templates目录存放模板文件。网站逻辑保存在Python程序文件server.py中。
# server.pyfrom flask import Flask # 导入 Flask 包
app = Flask(__name__)# 用当前脚本名称实例化Flask对象@app.route('/')# 将函数绑定到指定URLdefhello():return'Hello Flask!'if __name__ =='__main__':
app.run()# 启动一个本地开发服务器,激活该网页
例程首先创建 Flask 类的实例,作为Web服务器的网关接口(Web Server Gateway Interface, WSGI)。然后,route()装饰器用于指定URL的触发函数,将触发函数绑定到指定URL。最后,函数返回在浏览器中显示的内容。
(2)运行Python程序。
运行程序server.py,在控制台显示如下的消息,表明Web服务器已启动。
$ python server.py
* Serving Flask app 'server'* Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.* Running on http://127.0.0.1:5000/(Press CTRL+C to quit)
(3)在浏览器中访问http://127.0.0.1:5000/。
这将对服务器发送GET请求,该请求将返回相应的消息:
127.0.0.1 - - [26/Apr/2023 21:42:13] “GET / HTTP/1.1” 200 -
127.0.0.1 - - [26/Apr/2023 21:42:13] “GET /favicon.ico HTTP/1.1” 404 -
1.3 Flask 路由访问
Web框架使用路由技术直接访问所需的页面,而无需从主页导航。
Flask中使用route()装饰器将应用程序的URL绑定到函数,可以实现路由访问。例如:
@app.route('/hello')
将URL
/hello
规则绑定到hello_world()函数。
@app.route('/hello')defhello_world():return'hello world'
用户访问 http://localhost:5000/hello,将在浏览器中显示hello_world()函数的输出。
application对 象的add_url_rule()函数也可用于将URL与函数绑定,如上例所示,使用route()装饰器的目的也由以下表示:
defhello_world():return'hello world'
app.add_url_rule('/','hello', hello_world)
通过向规则参数添加变量部分,可以动态构建URL。此变量部分标记为
<converter:variable_name>
。它作为关键字参数传递给与规则相关联的函数。
例如,在下面的例程中,route()装饰器的规则参数包含附加到URL’/hello’的
<name>
。
from flask import Flask
app = Flask(__name__)@app.route('/')defhello():return'Hello world!'@app.route('/user')defhello_user():return'Hello user!'@app.route('/hello/<name>')defhello_name(name):return'Hello %s!'% name
if __name__ =='__main__':
app.run(host='0.0.0.0')
如果在浏览器中输入http://localhost:5000/hello/youcans作为URL,则’youcans’ 将作为参数提供给 hello_name()函数。该请求将返回相应的消息:
* Running on all addresses (0.0.0.0)* Running on http://127.0.0.1:80* Running on http://192.168.3.249:80
Press CTRL+C to quit
127.0.0.1--[27/Apr/202309:06:27]"GET /hello/youcans HTTP/1.1"200-127.0.0.1--[27/Apr/202309:06:27]"GET /favicon.ico HTTP/1.1"404-
2. Flask 部署 OpenCV DNN 模型的 Web 应用程序
本节介绍上传本地图片,对图片进行处理(使用OpenCV级联检测器检测上传图像中的人脸和人眼),返回处理图片并在网页上进行格式化显示。关心 YOLO5模型部署的读者,可以跳过本节阅读本文第3部分。
2.1 从指定的 url 地址读取图像
(1)首先从指定的 url 地址读取图像。
Flask中使用route()装饰器将应用程序的URL绑定到函数,可以接受URL参数实现路由访问。
@app.route('/enhance', methods=['GET'])defdetail_enhance():# 从指定的 url 地址读取图像with urllib.request.urlopen(request.args.get('url'))as url:
image_array = np.asarray(bytearray(url.read()), np.uint8)# 从指定的内存缓存中读取数据,并把数据转换成图像格式
imgCV = cv2.imdecode(image_array,-1)# 转换为 OpenCV 图像
使用函数request.args.get(‘url’)来获取URL参数,构建一个变量URL。当我们访问/user/时,就可以接收到URL地址,然后从URL地址读取图片。
注意不能使用函数cv.imread()读取图像,而要使用函数cv.imdecode()从指定的内存缓存中读取数据,并将传输数据转换为OpenCV图像。
(2)图像处理。
使用 OpenCV 级联分类器进行图像处理。
(3)将图像编码到内存缓冲区。
使用函数cv.imencode()将图像编码为流数据,存储到内存缓存中,方便网络传输。
(4)生成返回页面响应。
使用函数make_response()将缓存中的图像编码封装为响应对象,将页面响应返回到客户端。
2.2 OpenCV 级联分类器类 cv::CascadeClassifier
OpenCV 中定义了级联分类器类 cv::CascadeClassifier。在 Python 语言中,使用接口函数 cv.CascadeClassifier() 从文件加载级联分类器模型,成员函数 cv.CascadeClassifier.detectMultiScale() 对图像进行目标检测。
cv.CascadeClassifier(filename)
cv.CascadeClassifier.detectMultiScale(image[, scaleFactor=1.1,
minNeighbors=3, flags=0, minSize=Size(), maxSize=Size()]) → objects
OpenCV 提供了 Haar 级联检测器的预训练模型如下,可以从 OpenCV 安装包 \data\haarcascades中提取,或者从【GitHub】opencv/data下载。
使用 Haar 级联检测器检测图片中的人脸的步骤如下:
(1)创建一个 CascadeClassifier 级联分类器对象,使用 load() 方法从 .xml 文件加载级联分类器模型。
(2)读取待检测的图片。
(3)使用detectMultiScale()函数检测图片,返回检测到的边界矩形。
(4)将检测到的边界矩形绘制到检测图片上。
2.3 cvFlask03 项目的构建步骤
项目cvFlask03的文件树如下。
---项目文件名\
|---static\
|---templates\
| |---processed.html
| |---upload.html
|--- cvFlask03.py
cvFlask03.py中图像处理子程序的代码如下。
# cvFlask03.pydefimageProcessing(filepath):# 图片处理子程序:人脸检测+人眼检测
imgCV = cv2.imread(filepath)# 从 filepath 路径读取图片
gray = cv2.cvtColor(imgCV, cv2.COLOR_BGR2GRAY)# 加载 Haar 级联分类器 预训练模型
model_path ="../data/haarcascade_frontalface_alt2.xml"
face_detector = cv2.CascadeClassifier(model_path)# <class 'cv2.CascadeClassifier'>
eye_path ="../data/haarcascade_eye.xml"# 人眼检测器
eye_detector = cv2.CascadeClassifier(eye_path)# <class 'cv2.CascadeClassifier'># 使用级联分类器检测人脸
faces = face_detector.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=1,
minSize=(40,40), maxSize=(300,300))# 绘制人脸检测框for x, y, w, h in faces:
cv2.rectangle(imgCV,(x,y),(x+w,y+h),(0,0,255),3)# 在人脸区域内检测人眼
roi = gray[y:y+h, x:x+w]# 提取人脸# 检测人眼
eyes = eye_detector.detectMultiScale(roi, scaleFactor=1.1, minNeighbors=1,
minSize=(15,15), maxSize=(80,80))# 绘制人眼for ex, ey, ew, eh in eyes:
cv2.rectangle(imgCV,(x+ex,y+ey),(x+ex+ew,y+ey+eh),(255,0,0),2)return imgCV
cvFlask03的脚本运行过程的具体步骤如下。
(1)进入cvFlask03项目的根目录,运行程序cvFlask03.py,启动流媒体服务器。
(2)在浏览器输入URL(http://192.168.3.249:5000/upload),打开upload.html网页。在浏览器点击页面上的选择按钮,选择本地的图片上传,上传的图片保存到
static\images
目录。
(3)程序cvFlask03.py对上传的图片进行人脸检测和人眼检测,在图片上以红色和蓝色方框标记检测到的人脸和人眼。然后激活processed.html网页,显示原始图像和处理后的图像。
(4)手机连接到局域网,按照控制台显示的内容在浏览器输入IP地址(http://192.168.3.249:5000/upload),也可以上传手机中的图片进行处理,结果如图所示。
3. Flask 部署 YOLOv5 模型的 Web 应用程序
上传本地图片,使用YOLOv5 模型对图片进行处理,返回处理图片并在网页上进行格式化显示。
本例程与上节 OpenCV + Flask 例程类似,只是将检测器换成 YOLOv5 模型。
3.1 Flask 框架的处理流程
(1)首先从指定的 url 地址读取图像。
Flask中使用route()装饰器将应用程序的URL绑定到函数,可以接受URL参数实现路由访问。
(2)图像处理。
使用YOLOv5 模型对图片进行处理。
(3)将图像编码到内存缓冲区。
使用函数cv.imencode()将图像编码为流数据,存储到内存缓存中,方便网络传输。
(4)生成返回页面响应。
使用函数make_response()将缓存中的图像编码封装为响应对象,将页面响应返回到客户端。
3.2 Yolo5Flask01 项目的构建步骤
(1)项目 Yolo5Flask01 的文件树如下。
---项目文件名\
|---templates\
| |---index3process.html
| |---index3upload.html
|---models\
|---static\
|---utils\
|---weights\
|---Yolo5Flask03.py
(2)编写index3upload.html文档,用于上传本地图片,保存在templates目录。
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>OpenCV+Flask 上传图片</title></head><body><h1 align="center">OpenCV+Flask 例程:上传本地图片处理</h1><p align="center">Developed by youcans@xupt,2023</p><form action="" enctype='multipart/form-data' method='POST'><label>选择按钮:</label><input type="file" name="file" style="margin-top:25px;"/><br><label>选中文件:</label><input type="text"class="txt_input" name="name" value="png/jpg/jpeg/bmp" style="margin-top:20px;"/><br><label>上传按钮:</label><input type="submit" value="上传图片"class="button-new" style="margin-top:20px;"/><br></form></body></html>
(3)编写index3processed.html文档,用于显示上传图片和处理图片,保存在templates目录。
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>OpenCV+Flask 上传图片</title></head><body><h1 align="center">OpenCV+Flask 例程:上传本地图片进行处理</h1><p align="center">Developed by youcans@xupt,2023</p><form action="" enctype='multipart/form-data' method='POST'><label>选中文件:</label><input type="text"class="txt_input" name="name" value={{userinput}} style="margin-top:10px;"/><br></form><h2 align="center">图片处理结果</h2><table width="700" height="350" border="5" align="center" frame=void><tr><td align="center" valign="middle">原始图片</td><td align="center" valign="middle">处理结果</td></tr><tr><td align="center" valign="middle"><img src="{{ url_for('static', filename= './images/upload.jpg',_t=val1) }}" width="300" height="300" alt="上传的原始图片"/></td><td align="center" valign="middle"><img src="{{ url_for('static', filename= './images/processed.jpg',_t=val1) }}" width="300" height="300" alt="处理完成的图片"/></td></tr></table></body></html>
3.3 Yolo5Flask03 的主程序
Yolo5Flask03.py 图像处理子程序的代码如下。
# Yolo5Flask03.py# Yolo5+Flask 图像处理例程 03# 上传本地图片进行处理,在网页上显示处理结果# Copyright 2024 Youcans, XUPT# Crated:2024-9-10# coding:utf-8import cv2
import torch
from flask import Flask, render_template, request, make_response, jsonify
from models.experimental import attempt_load
from utils.datasets import LoadStreams, LoadImages
from utils.general import(check_img_size, non_max_suppression, apply_classifier,
scale_coords, xyxy2xywh, plot_one_box, strip_optimizer, set_logging)from utils.torch_utils import select_device, load_classifier, time_synchronized
from numpy import random
import os
import time
from datetime import timedelta
from pathlib import Path
import shutil
import platform
# 设置允许的文件格式
ALLOWED_EXTENSIONS =set(['png','jpg','JPG','PNG','bmp'])
app = Flask(__name__)# 用当前脚本名称实例化Flask对象
app.send_file_max_age_default = timedelta(seconds=1)# 设置静态文件缓存过期时间@app.route('/upload', methods=['POST','GET'])# 添加路由defupload():if request.method =='POST':
f = request.files['file']# 从表单的 file 字段获取文件,file为该表单的name值print("user_input:", f.filename)# tiger02.pngifnot(f and allowed_file(f.filename)):# 检查图片类型return jsonify({"error":1001,"msg":"上传图片必须是 png/jpg/jpeg/bmp 类型"})
user_input = request.form.get("name")# 获取表单输入的 name 值, png/jpg/jpeg/bmp
basepath = os.path.dirname(__file__)# 当前文件所在路径 C:\Users\David\cvFlask\# upload_filepath = os.path.join(basepath, 'static\images', secure_filename(f.filename)) # 合成上传图片的保存路径
upload_filepath = os.path.join(basepath,'static\images','upload.jpg')# 合成上传图片的保存路径print("upload_filepath", upload_filepath)# upload_path, C:\Users\David\cvFlask\static\images\upload.jpg
f.save(upload_filepath)# 将上传的图片保存到 upload_path 路径# OpenCV 图像处理
dst = imageProcessing(upload_filepath)# 调用图片处理子程序
cv2.imwrite(os.path.join(basepath,'static/images','processed.jpg'), dst)# 保存处理后的图片
user_input = f.filename # 上传图片的文件名return render_template('index3process.html', userinput=user_input, val1=time.time())return render_template('index3upload.html')defimageProcessing(filepath):# 图片处理子程序
source = filepath
imgCV = cv2.imread(source)# 从 filepath 路径读取图片print("source:", source, imgCV.shape)# 使用 YOLOv5 检测图片
output ='inference/output'
weights ='weights/yolov5s.pt'# Yolo5 权重模型的路径
view_img =True
save_txt ='store_true'
imgsz =640#check_img_size(imgsz, s=model.stride.max()) # check img_sizewith torch.no_grad():
detect(output, source, weights, view_img, save_txt, imgsz)
outFilePath ='inference/output/upload.jpg'
imgOut = cv2.imread(outFilePath)# 从 filepath 路径读取图片print("output:", outFilePath, imgOut.shape)
cv2.imshow("out", imgOut)
key = cv2.waitKey(3000)# 5000ms 后自动关闭
cv2.destroyAllWindows()return imgOut
defallowed_file(filename):return'.'in filename and filename.rsplit('.',1)[1]in ALLOWED_EXTENSIONS
defdetect(out, source, weights, view_img, save_txt, imgsz, save_img=False):
webcam =False# webcam = source == '0' or source.startswith('rtsp') or source.startswith('http')# Initialize
set_logging()
device = select_device('')if os.path.exists(out):
shutil.rmtree(out)# delete output folder
os.makedirs(out)# make new output folder
half = device.type!='cpu'# half precision only supported on CUDA# Load model
model = attempt_load(weights, map_location=device)# load FP32 model
imgsz = check_img_size(imgsz, s=model.stride.max())# check img_sizeif half:
model.half()# to FP16# Set Dataloader
vid_path, vid_writer =None,Noneif webcam:
view_img =True
torch.backends.cudnn.benchmark =True# set True to speed up constant image size inference
dataset = LoadStreams(source, img_size=imgsz)else:
save_img =True
dataset = LoadImages(source, img_size=imgsz)# Get names and colors
names = model.module.names ifhasattr(model,'module')else model.names
colors =[[random.randint(0,255)for _ inrange(3)]for _ inrange(len(names))]# Run inference
t0 = time.time()
img = torch.zeros((1,3, imgsz, imgsz), device=device)# init img
_ = model(img.half()if half else img)if device.type!='cpu'elseNone# run oncefor path, img, im0s, vid_cap in dataset:
img = torch.from_numpy(img).to(device)
img = img.half()if half else img.float()# uint8 to fp16/32
img /=255.0# 0 - 255 to 0.0 - 1.0if img.ndimension()==3:
img = img.unsqueeze(0)# Inference
t1 = time_synchronized()# pred = model(img, augment=opt.augment)[0]
pred = model(img)[0]# Apply NMS
conf_thres =0.4
iou_thres =0.5
pred = non_max_suppression(pred, conf_thres, iou_thres)
t2 = time_synchronized()# Process detectionsfor i, det inenumerate(pred):# detections per imageif webcam:# batch_size >= 1
p, s, im0 = path[i],'%g: '% i, im0s[i].copy()else:
p, s, im0 = path,'', im0s
save_path =str(Path(out)/ Path(p).name)
txt_path =str(Path(out)/ Path(p).stem)+('_%g'% dataset.frame if dataset.mode =='video'else'')
s +='%gx%g '% img.shape[2:]# print string
gn = torch.tensor(im0.shape)[[1,0,1,0]]# normalization gain whwhif det isnotNoneandlen(det):# Rescale boxes from img_size to im0 size
det[:,:4]= scale_coords(img.shape[2:], det[:,:4], im0.shape).round()# Print resultsfor c in det[:,-1].unique():
n =(det[:,-1]== c).sum()# detections per class
s +='%g %ss, '%(n, names[int(c)])# add to string# Write resultsfor*xyxy, conf, cls inreversed(det):if save_txt:# Write to file
xywh =(xyxy2xywh(torch.tensor(xyxy).view(1,4))/ gn).view(-1).tolist()# normalized xywhwithopen(txt_path +'.txt','a')as f:
f.write(('%g '*5+'\n')%(cls,*xywh))# label formatif save_img or view_img:# Add bbox to image
label ='%s %.2f'%(names[int(cls)], conf)
plot_one_box(xyxy, im0, label=label, color=colors[int(cls)], line_thickness=3)# Print time (inference + NMS)print('%sDone. (%.3fs)'%(s, t2 - t1))# Stream resultsif view_img:
cv2.imshow(p, im0)if cv2.waitKey(1)==ord('q'):# q to quitraise StopIteration
# Save results (image with detections)if save_img:if dataset.mode =='images':
cv2.imwrite(save_path, im0)else:if vid_path != save_path:# new video
vid_path = save_path
ifisinstance(vid_writer, cv2.VideoWriter):
vid_writer.release()# release previous video writer
fourcc ='mp4v'# output video codec
fps = vid_cap.get(cv2.CAP_PROP_FPS)
w =int(vid_cap.get(cv2.CAP_PROP_FRAME_WIDTH))
h =int(vid_cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
vid_writer = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*fourcc), fps,(w, h))
vid_writer.write(im0)if save_txt or save_img:print('Results saved to %s'% Path(out))print('Done. (%.3fs)'%(time.time()- t0))if __name__ =='__main__':# 启动一个本地开发服务器,激活该网页print("URL: http://127.0.0.1:5000/upload")
app.run(host='0.0.0.0', port=5000, debug=True)# 绑定 IP 地址和端口号
3.4 运行 Yolo5Flask03 项目
Yolo5Flask03 的脚本运行过程的具体步骤如下。
(1)进入Yolo5Flask03 项目的根目录,运行程序Yolo5Flask03.py,启动流媒体服务器。
(2)在浏览器输入URL(http://192.168.3.96:5000/upload),打开upload.html网页。在浏览器点击页面上的选择按钮,选择本地的图片上传,上传的图片保存到
static\images
目录。
(3)程序 Yolo5Flask03.py对上传的图片进行目标检测。然后激活processed.html网页,显示原始图像和处理后的图像。
(4)手机连接到局域网,按照控制台显示的内容在浏览器输入IP地址(http://192.168.3.96:5000/upload),也可以上传手机中的图片进行处理,结果如图所示。
4. YOLOv5 官方 Flask REST API 的使用
Flask REST API是利用Flask框架及其扩展构建的RESTful Web服务,这些服务遵循 REST 架构风格,通过HTTP请求和响应进行客户端和服务器之间的交互,提供数据资源的增删改查等功能。
4.1 Flask REST API 例程
YOLOv5 官方已经提供了一个简单的 Flask REST API,位于 utils\flask_rest_api。其中包括以下文件:
- restapi.py,用于运行 Flask REST API 以构建一个 YOLOv5s 模型。
- example_request.py,用于执行测试请求。
- readme.md,说明文档
官方文档中的例程 restapi.py 是 从PyTorch Hub 部署YOLOv5s模型,本文将其修改为从本地 部署 YOLOv5s 模型,程序如下:
# Ultralytics YOLOv5 🚀, AGPL-3.0 license"""Run a Flask REST API exposing one or more YOLOv5s models."""import argparse
import io
import torch
from flask import Flask, request
from PIL import Image
app = Flask(__name__)
models ={}
DETECTION_URL ="/v1/object-detection/<model>"@app.route(DETECTION_URL, methods=["POST"])defpredict(model):"""Predict and return object detections in JSON format given an image and model name via a Flask REST API POST
request.
"""if request.method !="POST":returnif request.files.get("image"):# Method 1# with request.files["image"] as f:# im = Image.open(io.BytesIO(f.read()))# Method 2
im_file = request.files["image"]
im_bytes = im_file.read()
im = Image.open(io.BytesIO(im_bytes))if model in models:
results = models[model](im, size=640)# reduce size=320 for faster inferencereturn results.pandas().xyxy[0].to_json(orient="records")if __name__ =="__main__":
parser = argparse.ArgumentParser(description="Flask API exposing YOLOv5 model")
parser.add_argument("--port", default=5000,type=int,help="port number")
parser.add_argument("--model", nargs="+", default=["yolov5s"],help="model(s) to run, i.e. --model yolov5n yolov5s")
opt = parser.parse_args()for m in opt.model:# 从本地加载 YOLOv5 模型
models[m]= torch.hub.load("./", m, source="local")
app.run(host="0.0.0.0", port=opt.port)# debug=True causes Restarting with stat
对官方的测试请求程序,修改测试图像的路径为 data/images/zidane.jpg(根据测试图片的位置决定)。
# Ultralytics YOLOv5 🚀, AGPL-3.0 license"""Perform test request."""import pprint
import requests
DETECTION_URL ="http://localhost:5000/v1/object-detection/yolov5s"
IMAGE ="data/images/zidane.jpg"# Read imagewithopen(IMAGE,"rb")as f:
image_data = f.read()
response = requests.post(DETECTION_URL, files={"image": image_data}).json()
pprint.pprint(response)
4.2 YOLOv5s 模型部署的步骤
(1)在 PyCharm 的命令行,先运行 restapi.py 部署 YOLOv5s 模型:
> python3 restapi.py --port5000
(2)然后运行测试请求程序:
$ python example_request.py
(3)运行结果
模型推理结果以JSON响应的形式返回:
[{"class":0,"confidence":0.8900438547,"height":0.9318675399,"name":"person","width":0.3264600933,"xcenter":0.7438579798,"ycenter":0.5207948685},{"class":0,"confidence":0.8440024257,"height":0.7155083418,"name":"person","width":0.6546785235,"xcenter":0.427829951,"ycenter":0.6334488392},{"class":27,"confidence":0.3771208823,"height":0.3902671337,"name":"tie","width":0.0696444362,"xcenter":0.3675483763,"ycenter":0.7991207838},{"class":27,"confidence":0.3527112305,"height":0.1540903747,"name":"tie","width":0.0336618312,"xcenter":0.7814827561,"ycenter":0.5065554976}]
以上,就完成了一个基本的 YOLOv5s 模型部署和测试请求的过程。
5. 基于图片文件的 Flask REST API
对官方例程进行修改,使其响应基于文件的请求。从指定的文件读取图像,进行模型预测。
例程如下:
(1)例程 FlaskRestAPI2.py,用于部署 YOLOv5s 模型:
# Ultralytics YOLOv5 🚀, AGPL-3.0 license"""Run a Flask REST API exposing one or more YOLOv5s models."""import argparse
import io
import numpy as np
import cv2
import torch
from flask import Flask, request
from PIL import Image
app = Flask(__name__)
models ={}
DETECTION_URL ="/v1/object-detection/<model>"@app.route(DETECTION_URL, methods=["POST"])defpredict(model):"""Predict and return object detections in JSON format given an image and model name via a Flask REST API POST
request.
"""if request.method !="POST":returnif request.data:
img = cv2.imdecode(np.frombuffer(request.data, dtype=np.uint8), cv2.IMREAD_COLOR)if model in models:
results = models[model](img)# reduce size=320 for faster inferencereturn results.pandas().xyxy[0].to_json(orient="records")if request.files.get("image"):
im_file = request.files["image"]
im_bytes = im_file.read()
im = Image.open(io.BytesIO(im_bytes))if model in models:
results = models[model](im, size=640)# reduce size=320 for faster inferencereturn results.pandas().xyxy[0].to_json(orient="records")if __name__ =="__main__":
parser = argparse.ArgumentParser(description="Flask API exposing YOLOv5 model")
parser.add_argument("--port", default=5000,type=int,help="port number")
parser.add_argument("--model", nargs="+", default=["yolov5s"],help="model(s) to run, i.e. --model yolov5n yolov5s")
opt = parser.parse_args()for m in opt.model:
models[m]= torch.hub.load("./", m, source="local")
app.run(host="0.0.0.0", port=opt.port)# debug=True causes Restarting with stat
(2)例程 RequestDemo2.py,用于发送请求:
# Ultralytics YOLOv5 🚀, AGPL-3.0 license"""Perform test request."""import pprint
import requests
import cv2
DETECTION_URL ="http://localhost:5000/v1/object-detection/yolov5s"
filepath ="data/images/zidane.jpg"
imgCV = cv2.imread(filepath)# 从 filepath 路径读取图片
imgCV = cv2.cvtColor(imgCV, cv2.COLOR_BGR2RGB)# 从指定的内存缓存中读取数据,并把数据转换成图像格式
img = cv2.imencode(".jpg", imgCV)[1].tobytes()
response = requests.post(DETECTION_URL, data=img).json()
pprint.pprint(response)
(2)部署YOLOv5s 模型和发送请求:
- 在 PyCharm 的命令行,先运行 FlaskRestAPI2.py 部署 YOLOv5s 模型:
python3 FlaskRestAPI2.py --port 5000
- 然后运行测试请求程序 RequestDemo2:
$ python RequestDemo2.py
- 运行结果与上节相同,模型推理结果以JSON响应的形式返回。
6. 返回检测结果图片
在上例基础上进行修改,使其响应基于文件的请求,并返回模型预测结果的图片。
例程如下:
(1)例程 FlaskRestAPI3.py,用于部署 YOLOv5s 模型:
# Ultralytics YOLOv5 🚀, AGPL-3.0 license"""Run a Flask REST API exposing one or more YOLOv5s models."""# 运行 Flask REST API 以构建一个 YOLOv5s 模型,响应基于图片文件的请求,并返回模型预测结果图片import argparse
import io
import numpy as np
import cv2
import torch
from flask import Flask, request
from PIL import Image
app = Flask(__name__)
models ={}
DETECTION_URL ="/v1/object-detection/<model>"@app.route(DETECTION_URL, methods=["POST"])defpredict(model):"""Predict and return object detections in JSON format given an image and model name via a Flask REST API POST
request.
"""if request.method !="POST":returnif request.data:
img = cv2.imdecode(np.frombuffer(request.data, dtype=np.uint8), cv2.IMREAD_COLOR)if model in models:
results = models[model](img)# reduce size=320 for faster inference
results = results.render()[0]return cv2.imencode(".jpg", results)[1].tobytes()if request.files.get("image"):# Method 1# with request.files["image"] as f:# im = Image.open(io.BytesIO(f.read()))# Method 2
im_file = request.files["image"]
im_bytes = im_file.read()
im = Image.open(io.BytesIO(im_bytes))if model in models:
results = models[model](im, size=640)# reduce size=320 for faster inferencereturn results.pandas().xyxy[0].to_json(orient="records")if __name__ =="__main__":
parser = argparse.ArgumentParser(description="Flask API exposing YOLOv5 model")
parser.add_argument("--port", default=5000,type=int,help="port number")
parser.add_argument("--model", nargs="+", default=["yolov5s"],help="model(s) to run, i.e. --model yolov5n yolov5s")
opt = parser.parse_args()for m in opt.model:
models[m]= torch.hub.load("./", m, source="local")
app.run(host="0.0.0.0", port=opt.port)# debug=True causes Restarting with stat
(2)例程 RequestDemo3.py,用于发送请求和处理返回的预测结果图片:
# Ultralytics YOLOv5 🚀, AGPL-3.0 license"""Perform test request."""import pprint
import requests
import cv2
import numpy as np
import matplotlib.pyplot as plt
DETECTION_URL ="http://localhost:5000/v1/object-detection/yolov5s"
filepath ="data/images/zidane.jpg"
imgCV = cv2.imread(filepath)# 从 filepath 路径读取图片
imgCV = cv2.cvtColor(imgCV, cv2.COLOR_BGR2RGB)# 从指定的内存缓存中读取数据,并把数据转换成图像格式
img = cv2.imencode(".jpg", imgCV)[1].tobytes()
response = requests.post(DETECTION_URL, data=img)# 对返回的数据流进行解码,得到处理后的图片
img = cv2.imdecode(np.frombuffer(response.content, dtype=np.uint8), cv2.IMREAD_COLOR)# pprint.pprint(response)
plt.imshow(img)
plt.show()
(2)部署YOLOv5s 模型和发送请求:
- 在 PyCharm 的命令行,先运行 FlaskRestAPI3.py 部署 YOLOv5s 模型:
python3 FlaskRestAPI3.py --port 5000
- 然后运行测试请求程序 RequestDemo3:
$ python RequestDemo3.py
- 运行结果,返回模型推理结果的图片,解码后显示结果如下。
限于篇幅,本节只介绍上传图片进行目标检测。关于视频流的处理,将在后文介绍。
【本节完】
版权声明:
欢迎关注『youcans动手学模型』系列
转发请注明原文链接:
【YOLO5 项目实战】(9)将 YOLO5 部署到 Web 端
Copyright 2024 youcans, Xidian
Crated:2024-09-10
版权归原作者 youcans_ 所有, 如有侵权,请联系我们删除。