河南建设信息网站,首都开发公司,郑州网站空间,泊头哪里建网站呢在深度学习模型部署时#xff0c;从pytorch转换onnx的过程中#xff0c;踩了一些坑。本文总结了这些踩坑记录#xff0c;希望可以帮助其他人。
首先#xff0c;简单说明一下pytorch转onnx的意义。在pytorch训练出一个深度学习模型后#xff0c;需要在TensorRT或者openvin…在深度学习模型部署时从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。