【yolo小白】yolo基于flask部署(3)

482 阅读4分钟

前置 —— yolo所需环境

运行环境

  • python 3.8
  • anaconda 2020
  • flask
  • opencv

对于 yolo 项目的运行所需要的环境可以参考一下这篇文章


安装配置完对应所需软件包之后,就可以 新建 python 虚拟环境,然后安装 flaskopencv。 安装命令如下:

// 创建python虚拟环境
conda create -n yoloFlask python==3.8

// 激活环境
conda activate yoloFlask

// 1.1 安装下载 flask 和 opencv
pip install flask opencv-python

flask创建项目和配置环境

创建flask项目 image.png

配置环境 image.png

image.png

运行项目

python app.py

出现下面的情况,表面运行成功。 image.png

yolo项目搭建

  1. 创建完 flask 项目之后就可以在 github 拉取一下 yolo 项目了。

  2. 下载对应依赖:

pip install -r .\requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
  1. 我们将对应 yolo 项目中的一些模块移动到我们刚搭建的 flask 项目目录中。

    • data
    • models
    • runs
    • utils
    • weights

flask 实现 yolo 的运用 (本地部署)

app.py

@app.route('/')
def index():
    return render_template('index.html')  # template文件夹下的index.html

def gen(camera):
    while True:
        frame = camera.get_frame()
        # 使用generator函数输出视频流, 每次请求输出的content类型是image/jpeg
        if frame is None:
            break
        yield (b'--frame\r\n'
               b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n\r\n')

@app.route('/video_feed')  # 这个地址返回视频流响应
def video_feed():
    return Response(gen(Camera()),
                    mimetype='multipart/x-mixed-replace; boundary=frame')

@app.route('/detect', methods=['POST'])
def vedio_stream():
    if request.method == 'POST':
        stream = request.files['videoFile'].stream
        # 将文件流写入临时文件
        temp_video_path = 'temp_video.mp4'
        with open(temp_video_path, 'wb') as temp_video_file:
            temp_video_file.write(stream.read())
        video_manager.cap = cv2.VideoCapture(temp_video_path)

        return redirect('/')  # 重定向到显示处理视频的页面
    return 'request method error!'

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

camera.py

from random import randint

import cv2
import numpy as np
import torch

from models.experimental import attempt_load
from utils.augmentations import letterbox
from utils.general import non_max_suppression, scale_boxes
from utils.torch_utils import select_device

from video_manager import video_manager

# cap = cv2.VideoCapture('cat.mp4')
class Camera(object):
    def __init__(self):
        # 通过opencv获取实时视频流
        self.img_size = 640
        self.threshold = 0.4
        self.max_frame = 160
        self.video = video_manager.cap  # 视频流
        # self.video = cv2.VideoCapture(path)
        self.weights = 'weights/yolov5n.pt'  # yolov5权重文件
        self.device = 'cuda:0' if torch.cuda.is_available() else 'cpu'
        self.device = select_device(self.device)
        model = attempt_load(self.weights)
        model.to(self.device).eval()
        model.half()
        # torch.save(model, 'test.pt')
        self.m = model
        self.names = model.module.names if hasattr(
            model, 'module') else model.names
        self.colors = [
            (randint(0, 255), randint(0, 255), randint(0, 255)) for _ in self.names
        ]

    def __del__(self):
        if self.video is None:
            # 没有上传视频时,不执行读取帧的操作
            return None
        self.video.release()

    def get_frame(self):
        if self.video is None:
            # 没有上传视频时,不执行读取帧的操作
            return None

        ret, frame = self.video.read()  # 读视频

        if not ret:
            # 视频已经播放完毕,没有更多的帧可供处理
            return None

        im0, img = self.preprocess(frame)  # 转到处理函数

        pred = self.m(img, augment=False)[0]  # 输入到模型
        pred = pred.float()
        pred = non_max_suppression(pred, self.threshold, 0.3)

        pred_boxes = []
        image_info = {}
        count = 0
        for det in pred:
            if det is not None and len(det):
                det[:, :4] = scale_boxes(
                    img.shape[2:], det[:, :4], im0.shape).round()

                for *x, conf, cls_id in det:
                    lbl = self.names[int(cls_id)]
                    x1, y1 = int(x[0]), int(x[1])
                    x2, y2 = int(x[2]), int(x[3])
                    pred_boxes.append(
                        (x1, y1, x2, y2, lbl, conf))
                    count += 1
                    key = '{}-{:02}'.format(lbl, count)
                    image_info[key] = ['{}×{}'.format(
                        x2 - x1, y2 - y1), np.round(float(conf), 3)]

        frame = self.plot_bboxes(frame, pred_boxes)

        ret, jpeg = cv2.imencode('.jpg', frame)
        return jpeg.tobytes()

    def preprocess(self, img):

        img0 = img.copy()
        img = letterbox(img, new_shape=self.img_size)[0]
        img = img[:, :, ::-1].transpose(2, 0, 1)
        img = np.ascontiguousarray(img)
        img = torch.from_numpy(img).to(self.device)
        img = img.half()  # 半精度
        img /= 255.0  # 图像归一化
        if img.ndimension() == 3:
            img = img.unsqueeze(0)

        return img0, img

    def plot_bboxes(self, image, bboxes, line_thickness=None, default_color=None):
        tl = line_thickness or round(0.002 * (image.shape[0] + image.shape[1]) / 2) + 1  # line/font thickness

        for (x1, y1, x2, y2, cls_id, conf) in bboxes:
            if cls_id in self.names:  # 确保 cls_id 存在于 names 列表中
                idx = self.names.index(cls_id)
                color = self.colors[idx]  # 使用索引获取颜色
            else:
                color = default_color  # 如果 cls_id 不存在,则使用默认颜色

            c1, c2 = (x1, y1), (x2, y2)
            cv2.rectangle(image, c1, c2, color, thickness=tl, lineType=cv2.LINE_AA)

            tf = max(tl - 1, 1)  # font thickness
            t_size = cv2.getTextSize(cls_id, 0, fontScale=tl / 3, thickness=tf)[0]
            c2 = c1[0] + t_size[0], c1[1] - t_size[1] - 3
            cv2.rectangle(image, c1, c2, color, -1, cv2.LINE_AA)  # filled
            cv2.putText(image, '{}-{:.2f}'.format(cls_id, conf), (c1[0], c1[1] - 2), 0, tl / 3, [225, 255, 255],
                        thickness=tf, lineType=cv2.LINE_AA)

        return image

这里与参考资料做了些更改,添加了一个全局类,

video_manager.py

class VideoManager:
    def __init__(self):
        self.cap = cv2.VideoCapture(0)

video_manager = VideoManager()

前端页面是一样的,使用的就是一个简单的访问请求,显示

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>视频检测</title>
    <style>
      div{
        margin: 0 auto;
        text-align: center;
        width: 1200px;
        height: 800px;
      }
      img{
        width: 75%;
        height: 75%;
        border: 1px solid black;
      }
    </style>
  </head>
  <body>
    <div>
      <h1>视频检测</h1>
        <form id="uploadForm" enctype="multipart/form-data" method="post" action="http://127.0.0.1:5000/detect">

        <input type="file" name="videoFile" accept="video/*">

        <button type="submit">开始检测</button>

        </form>
    <img src="{{ url_for('video_feed') }}">
    </div>

  </body>
</html>

最后运行一下

python app.py

项目默认采用的是本地摄像头。你也可以点击上传视频检测。

最后的效果如下:

默认采用的是本地摄像头。 image.png

也可以选择文件上传检测

image.png

作为初学者,这篇文章介绍的主要是然后使用 flask 部署 yolo ,后面我页面对于代码进行研究。

感谢贡献

预告

下一篇是将flask基于docker部署到服务器上。