手势识别检测——基于PP-YOLOE算法模型剪枝及推理

手势识别检测——基于PP-YOLOE算法模型剪枝及推理

1 环境准备

1.1 工具及环境要求

工具
本地环境要求
  • openvino==2022.2.0
  • paddle2onnx==1.0.5
  • paddlepaddle==2.4.2
  • opencv-python==4.2.0.32
  • onnx==1.11.0
  • tensorflow==2.9.1

1.2 工具介绍

1.2.1 python
可以安装python到本地,也可以直接跳过本步,去anaconda创建python的虚拟环境
1.2.2 anaconda

anaconda仅作为虚拟环境,会创建及进入虚拟环境即可,此处不需要费时间

HINT:建议虚拟环境安装python3.7.9版本,此版本经本人实测无问题,其余版本不敢保证后续依赖项是否出错。

自学参考教程(仅供参考,本人非本人严格审核过文章):

Anaconda介绍、安装及使用保姆级教程 - 知乎 (zhihu.com)

安装conda搭建python环境(保姆级教程)_conda创建python虚拟环境-CSDN博客

Anaconda安装(Python) - 知乎 (zhihu.com)

1.2.3 labelimg

labelimg是一个有图形界面的图像标注工具,用来给数据打标签。

conda activate [虚拟环境名称] #激活anaconda虚拟环境
pip3 install labelimg #安装labelimg
pip3 install labelimg -i https://pypi.tuna.tsinghua.edu.cn/simple
#上一个命令如果速度慢,可以试着开VPN,速度应该会快,无VPN可使用国内清华镜像,即https://pypi.tuna.tsinghua.edu.cn/simple
1.2.4 AI Studio
打个广告,帮我助力一下,我的算力用完了
AI Studio学习与实训社区上线 Tesla A100!为我助力赢10点免费算力,助力成功你可领100点算力卡哦~https://aistudio.baidu.com/aistudio/newbie?invitation=1&sharedUserId=3735541&sharedUserName=CharlyX%20Lee

AI Studio是基于百度深度学习平台飞桨的人工智能学习与实训社区,提供在线编程环境、免费GPU 算力、海量开源算法和开放数据,帮助开发者快速创建和部署模。初次使用的小伙伴记得注册之后完成新手礼包获取算力卡。

在这里插入图片描述

完成任务过后,你也可以拥有和很多的GPU使用时间。

在这里插入图片描述

1.2.5 YOLO2COCO

因为我们拿到的数据集是yolo格式的,所以需要借助这个工具把YOLO格式的标签数据转成COCO格式数据集。

1.2.6 PaddleUtils

这是一个paddlepaddle模型剪枝工具,我们需要把训练得到的ppyoloe手势检测模型进行裁剪。

1.2.7 paddleyolo

paddleyolo里面有很多目标检测算法,其中包括ppyoloe这个算法,使用的时候只需要配置一些文件就可以训练我们的模型了,非常方便。因为训练模型需要GPU,所以需要在AI Studio里面使用paddleyolo。

1.3 库的安装

pip install paddlepaddle -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install openvino-dev[ONNX,pytorch,tensorflow]==2022.3.1 -i https://pypi.tuna.tsinghua.edu.cn/simple
本人记不清所有的环境配置有哪些,后续运行过程中,如果发现有依赖包缺失及时安装即可。

2 数据准备

2.1 数据介绍

目录结构
----dataset
----000-one
----gesture-one-2021-03-07_23-07-48-2_61613.jpg
----gesture-one-2021-03-07_23-07-48-4_40096.jpg
·
·
·
----013-pink
----gesture-pink-2021-03-07_23-07-55-1_39459.jpg
----gesture-pink-2021-03-07_23-07-55-2_5978.jpg
.
.
.

图片数量有2017张,每类手势的数据集数量不一。图片大致如下所示:

在这里插入图片描述

数据来自校赛官方提供的数据集,我们将使用labelimg对数据进行标注,即打标签。

2.2 数据标注

conda activate [环境名称] #打开anaconda虚拟环境

labelimg #启动labelimg

在这里插入图片描述

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

在这里插入图片描述

数据格式选用yolo,因为公开数据集是yolo格式,所以统一用yolo。图片中是手势“1”,所以数据标注为000-one。
在这里插入图片描述

生成的标签txt文件。除了根据图片名字生成相应的标签txt,还有一个classes.txt。

在这里插入图片描述

本次手势识别共有14中标签,classes.txt如下所示:

在这里插入图片描述

生成的标签txt文件如下图所示:

在这里插入图片描述

0,1,…,13表示标签号,图片中标注了多少个框,相应的生成几排数据。如下图中,假如图片是680x640,0或者1后面的几个小数分别代表框的中心坐标x,y,框的宽w,框的高h。为什么是小数?是因为x/680,y/640,w/680,h/640。

在这里插入图片描述

2.3 数据转换

数据集拆分,严格意义上讲,应该把数据拆成训练集、测试集、验证集,我们为了方便,就只需要拆分成训练集和验证集就可以了。在YOLO2COCO\dataset文件下建立一个yolo_mask文件夹,然后将dataset文件夹和label文件夹移动到此文件夹下。点击YOLO2COCO获取工具。

在这里插入图片描述

下载到本地,自选文件夹解压即可。(这里我们为了方便,保存在本次项目同目录下)

在这里插入图片描述

我们先看看YOLO2COCO给的模板

在这里插入图片描述

打开train.txt看一下内容(val.txt里面的内容类似),其实就是从dataset开始的图像的相对路径。
在这里插入图片描述

先把我们之前的数据集及打好标签的数据标注移动至gesture文件夹(自行新建文件夹可自由命名)下面,再将数据标注文件夹中产生的classes.txt移动至gesture文件夹,形如这样。

在这里插入图片描述

写一个python脚本帮我们生成需要的train.txt,val.txt(我们将原数据集按7:3的比例拆分成训练集和验证集)

import os
def train_val(labels_path, data_path, ratio=0.3):
    one_num = 0#计数000-one的数量
    five_num = 0#计数001-five的数量
    fist_num = 0#计数002-fist的数量
    ok_num = 0#计数003-ok的数量
    heartSingle_num = 0#计数004-heartSingle的数量
    yearh_num = 0#计数005-yearh的数量
    three_num = 0#计数006-three的数量
    four_num = 0#计数007-four的数量
    six_num = 0#计数008-six的数量
    Iloveyou_num = 0#计数009-Iloveyou的数量
    gun_num = 0#计数010-gun的数量
    thumbUp_num = 0#计数011-thumbUp的数量
    nine_num = 0#计数012-nine的数量
    pink_num = 0#计数013-pink的数量
    image_dir = "\\".join(data_path.split("\\")[-2:]) + "\\dateset"#根据yolo2coco要求制定路径
    txt_files = os.listdir(labels_path)
    f_train = open("train.txt", "w")
    f_val = open("val.txt", "w")
    n1 = 0
    n0 = 0
    n2 = 0
    n3 = 0
    n4 = 0
    n5 = 0
    n6 = 0
    n7 = 0
    n8 = 0
    n9 = 0
    n10 = 0
    n11 = 0
    n12 = 0
    n13 = 0
    for txt in txt_files:
        f_txt = open(os.path.join(labels_path, txt), 'r' ,encoding='utf-8')#打开txt文件
        first_two_chars = f_txt.read(2)
        if first_two_chars == "1 ":#读取每个文件的第一行,判断是000-one
            one_num += 1#000-one加1
        elif first_two_chars == "0 ":#读取每个文件的第一行,判断是001-five
            five_num += 1#001-five加1
        elif first_two_chars == "2 ":#读取每个文件的第一行,判断是002-fist
            fist_num += 1#002-fist加1
        elif first_two_chars == "3 ":#读取每个文件的第一行,判断是003-ok
            ok_num += 1#003-ok加1
        elif first_two_chars == "4 ":#读取每个文件的第一行,判断是004-heartSingle
            heartSingle_num += 1#004-heartSingle加1
        elif first_two_chars == "5 ":#读取每个文件的第一行,判断是005-yearh
            yearh_num += 1#005-yearh加1
        elif first_two_chars == "6 ":#读取每个文件的第一行,判断是006-three
            three_num += 1#006-three加1
        elif first_two_chars == "7 ":#读取每个文件的第一行,判断是007-four
            four_num += 1#007-four加1
        elif first_two_chars == "8 ":#读取每个文件的第一行,判断是008-six
            six_num += 1#008-six加1
        elif first_two_chars == "9 ":#读取每个文件的第一行,判断是009-Iloveyou
            Iloveyou_num += 1#009-Iloveyou加1
        elif first_two_chars == "10":#读取每个文件的第一行,判断是010-gun
            gun_num += 1#010-gun加1
        elif first_two_chars == "11":#读取每个文件的第一行,判断是011-thumbUp
            thumbUp_num += 1#011-thumbUp加1
        elif first_two_chars == "12":#读取每个文件的第一行,判断是012-nine
            nine_num += 1#012-nine加1
        elif first_two_chars == "13":#读取每个文件的第一行,判断是013-pink
           pink_num += 1#013-pink加1
        f_txt.close()

    for txt in txt_files:
        f_txt = open(os.path.join(labels_path, txt), 'r')
        first_two_chars = f_txt.read(2)
        if first_two_chars == "1 ":#读取每个文件的第一行,判断是000-one
            n1 += 1
            if n1 >= int(one_num * ratio):
                f_train.writelines(image_dir+"\\" + "000-one"+"\\"+txt.split(".")[0] + ".jpg" + "\n")#往文件里面写路径,记得换行
            else:
                f_val.writelines(image_dir+"\\" + "000-one"+"\\"+ txt.split(".")[0] + ".jpg" + "\n")#往文件里面写路径,记得换行
        elif first_two_chars == "0 ":#读取每个文件的第一行,判断是001-five
            n0 += 1
            if n0 >= int(five_num * ratio):
                f_train.writelines(image_dir+"\\"+ "001-five"+"\\" + txt.split(".")[0] + ".jpg" + "\n")#往文件里面写路径,记得换行
            else:
                f_val.writelines(image_dir+"\\"+ "001-five"+"\\" + txt.split(".")[0] + ".jpg" + "\n")#往文件里面写路径,记得换行
        elif first_two_chars == "2 ":#读取每个文件的第一行,判断是002-fist
            n2 += 1
            if n2 >= int(fist_num * ratio):
                f_train.writelines(image_dir+"\\" + "002-fist"+"\\" + txt.split(".")[0] + ".jpg" + "\n")#往文件里面写路径,记得换行
            else:
                f_val.writelines(image_dir+"\\" + "002-fist"+"\\" + txt.split(".")[0] + ".jpg" + "\n")#往文件里面写路径,记得换行
        elif first_two_chars == "3 ":#读取每个文件的第一行,判断是003-ok
            n3 += 1
            if n3 >= int(ok_num * ratio):
                f_train.writelines(image_dir+"\\" + "003-ok"+"\\" + txt.split(".")[0] + ".jpg" + "\n")#往文件里面写路径,记得换行
            else:
                f_val.writelines(image_dir+"\\" + "003-ok"+"\\" + txt.split(".")[0] + ".jpg" + "\n")#往文件里面写路径,记得换行
        elif first_two_chars == "4 ":#读取每个文件的第一行,判断是004-heartSingle
            n4 += 1
            if n4 >= int(heartSingle_num * ratio):
                f_train.writelines(image_dir+"\\" + "004-heartSingle"+"\\" + txt.split(".")[0] + ".jpg" + "\n")#往文件里面写路径,记得换行
            else:
                f_val.writelines(image_dir+"\\" + "004-heartSingle"+"\\" + txt.split(".")[0] + ".jpg" + "\n")#往文件里面写路径,记得换行
        elif first_two_chars == "5 ":#读取每个文件的第一行,判断是005-yearh
            n5 += 1
            if n5 >= int(yearh_num * ratio):
                f_train.writelines(image_dir+"\\" + "005-yearh"+"\\"+ txt.split(".")[0] + ".jpg" + "\n")#往文件里面写路径,记得换行
            else:
                f_val.writelines(image_dir+"\\" + "005-yearh"+"\\"+ txt.split(".")[0] + ".jpg" + "\n")#往文件里面写路径,记得换行
        elif first_two_chars == "6 ":#读取每个文件的第一行,判断是006-three
            n6 += 1
            if n6 >= int(three_num * ratio):
                f_train.writelines(image_dir+"\\" + "006-three"+"\\"+ txt.split(".")[0] + ".jpg" + "\n")#往文件里面写路径,记得换行
            else:
                f_val.writelines(image_dir+"\\" + "006-three"+"\\"+ txt.split(".")[0] + ".jpg" + "\n")#往文件里面写路径,记得换行
        elif first_two_chars == "7 ":#读取每个文件的第一行,判断是007-four
            n7 += 1
            if n7 >= int(four_num * ratio):
                f_train.writelines(image_dir+"\\" + "007-four"+"\\"+ txt.split(".")[0] + ".jpg" + "\n")#往文件里面写路径,记得换行
            else:
                f_val.writelines(image_dir+"\\" + "007-four"+"\\"+ txt.split(".")[0] + ".jpg" + "\n")#往文件里面写路径,记得换行
        elif first_two_chars == "8 ":#读取每个文件的第一行,判断是008-six
            n8 += 1
            if n8 >= int(six_num * ratio):
                f_train.writelines(image_dir+"\\" + "008-six"+"\\"+ txt.split(".")[0] + ".jpg" + "\n")#往文件里面写路径,记得换行
            else:
                f_val.writelines(image_dir+"\\" + "008-six"+"\\"+ txt.split(".")[0] + ".jpg" + "\n")#往文件里面写路径,记得换行
        elif first_two_chars == "9 ":#读取每个文件的第一行,判断是009-Iloveyou
            n9 += 1
            if n9 >= int(Iloveyou_num * ratio):
                f_train.writelines(image_dir+"\\" + "009-Iloveyou"+"\\"+ txt.split(".")[0] + ".jpg" + "\n")#往文件里面写路径,记得换行
            else:
                f_val.writelines(image_dir+"\\" + "009-Iloveyou"+"\\"+ txt.split(".")[0] + ".jpg" + "\n")#往文件里面写路径,记得换行
        elif first_two_chars == "10":#读取每个文件的第一行,判断是010-gun
            n10 += 1
            if n10 >= int(gun_num * ratio):
                f_train.writelines(image_dir+"\\" + "010-gun"+"\\"+ txt.split(".")[0] + ".jpg" + "\n")#往文件里面写路径,记得换行
            else:
                f_val.writelines(image_dir+"\\" + "010-gun"+"\\"+ txt.split(".")[0] + ".jpg" + "\n")#往文件里面写路径,记得换行
        elif first_two_chars == "11":#读取每个文件的第一行,判断是011-thumbUp
            n11 += 1
            if n11 >= int(thumbUp_num * ratio):
                f_train.writelines(image_dir+"\\" + "011-thumbUp"+"\\"+ txt.split(".")[0] + ".jpg" + "\n")#往文件里面写路径,记得换行
            else:
                f_val.writelines(image_dir+"\\" + "011-thumbUp"+"\\"+ txt.split(".")[0] + ".jpg" + "\n")#往文件里面写路径,记得换行
        elif first_two_chars == "12":#读取每个文件的第一行,判断是012-nine
            n12 += 1
            if n12 >= int(nine_num * ratio):
                f_train.writelines(image_dir+"\\" + "012-nine"+"\\"+ txt.split(".")[0] + ".jpg" + "\n")#往文件里面写路径,记得换行
            else:
                f_val.writelines(image_dir+"\\" + "012-nine"+"\\"+ txt.split(".")[0] + ".jpg" + "\n")#往文件里面写路径,记得换行
        elif first_two_chars == "13":#读取每个文件的第一行,判断是013-pink
            n13 += 1
            if n13 >= int(pink_num * ratio):
                f_train.writelines(image_dir+"\\" + "013-pink"+"\\"+ txt.split(".")[0] + ".jpg" + "\n")#往文件里面写路径,记得换行
            else:
                f_val.writelines(image_dir+"\\" + "013-pink"+"\\"+ txt.split(".")[0] + ".jpg" + "\n")#往文件里面写路径,记得换行
        f_txt.close()
    f_train.close()
    f_val.close()
if __name__ == "__main__":
    data_path = os.path.join(os.getcwd(), 'E:\大学学习\大三活动\人工智能挑战赛\YOLO2COCO-main\dataset\gesture')#获取文件夹gesture的绝对路径
    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如下,convert.py是上述源代码文件,放置位置如下图所示。(本人使用的是pycharm进行编写及运行,也完全可以通过记事本写好代码,扩展名转成.py格式,再通过命令行窗口运行)

在这里插入图片描述

接下来可以转换数据了。在YOLO2COCO-main文件夹下启动cmd

在这里插入图片描述

进入之前创建的conda虚拟环境,根据自己的路径目录情况执行以下命令。(注意:一定要是全英文路径,本人亲测中文路径各种报错)
在这里插入图片描述

python yolov5_2_coco.py --data_dir C:\Users\12637\Desktop\YOLO2COCO-main\dataset\gesture

到此数据准备已全部结束,我们将新生成的文件夹压缩,准备上传至AI Studio开始训练模型。

在这里插入图片描述

3 模型训练

3.1 创建数据集

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

在这里插入图片描述

3.2 创建项目

点进创建好的数据集,选择创建项目
在这里插入图片描述

我们先选择基础版,将文件配置好,节省算力卡时间(个人建议:算力卡只有训练模型的时候再用)

在这里插入图片描述

你创建好的数据集存放位置在data目录下,该环境内命令类似于Linux(没有相关经验也毫无影响,想用到啥直接上网搜Linux相关操作命令即可)

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

git clone paddleyolo github 链接

在这里插入图片描述

注意:在ppyoloe.ipynb文件里面写的代码或者markdown都要ctrl+s保存一下,养成一个优良的习惯。

在这里插入图片描述

3.3 模型配置

在configs下面有很多模型,文件夹名就是模型名字,除了可以使用ppyoloe,还有yolov5,yolov8等。

在这里插入图片描述

本人本打算使用经典的YOLO_V5算法,但PP-YOLOE表现实在过于惊艳,如上图所示,故本次模型将基于PP-YOLOE

在这里插入图片描述

选择一个模型文件夹,修改文件夹里面的配置文件,我们选择的是small版本的ppyoloe,需要训练80 epochs。

在这里插入图片描述

我们需要把num_classes修改成14,因为我们的数据集只有14个标签。改完记得ctrl+s保存。

在这里插入图片描述

因为配置文件中要求数据放到dataset/mask里面,所以需要把数据集放置到此处。

新建一个gesture文件夹,把解压过后的数据文件夹拖到gesture里面,并移动到PaddleYOLO/dataset目录下。

在这里插入图片描述

在这里插入图片描述

3.4 模型训练

因为我们是基础版本环境,没有GPU,所以需要切换环境。如果你像下面这样切换环境,大概率你是切换不了的,因为文件过多过大。而且可能会导致死机等一系列不必要现象发生。

在这里插入图片描述

切到这个页面停止。

在这里插入图片描述

重启环境,但是这次要选择GPU环境,根据自己的算力卡富余量决定选哪个选项,土豪的话,选择付费资源中 四个GPU直接起飞。个人建议,该项目V100 32GB完全足够使用

在这里插入图片描述

在ppyoloe模型文件夹里面有个readme_cn.md,里面有训练模型的命令。将改命令复制到ppyoloe.ipynb文件里面执行。

在这里插入图片描述

命令是多卡训练,我们需要稍作修改,换成单卡训练,因为你启动工程的时候就选了一个卡。

!python tools/train.py -c configs/ppyoloe/ppyoloe_plus_crn_s_80e_coco.yml 

在这里插入图片描述

模型训练标志,此时是0 epoch,eta时间估计大概要38分22秒训练完成。时间会变,但是大概就40分钟左右。

(注意:本人此次训练模型时仅做以测试,只训练了前4个标签,实际14个标签训练时长一定会比这个长)

在这里插入图片描述

找到自己的模型放置位置,在生成的output文件夹下。如下图操作可以获取模型model_final.pdparams的绝对路径,然后粘贴至weights=后面,导出模型。

!python tools/export_model.py -c configs/ppyoloe/ppyoloe_plus_crn_s_80e_coco.yml -o weights=模型参数绝对路径

在这里插入图片描述

导出模型会放置output_inference文件夹下。下载导出的模型,用于下一步模型转换当中。

在这里插入图片描述

4 模型转换及剪枝

我们要把paddlepaddle模型转成OpenVINO能使用的模型文件

4.1 可视化模型

把之前下载的四个文件打包进ppyoloe_crn_s_80里面。

在这里插入图片描述

进入模型可视化网站,选择我们上一课程训练好的模型。

在这里插入图片描述

可以看到模型输入是3x640x640,3是图像的RGB通道,也就是说我们传入的图像是有色图。

在这里插入图片描述

定位到模型的输出,在可视化图像的最下方,我们要找到需要模型减支的地方。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

4.2 模型剪枝

模型减支的目的就是从输入到两个输出concat_14.tmp_0,tmp_16为止,后面的节点都删掉。

模型减支工具的paddle目录下打开cmd,进入虚拟环境。

我们将运行这个模型减支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

在这里插入图片描述

运行成功后会新增一个剪枝完成的模型文件夹export_model。

在这里插入图片描述

再次打开可视化工具查看模型的情况。可以看到我们保留的两个结点作为输出,部分结点被裁掉了。
在这里插入图片描述

4.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

在这里插入图片描述

生成三个文件(.bin .mapping .xml)下一步模型推理的时候要用到的模型2文件

在这里插入图片描述

5 模型推理

5.1 模型推理准备

我们新建一个文件夹,将之前的三个文件放进其中,并新建一个labels.txt用于区分14种标签(可以将之前的class.txt重命名),再新建一个python文件,开始模型推理代码编写。

在这里插入图片描述

5.2 推理代码编写

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,4)
    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), (0, 0, 0),  (255, 0, 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("GestureDetection", 0)  # 0可调大小,注意:窗口名必须imshow里面的一窗口名一直
    cv.resizeWindow("GestureDetection", 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('GestureDetection', 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("GestureDetection", 0)  # 0可调大小,注意:窗口名必须imshow里面的一窗口名一直
    cv.resizeWindow("GestureDetection", 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('GestureDetection', 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()

我们在本程序中决定使用异步推理

5.3 总结

推理结果显示,用Intel CPU很丝滑,在OpenVINO 模型推理器类中,device_name除了可以使用Intel的CPU、GPU、VPU还有FPGA。至此,手势识别实时检测到此结束。

到此本项目基本做完,后续本人有可能还会有新的突发奇想,本人有时间将继续更新~

  • 18
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CharlyX

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值