极市导读
最近准备整理一下使用TNN、MNN和NCNN的系列笔记,好记性不如烂笔头(记性也不好),方便自己以后踩坑的时候爬的利索点~ >>加入极市CV技术交流群,走在计算机视觉的最前沿
这里 ,目前70多C++推理例子,能编个lib来用,感兴趣的同学可以看看,就不多介绍了
【Lite.AI.ToolKit : A lite C++ toolkit of awesome AI models】— https://github.com/DefTruth/lite.ai.toolkit 最近会陆续更新一些文章,欢迎关注 ~
首先说明一下,选择什么样的推理引擎完全是个人喜好,所以这篇短文仅记录技术问题。今天,想捏一下SCRFD的C++推理,我的习惯通常是同时捏多个版本,比如MNN、NCNN、TNN和ONNXRuntime。今天在将onnx转ncnn的时候遇到无法转换的问题。这里仅简单记录一下,一个取巧的方式。如果你确实希望得到一个ncnn的模型文件,并不在乎是什么方式,那么在你遇到奇怪的op无法转换时,可以尝试下文的方式。
onnx模型文件来源于https://github.com/ppogg/onnx-scrfd-flaskgithub.com/ppogg/onnx-scrfd-flask
嗯,刚开始,我转换时,遇到的问题长这样:
➜ onnx2ncnn SCRFD/scrfd_1g.onnx SCRFD/scrfd_1g.param SCRFD/scrfd_1g.bin
Shape not supported yet!
Gather not supported yet!
# axis=0
Shape not supported yet!
Gather not supported yet!
# axis=0
Unsupported unsqueeze axes !
Unsupported unsqueeze axes !
Shape not supported yet!
Unknown data type 0
Unsupported Resize scales and sizes are all empty!
Shape not supported yet!
Gather not supported yet!
# axis=0
Shape not supported yet!
Gather not supported yet!
# axis=0
Unsupported unsqueeze axes !
...
这?!这一大堆不就是传说中的胶水op?基操onnxsim先过一遍。
➜ python3 -m onnxsim scrfd_1g.onnx scrfd_1g-sim.onnx --dynamic-input-shape --input-shape 1,3,320,320
Simplifying...
Checking 0/3...
Checking 1/3...
Checking 2/3...
Ok!
再来转ncnn看看?
➜ onnx2ncnn SCRFD/scrfd_1g-sim.onnx SCRFD/scrfd_1g.param SCRFD/scrfd_1g.bin
Shape not supported yet!
Gather not supported yet!
# axis=0
Shape not supported yet!
Gather not supported yet!
# axis=0
Unsupported unsqueeze axes !
Unsupported unsqueeze axes !
Shape not supported yet!
Unknown data type 0
Unsupported Resize scales and sizes are all empty!
Shape not supported yet!
Gather not supported yet!
# axis=0
Shape not supported yet!
Gather not supported yet!
# axis=0
还是失败了,所以这一堆胶水op要准备造反了??那咋办????
当然,这时还可以考虑参考大佬的文章来手捏param
【nihui:手工优化ncnn模型结构】— https://zhuanlan.zhihu.com/p/93017149
但,问题来了,手工优化ncnn模型结构,这时会遇到两个比较主要的问题:
但是,如果你确实希望得到一个ncnn的模型文件,并不在乎是什么方式,那么在你遇到奇怪的op无法转换时,可以另一个(取巧)的方式。
这个方式,就是利用TNN。是的,你没听错,就是利用TNN作为中间商\。你的目的是得到一个能用的ncnn文件,所以,不用管是什么方式。经常用TNN的同学一定清楚,ONNX转TNN的模型的时候,可以指定一个 --optimize 参数。指定这个参数后,TNN会对输入的原始onnx文件,先做一个优化,会包含一些胶水op的去除和合并,感觉是和onnxsim做类似的事情,但似乎针对推理引擎本身进行了优化,一些在onnxsim之后依然被保留的胶水op,会被TNN的optimize阶段进行合并或去除,然后得到一个 xxx.opt.onnx 的中间文件。TNN会继续通过这个优化后的onnx文件生成最终的tnn模型文件。
/opt/TNN/tools/convert2tnn# python3 ./converter.py onnx2tnn ./tnn_models/SCRFD/scrfd_1g.onnx -o ./tnn_models/SCRFD/ -optimize -v v1.0 -align -in 1,3,320,320
---------- convert model, please wait a moment ----------
Converter ONNX to TNN Model...
Converter ONNX to TNN check_onnx_dim...
Converter ONNX to TNN check_onnx_dim...
Converter ONNX to TNN model succeed!
---------- align model (tflite or ONNX vs TNN),please wait a moment ----------
input.1: input shape of onnx and tnn is aligned!
Run tnn model_check...
---------- Congratulations! ----------
The onnx model is aligned with tnn model
生成的文件包括:
/opt/TNN/tools/convert2tnn/tnn_models/SCRFD# ls | grep opt
scrfd_1g.opt.onnx
scrfd_1g.opt.tnnmodel
scrfd_1g.opt.tnnproto
scrfd_2.5g.opt.onnx
scrfd_2.5g.opt.tnnmodel
scrfd_2.5g.opt.tnnproto
scrfd_2.5g_bnkps_shape160x160.opt.onnx
scrfd_2.5g_bnkps_shape160x160.opt.tnnmodel
scrfd_2.5g_bnkps_shape160x160.opt.tnnproto
scrfd_2.5g_bnkps_shape320x320.opt.onnx
scrfd_2.5g_bnkps_shape320x320.opt.tnnmodel
scrfd_2.5g_bnkps_shape320x320.opt.tnnproto
scrfd_500m.opt.onnx
scrfd_500m.opt.tnnmodel
scrfd_500m.opt.tnnproto
scrfd_500m_bnkps_shape160x160.opt.onnx
scrfd_500m_bnkps_shape160x160.opt.tnnmodel
scrfd_500m_bnkps_shape160x160.opt.tnnproto
scrfd_500m_bnkps_shape320x320.opt.onnx
scrfd_500m_bnkps_shape320x320.opt.tnnmodel
scrfd_500m_bnkps_shape320x320.opt.tnnproto
讲到这里,大家应该想得到,我说的trick是什么了。没错,就是利用这里的 xxx.opt.onnx 去转换ncnn模型。这真是个野路子啊。我们来看看结果如何吧。来转换ncnn试一下
➜ onnx2ncnn SCRFD/scrfd_1g.opt.onnx SCRFD/scrfd_1g.param SCRFD/scrfd_1g.bin
➜ onnx2ncnn SCRFD/scrfd_2.5g.opt.onnx SCRFD/scrfd_2.5g.param SCRFD/scrfd_2.5g.bin
➜ onnx2ncnn SCRFD/scrfd_2.5g_bnkps_shape160x160.opt.onnx SCRFD/scrfd_2.5g_bnkps_shape160x160.param SCRFD/scrfd_2.5g_bnkps_shape160x160.bin
➜ onnx2ncnn SCRFD/scrfd_2.5g_bnkps_shape320x320.opt.onnx SCRFD/scrfd_2.5g_bnkps_shape320x320.param SCRFD/scrfd_2.5g_bnkps_shape320x320.bin
➜ onnx2ncnn SCRFD/scrfd_500m.opt.onnx SCRFD/scrfd_500m.param SCRFD/scrfd_500m.bin
➜ onnx2ncnn SCRFD/scrfd_500m_bnkps_shape160x160.opt.onnx SCRFD/scrfd_500m_bnkps_shape160x160.param SCRFD/scrfd_500m_bnkps_shape160x160.bin
➜ onnx2ncnn SCRFD/scrfd_500m_bnkps_shape320x320.opt.onnx SCRFD/scrfd_500m_bnkps_shape320x320.param SCRFD/scrfd_500m_bnkps_shape320x320.bin
舒服~ 都转换成功了,看来TNN的 --optimze 比 onnxsim 会更有针对性一些。我们来用ncnnoptimize过一遍,看看有没有问题。
➜ ncnnoptimize SCRFD/scrfd_1g.param SCRFD/scrfd_1g.bin SCRFD/scrfd_1g.opt.param SCRFD/scrfd_1g.opt.bin 0
fuse_convolution_activation Conv_0 Relu_1
fuse_convolution_activation Conv_4 Relu_5
fuse_convolution_activation Conv_8 Relu_9
fuse_convolution_activation Conv_12 Relu_13
fuse_convolution_activation Conv_16 Relu_17
fuse_convolution_activation Conv_20 Relu_21
fuse_convolution_activation Conv_24 Relu_25
fuse_convolution_activation Conv_28 Relu_29
fuse_convolution_activation Conv_32 Relu_33
fuse_convolution_activation Conv_36 Relu_37
fuse_convolution_activation Conv_40 Relu_41
fuse_convolution_activation Conv_44 Relu_45
fuse_convolution_activation Conv_48 Relu_49
fuse_convolutiondepthwise_activation Conv_2 Relu_3
fuse_convolutiondepthwise_activation Conv_6 Relu_7
fuse_convolutiondepthwise_activation Conv_10 Relu_11
fuse_convolutiondepthwise_activation Conv_14 Relu_15
fuse_convolutiondepthwise_activation Conv_18 Relu_19
fuse_convolutiondepthwise_activation Conv_22 Relu_23
fuse_convolutiondepthwise_activation Conv_26 Relu_27
fuse_convolutiondepthwise_activation Conv_30 Relu_31
fuse_convolutiondepthwise_activation Conv_34 Relu_35
fuse_convolutiondepthwise_activation Conv_38 Relu_39
fuse_convolutiondepthwise_activation Conv_42 Relu_43
fuse_convolutiondepthwise_activation Conv_46 Relu_47
Input layer input.1 without shape info, shape_inference skipped
Input layer input.1 without shape info, estimate_memory_footprint skipped
是的,也很顺利。那么,看到这里,自然而然地,大家就会剩下最后一个疑问了。为了用这种方式,不还得编译个TNN,不也很麻烦?emmmm,其实,不用编译TNN哦。TNN官方提供了tnn_converter的镜像哦,如果你只是为了转换模型,获得中间的 xxx.opt.onnx,而不需要用TNN做推理。那么直接用 tnn_converter的镜像就完事了,不需要编译TNN库。搭建tnn_converter,可以参考我的文章:
【DefTruth:tnn-convert搭建简记-YOLOP转TNN】— https://zhuanlan.zhihu.com/p/431418709
码字不易,持续更新,欢迎点赞关注~
如果觉得有用,就请分享到朋友圈吧!
公众号后台回复“transformer”获取最新Transformer综述论文下载~
# CV技术社群邀请函 #
备注:姓名-学校/公司-研究方向-城市(如:小极-北大-目标检测-深圳)
即可申请加入极市目标检测/图像分割/工业检测/人脸/医学影像/3D/SLAM/自动驾驶/超分辨率/姿态估计/ReID/GAN/图像增强/OCR/视频理解等技术交流群
每月大咖直播分享、真实项目需求对接、求职内推、算法竞赛、干货资讯汇总、与 10000+来自港科大、北大、清华、中科院、CMU、腾讯、百度等名校名企视觉开发者互动交流~