详谈PyTorch OCR模型的安卓端部署

文章转载于微信公众号:giantpandacv
作者: 阿呆 开发环境选择 本文操作系统为windows,因为windows上的安卓模拟器选择较多,并且真机调试也比较方便; 交叉编译在windows和ubuntu上都进行了尝试,都可行,但是如果是ubuntu上交叉编译之后再挪到windows的话,容易出幺蛾子; 我目前使用的最稳定的工具版本组合是:ndk18、androidstudio4.1、cmake3.10、gradle6.5、mingw(codeblocks自带)。 1. pytorch模型转ncnn 这一小节是介绍如何将自己重新训练过的pytorch模型转成ncnn,如果没有重训练需求的话,可以直接跳过这一节。
(1) 整体步骤 理想情况下,从pytorch转到ncnn只需要下面两步:
pytorch转onnx torch.onnx._export(model, x, path, opset_version=11)  onnx转ncnn ./onnx2ncnn model.onnx model.param model.bin  遇到问题的适合解决思路如下:
convert.png
下面介绍一下我在做chineseocrlite中的psenet模型转换的过程中遇到的问题。
(2)实际操作的时候可能会遇到各种问题 问题1:relu6不支持 概述:relu6算子在转换的时候容易出现不支持的情况,需要使用其他算子替代
解决:使用torch.clamp替代(虽然relu6可以通过组合relu的方式实现,但是组合得到的relu6在ncnn中容易转换失败,不建议使用。)
def relu6(x,inplace=true):   return torch.clamp(x,0,6)  问题2:resize算子转换问题 概述:因为各个框架对resize算子的支持都不尽相同,在转换过程中总会出现一些问题,pytorch中的interpolate算子转换成onnx之后变成很多零散的算子,如cast、shape等,这些在ncnn里面不支持。你可以选择手动修改文件,也可以使用下面这个自动的方法:
解决:使用onnx/_simplifier对onnx模型进行简化,可以合并这些零散的算子。
python -m onnxsim model.onnx model_sim.onnx  问题3:关于转onnx及使用onnx/_simplifier过程中出现的一系列奇怪问题 概述:使用不同版本的onnx可能会遇到不同的问题,比如提示conv层无输入等(具体错误名称记不清了)。
解决:下载最新onnx源码编译安装(onnx/_simplifier中出现的一些错误也可以通过安装最新onnx来解决)
git clone https://github.com/onnx/onnx.git sudo apt-get install protobuf-compiler libprotoc-dev  cd onnx  python setup.py install  问题4:模型输出结果的尺寸固定 概述:直接转换得到的onnx模型的resize算子都是固定输出尺寸的,无论输入多大的图片都会输出同样大小的特征图,这无疑会影响到模型的精度及灵活性。
解决:修改ncnn模型的param文件,将resize算子修改成按比例resize。
直接转换得到的param文件中的interp算子是这样的:
interp    913      1 1 901 913 0=2 1=1.000000e+00 2=1.000000e+00 3=640 4=640  从下面的ncnn源码中可以看到,0代表resize/_type,1和2分别是高和宽的缩放比例,3和4分别是输出的高和宽。
int interp::load_param(const paramdict& pd)  {      resize_type = pd.get(0, 0);      height_scale = pd.get(1, 1.f);      width_scale = pd.get(2, 1.f);      output_height = pd.get(3, 0);      output_width = pd.get(4, 0);      return 0;  }  我们只需将其修改成如下格式即可实现按比例resize:
interp      913       1 1 901 913 0=1 1=4.000000e+00 2=4.000000e+00  问题5:ncnn模型输出结果与onnx模型不同 解决:逐层对比ncnn与onnx模型的输出结果
使用onnxruntime(python)和ncnn(c++)分别提取每个节点的输出,进行对比。对于ncnn比较简单,可以使用
extractor.extract(node_name,preds);  来提取不同节点的输出。
问题5衍生问题1:onnx没有提供提取中间层输出的方法 解决:给要提取的层添加一个输出节点,代码如下:
def find_node_by_name(graph, node_name):   for node in graph.node:     if node.output[0] == node_name:       return node   return none           def add_extra_output_node(model,target_node, output_name):   extra_output = helper.make_empty_tensor_value_info(output_name)   target_output = target_node.output[0]   identity_node = helper.make_node(identity,inputs=[target_output],outputs=[output_name],name=output_name)   model.graph.node.append(identity_node)   model.graph.output.append(extra_output)   return model  修改模型之后再使用
out = sess.run([output_name],{input.1:img.astype(np.float32)})  就可以获取到模型的中间层输出了。
问题5衍生问题2:发现最后一个resize层的输出有差异 解决:参考chineseocr/_lite里面的代码把mode由bilinear改成了nearest(这里错误的原因可能是wenmuzhou/psenet.pytorch中的模型最后一个f.interpolate中的align/_corners参数设置成了true。据说ncnn只实现了align/_corners为false的情况)。
这里修改之后的模型跟原模型之间是会有少许误差的,如果误差不可接受,就要重新训练才行。
2. 交叉编译opencv与ncnn 交叉编译工作可以在windows上进行,使用的是mingw + cmkae3.10 + androidndk18。可以参考windows下编译opencv android(https://www.cnblogs.com/zhxmdefj/p/13094954.html)
没有windows c++环境的话,也可以选择在linux上进行。
如果是在linux交叉编译,然后复制到windows的话,需要修改一下opencv中cmake配置文件中的路径。 (1)android ndk下载 最初选择的是r20b,因为和cmake之间的兼容问题,切换到了18b。
wget https://dl.google.com/android/repository/android-ndk-r18b-linux-x86_64.zip?hl=zh_cn  mv android-ndk-r18b-linux-x86_64.zip?hl=zh_cn android-ndk-r18b-linux-x86_64.zip  unzip android-ndk-r18b-linux-x86_64.zip (2)编译opencv 利用android中提供的android.toolchain.cmake 工具链可以快速的编译opencv的arm版。
这里选择的arm平台是armeabi-v7a,便于在老旧手机上运行。
folde if [[ ! -d $folder ]]; then     echo $folder not found, creating folder...     mkdir build_arm fi cd build_arm cmake /     -dcmake_toolchain_file=/     /home/dai/soft/android-ndk-r18b/build/cmake/android.toolchain.cmake /     -dandroid_ndk=/home/dai/soft/android-ndk-r18b /     -dcmake_build_type=release  /     -dbuild_android_projects=off /     -dbuild_android_examples=off /     -dandroid_abi=armeabi-v7a /     -dandroid_native_api_level=21  .. make -j4 (3)编译ncnn 编译选项参考ncnn wiki(https://github.com/tencent/nc...
folde if [[ ! -d $folder ]]; then     echo $folder not found, creating folder...     mkdir build_arm fi cd build_arm cmake /     -dcmake_toolchain_file=/     /home/dai/soft/android-ndk-r18b/build/cmake/android.toolchain.cmake /     -dandroid_ab /     -dandroid_arm_neon=on /     -dandroid_platform=android-14 /     .. make -j4 (4)chineseocr/_lite的pc端测试 与ncnn有关的代码位于ncnn/_project目录下。在有opencv和ncnn库的基础上,可以先在pc端跑一下识别代码。
cd ncnn_project/ocr mkdir build_arm  cd build_arm  cmake ..  make -j4 编译完成之后
./textrecognition ../test1.jpg 可以看到输出结果:
psenet前向时间:0.462291s psenet decode 时间:0.0604791s boxzie10 预测结果: 一 统 ;名 称 丹正珍 类住 型 有限责 所 中山市 角度检测和文字识别总时间:1.52042s 3. ncnn模型的安卓端部署 因为代码较长,这一部分只介绍把pc端代码迁移到安卓端的思路,想看详细代码的同学请移步文末的github地址。
迁移的整体思路如下图所示:
android/_flow.png
下面一一介绍图中内容
ui界面 这个demo的ui界面中至少应包含三个元件:
button——用于选择相册图片
imageview——用于展示图片及文本检测框
textview——用于展示识别结果
界面截图如下(textview在没有文字的时候是不显示的):
ui.jpg
界面res/layout/activity/_main.xml文件修改。 java部分 模型推理是在c++中完成的,java部分的代码主要是利用安卓的api实现图片读取、文本检测框绘制和结果展示等功能。
需要传入到c++函数的内容包括bitmap图片和assetmanager对象。
从c++接收到的是一个包含文本框和识别结果的字符串。
c++部分 c++负责模型推理,推理代码与pc端无异,只是安卓端的文件读取与pc端不同,需要修改文件读取代码,比如crnn的模型加载代码就需要改成下面的样子:
int model::init(aassetmanager *mgr, const std::string crnn_param, const std::string crnn_bin) {     int ret1 = crnn.load_param(mgr, crnn_param.c_str());     int ret2 = crnn.load_model(mgr, crnn_bin.c_str());     logi(ret1 is %d, ret2 is %d, ret1, ret2);     return (ret1||ret2); } 另外还需要把java部分传过来的bitmap转换成cv::mat,代码如下:
// convert bitmap to mat     int *data = null;     androidbitmapinfo info = {0};     androidbitmap_getinfo(env, bitmap, &info);     androidbitmap_lockpixels(env, bitmap, (void **) &data);     // 这里偷懒只写了rgba格式的转换     logi(info format rgba ? %d, info.format == android_bitmap_format_rgba_8888);     cv::mat test(info.height, info.width, cv_8uc4, (char*)data); // rgba     cv::mat img_bgr;     cvtcolor(test, img_bgr, cv_rgba2bgr); 最终识别结果 最终得到的demo识别结果如下图所示:
result.jpg
本项目完整代码请移步github:
https://github.com/arctanxy/d...
推荐阅读
深度学习量化技术科普 简单粗暴的多对象目标跟踪神器 – deepsort 低比特量化之dorefa-net理论与实践 更多嵌入式ai技术干货请关注嵌入式ai专栏。

5G时代工业物联网强者再起航
KA2915引脚功能的电压资料参数
英飞凌创新“耦合模块”助力土耳其护照实现带安全芯片的超薄PC电子资料页,提升旅行证件的耐久性和防伪
国美U7和华为畅享7S哪个更值得买
Ruff南潮科技亮相云栖大会5G+AIoT创新峰会
详谈PyTorch OCR模型的安卓端部署
NVIDIA即将斥资70亿美元收购以色列服务器芯片厂商Mellanox
瑞萨电子推出全新RAA2230XX降压稳压器产品
基于气胸超声诊断的基本原理、实现与分析
Q1季度电信业务增速同比提高0.8个百分点,同比增长17.7%
台式万用表如何使用
ARTM-24安科瑞智能24路温度巡检仪
如何提高电子元器件可靠性?
车灯照明差需要注意,莫要留下行车安全的隐患!
5G技术推动内需市场的转型升级
CKS32F4xx系列低功耗模式SLEEP模式
刹车电机的结构原理
哈佛研究世界第一个全软体机器人:宛如章鱼
中国移动正式发布了2020年至2021年分布式块存储产品集采招标公告
万元以下听个响?来试试这款3000元的Hi-Fi音箱吧!好声音之外,还有时尚的设计