热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

c#和java部署pytorch同事识别两个图片_手把手教你移动端AI应用开发(三)——部署环节关键代码最详解读...

AI应用的核心包括以下两大块:如何开发一个模型、以及如何将模型部署到项目进行应用。现在有许多关于AI的教程,比如如何进行目标检测、图像分类、NLP以及构

AI应用的核心包括以下两大块:如何开发一个模型、以及如何将模型部署到项目进行应用。

现在有许多关于AI的教程,比如如何进行目标检测、图像分类、NLP以及构建聊天机器人等,反复强调相同的几点:

  1. 首先,使用像飞桨这样的深度学习平台开发模型。
  2. 然后,将模型打包到网页Paddle.js、移动端Paddle Lite、单机Paddle Inference、或者服务器Paddle Servering。

如何开发一个模型,无论是学术论文还是工业实践,相关的详细讲解随处可见;而如何实现第二点的细节,相关的讲解却很少。

本文将为大家详细解读将模型集成到移动端应用的核心代码。其他部署详解后续会陆续推出,敬请期待哦!

集成流程

对所有模型来说,将模型集成到移动端应用的流程是相同的:

af9ec45134da89fe1410faec808dcace.png

集成流程分两大阶段:

  1. 模型训练阶段:主要解决模型训练,利用标注数据训练出对应的模型文件。面向端侧进行模型设计时,需要考虑模型大小和计算量。
  2. 模型部署阶段:
    • 模型转换:如果是Caffe, TensorFlow或ONNX平台训练的模型,需要使用X2Paddle工具将模型转换到飞桨的格式。本次使用的ocr模型是使用Paddle平台训练的模型,因此不需要进行转换。
    • (可选)模型压缩:主要优化模型大小,借助PaddleSlim提供的剪枝、量化等手段降低模型大小,以便在端上使用。
    • 将模型部署到Paddle Lite。
    • 在终端上通过调用Paddle Lite提供的API接口(C++、Java、Python等API接口),完成推理相关的计算。

具体实现方法

移动端的AI应用开发具体实现,包含以下操作:

5fb9c83ca1524ace209b6353fb8851bd.png

  1. 生成和优化模型。先经过模型训练得到Paddle模型,该模型不能直接用于Paddle Lite部署,需先通过Paddle Lite的opt离线优化工具优化,然后得到Paddle Lite nb模型。如果是Caffe, TensorFlow或ONNX平台训练的模型,需要使用X2Paddle工具将模型转换到Paddle模型格式,再使用opt优化。X2Paddle使用方法:https://paddle-lite.readthedocs.io/zh/latest/user_guides/x2paddle.html
    opt工具使用方法:https://paddle-lite.readthedocs.io/zh/latest/user_guides/model_optimize_tool.html
  2. 获取Paddle Lite推理库。Paddle Lite新版本发布时已提供预编译库,因此无需进行手动编译,直接下载编译好的推理库文件即可。
  3. 构建推理程序。使用前续步骤中编译出来的推理库、优化后模型文件,首先经过模型初始化,配置模型位置、线程数等参数,然后进行图像预处理,如图形转换、归一化等处理,处理好以后就可以将数据输入到模型中执行推理计算,并获得推理结果。

Paddle Lite预测库

Paddle Lite库可以通过飞桨下载,链接:

https://paddle-lite.readthedocs.io/zh/latest/user_guides/release_lib.html。

d7a5b3cfd71f62587ac39603b1655193.png

模型文件

8e6cad7c65a1f34c5742ba7403083de4.png

模型文件assets包含了两个深度学习模型,图片作为输入,同时将模型导入Paddle Lite中,输出即为检测的结果,模型的作用如下:

1. ch_det_mv3_db_opt.nb:文字检测的模型,输入为图像,输出为文字的区域坐标

2. ch_rec_mv3_crnn_opt.nb:文字识别的模型,输入的文字检测的结果,输出为文字识别结果

OCR的过程其实是两个模型的串行工作过程,将文字检测模型的输出结果作为文字识别模型的输入,最后输出最终的结果。

这两个模型,可以通过PaddleOCR github下载:

优化前的模型下载链接:

https://github.com/PaddlePaddle/PaddleOCR/blob/develop/README_cn.md#%E4%B8%AD%E6%96%87ocr%E6%A8%A1%E5%9E%8B%E5%88%97%E8%A1%A8

opt优化后的模型链接:

https://github.com/PaddlePaddle/PaddleOCR/blob/develop/deploy/lite/readme.md#21-%E6%A8%A1%E5%9E%8B%E4%BC%98%E5%8C%96

推理程序代码解读

推理程序代码目录结构:

|-app # 程序module的主目录
|-build # app模块编译输出的文件(包括最终生成的apk)
|-libs # 依赖库
|-OpenCV # OpenCV库
|-PaddleLite # PaddleLite库,用于调用模型进行推理预测
|-src # app应用的源代码目录|-src/main/assets # 模型文件、测试图片|-src/main/cpp # (C++源代码方式)C++ 程序代码目录|-src/main/java # java程序代码目录|-src/main/jniLibs # (so方式)与cpp 目录的操作二选一|-src/main/res #存放app中显示的图形、文本、声音等一些资源文件|-src/main/res/drawable # 各种位图文件(.png、.jpg等)和drawable类型的XML文件|-src/main/res/ # 布局文件|-src/main/AndroidManifest.xml # 项目的清单文件(名称、版本、SDK、权限等配置信息)
|-build.gradle # 项目的gradle编译文件

其中,src是主要源代码目录,下文详细逐一介绍。

01 C++ 程序代码目录(JNI调用C++自定义类)

C++(cpp)程序代码是移动端app的核心算法代码。C++程序代码的作用:向下调用OpenCV库和Paddle Lite库中的函数,来实现模型的推理预测功能(底层实现);向上提供接口给上层的功能应用层的java程序调用。

C++代码目录如下:

|-app/src/main/cpp|-CMakeLists.txt # 重新编译C++的源代码和库,生成能被本项目中的C++的程序所使用的库|- common.h # 常量定义和日志函数|- native.cpp # 和java层交互的c++函数|- native.h # jni的封装函数|- ocr_clipper.cpp # 检测模型DB后处理用到的第三方库|- ocr_clipper.hpp |- ocr_crnn_process.cpp # 识别模型CRNN预处理函数, 获取OpenCV的Mat图片后再放到preprocess做DB模型的预处理|- ocr_crnn_process.h|- ocr_db_post_process.cpp #检测模型DB后处理函数|- ocr_db_post_process.h|- ocr_predictor.cpp # OCR 模型预测函数|- ocr_predictor.h|- ppredictor.cpp # 准备模型预测所需要的初始化,加载模型,从网络结果中获取输出等步骤|- ppredictor.h|- predictor_input.cpp # 输入数据|- predictor_input.h |- predictor_output.cpp # 获取预测结果的输出结果信息|- predictor_output.h|- preprocess.cpp # 图片预处理函数,用于检测模型DB|- preprocess.h

具体推理步骤如下所示:

1. 检测预处理

2. 检测模型预测-> 得到预测结果-> 检测后处理-> 获得检测的文本框

3. 根据检测文本框,从原图中把检测到的文本行剪切出来;

4. 将每个剪切出来的文本行,输入给识别网络预处理

5. 识别网络预处理后,输入给识别网络预测

6. 识别网络预测结果解析得到预测文本

代码包括四个部分:

1. 检测模型预处理,后处理;

|- preprocess.cpp 识别模型CRNN预处理函数
|- preprocess.h
|- ocr_db_post_process.cpp 检测模型DB后处理函数
|- ocr_db_post_process.h

2. 识别模型预处理

|- ocr_crnn_process.cpp 识别模型CRNN模型的预处理,结果是OpenCv的Mat,然后再放到preprocess.cpp做图片的预处理
|- ocr_crnn_process.h

3. 模型预测

|- ocr_predictor.cpp OCR 模型预测函数
|- ocr_predictor.h

4. 模型预测准备,包括模型初始化,给输入数据分配内存等。

|- ppredictor.cpp 准备模型预测所需要的初始化,加载模型,从网络结果中获取输出等步骤
|- ppredictor.h
|- predictor_input.cpp 输入数据分配内存
|- predictor_input.h
|- predictor_output.cpp 获取预测结果的输出结果信息
|- predictor_output.h

其中,模型ch_det_mv3_db_opt.nb:

  1. 预处理在Predictor.java中完成
  2. 推理在ocr_ppredictor.cpp中的_det_predictor
  3. 后处理在ocr_db_post_process.cpp里

模型ch_rec_mv3_crnn_opt.nb:

  1. 根据ocr_db_post_process.cpp结果,在ocr_crnn_process抠出多张含有文字的小图
  2. 对每张小图进行预处理,在preprocess.cpp里完成
  3. 对每张小图进行推理在ocr_ppredictor.cpp中的_rec_predictor。
  4. 所有小图的结果,序列化成float,传输到java层
  5. 在OCRPredictorNative.java 解析成最终结果

核心预测代码:

https://github.com/PaddlePaddle/PaddleOCR/blob/7b201a385547152a015c27dc3d17e9c3ae12a5fb/deploy/android_demo/app/src/main/cpp/ocr_ppredictor.cpp#L67

std::vector
OCR_PPredictor::infer_ocr(const std::vector &dims, const float *input_data, int input_len,int net_flag, cv::Mat &origin) {// _det_predictor:检测预测网络// 获取输出数据,并转换为网络预测支持的数据格式;PredictorInput input = _det_predictor->get_first_input();input.set_dims(dims);input.set_data(input_data, input_len);// 执行预测,得到检测网络预测的文本框std::vector results = _det_predictor->infer();PredictorOutput &res = results.at(0);// 对文本框做简单的过滤std::vector>> filtered_box= calc_filtered_boxes(res.get_float_data(), res.get_size(), (int) dims[2], (int) dims[3],origin);LOGI("Filter_box size %ld", filtered_box.size());// 执行识别模型预测,并直接返回识别模型预测结果return infer_rec(filtered_box, origin);
}std::vector
OCR_PPredictor::infer_rec(const std::vector>> &boxes,const cv::Mat &origin_img) {//识别模型预处理参数std::vector mean = {0.5f, 0.5f, 0.5f};std::vector scale = {1 / 0.5f, 1 / 0.5f, 1 / 0.5f};std::vector dims = {1, 3, 0, 0};std::vector ocr_results;PredictorInput input = _rec_predictor->get_first_input();// 通过for训练,每次读取检测模型预测的检测框for (auto bp = boxes.crbegin(); bp != boxes.crend(); ++bp) {const std::vector> &box = *bp;// 根据检测框将检测到的文本行剪切出来cv::Mat crop_img = get_rotate_crop_image(origin_img, box);float wh_ratio = float(crop_img.cols) / float(crop_img.rows);// 识别模型预处理cv::Mat input_image = crnn_resize_img(crop_img, wh_ratio);input_image.convertTo(input_image, CV_32FC3, 1 / 255.0f);const float *dimg = reinterpret_cast(input_image.data);int input_size = input_image.rows * input_image.cols;dims[2] = input_image.rows;dims[3] = input_image.cols;input.set_dims(dims);neon_mean_scale(dimg, input.get_mutable_float_data(), input_size, mean, scale);// 执行识别模型预测std::vector results = _rec_predictor->infer();OCRPredictResult res;// 解析识别模型预测结果,得到预测的字的索引res.word_index = postprocess_rec_word_index(results.at(0));if (res.word_index.empty()) {continue;}// 计算预测的文本行的置信度res.score = postprocess_rec_score(results.at(1));res.points = box;ocr_results.emplace_back(std::move(res));}LOGI("ocr_results finished %lu", ocr_results.size());// 返回识别结果return ocr_results;
}

02 java程序代码目录

Java程序属于上层功能应用的开发,主要工作是调用函数的接口:

  • 读取相册中的图像
  • 创建Paddle Lite的预测对象Predictor
  • 将模型文件和图像送入Predictor中进行推理预测
  • 预测的结果送入OcrResultModel中,在输出的图像上绘制预测结果,并在APP上显示

修改MiniActivity.java中的代码:predictor.init( ):Predictor的初始化,配置一些预测的参数(输入的尺寸、模型的路径等);predictor.process( ):进行推理预测。

MiniActivity是入口的java文件,相当于是APP的主函数、程序入口,其他.java文件被它调用activity_mini.xml是MiniActivity对应的UI布局,是APP控件开发,定义了APP的各个控件的布局

本项目增加了三个控件:(控件在.java文件中的调用通过其ID,类似于变量名)。

f2d891f3b2dfa7d8be3ccd3bab383afa.png

03 jniLibs(so方式集成C++代码)

C++的文件,最终都会编译成so文件,然后同java编译dex文件,一起打包成apk文件。

我们也可以直接使用apk文件里编译好的so文件。

示例中的方式是从官方demo的apk文件里提取的so文件。

f66ad3ed592efa1e81b7e5b4d09005c8.png

04 build.gradle

app目录下的build.gradle文件用来配置对应的APP。需要设置compileSdkVersion和targetSdkVersion的版本与前面软件中配置的SDK相同。同时,添加abiFilters 'armeabi-v7a', 'arm64-v8a'指定编译的平台,如果不指定就会默认编译出所有平台的目标文件,而我们的库只支持了arm-v7和arm-v8,运行时可能会报错。

根目录也就是Project下的build.gradle文件用来配置整个Project,本次项目不需要修改。

补充说明

1. 橙色的文件夹都是build编译生成的目标文件(不用手动编辑)

2. libs是存放静态库或者动态库(不用修改)

3. src/main/里的java和cpp文件夹存放app运行的源代码,包括Java和C++的代码(上层的应用开发使用Java,底层的具体实现使用C++,此项目中两者都要开发)。

4. OpenCV库可以通过OpenCV官网下载,链接:https://opencv.org/releases/,本次用的是4.2 android。

亲自实践一把!

看到这里,是不是觉得开发一个移动端AI应用也没那么难呢?飞桨提供了很多的开源模型,有兴趣的朋友可以参考本教程,发挥自己的想象力,开发更多有趣、有意义的应用哈。



推荐阅读
  • 微软发布紧急安全更新,所有Windows 10版本均面临影响!
    微软于周五紧急发布了两项安全更新,旨在解决Windows 10所有版本中Windows Codecs库和Visual Studio Code应用存在的安全隐患。此次更新是继本周初发布的月度例行安全补丁之外的额外措施,凸显了这些问题的紧迫性和重要性。这些漏洞可能被攻击者利用,导致系统权限提升或远程代码执行等严重后果。建议用户尽快安装更新,以确保系统的安全性。 ... [详细]
  • 当前,众多初创企业对全栈工程师的需求日益增长,但市场中却存在大量所谓的“伪全栈工程师”,尤其是那些仅掌握了Node.js技能的前端开发人员。本文旨在深入探讨全栈工程师在现代技术生态中的真实角色与价值,澄清对这一角色的误解,并强调真正的全栈工程师应具备全面的技术栈和综合解决问题的能力。 ... [详细]
  • 本文介绍了使用 Python 编程语言高效抓取微博文本和动态网页图像数据的方法。通过详细的示例代码,展示了如何利用爬虫技术获取微博内容和动态图片,为数据采集和分析提供了实用的技术支持。对于对网络数据抓取感兴趣的读者,本文具有较高的参考价值。 ... [详细]
  • 利用PaddleSharp模块在C#中实现图像文字识别功能测试
    PaddleSharp 是 PaddleInferenceCAPI 的 C# 封装库,适用于 Windows (x64)、NVIDIA GPU 和 Linux (Ubuntu 20.04) 等平台。本文详细介绍了如何使用 PaddleSharp 在 C# 环境中实现图像文字识别功能,并进行了全面的功能测试,验证了其在多种硬件配置下的稳定性和准确性。 ... [详细]
  • Python与R语言在功能和应用场景上各有优势。尽管R语言在统计分析和数据可视化方面具有更强的专业性,但Python作为一种通用编程语言,适用于更广泛的领域,包括Web开发、自动化脚本和机器学习等。对于初学者而言,Python的学习曲线更为平缓,上手更加容易。此外,Python拥有庞大的社区支持和丰富的第三方库,使其在实际应用中更具灵活性和扩展性。 ... [详细]
  • 在 Windows 10 系统下配置 Python 3 和 OpenCV 3 的环境时,建议使用 Anaconda 分发版以简化安装过程。Anaconda 可以从其官方网站(https://www.anaconda.com/download)下载。此外,本文还推荐了几本关于 Python 和 OpenCV 的专业书籍,帮助读者深入理解和应用相关技术。 ... [详细]
  • 本文分享了将物理服务器上的操作系统、应用软件及数据迁移到阿里云ECS服务器的实际经验。P2V迁移通过利用多种工具软件,将物理服务器的系统状态和数据完整地复制到虚拟磁盘中,确保在阿里云平台上顺利运行。该过程不仅涉及技术细节,还涵盖了迁移前的准备、迁移中的监控以及迁移后的验证等多个环节,为用户提供了一套全面的迁移方案。 ... [详细]
  • Java服务问题快速定位与解决策略全面指南 ... [详细]
  • 如何在Oracle ASM_Diskgroup中重命名现有磁盘
    如何在Oracle ASM_Diskgroup中重命名现有磁盘 ... [详细]
  • 本文推荐了六款高效的Java Web应用开发工具,并详细介绍了它们的实用功能。其中,分布式敏捷开发系统架构“zheng”项目,基于Spring、Spring MVC和MyBatis技术栈,提供了完整的分布式敏捷开发解决方案,支持快速构建高性能的企业级应用。此外,该工具还集成了多种中间件和服务,进一步提升了开发效率和系统的可维护性。 ... [详细]
  • 在Linux环境下,本文详细探讨了Apache服务器中CGI技术的应用与实现。首先,通过使用yum包管理器安装了必要的软件,如PHP。安装完成后,对Apache服务器进行了配置,确保CGI功能正常运行。此外,还介绍了如何编写和调试CGI脚本,以及如何在实际环境中部署这些脚本以提供动态网页内容。实验结果表明,通过合理的配置和优化,Apache服务器能够高效地支持CGI应用程序,为用户提供丰富的交互体验。 ... [详细]
  • 最近,我在CentOS 5服务器上成功部署了GForge 5.7 Community Edition。与Advanced Server版本相比,虽然功能略有简化,但仍然能够满足大多数开源项目管理的需求。为了确保数据安全,我开发了一套全自动备份脚本,该脚本能够定期备份GForge的数据和配置文件,并将其存储在远程服务器上,以防止数据丢失。此外,该脚本还具备错误检测和日志记录功能,便于故障排查和维护。 ... [详细]
  • 本文详细介绍了如何在 Grafana 中独立于 Alertmanager 配置邮件和微信告警。具体步骤包括配置 SMTP 服务器以实现邮件告警,以及设置微信告警的集成方式。通过这些配置,用户可以更灵活地管理和接收来自 Grafana 的告警通知,确保及时响应系统异常。文章还提供了详细的配置示例和常见问题的解决方案,帮助用户顺利完成设置。 ... [详细]
  • 本文深入探讨了 Python Watchdog 库的使用方法和应用场景。通过详细的代码示例,展示了如何利用 Watchdog 监控文件系统的变化,包括文件的创建、修改和删除等操作。文章不仅介绍了 Watchdog 的基本功能,还探讨了其在实际项目中的高级应用,如日志监控和自动化任务触发。读者将能够全面了解 Watchdog 的工作原理及其在不同场景下的应用技巧。 ... [详细]
  • ros:cartographer(二)整体介绍
    cartographer的doc中给出了这个图。现在我还没看过源码,所以只能猜测一些内容。待看完源码后,再详细介绍这个图。直观地说,左侧 ... [详细]
author-avatar
苏绿儿520
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有