YOLOv3 从入门到部署(四)YOLOv3模型导出onnx(基于pytorch)

2023-10-31

YOLOv3 从入门到部署(四)YOLOv3模型导出onnx(基于pytorch)

目录

附上代码
https://github.com/qqsuhao/YOLOv3-YOLOv3-tiny-yolo-fastest-xl–pytorch

概述

本篇博客我们重点讲解如何将“YOLOv3 从入门到部署(二)”中构建的模型转换为onnx。关于onnx的介绍,读者可以查询其他资料。本章节讲述的内容需要大量参考YOLOv3 从入门到部署(二)

DNN模块是opencv的一个深度学习推理模块,我们使用的是opencv 4.5.1。我们最终要使用DNN模块加载我们导出的onnx。要完成这件事我们必须要确保两件事:onnx的导出过程是成功的;onnx可以被DNN正确加载。为什么这么说呢?因为DNN模块尚不完整,有很多onnx中的网络层DNN是不支持的,这会导致即使onnx导出成功,也无法被DNN正确加载。

pytorch导出onnx采坑

pytorch导出onnx有很多资料,但是我也相信很多人在将自己的模型导出为onnx的时候总是会遇到各种各样的问题。我把自己的一些经验和解决办法总结如下(有些经验不只是为了成功导出onnx,也是为了让DNN正确加载onnx):

  • 尽量不要出现tensor转为numpy或者python的数据类型:打个比方,pytorch的主角是tensor,在转为onnx的时候,pytorch会根据tensor的流向去构建onnx。一旦tensor的流向中断,onnx就会出现警告或者报错。那么什么时候tensor的流向会中断呢?就是在进行数据类型转换的时候。因此不要把tensor转换为numpy或者python的int,float等类型,更不要把tensor转换成列表。
  • tensor不要和python的数据类型直接做运算:比如我们的程序中有一句self.stride = torch.floor_divide(self.img_dim, self.grid_size),正常的我们可能会直接使用self.img_dim/self.grid_size然后取整,但是如果要转为onnx,这么写就会报出warning。这是因为在我们的程序中,self.img_dim/是python的int类型,而self.grid_size=inputs.size(2)是一个tensor。这样的话再运算过程中,tensor会被隐式地转换为python的数据类型,转为onnx的时候会被警告有tensor被转换为其它数据类型。
  • 不要使用tensor的广播机制,因为DNN不支持:(这一点主要是为了让DNN正确加载onnx,不遵守这一条不会导致转onnx出问题。)4.5.1的版本下,DNN是不支持广播机制。作为广播机制,就是说在tensor和tensor做运算的时候,两者维度可以不一样。pytorch会根据广播机制自动将缺失的维度补全。但是DNN不吃这一套,所以要想办法尽量避免使用广播机制。比如https://github.com/eriklindernoren/PyTorch-YOLOv3中源程序是
X = x.data + self.grid_x

其中x是一个有着4个维度的tensor,而self.grid_x只是一个二维的tensor,在做加法的时候pytorch会默认使用广播机制,给x的每个元素加上self.grid_x,但是这样的操作在DNN中不被支持。因此我们将其改为

FloatTensor = torch.cuda.FloatTensor if inputs.is_cuda else torch.FloatTensor
X = FloatTensor()           # x 和 self.grid_x维度并不完全相同,为了转onnx成功,需要写成这样
     for i in range(self.num_anchors):
         X = torch.cat( (X, torch.add(x[:, i:i+1, :, :], self.grid_x)), 1)

我们使用for循环逐次和x的元素相加;并且使用torch.add(),而不是普通的加号。

  • DNN不支持torch.arange(),torch.squeeze(),torch.exp():目前4.5.1的opencv的DNN并不支持这些操作,甚至更多操作。但是以后的opencv可能会逐渐支持。解决的办法是能避免使用就避免使用,实在没办法,就在后续使用DNN加载onnx前,使用DNN相关函数自定义这些不被支持的模块。在我们的程序中,由于我们无论如何都要使用exp,因此我们决定在DNN中自定义exp模块,具体方法后边展示。源代码中使用
self.grid_x = torch.arange(g).repeat(g, 1).view([1, 1, g, g]).type(FloatTensor)

创建self.grid_x;为了避免使用arange,我们将代码改为如下:

self.grid_x = FloatTensor([i for j in range(self.grid_size) for i in range(self.grid_size)])\
            .view([1, 1, self.grid_size, self.grid_size])
  • 不要对切片进行赋值操作:onnx不支持对切片进行赋值操作,因此要避免使用。

  • onnx支持多输入多输出:onnx支持模型有多个输入和输出。在我们的模型中,输入只有一个,但是输出有两个。尽管我们将两个yolo层的输出结果保存在一个列表中,但是模型输出的tensor是有两个,因此相当于两个输出。

转onnx代码

conifg_path = "./configs/yolo-fastest-xl.cfg"
weights_path = "./weights/yolo-fastest-xl.weights"
save_path = "./weights/yolo-fastest-xl.onnx"


net = YOLOv3(conifg_path)
# If specified we start from checkpoint
if weights_path:
    if weights_path.endswith(".pth"):
        net.load_state_dict(torch.load(weights_path))       # 加载pytorch格式的权重文件
        print("load_state_dict")
    else:
        net.load_darknet_weights(weights_path)          # 加载darknet格式的权重文件。以.weight为后缀
        print("load_darknet_weights")

net.eval()
inputs = torch.rand(1, 3, 320, 320)
torch.onnx.export(net, inputs, save_path, input_names=["input"], output_names=["outputs0", "outputs1"],
                  verbose=True, opset_version=11)
model = onnx.load(save_path)
onnx.checker.check_model(model)
  • torch.onnx.export:input_names和output_names要对应模型输出输出的额个数,具体的名字可以自己随便起;verbose=True会在转onnx的过程中打印转换细节;opset_version=11我目前也不太了解。
  • inputs = torch.rand(1, 3, 320, 320):这里的输入图像是一张320*320的三通道图片,与最终模型推理时的输入尺寸要一致。
  • onnx.checker.check_model(model):验证onnx。

使用DNN加载onnx进行验证

我们前面说过需要自行定义DNN中的exp模块,具体方法如下

import
# 添加 ExpLayer
class ExpLayer(object):
    def __init__(self, params, blobs):
        super(ExpLayer, self).__init__()

    def getMemoryShapes(self, inputs):
        return inputs

    def forward(self, inputs):
        return [np.exp(inputs[0])]
cv2.dnn_registerLayer('Exp', ExpLayer)

# opencv dnn加载
net = cv2.dnn.readNetFromONNX(save_path)
img = inputs.numpy() * 255
img = img[0]
img = img.transpose((1, 2, 0))
img = img.astype('uint8')
blob = cv2.dnn.blobFromImage(img, size=(320, 320))      # img 必须是uint8
print(blob.shape)
net.setInput(blob)
out_blob = net.forward(net.getUnconnectedOutLayersNames())
print(out_blob[1].shape)

out = cv2.dnn.imagesFromBlob(out_blob[1])
print(out[0].shape)
  • cv2.dnn.blobFromImage:这个函数相当于一个预处理函数,可以对input进行归一化,resize等操作。
  • out_blob是一个列表,有两个元素,分别是两个yolo层的输出
  • cv2.dnn.imagesFromBlob:这里需要仔细讲一下这个函数。在C++的opencv中,图像实用Mat类型来存放的,Mat是一个二维矩阵,通道数是channel,因此Mat可以看做是一个有着三个维度的矩阵。但是我们模型的输入是四个维度的矩阵,多出来的维度表示样本数量;因此我们才会使用blobFromImage将一个普通的3*320*320的图片转换为blob,维度为1*3*320*320;在C++中就是将三个维度的Mat转换为四个维度的blob(不过注意blob也是Mat类型)。模型的输出也是四个维度的blob,而yolo层的输出其实是一个二维矩阵,行数表示目标数量。列数是85。因此我们需要使用imagesFromBlob,再将四个维度的blob转换为三个维度的Mat。在C++中,如果一个blob的维度是4*3*320*320,那么imagesFromBlob会返回一个vector,其中有四个元素,每个元素是一个维度为3*320*320的Mat。在后续的博客中,我们使用C++ opencv部署onnx的时候会继续讲这件事。

目录

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

YOLOv3 从入门到部署(四)YOLOv3模型导出onnx(基于pytorch) 的相关文章

随机推荐

  • 使jira支持reopen率的统计

    jira本身并不能统计bug的reopen率 虽然bug工作流程中有reopen节点 只能借助第三方插件来处理 插件名称 Enhancer Plugin for JIRA 此插件支持自定义字段 自定义计数器等等高级操作 在插件管理中搜索插件
  • docker基础3——制作镜像(基于容器)

    文章目录 一 基本了解 1 1 镜像结构 1 2 docker存储驱动 1 2 1 AUFS 1 2 2 OverlayFS 1 2 3 DeviceMapper 1 3 镜像仓库 二 镜像制作 2 1 基于容器制作镜像 三 镜像导入与导出
  • 双指针的实践

    一 原理 双指针 指的是在遍历对象的过程中 不是普通的使用单个指针进行访问 而是使用两个相同方向 快慢指针 或者相反方向 对撞指针 的指针进行扫描 从而达到相应的目的 换言之 双指针法充分使用了数组有序这一特征 从而在某些情况下能够简化一些
  • 第四章 频域滤波(傅里叶变换频域显示特性)

    一 傅里叶变换频域显示特性 在光学傅里叶变换中 人们已经习惯变换域中 的低谱部分位于中央 频域频谱分布中间低 周围高的特性 有利于频谱的解析和进行各种计算与分析 1 图像中心化 借助傅里叶变换的周期性和频率位移性质 可以对频域进行换位以使频
  • Navicat 11连接MYSQL 8.0问题

    一 问题 MySQL8 0 来使用的时候 通过sqlyog 或者程序中连接数据库时 提示 Authentication plugin caching sha2 password cannot be loaded 的错误 8 0改变了身份验证
  • 第三大的数、字符串中的单词数、排列硬币

    Java学习路线 搬砖工逆袭Java架构师 简介 Java领域优质创作者 CSDN哪吒公众号作者 Java架构师奋斗者 百日刷题计划 第 16 100 天 扫描主页左侧二维码 加入群聊 一起学习 一起进步 欢迎点赞 收藏 留言 大连棒棰岛
  • hbase集群在启动的时候报错:JAVA_HOME is not set and Java could not be found

    hbase集群在启动的时候报错 JAVA HOME is not set and Java could not be found 出现这种错误 一般应该是hbase下conf文件下的hbase env sh文件中的java home的环境变
  • 信息学奥赛一本通 1224:最大子矩阵

    题目链接 ybt 1224 最大子矩阵 ybt 1282 最大子矩阵 OpenJudge 2 6 1768 最大子矩阵 洛谷 P1719 最大加权矩形 题目考点 1 动态规划 线性动规 最大子段和 2 前缀和 解题思路 求二维最大子矩阵和
  • Android扫描银行卡获取银行卡号

    card io开源的银行卡扫描的三方库真的是很好用啊 首先需要在你的module的gradle的依赖文件中添加依赖 compile io card android sdk 5 5 1 2 清单文件中加入如下Activity
  • 腾讯云Linux服务器如何安装Nginx?(CentOS 8)

    安装及配置 Nginx 执行以下命令 安装 Nginx 说明 本文以安装 Nginx 1 18 0 为例 您可通过 Nginx 官方安装包 获取适用于 CentOS 8的更多版本 dnf y install http nginx org p
  • 跨站请求伪造CSRF(Cross-site request forgery)

    目录 一 什么是CSRF 二 可能存在CSRF攻击的三个条件 一个相关的动作 基于 Cookie 的会话处理 没有不可预测的请求参数 二 常见的CSRF攻击 1 CSRF令牌的验证取决与请求方法 2 CSRF令牌的验证取决与令牌是否存在 3
  • 基于C#开发的,支持多平台二维图表开源编辑器

    推荐一个基于C 开发的 支持多平台的二维图表开源编辑器 01 项目简介 Core2D是一个支持跨平台 多平台的应用程序 内置wyswig矢量图形编辑器 可用于数据驱动生成二维图 1 wyswig矢量图形编辑器 内置了一个强大的图形编辑器 它
  • 从小白到高手---api接口和drf(Django Rest_Framework)使用超详解

    1 api接口 为了在团队内部形成共识 防止个人习惯差异引起的混乱 我们需要找到一种大家都觉得很好的接口实现规范 而且这种规范能够让后端写的接口 用途一目了然 减少双方之间的合作成本 目前市面上大部分公司开发人员使用的接口服务架构主要有 r
  • Java IO File类中的知识点

    public class Demo3 public static void main String args TODO Auto generated method stub test1 1 名称 test2 2 判断信息 test3 创建删
  • ggplot2杂记

    ggplot2杂记 本文是我在阅读 lt
  • Quartz 之 Job参数 和 Job状态

    项目地址 https github com yuleiqq quartz example tree master quartz study 此示例旨在演示如何将运行时参数传递给quartz作业 以及如何维护作业中的状态 程序将执行以下操作
  • 自学Python真的可以吗?

    自学当然可以学成功python了 但是前提是你需要认真去学 而不是三天打渔两天晒网的 因为python初学很容易 稍微过几天忘记也很容易 所以一定要坚持学习 并且通过平时多加练习来熟练掌握各个知识点 一 学习建议 Python涉及到的学习方
  • 如何解决上传IPA反馈ERROR ITMS-90189版本号重复问题

    在使用App Store Connect上架iOS应用时 如果上传IPA文件时出现ERROR ITMS 90189版本号重复问题 可能是因为该版本号已经被其他应用占用 为了解决这个问题 您可以遵循以下步骤 1 在App Store Conn
  • Qt_shadow build

    就是构建生成的目录和源代码目录分开 比如工程目录叫test 则编译器自动生成一个test build desktop目录存放所有编译过程中生成的文件 当然与之类似的还有在pro文件中添加 MOC DIR temp moc RCC DIR t
  • YOLOv3 从入门到部署(四)YOLOv3模型导出onnx(基于pytorch)

    YOLOv3 从入门到部署 四 YOLOv3模型导出onnx 基于pytorch 文章目录 YOLOv3 从入门到部署 四 YOLOv3模型导出onnx 基于pytorch 目录 概述 pytorch导出onnx采坑 转onnx代码 使用D