【YOLO】目标识别模型的导出和opencv部署(三)_opencv yolov5-程序员宅基地

技术标签: YOLO  python  dnn  # Python  onnx  opencv  

0 前期教程

1 什么是模型部署

  前期教程当中,介绍了yolov5环境的搭建以及如何利用yolov5进行模型训练和测试,虽然能够实现图片或视频的目标识别,但都是基于pytorch这个深度学习框架来实现的。仅仅是为了使用训练好的模型,就需要附加一个巨大的框架,这样程序会显得很臃肿,不够优雅。因此,摆脱对深度学习框架的依赖,是非常有必要的。此即深度学习模型的部署。

2 怎么部署

  这里使用的是opencv的dnn模块,可以实现读取并使用深度学习模型。但是,这个模块不支持pytorch模型,即训练好的pt格式的文件,因此,使用该模型时,还需要先将pt文件转换为opencv能够读取的模型格式,即onnx。

  模型格式的转换使用的是yolov5自带的export.py文件,它提供了多种常见深度学习框架对应的文件格式。老规矩,使用前先看文件开头的注释:

在这里插入图片描述

我们需要的是onnx格式,因此在运行前先安装onnx:

pip install onnx

然后运行export.py文件:

python export.py --weights 'C:\Users\Zeoy\Desktop\Code\Python\yolov5-master\runs\train\exp19\weights\best.pt' --include onnx

生成的onnx文件也在原best.pt所在文件夹下。

  转换完毕,接下来就是使用,运行如下所示代码:

import cv2
import numpy as np

class Onnx_clf:
    def __init__(self, onnx:str='Material/best.onnx', img_size=640, classlist:list=['bottle']) -> None:
        '''	@func: 读取onnx模型,并进行目标识别
            @para	onnx:模型路径
                 	img_size:输出图片大小,和模型直接相关
                    classlist:类别列表
            @return: None
        '''
        self.net = cv2.dnn.readNet(onnx) # 读取模型
        self.img_size = img_size # 输出图片尺寸大小
        self.classlist = classlist # 读取类别列表

    def img_identify(self, img, ifshow=True) -> np.ndarray:
        '''	@func: 图片识别
            @para	img: 图片路径或者图片数组
                    ifshow: 是否显示图片
            @return: 图片数组
        '''
        if type(img) == str: src = cv2.imread(img)
        else: src = img
        height, width, _ = src.shape #注意输出的尺寸是先高后宽
        _max = max(width, height)
        resized = np.zeros((_max, _max, 3), np.uint8)
        resized[0:height, 0:width] = src  # 将图片转换成正方形,防止后续图片预处理(缩放)失真
        # 图像预处理函数,缩放裁剪,交换通道  img     scale              out_size              swapRB
        blob = cv2.dnn.blobFromImage(resized, 1/255.0, (self.img_size, self.img_size), swapRB=True)
        prop = _max / self.img_size  # 计算缩放比例
        dst = cv2.resize(src, (round(width/prop), round(height/prop)))
        # print(prop)  # 注意,这里不能取整,而是需要取小数,否则后面绘制框的时候会出现偏差
        self.net.setInput(blob) # 将图片输入到模型
        out = self.net.forward() # 模型输出
        # print(out.shape)
        out = np.array(out[0])
        out = out[out[:, 4] >= 0.5]  # 利用numpy的花式索引,速度更快, 过滤置信度低的目标
        boxes = out[:, :4]
        confidences = out[:, 4]
        class_ids = np.argmax(out[:, 5:], axis=1)
        class_scores = np.max(out[:, 5:], axis=1)
        # out2 = out[0][out[0][:][4] > 0.5]
        # for i in out[0]: # 遍历每一个框
        #     class_max_score = max(i[5:])
        #     if i[4] < 0.5 or class_max_score < 0.25: # 过滤置信度低的目标
        #         continue
        #     boxes.append(i[:4]) # 获取目标框: x,y,w,h (x,y为中心点坐标)
        #     confidences.append(i[4]) # 获取置信度
        #     class_ids.append(np.argmax(i[5:])) # 获取类别id
        #     class_scores.append(class_max_score) # 获取类别置信度
        indexes = cv2.dnn.NMSBoxes(boxes, confidences, 0.25, 0.45) # 非极大值抑制, 获取的是索引
        # print(indexes)
        iffall = True if len(indexes)!=0 else False
        # print(iffall)
        for i in indexes:   # 遍历每一个目标, 绘制目标框
            box = boxes[i]
            class_id = class_ids[i]
            score = round(class_scores[i], 2)
            x1 = round((box[0] - 0.5*box[2])*prop)
            y1 = round((box[1] - 0.5*box[3])*prop)
            x2 = round((box[0] + 0.5*box[2])*prop)
            y2 = round((box[1] + 0.5*box[3])*prop)
            # print(x1, y1, x2, y2)
            self.drawtext(src,(x1, y1), (x2, y2), self.classlist[class_id]+' '+str(score))
            dst = cv2.resize(src, (round(width/prop), round(height/prop)))
        if ifshow:
            cv2.imshow('result', dst)
            cv2.waitKey(0)
        return dst, iffall

    def video_identify(self, video_path:str) -> None:
        '''	@func: 视频识别
            @para  video_path: 视频路径
            @return: None
        '''
        cap = cv2.VideoCapture(video_path)
        fps = cap.get(cv2.CAP_PROP_FPS)
        # print(fps)
        while cap.isOpened():
            ret, frame = cap.read()
            #键盘输入空格暂停,输入q退出
            key = cv2.waitKey(1) & 0xff
            if key == ord(" "): cv2.waitKey(0)
            if key == ord("q"): break
            if not ret: break
            img, res = self.img_identify(frame, False)
            cv2.imshow('result', img)
            print(res)
            if cv2.waitKey(int(1000/fps)) == ord('q'):
                break
        cap.release()
        cv2.destroyAllWindows()

    @staticmethod
    def drawtext(image, pt1, pt2, text):
        '''	@func: 根据给出的坐标和文本,在图片上进行绘制
            @para	image: 图片数组; pt1: 左上角坐标; pt2: 右下角坐标; text: 矩形框上显示的文本,即类别信息
            @return: None
        '''
        fontFace = cv2.FONT_HERSHEY_COMPLEX_SMALL  # 字体
        # fontFace = cv2.FONT_HERSHEY_COMPLEX  # 字体
        fontScale = 1.5  # 字体大小
        line_thickness = 3  # 线条粗细
        font_thickness = 2  # 文字笔画粗细
        line_back_color = (0, 0, 255)  # 线条和文字背景颜色:红色
        font_color = (255, 255, 255)  # 文字颜色:白色

        # 绘制矩形框
        cv2.rectangle(image, pt1, pt2, color=line_back_color, thickness=line_thickness)
        # 计算文本的宽高: retval:文本的宽高; baseLine:基线与最低点之间的距离(本例未使用)
        retval, baseLine = cv2.getTextSize(text,fontFace=fontFace,fontScale=fontScale, thickness=font_thickness)
        # 计算覆盖文本的矩形框坐标
        topleft = (pt1[0], pt1[1] - retval[1]) # 基线与目标框上边缘重合(不考虑基线以下的部分)
        bottomright = (topleft[0] + retval[0], topleft[1] + retval[1])
        cv2.rectangle(image, topleft, bottomright, thickness=-1, color=line_back_color) # 绘制矩形框(填充)
        # 绘制文本
        cv2.putText(image, text, pt1, fontScale=fontScale,fontFace=fontFace, color=font_color, thickness=font_thickness)

if __name__ == '__main__':
    clf = Onnx_clf()
    import tkinter as tk
    from tkinter.filedialog import askopenfilename
    tk.Tk().withdraw() # 隐藏主窗口, 必须要用,否则会有一个小窗口
    source = askopenfilename(title="打开保存的图片或视频")
    # source = r'C:\Users\Zeoy\Desktop\YOLOData\data\IMG_568.jpg'
    if source.endswith('.jpg') or source.endswith('.png') or source.endswith('.bmp'):
        res, out = clf.img_identify(source, False)
        print(out)
        cv2.imshow('result', res)
        cv2.waitKey(0)
    elif source.endswith('.mp4') or source.endswith('.avi'):
        print('视频识别中...按q退出')
        clf.video_identify(source)
    else:
        print('不支持的文件格式')

  关于这个代码流程的一些解释:

  • 首先是调用readNet函数读取onnx模型文件

  • 然后对输入图片进行预处理。具体包括:首先需要用numpy将图片变成正方形(因为模型训练时用的就是正方形图片),不是直接拉伸,而是对短边进行填充值为0的像素,然后再调用blobFromImage函数对得到的正方形图片进行预处理,包括像素值归一化处理,设置输出图像大小,将颜色空间转换为RGB等,具体参数可以参考这篇博客。注意,这里的输出图像大小要和训练时选择的img-size参数保持一致,默认是640,同时要记录一下正方形图片相对于输出图片大小的缩放比例,即正方形边长 / 640,是一个浮点数。

  • 接下来就是图片的输入和输出,setInput函数输入预处理好的图片块,然后调用forward函数得到模型输出,这些模型输出即是圈出的目标对应的方框。

  • 上面得到的方框数量有2w多个,但并不是所有的都是目标,需要根据置信度进行选择,这里用的是numpy的花式索引,速度比循环操作大大加快。然后调用NMSBoxes非极大值抑制,得到确定的目标,然后再循环进行画框输出即可。

  • 具体内容就是读代码和注释即可理解。

参考链接

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/ZHOU_YONG915/article/details/131381106

智能推荐

解决升级JDK后:找不到sun.misc.Unsafe的类文件_jdk.unsupported位置-程序员宅基地

文章浏览阅读1.1k次。JDK9以后已经将sun.misc.Unsafe弃用,同时改进了lib文件的存储方式,将sun.misc.Unsafe全部存储在了jdk.unsupported里面。_jdk.unsupported位置

C++字符串相加出错:invalid operands of types ‘const char [759]‘ and ‘const char [15]‘ to binary ‘operator+‘_invaild operands oftypes-程序员宅基地

文章浏览阅读1.2k次。但“”括起来的字符串被当成是字符串类型的,而非string类型,而字符串类型与C语言一样是不允许相加的,因此需要强制将其中一个字符串转为String类型。在C语言中字符串是不能直接相加的,在C++中,字符串是一个String类,允许相加。_invaild operands oftypes

C++信息学奥赛1138:将字符串中的小写字母转换成大写字母-程序员宅基地

文章浏览阅读1.9k次。C++信息学奥赛1138:将字符串中的小写字母转换成大写字母该段代码实现了将输入字符串中的小写字母转换为大写字母的功能。_信息学奥赛1138

初学python爬虫,记录一下学习过程,requests xpath提取图片地址并保存图片_x_path保存图片-程序员宅基地

文章浏览阅读1.4k次。系统练习requests xpath提取图片并保存本地'''requests库请求目标网址xpath提取网页的图片地址面向函数编程'''#导入第三方库import requestsfrom lxml import etree#定制请求头headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '..._x_path保存图片

Vue学习_vue框架下的bs-程序员宅基地

文章浏览阅读381次。1、vue介绍Vue属于JS框架,快速构建前端界面的技术。目前主流版本Vue2和Vue3。Vue核心思想:不操作dom,通过控制数据(数据驱动),就可以完成页面的所有操作。学习Vue框架:需要与前面学习的HTML、CSS、JS(DOM)进行对比,知道框架到底帮助我们将哪些技术进行封装(不用书写),哪些技术进行改变。官网:https://cn.vuejs.org/2、BS和CS架构软件设计架构中有两种架构方式:BS架构:Browser Server :浏览器与服务器模型架构主要基于浏览器编写_vue框架下的bs

指标体系如何建设_指标体系怎么构建-程序员宅基地

文章浏览阅读1.1w次,点赞11次,收藏115次。几乎所有的数据分析工作都会提到一个词——“建立数据指标体系”,虽然这个词对于大家来说并不陌生,但是数据指标到底是什么以及如何具体的搭建,很多人还是一头雾水的。一、数值指标概述1.1 数值指标价值在了解什么是数据指标之前,我们思考一下:为什么会出现指标?它是为了解决什么问题?人类及科学的发展是与时俱进的,早期为了使自然科学的实验及结果更具统一性及方便标准化衡量,一些标准化的专业指标应运而生。随着人类社会的发展,社会科学也越来越需要统计学来进行事物的衡量,一系列统计学指标也逐步产生了。随着新._指标体系怎么构建

随便推点

Oracle分区查询_oracle查询分区数据-程序员宅基地

文章浏览阅读1.4w次,点赞4次,收藏19次。根据分区查询速度会快很多:分区查询:SELECT * FROM USER_TAB_PARTITIONS WHERE TABLE_NAME = '表名'分区键查询:SELECT * FROM all_PART_KEY_COLUMNS where name='表名';根据分区名查询:select * from 表名 partition('分区名');根据分区键查询:select * from 表名 partition where ACTDATETIME>=to_date('2022_oracle查询分区数据

使用JS判断移动设备的终端类型(浏览器UserAgent)_user-agent判断终端类型-程序员宅基地

文章浏览阅读8.5k次。JavaScript 是如何判断移动设备的类型呢?答案是:User Agent。什么是 User Agent?懂一点网页制作的人应该都明白。简单的说,User Agent 就是用来识别浏览器名称、版本、引擎以及操作系统等信息的内容。 User Agent 的判断是识别浏览器的关键,不仅仅如此,移动互联网开发势头迅猛,通过 User Agent 判断桌面端设备或移动设备就变的很为重要。当然,通过_user-agent判断终端类型

gz文件合并解压_hic的两个r1 gz文件合并-程序员宅基地

文章浏览阅读2.6k次。从veritas网站下载一个storage foundation 5.0的软件,for solaris的,下载了三段文件:sxrt5.0.dvd1.tar.gzaa sxrt5.0.dvd1.tar.gzab sxrt5.0.dvd1.tar.gzac gzcat sxrt5.0.dvd1.tar.gza[a-c]|tar xvf -二楼的办法_hic的两个r1 gz文件合并

SpringBoot:起步依赖-自动配置_spring-configuration-metadata.json-程序员宅基地

文章浏览阅读4.2k次,点赞3次,收藏8次。SpringBoot:起步依赖-自动配置_spring-configuration-metadata.json

cocos creator学习笔记1_cocoscreator 子控件居中-程序员宅基地

文章浏览阅读358次。Widgt组件(UI组件)Widget (对齐挂件) 是一个很常用的 UI 布局组件。它能使当前节点自动对齐到父物体的任意位置,或者约束尺寸,让你的游戏可以方便地适配不同的分辨率。Widget (对齐挂件) 是一个很常用的 UI 布局组件。它能使当前节点自动对齐到父物体的任意位置,或者约束尺寸,让你的游戏可以方便地适配不同的分辨率。Top,Left,Right,Buttom对齐对应边界HorizontalCenter水平方向居中VerticalCenter竖直方向居中Align Mode 指_cocoscreator 子控件居中

数据结构 —— 八大排序(超详细图解 & 函数实现)_数据结构排序-程序员宅基地

文章浏览阅读1k次,点赞16次,收藏15次。排序算法主要分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。常见的内部排序算法有:插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、计数排序等。本文将针对上述八大排序算法进行图解剖析。_数据结构排序

推荐文章

热门文章

相关标签