Bootstrap

毕业实习——基于OpenVINO的口罩检测任务

实习安排

  • 第一天:Python基础,图像处理
  • 第二天:全连接神经网络,卷积神经网络
  • 第三天:PaddlePaddle搭建神经网络,手写体识别训练模型
  • 第四天:手写体识别模型检测任务测试,模型参数 提取
  • 第五天:C语言实现手写体识别算子,HLS编译算子
  • 第六天:编写SoC端测试代码,项目整合
  • 第七天:OpenVINO工具套件介绍与使用
  • 第八天:AI Studio使用及配置ppyoloe模型参数
  • 第九天:基于OpenVINO的口罩检测任务
  • 第十天:学生拓展项目,项目答辩

一、环境准备及数据准备

1.环境准备

  • 工具:labelImgAI StudioYOLO2COCOPaddleUtilsPaddleYOLO
  • 本地环境要求:
    openvino2022.2.0
    paddle2onnx
    1.0.5
    paddlepaddle2.4.2
    opencv-python
    4.2.0.32
    onnx1.11.0
    tensorflow
    2.9.1
  • labelimg是一个有图形界面的图像标注工具,用来给数据打标签。
  • 安装工具之前需要建立python虚拟环境,虚拟环境建立请移步至《Python安装及虚拟环境建立》教程
  • AI Studio是基于百度深度学习平台飞桨的人工智能学习与实训社区,提供在线编程环境、免费GPU 算力、海量开源算法和开放数据,帮助开发者快速创建和部署模。初次使用的小伙伴记得注册之后完成新手礼包获取算力卡。
  • 借助YOLO2COCO把YOLO格式的标签数据转成COCO格式数据集。
  • 这是一个paddlepaddle模型减支工具,我们需要把训练得到的ppyoloe口罩检测模型进行裁剪。
  • paddleyolo里面有很多目标检测算法,其中包括ppyoloe这个算法,使用的时候只需要配置一些文件就可以训练我们的模型了,非常方便。因为训练模型需要GPU,所以需要在AI Studio里面使用paddleyolo。
  • 库的安装

openvino的安装

pip install openvino-dev[onnx,tensorflow]==2022.2.0 

其他的都pip install 库名==版本,如果安装很慢,可以加一个镜像比如

pip install paddle2onnx==1.0.5 -i https://pypi.tuna.tsinghua.edu.cn/simple/

2.数据准备

目录结构
----mask
----images
----mask_00133.jpg
----mask_00134.jpg
.
.
.
----labels
----mask_00133.txt
----mask_00134.txt
.
.
.
图片数量有2076张,人脸数据较多,口罩数据较少。图片大致如下所示:
在这里插入图片描述
数据来自一些公开的数据集,为了让模型训练得更好,自己也收集了一些数据,并使用labelimg对数据进行标注。

  • 启动labelimg
    在虚拟环境下输入:labelimg
    在这里插入图片描述

  • 指定要标注的图片文件夹,我们的文件夹是images。指定labels文件夹,被标注的图片生成的.txt标签文件会自动保存到labels文件夹下。
    在这里插入图片描述

  • 数据格式选用yolo,因为公开数据集是yolo格式,所以统一用yolo。
    在这里插入图片描述

  • 操作:按下w选取区域,并输入标签,每输入一张图片都要保存,按a和d进行左右切换图片,每张图片可选取多个区域。

  • 生成的标签txt文件。除了根据图片名字生成相应的标签txt,还有一个classes.txt,可以将其移除该文件夹,注意是移除,不是删除。

  • 因为只有两个标签mask和nomask,所以classes.txt如下所示:在这里插入图片描述

  • 生成的标签txt文件

在这里插入图片描述
0表示nomask,1表示mask,图片中标注了多少个框,相应的生成几排数据。如下魔性图中,假如图片是680x640,0或者1后面的几个小数分别代表框的中心坐标x,y,框的宽w,框的高h。为什么是小数?是因为x/680,y/640,w/680,h/640。

在这里插入图片描述
数据转换
数据集拆分,严格意义上讲,应该把数据拆成训练集、测试集、验证集,我们为了方便,就只需要拆分成训练集和验证集就可以了。在YOLO2COCO\dataset文件下建立一个yolo_mask文件夹,然后将mask移动到此文件夹下。点击YOLO2COCO获取工具。
在这里插入图片描述
YOLO2COCO给的模板
在这里插入图片描述
先把第上面产生的classes.txt移动至yolo_mask文件夹下面,形如这样。
在这里插入图片描述
现在编写一个python生产我们需要的train.txt,val.txt。

import os
def train_val(labels_path, data_path, ratio=0.3):
    nomask_num = 0#计数nomask的数量
    mask_num = 0#计数mask的数量
    image_dir = "\\".join(data_path.split("\\")[-3:]) + "\\images"#根据yolo2coco要求制定路径
    txt_files = os.listdir(labels_path)
    f_train = open("train.txt", "w")
    f_val = open("val.txt", "w")
    m = 0
    n = 0
    for txt in txt_files:
        f_txt = open(os.path.join(labels_path, txt), 'r')#打开txt文件
        if f_txt.read()[0] == "0":#读取每个文件的第一行,判断是nomask(0)还是mask(1)
            nomask_num += 1#不戴口罩加1
        else:
            mask_num += 1#戴口罩加1
        f_txt.close()

    for txt in txt_files:
        f_txt = open(os.path.join(labels_path, txt), 'r')
        if f_txt.read()[0] == "0":#读取每个文件的第一行,判断是nomask(0)还是mask(1)
            n += 1
            if n >= int(nomask_num * ratio):
                f_train.writelines(image_dir+"\\" + txt.split(".")[0] + ".jpg" + "\n")#往文件里面写路径,记得换行
            else:
                f_val.writelines(image_dir+"\\" + txt.split(".")[0] + ".jpg" + "\n")#往文件里面写路径,记得换行
        else:
            m += 1
            if m >= int(mask_num * ratio):
                f_train.writelines(image_dir+"\\" + txt.split(".")[0] + ".jpg" + "\n")#往文件里面写路径,记得换行
            else:
                f_val.writelines(image_dir+"\\" + txt.split(".")[0] + ".jpg" + "\n")#往文件里面写路径,记得换行
        f_txt.close()
    f_train.close()
    f_val.close()
if __name__ == "__main__":
    data_path = os.path.join(os.getcwd(), 'mask')#获取文件夹mask的绝对路径
    labels_path = os.path.join(data_path, "labels")#获取labels文件夹的绝对路径
    train_val(labels_path=labels_path, data_path=data_path, ratio=0.3)

生成的train.txt,val.txt如下,train_val_split是上述源代码文件,放置位置如下图所示,因为使用的jupyter notebook编写,所以是.ipynb,可以转成.py文件并在该文件夹下运行。

在这里插入图片描述
接下来可以转换数据了,clean.ipynb和train_val_split.ipynb不会影响转换过程,所以不用删除,毕竟是自己写的,删掉可惜了。在YOLO2COCO文件夹下启动cmd在这里插入图片描述
根据自己的目录情况执行以下命令。

python yolov5_2_coco.py --dir_path E:\paddle_openvino\YOLO2COCO\dataset\yolo_mask

在这里插入图片描述
最后打包好数据,准备上传AI Studio吧。
在这里插入图片描述

二、模型训练

1.创建数据集

因为数据集过大,启动一个项目,然后直接上传是不行的,所以先创建一个数据集,再在数据集基础上创建项目。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.创建项目

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • copy paddleyolo github 链接
    在这里插入图片描述
  • 注意在ppyoloe.ipynb文件里面写的代码或者markdown都要ctrl+s保存一下,养成一个优良的习惯。
    在这里插入图片描述

3.模型配置

  • 在configs下面有很多模型,文件夹名就是模型名字,除了可以使用ppyoloe,还有yolov5,yolov8等。
    在这里插入图片描述
  • 选择一个模型文件夹,修改文件夹里面的配置文件,我们选择的是small版本的ppyoloe,需要训练80 epochs。
    在这里插入图片描述
  • 我们需要把num_classes修改成2,因为我们的数据集只有两个标签,分别是nomask,mask。改完记得ctrl+s保存。
    在这里插入图片描述
    因为配置文件中要求数据放到dataset/mask里面,所以需要把数据集放置到此处。
    在这里插入图片描述
  • 新建一个mask文件夹,把解压过后的数据文件夹拖到mask里面。
    在这里插入图片描述
  • 为了方便,直接剪切。
    在这里插入图片描述
  • 粘贴到dataset文件夹下,注意红色框的路径。
    在这里插入图片描述

4.模型训练

  • 在ppyoloe模型文件夹里面有个readme_cn.md,里面有训练模型的命令。将改命令复制到ppyoloe.ipynb文件里面执行。
    在这里插入图片描述在这里插入图片描述
  • 模型训练标志,此时是0 epoch,eta时间估计大概要2个小时17分16秒训练完成。时间会变,但是大概就两个小时左右,所以可以开个黑回来刚好合适。

在这里插入图片描述

  • 找到自己的模型放置位置,在生成的output文件夹下。如下图操作可以获取模型model_final.pdparams的绝对路径,然后粘贴至weights=后面,导出模型。
!python tools/export_model.py -c configs/ppyoloe/ppyoloe_plus_crn_s_80e_coco.yml -o weights=模型参数绝对路径

在这里插入图片描述
导出模型会放置output_inference文件夹下。
在这里插入图片描述

三、模型转换

1.可视化模型

把上节课下载的四个文件打包进ppyoloe_crn_s_80里面。虽然文件夹名字有点长,但是通过名字就大概知道是什么版本的模型,训练了多久,何乐而不为?
在这里插入图片描述
进入模型可视化网站,选择我们上一课程训练好的模型。
在这里插入图片描述
可以看到模型输入是3x640x640,3是图像的通道,也就是说我们传入的图像是有色图。
在这里插入图片描述
定位到模型的输出,在可视化图像的最下方,我们要找到需要模型减支的地方。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.模型减支

模型减支的目的就是从输入到两个输出concat_14.tmp_0,tmp_16为止,后面的节点都删掉。在模型减支工具的paddle目录下打开cmd。
在这里插入图片描述
激活满足本项目要求的虚拟环境。

activate 虚拟环境名

我们将运行这个模型减支py文件。
在这里插入图片描述
运行以下命令。

python prune_paddle_model.py --model_dir ppyoloe_crn_s_80 --model_filename model.pdmodel --params_filename model.pdiparams --output_names tmp_16 concat_14.tmp_0 --save_dir export_model

在这里插入图片描述
运行过后新增一个减支完成的模型文件夹。
在这里插入图片描述

3.模型转化

先把paddle模型转换为onnx,需要在环境里面提前安装paddle2onnx。执行以下命令。

paddle2onnx --model_dir export_model --model_filename model.pdmodel --params_filename model.pdiparams --input_shape_dict "{'image':[1,3,640,640]}" --opset_version 11 --save_file ppyoloe_crn_s_80.onnx

在这里插入图片描述
执行生成的ppyoloe_crn_s_80.onnx。
在这里插入图片描述
onnx转xml,bin(OpenVINO)。

mo --input_model ppyoloe_crn_s_80.onnx

在这里插入图片描述
执行结果。
在这里插入图片描述

四、模型推理

增加一个文件labels.txt,内容是我们的标签,注意放置位置。
在这里插入图片描述

增加一个inference.ipynb用于编写推理代码,注意放置位置。
在这里插入图片描述
推理代码

from openvino.runtime import Core
import openvino.runtime as ov
import cv2 as cv
import numpy as np
import tensorflow as tf

OpenVINO 模型推理器(class)

class Predictor:
    """
    OpenVINO 模型推理器
    """
    def __init__(self, model_path):
        ie_core = Core()
        model = ie_core.read_model(model=model_path)
        self.compiled_model = ie_core.compile_model(model=model, device_name="CPU")
    def get_inputs_name(self, num):
        return self.compiled_model.input(num)
    
    def get_outputs_name(self, num):
        return self.compiled_model.output(num)
    
    def predict(self, input_data):
        return self.compiled_model([input_data])
    
    def get_request(self):
        return self.compiled_model.create_infer_request()

图像预处理

def process_image(input_image, size):
    """输入图片与处理方法,按照PP-Yoloe模型要求预处理图片数据

    Args:
        input_image (uint8): 输入图片矩阵
        size (int): 模型输入大小

    Returns:
        float32: 返回处理后的图片矩阵数据
    """
    max_len = max(input_image.shape)
    img = np.zeros([max_len,max_len,3],np.uint8)
    img[0:input_image.shape[0],0:input_image.shape[1]] = input_image # 将图片放到正方形背景中
    img = cv.cvtColor(img,cv.COLOR_BGR2RGB)  # BGR转RGB
    img = cv.resize(img, (size, size), cv.INTER_NEAREST) # 缩放图片
    img = np.transpose(img,[2, 0, 1]) # 转换格式
    img = img / 255.0 # 归一化
    img = np.expand_dims(img,0) # 增加维度
    return img.astype(np.float32)

图像后处理

def process_result(box_results, conf_results):
    """按照PP-Yolove模型输出要求,处理数据,非极大值抑制,提取预测结果

    Args:
        box_results (float32): 预测框预测结果
        conf_results (float32): 置信度预测结果
    Returns:
        float: 预测框
        float: 分数
        int: 类别
    """
    conf_results = np.transpose(conf_results,[0, 2, 1]) # 转置
    # 设置输出形状
    box_results =box_results.reshape(8400,4) 
    conf_results = conf_results.reshape(8400,2)
    scores = []
    classes = []
    boxes = []
    for i in range(8400):
        conf = conf_results[i,:] # 预测分数
        score = np.max(conf) # 获取类别
        # 筛选较小的预测类别
        if score > 0.5:
            classes.append(np.argmax(conf)) 
            scores.append(score) 
            boxes.append(box_results[i,:])
    scores = np.array(scores)
    boxes = np.array(boxes)
    
    result_box = []
    result_score = []
    result_class = []
    # 非极大值抑制筛选重复的预测结果
    if len(boxes) != 0:
        # 非极大值抑制结果
        indexs = tf.image.non_max_suppression(boxes,scores,len(scores),0.25,0.35)
        for i, index in enumerate(indexs):
            result_score.append(scores[index])
            result_box.append(boxes[index,:])
            result_class.append(classes[index])
    # 返回结果
    return np.array(result_box),np.array(result_score),np.array(result_class)

画出预测框

def draw_box(image, boxes, scores, classes, labels):
    """将预测结果绘制到图像上

    Args:
        image (uint8): 原图片
        boxes (float32): 预测框
        scores (float32): 分数
        classes (int): 类别
        lables (str): 标签

    Returns:
        uint8: 标注好的图片
    """
    colors = [(0, 0, 255), (0, 255, 0)]
    scale = max(image.shape) / 640.0 # 缩放比例
    if len(classes) != 0:
        for i in range(len(classes)):
            box = boxes[i,:]
            x1 = int(box[0] * scale)
            y1 = int(box[1] * scale)
            x2 = int(box[2] * scale)
            y2 = int(box[3] * scale)
            label = labels[classes[i]]
            score = scores[i]
            cv.rectangle(image, (x1, y1), (x2, y2), colors[classes[i]], 2, cv.LINE_8)
            cv.putText(image,label+":"+str(score),(x1,y1-10),cv.FONT_HERSHEY_SIMPLEX, 0.55, colors[classes[i]], 2)
        
    return image

读取标签

def read_label(label_path):
    with open(label_path, 'r') as f:
        labels = f.read().split()
    return labels

同步推理

label_path = "labels.txt"
yoloe_model_path = "ppyoloe_crn_s_80.xml"
predictor = Predictor(model_path = yoloe_model_path)
boxes_name = predictor.get_outputs_name(0)
conf_name = predictor.get_outputs_name(1)
labels = read_label(label_path=label_path)
cap = cv.VideoCapture(0)
while cap.isOpened():
    ret, frame = cap.read()
    frame = cv.flip(frame, 180)
    cv.namedWindow("MaskDetection", 0)  # 0可调大小,注意:窗口名必须imshow里面的一窗口名一直
    cv.resizeWindow("MaskDetection", 640, 480)    # 设置长和宽
    input_frame = process_image(frame, 640)
    results = predictor.predict(input_data=input_frame)
    boxes, scores, classes = process_result(box_results=results[boxes_name], conf_results=results[conf_name])
    result_frame = draw_box(image=frame, boxes=boxes, scores=scores, classes=classes, labels=labels)
    cv.imshow('MaskDetection', result_frame)
    key = cv.waitKey(1)
    if key == 27: #esc退出
        break
cap.release()
cv.destroyAllWindows()

异步推理

label_path = "labels.txt"
yoloe_model_path = "ppyoloe_crn_s_80.xml"
predictor = Predictor(model_path = yoloe_model_path)
input_layer = predictor.get_inputs_name(0)
labels = read_label(label_path=label_path)
cap = cv.VideoCapture(0)
curr_request = predictor.get_request()
next_request = predictor.get_request()
ret, frame = cap.read()
curr_frame = process_image(frame, 640)
curr_request.set_tensor(input_layer, ov.Tensor(curr_frame))
curr_request.start_async()
while cap.isOpened():
    ret, next_frame = cap.read()
    next_frame = cv.flip(next_frame, 180)
    cv.namedWindow("MaskDetection", 0)  # 0可调大小,注意:窗口名必须imshow里面的一窗口名一直
    cv.resizeWindow("MaskDetection", 640, 480)    # 设置长和宽
    in_frame = process_image(next_frame, 640)
    next_request.set_tensor(input_layer, ov.Tensor(in_frame))
    next_request.start_async()
    if curr_request.wait_for(-1) == 1:
        boxes_name = curr_request.get_output_tensor(0).data
        conf_name = curr_request.get_output_tensor(1).data
        boxes, scores, classes = process_result(box_results=boxes_name, conf_results=conf_name)
        frame = draw_box(image=frame, boxes=boxes, scores=scores, classes=classes, labels=labels)
        cv.imshow('MaskDetection', frame)
    frame = next_frame
    curr_request, next_request = next_request, curr_request
    key = cv.waitKey(1)
    if key == 27: #esc退出
        break
cap.release()
cv.destroyAllWindows()

最后run all执行即可

五、结果演示

在这里插入图片描述
在这里插入图片描述

六、拓展项目

除了完成这次口罩检测任务,我们还做了一些其他的任务:包括检测表情(笑与不笑)、检测性别(男或女)。
结果如下:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

七、参考链接

口罩检测——环境准备(1)
口罩检测——数据准备(2)
口罩检测——模型训练(3)
口罩检测——模型转换(4)
口罩检测——模型推理(5)

;