AI应用的核心包括以下两大块:如何开发一个模型、以及如何将模型部署到项目进行应用。
现在有许多关于AI的教程,比如如何进行目标检测、图像分类、NLP以及构建聊天机器人等,反复强调相同的几点:
如何开发一个模型,无论是学术论文还是工业实践,相关的详细讲解随处可见;而如何实现第二点的细节,相关的讲解却很少。
本文将为大家详细解读将模型集成到移动端应用的核心代码。其他部署详解后续会陆续推出,敬请期待哦!
对所有模型来说,将模型集成到移动端应用的流程是相同的:
集成流程分两大阶段:
移动端的AI应用开发具体实现,包含以下操作:
Paddle Lite库可以通过飞桨下载,链接:
https://paddle-lite.readthedocs.io/zh/latest/user_guides/release_lib.html。
模型文件
模型文件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. 检测模型预处理,后处理;
|- 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:
模型ch_rec_mv3_crnn_opt.nb:
核心预测代码: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程序属于上层功能应用的开发,主要工作是调用函数的接口:
修改MiniActivity.java中的代码:predictor.init( ):Predictor的初始化,配置一些预测的参数(输入的尺寸、模型的路径等);predictor.process( ):进行推理预测。
MiniActivity是入口的java文件,相当于是APP的主函数、程序入口,其他.java文件被它调用activity_mini.xml是MiniActivity对应的UI布局,是APP控件开发,定义了APP的各个控件的布局
本项目增加了三个控件:(控件在.java文件中的调用通过其ID,类似于变量名)。
03. jniLibs(so方式集成C++代码)
C++的文件,最终都会编译成so文件,然后同java编译dex文件,一起打包成apk文件。我们也可以直接使用apk文件里编译好的so文件。
示例中的方式是从官方demo的apk文件里提取的so文件。
04. build.gradle
app目录下的build.gradle文件用来配置对应的APP。需要设置compileSdkVersion和targetSdkVersion的版本与前面软件中配置的SDK相同。同时,添加abiFilters 'armeabi-v7a', 'arm64-v8a'指定编译的平台,如果不指定就会默认编译出所有平台的目标文件,而我们的库只支持了arm-v7和arm-v8,运行时可能会报错。
根目录也就是Project下的build.gradle文件用来配置整个Project,本次项目不需要修改。
看到这里,是不是觉得开发一个移动端AI应用也没那么难呢?飞桨提供了很多的开源模型,有兴趣的朋友可以参考本教程,发挥自己的想象力,开发更多有趣、有意义的应用哈。
如在使用过程中有问题,可加入飞桨官方QQ群进行交流:1108045677。
感兴趣的话收好下面的传送门,亲自体验起来吧~
·百度飞桨Paddle Lite·
Paddle Lite定位支持包括手机移动端在内更多场景的轻量化高效预测,支持更广泛的硬件和平台,是一个高性能、轻量级的深度学习预测引擎。在保持和PaddlePaddle无缝对接外,也兼容支持其他训练框架产出的模型。
https://github.com/PaddlePaddle/Paddle-Lite
·百度飞桨PaddleOCR·
PaddleOCR旨在打造一套丰富、领先、且实用的OCR工具库,助力使用者训练出更好的模型,并应用落地。
https://github.com/PaddlePaddle/PaddleOCR
·EasyEdge·
为开发者提供了多个基于飞桨开源模型和轻量化推理框架Paddle Lite实现的端计算模型SDK和Demo APP,可快速体验通过EasyEdge生成的端计算模型。
https://ai.baidu.com/easyedge/app/openSource