在深度学习模型部署时,从pytorch转换onnx的过程中,踩了一些坑。本文总结了这些踩坑记录,希望可以帮助其他人。

首先,简单说明一下pytorch转onnx的意义。在pytorch训练出一个深度学习模型后,需要在TensorRT或者openvino部署,这时需要先把Pytorch模型转换到onnx模型之后再做其它转换。因此,在使用pytorch训练深度学习模型完成后,在TensorRT或者openvino或者opencv和onnxruntime部署时,pytorch模型转onnx这一步是必不可少的。接下来通过几个实例程序,介绍pytorch转换onnx的过程中遇到的坑。

『opencv里的深度学习模块不支持3维池化层』

起初,我在微信公众号里看到一篇文章《使用Python和YOLO检测车牌》。文中展示的检测结果如下,其实这种检测结果并不是一个优良的结果,可以看到检测框里的车牌是倾斜的,如果要识别车牌里的文字,那么倾斜的车牌会严重影响车牌识别结果的。

对于车牌识别这种场景,在做车牌检测时,一种优良的检测结果应该是这样的,如下图所示。

在输出车牌检测框的同时输出检测到的车牌的4个角点。有了这4个角点之后,对车牌做透视变换,这时的车牌就是水平放置的,最后做车牌识别,这样就做成了一个车牌识别系统,在这个系统里包含车牌检测,车牌矫正,车牌识别三个模块。车牌检测模块使用retinaface,原始的retinaface是做人脸检测的,它能输出人脸检测矩形框和人脸5个关键点。考虑到车牌只有4个点,于是修改retinaface的网络结构使其输出4个关键点,然后在车牌数据集训练,训练完成后,以一幅图片上做目标检测的结果如上图所示。车牌矫正模块使用了传统图像处理方法,关键函数是opencv里的getPerspectiveTransform和warpPerspective。车牌识别模块使用Intel公司提出的LPRNet。

接下来我就尝试把pytorch模型转换到onnx文件,然后使用opencv做车牌检测与识别。然而在转换完成onnx文件后,使用opencv读取onnx文件遇到了一些坑,我在网上搜索,也没有找到解决办法。

转换过程分两步,首先是转换车牌检测retinaface到onnx文件,这一步倒是很顺利,转换没有出错,并且使用opencv读取onnx文件做前向推理的输出结果也是正确的。第二步转换车牌识别LPRNet到onnx文件,由于Pytorch自带torch.onnx.export转换得到的ONNX,因此转换的代码很简单,在生成onnx文件后,opencv读取onnx文件出现了模型其妙的错误。程序运行的结果截图如下


从打印结果看,torch.onnx.export生成onnx文件时没有问题的,但是在cv2.dnn.readNet这一步出现异常导致程序中断,并且打印出的异常信息是一连串的数字,去百度搜索也么找到解决办法。观察LPRNet的网络结构,发现在LPRNet里定义了3维池化层,代码截图如下


于是,我做了一个实验,定义一个只含有3维池化层的网络,转换生成onnx文件,然后opencv读取onnx文件做前向推理,程序运行结果如下。

可以看到在这时能成功读取onnx文件,但是在执行前向计算model.forward时出错,换成3维平均池化,运行结果如下

可以看到依然出错,这说明opencv的深度学习模块里不支持3维池化。不过,对比3维池化和2维池化的前向计算原理可以发现,3维池化其实等价于2个2维池化。程序实例如下

程序最后最后运行结果打印信息是相等。从这里就可以看出opencv里的深度学习模块并不支持3维池化的前向计算,这期待后续新版本的opencv里能添加3维池化的计算。这时在LPRNet网络结构定义文件里修改3维池化层,重新生成onnx文件,opencv读取onnx文件执行前向计算后依然出错,运行结果如下。

于是继续观察LPRNet的网络结构,在forward函数里看到有求平均值的操作,代码截图如下所示

注意到第一个torch.mean函数里没有声明在哪个维度求平均值,这说明它是对一个4维四维张量的整体求平均值,这时候从一个4维空间搜索成一个点,也就是一个标量数值。但是在pytorch里,对一个张量求平均值后依然是一个张量,只不过它的维度shape是空的,示例代码如下。这时如果想要访问平均值,需要加上.item(),这个是需要注意的一个pytorch知识点。

在修改这个代码bug后重新生成onnx文件,使用opencv读取onnx文件做前向计算就不再出现异常错误了。

通过以上几个程序实验,可以总结出opencv读取onnx文件做深度学习前向计算的2个坑:

(1) .opencv里的深度学习模块不支持3维池化计算,解决办法是修改原始网络结构,把3维池化转换成两个2维池化,重新生成onnx文件

(2) .当神经网络里有torch.mean和torch.sum这种把4维张量收缩到一个数值的运算时,opencv执行forward会出错,这时的解决办法是修改原始网络结构,在torch.mean的后面加上.item()

在解决这些坑之后,编写了一套使用opencv做车牌检测与识别的程序,包含C++和python两个版本的代码。使用opencv的dnn模块做前向计算,后处理模块是自己使用C++和Python独立编写的。

『opencv与onnxruntime的差异』

起初在github上看到一个使用DBNet检测条形码的程序,不过它是基于pytorch框架做的。于是我编写一套程序把pytorch模型转换到onnx文件,使用opencv读取onnx文件做前向计算。编写完程序后在运行时没有出错,但是最后输出的结果跟调用pytorch 的输出结果不一致,并且从可视化结果看,没有检测出图片中的条形码。这时在看到网上有很多使用onnxruntime部署onnx模型的文章,于是决定使用onnxruntime部署,编写完程序后运行,选取几张快递单图片测试,结果如下图所示DBNet检测到的4个点,图中绿色的点,红色的线是把4个连接起来的直线。


并且我还编写了一个函数比较opencv和onnxruntime的输出结果,程序代码和运行结果如下,可以看到在相同输入,读取同一个onnx文件的前提下,opencv和onnxruntime的输出结果竟然不相同。

ONNXRuntime是微软推出的一款推理框架,用户可以非常便利的用其运行一个onnx模型。从这个实验,可以看出相比于opencv库,onnxruntime库对onnx模型支持的更好。

『onnxruntime支持3维池化和3维卷积』

在第1节讲到opencv不支持3维池化,那么onnxruntime是否支持呢?接着编写了一个程序探索onnxruntime对3维池化的支持情况,代码和运行结果如下,可以看到程序报错了。

查看nn.MaxPool3d的说明文档,截图如下,可以看到它的输入和输出是5维张量,于是修改上面的代码,把输入调整到5维张量。

代码和运行结果如下,可以看到这时候onnxruntime库能正常读取onnx文件,并且它的输出结果跟pytorch的输出结果相等。

继续实验,把三维池化改作三维卷积,代码和运行结果如下,可以看到平均差异在小数点后11位,可以忽略不计。

在第1节讲到过opencv不支持3维池化,那时候的输入张量是4维的,如果把输入张量改成5维的,那么opencv是否就能进行3维池化计算呢?为此,编写代码,验证这个想法。代码和运行结果如下,可以看到在cv2.dnn.blobFromImage这行代码出错了。

查看cv2.dnn.blobFromImage这个函数的说明文档,截图如下,可以看到它的输入image是4维的,这说明它不支持5维的输入。

经过这一系列的程序实验论证,可以看出onnxruntime库对onnx模型支持的更好。如果深度学习模型有3维池化或3维卷积层,那么在转换到onnx文件后,使用onnxruntime部署深度学习是一个不错的选择。

『onnx动态分辨率输入』

不过我在做pytorch导出onnx文件时,还发现了一个问题。在torch.export函数里有一个输入参数dynamic_axes,它表示动态的轴,即可变的维度。假如一个神经网络输入是动态分辨率的,那么需要定义dynamic_axes = {‘input’: {2: ‘height’, 3: ‘width’}, ‘output’: {2: ‘height’, 3: ‘width’}},接下来我编写一个程序来验证,代码和运行结果的截图如下

可以看到,在生成onnx文件后,使用onnxruntime库读取,对输入blob的高增加10个像素单位,在run这一步出错了。使用opencv读取onnx文件,代码和运行结果的截图如下,可以看到依然出错了。

通过这个程序实验,让人怀疑torch.export函数的输入参数dynamic_axes是否真的支持动态分辨率输入的。

以上这些程序实验是我在编写算法应用程序时记录下的一些bug和解决方案的,希望能帮助到深度学习算法开发应用人员少走弯路。

此外,DBNet的官方代码里提供了转换到onnx模型文件,于是我依然编写了一套使用opencv部署DBNet文字检测的程序,依然是包含C++和Python两个版本的代码。官方代码的模型是在ICDAR场景文本检测数据集上训练的,考虑到车牌里也含有文字,我把文章开头展示的汽车图片作为输入,程序检测结果如下,可以看到依然能检测到车牌的4个角点,只是不够准确。如果想要获得准确的角点定位,可以在车牌数据集上训练DBNet。

pytorch转onnx踩坑日记相关推荐

  1. 全志哪吒D1-H Tina Linux Ubuntu 22.04入门踩坑日记

    哪吒D1-H Tina Linux入门踩坑日记 系统环境 源码编译 mklibs-readelf的C++标准问题 m4的SIGSTKSZ问题 libfakeroot的_STAT_VER问题 read_ ...

  2. Win11 + Ubuntu18.04 双系统踩坑日记

    Win11 + Ubuntu18.04 双系统踩坑日记 前言 准备工作 硬件配置 镜像下载 Win11镜像下载 Ubuntu镜像下载 启动盘准备 Win11启动盘 Ubuntu启动盘 Win11安装 ...

  3. 【Flutter混合开发踩坑日记之‘applicationVariants‘ for extension ‘android‘】

    Flutter混合开发踩坑日记之'applicationVariants' for extension 'android' 正文 坑一:Could not get unknown property ' ...

  4. Pytorch中Dataloader踩坑:RuntimeError: DataLoader worker (pid(s) 6700, 10620) exited unexpectedly

    Pytorch中Dataloader踩坑 环境: 问题背景: 观察报错信息进行分析 根据分析进行修改尝试 总结 环境: 系统:windows10 Pytorch版本:1.5.1+cu101 问题背景: ...

  5. Swarm-BZZ踩坑日记之 如何让METMASK小狐狸显示gbzz

    刚入门bzz的新手还不知道小狐狸是什么的请移步上一章节:Swarm-BZZ踩坑日记之 如何在METMASK小狐狸导入节点地址 在浏览器安装好小狐狸,并添加自己的钱包地址后 会发现只显示ETH,并不显示 ...

  6. ReactNative 在丁香医生项目中引入的踩坑日记

    ReactNative 在丁香医生项目中引入的踩坑日记 this没绑定到函数导致空指针 参考 React-Native 踩坑第二弹-undefined is not a function(evalua ...

  7. springboot踩坑日记—nacos: Error watching Nacos Service change

    springboot踩坑日记-nacos: Error watching Nacos Service change Spring Boot :: (v2.1.5.RELEASE) 错误代码: 07-3 ...

  8. 微信小程序踩坑日记-微信小程序首次加载样式错乱问题

    微信小程序踩坑日记-微信小程序首次加载样式错乱问题 在实际开发项目中,遇到了个棘手的问题,就是在某些因素下,进入小程序发现有些样式发生偏移.错乱等问题 问题原因:-未知(估计是组件的问题) ↓ 解决办 ...

  9. c++字符串操作之std::ostringstream踩坑日记

    c++字符串操作之std::ostringstream踩坑日记 在开发过程中经常会遇到字符串操作,而std::string又没有format操作,这就很难受了. 于是我找到了std::ostrings ...

最新文章

  1. halcon学习笔记——(5)HDevelop language(异常处理)
  2. 文本文件如何在其他地方打开
  3. 关于Python异常处理,你需要了解的知识点
  4. vc 文本框 只显示下划线_Word手动添加下划线就out了?虚线、波浪线、双下划线一键搞定...
  5. 当ASP.NET Forms验证方式遭遇苹果IOS
  6. vns可变领域_技师可变工程师!
  7. 来自山西机器人乐队_冰山上的机器人X三十禁放映 | 庞宽、李霄云对谈预告
  8. 超级便宜的鸿蒙开发板环境搭建-编译-烧录
  9. ADOBE AIR 技术
  10. 图解Kafka中的数据采集和统计机制
  11. activiti画图工具下载
  12. 机器人码垛手持式编程_工业机器人的码垛编程方法
  13. 关于使用vue-quill-editor出现p标签的解决方案
  14. 携手腾讯官方打造,微信(统信UOS版)首发
  15. CentOS 安装Httpie
  16. 循环问“老婆,你爱我吗?”,如果回答的是“爱”, 那么就结束循环,否则就继续问。用程序描述这个故事!
  17. svn: 'path' has no ancestry information
  18. html显示圆圈头像,css实现多说头像圆形旋转特效
  19. 残留+恶意+软件+android,Android的PP Assistant教您如何保护手机的安全性
  20. 那年,我们放生的烟火

热门文章

  1. TOOLS——SecureCRT8.5的下载、安装、注册、连接、用到的配置以及可能遇到的错误、常用快捷键
  2. 运维监控系列(16)-Alertmanager路由、抑制、静默功能使用详解。
  3. 钢铁企业以撮合模式切入B2B平台,汇聚势能实现价值最大化
  4. mysql5.7.23绿色版安装
  5. C#中的文件操作大全
  6. Yocto开发篇 | Ubuntu构建与体验Poky发行版(Yocto 3.1版本)
  7. python计算存款_第一部分:趣味算法入门;第七题:最佳存款方案
  8. arm学习笔记005之Linux平台下TFTP服务器构建
  9. IDEA翻译插件 : Translation 安装及使用 解决Google翻译不能使用【保姆级教学】
  10. 电力电子技术(14)——整流电路的有源逆变工作状态