注:此次记录和梳理android AI 算法模型部署和实现的流程,包含算法部署、从andorid 前段获取bitmap数据进行输入,结果展示和算法so打包和交付流程进行介绍
一:流程:
如果我们训练一个cnn 模型,怎样才能将模型实现在android 平台上运行,并实现结果展示呢?本文章流程主要介绍比如分类、检测、分割等算法训练的pytorch(x.pt)模型,实现在android 上运行并将结果返回在前段界面上进行展示(便于我们测试结果),最终将我们的算法推理代码封装起来,提供给前段android 应用的同事进行扩展产品开发的方案。
一般我们实现简单的算法推理部署可能做到将x.pt 模型转换到对象平台支持的框架即可,再或者是将python 推理代码转换为x.cpp/x.h 的样例给其他人员即可,目前由于算法卷的厉害,还是探索下算法部署和应用流程哈。以下是以android 上部署算法为例需要考虑的几个方面:
1).算法模型部署方案选择
2).算法接口和java如何交互实现
3).算法模型输入如何导入
4).算法模型输出如何展示
5).算法工程如何仅通过给其他开发人员二次开发
1.1 算法模型部署方案选择
目前android 上部署算法模型,需要考虑支持的推理算法框架,常用的有:ncnn、tnn、mnn、tflite、rknn(存在硬件限制)等。以上框架都需要考虑将python 训练的模型转换为对象框架的模型,有可直接转换、有存在以onnx 为桥梁的转换方案,具体看框架支持的op 情况和是否调用开源模型直接使用。
现阶段大多数使用者还是基于不同训练框架先转换到onnx再转到对象平台,因本次介绍以人脸识别、paddleocr 模型 为例,所以流程上选择为onnx 为桥梁到ncnn,工作中有时会兼容很多对象平台(瑞芯微、英伟达、海思、服务端)所以建议训练的模型转换到onnx 备着方便些。
部署android 端,目前我所接触的部署硬件为:纯cpu 版本的系统和支持rknn的瑞芯微系列硬件平台,本次以通用cpu 部署进行介绍。
在android 系统平台实现算法部署推理,我们要结合ncnn 框架实现,需要准备编译ncnn 库,使用生成的转换脚本、量化脚本、模型加密等,pt 模型我们要在python 代码中调用torch.onnx.export(目前参考很多,或者chatgpt一建帮忙,这里推荐贴个镜像地址方便秩咨询:https://www.aichatgpt5.com/),然后将onnx 模型调用onnx2ncnn 进行转换,这里要注意,有时候保存的模型可能存在很多冗余的op分支,可用onnxsimper 库进行裁剪下再转换。
2. 算法接口和java如何交互实现
JNI接口:通常我们将训练的.pt模型转换为可部署的模型ncnn模型为例,需要将算法推理部分以c/c++ 的形式写出模型初始化,模型推理的代码函数。但是对于这种c部分的代码,java端无法直接调用,因此需要包装成jni 接口提供给java调用。jni 接口疯转c++实现需要坐native的实现。、
(1).在java 层定义jni 接口,有固定的格式,以实现一个导入路径给c++ 的函数,在java层的实现如下:
package com.project.youfile;
public static native void pathString(String rootPath);
public class jnitest
{
public static native void pathString(String rootPath);
static {
System.loadLibrary("libxxxaaa");
}
}
其中public static native void pathString(String rootPath); 就是一个jni接口;
(2):如何在c++中实现:
a.首先需在build.gadle 配置sdk,ndk(具体版本需要根据可根据添加的第三方库统一)以及需要编译c的camke
添加cmake 为例:
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
b.建立文件夹:创建c++ 的相关代码和工程目录,将c相关的代码和依赖放置在其中,并在cmakelist.txt 中添加相关编译。
b.nativate.cpp 中实现:再创建一个native.cpp 函数,作为调用自行编译的函数,在封装native接口:以上面的pathString 接口为例:
#include <jni.h>
#include <string>
#include <iostream>
#include <android/asset_manager.h>
#include <android/asset_manager_jni.h>
using namespace std;
string root_path ="";
JNIEXPORT void JNICALL Java_com_project_youfile_你接口的java类名_rootpathSet(
JNIEnv *env, jclass clazz, jstring _root_path) {
const char *str = env->GetStringUTFChars(_root_path, NULL);
root_path = str;
env->ReleaseStringUTFChars(_root_path, str);
}
以上就创建一个jni接口,作用是将java 的string 传递给c使用;
更新中。。。。