在Android Studio 3上搭建基于Tensorflow+OpenCV+JNI的Android应用(详细流程)
前言
此前因项目需要,要将PC端基于Tensorflow库,OpenCV库以及自己用C++编写的源码移植到Android平台,其中有很多坑,我站在前人的肩膀上已逐一踩过,特在此把我搭建app的过程记录一下。
开发环境
- Android Studio 3+
- SDK API 26
- NDK 14b
- OpenCV 3.2.0
- Tensorflow 1.5(这个版本随意)
搭建原理
OpenCV和Tensorflow是两个第三方库,它们都提供了java的接口可供调用,即可以在android端直接进行使用,但是从源码角度分析,两个第三方库的底层都是使用C++进行编写的,所以必须作为动态链接库(.so文件)使用。
同时,当我们在进行移动端的图像处理时,由于java语言本身的效率较低,我们会把复杂的算法部分用c++编写,当作一个动态链接库供java调用,这个过程叫做java与c++的动态联编。
综上所述,我们用自己编译的动态链接库以及多个第三方的动态链接库需要使用。使用JNI(java native interference)技术实现动态联编。
搭建JNI项目
-
JNI技术及原理概述(可略过)
JNI的本质是java和c++两种语言环境下,变量的转换。例如,在java语言中,存在int型,在c++中也存在int型,但是它们的内存分配原理各不相同,要进行两种语言的交流,就是将int型的变量由java的转变为c++的或由c++的转变为java的。如下图所示,为两种语言对照表,本地类型即c++,JNI别名即传输过程中为了区分两种语言的不同类型,所定义的变量别名。
在进行图像传输时,由于图像类型不属于上述任何一种基础类型,所以不能直接传送图像,一般可以转换为二进制以byte型传输,或者是以long类型传输图像的首地址。后者的效率较高,建议使用long型传输图像。 -
搭建一个空的JNI项目
-
新建项目,选中下方的“include c++ support”,如下图所示;
-
选择“Phone and Tablet”选项的API 26或API 28(目前大部分手机都是API 26,27,28的);
-
后续步骤直接下一步,直到最后一步中选择C++ 11并完成;
-
项目会进行初始化并下载必要的依赖,根据提示信息下载即可。再次编译后会有报错,提示SDK版本错误。改变SDK的编译版本即可,选择左侧的“build.gradle(Module: app)”,并按照下图修改版本信息;
-
之后依然会有NDK版本错误,选择“Project Structure”,并在“Android NDK Location”中导入本地安装的NDK路径(绝对不能使用高版本);
-
重新编译工程,连上手机测试,能出现“Hello form C++”则证明搭建成功(这里只演示了最简单的一个例子,至于java和c++的图像传输,由于代码较多此处不给出,有人看得话我会另写文章说明)。
-
-
引入OpenCV的库
-
按照下图,引入OpenCV库,将OpenCV目录下的sdk/java添加进工程;
-
按照下图,打开“Project Structure”,为当前工程加入依赖后,确认即可;
-
如果出现如下提示,则将选中部分删除;
-
选择左侧的“build.gradle(Module: OpenCVLibrary320)”修改OpenCV的版本号,更改的同工程一般;
-
重新编译,已经不再报错。之后,开始正式的开发,由于OpenCV库需要动态加载,所以要在需要使用OpenCV方法的页面内加入如下代码。
// 绑定库导入监听事件 private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) { @Override public void onManagerConnected(int status) { // TODO Auto-generated method stub switch (status){ case BaseLoaderCallback.SUCCESS: Log.i(TAG, "加载成功"); break; default: super.onManagerConnected(status); Log.i(TAG, "加载失败"); break; } } }; @Override public void onResume() { super.onResume(); if (!OpenCVLoader.initDebug()) { Log.d(TAG, "Internal OpenCV library not found. Using OpenCV Manager for initialization"); OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_3_2_0, this, mLoaderCallback); } else { Log.d(TAG, "OpenCV library found inside package. Using it!"); mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS); } }
-
在Android平台使用Tensorflow
-
将Tensorflow源码编译为SO文件
如何把C++的源代码编译成动态链接库供Android调用,有很多blog都有介绍,我不在此处赘述。可以参见他们的,例如这个blog,也可以直接下载现成的libtensorflow_inference.so库使用。
-
导入Tensorflow库
-
如下图,将jar包放入“根目录\app\libs下”;
-
如下图,将SO文件放入“根目录\app\libs\armeabi-v7a下”,若无文件夹则新建之;
-
打开“build.gradle(module.app)”,并在defaultConfig中加入如下代码,最后效果参见下图;
multiDexEnabled true ndk { abiFilters "armeabi-v7a" }
-
在“build.gradle(module.app)”中添加如下代码,最后效果参见下图;
sourceSets { main { jniLibs.srcDirs = ['libs'] } }
-
在“build.gradle(module.app)”的“dependencies”中添加如下代码;
compile files('libs/libandroid_tensorflow_inference_java.jar')
-
重新编译,此时项目内已经可以引入tensorflow库了。
-
-
使用Tensorflow库
-
构造一个名为“TFPredict”的类,专门用来控制tensorflow的导入与使用,切忌在Activity文件里直接使用static方法引入tensorflow库文件,这会与自己写的JNI代码冲突;
-
在TFPredict类中加入如下代码,效果如下图所示;
public class TFPredict { private static final String TAG = "TFPredict: "; // 加载libtensorflow_inference.so库文件 static { System.loadLibrary("tensorflow_inference"); Log.e(TAG,"libtensorflow_inference.so库加载成功!"); } // 使用TF计算两个浮点型的和 public static void tfAdd(float a, float b) { try (Graph g = new Graph(); Session s = new Session(g)) { // 使用占位符构造一个图,添加两个浮点型的张量 Output x = g.opBuilder("Placeholder", "x").setAttr("dtype", DataType.FLOAT).build().output(0); Output y = g.opBuilder("Placeholder", "y").setAttr("dtype", DataType.FLOAT).build().output(0); Output z = g.opBuilder("Add", "z").addInput(x).addInput(y).build().output(0); System.out.println( TAG + "z = " + z); // 计算张量加值 try (Tensor tx = Tensor.create(a); Tensor ty = Tensor.create(b); Tensor tz = s.runner().feed("x", tx).feed("y", ty).fetch("z").run().get(0)) { System.out.println("output = " + a + " + " + b + " = " + tz.floatValue()); } } } }
-
测试一下是否能成功调用函数,在MainActivity特定位置使用tfAdd函数,随便输入两个浮点值,若在控制台出现如下图所示效果,则表明库导入成功;
TFPredict.tfAdd(4, 5);
-
最后需要说明的是,我所使用的是早期版本的Tensorflow(1.5版本),它所提供的Java API比js,python和c++的都要少很多,基本上只能用来读取模型做预测,要在移动端用java写CNN或是LSTM是做不到的。
-
本文地址:https://blog.csdn.net/qq_36926782/article/details/107501024