前言
当前,深度学习有很多框架:tensorflow、pytorch、caffe、keras等。很多场景下,需要训练好的模型在移动端运行。移动端的框架又有很多TensorFlow Lite、Core ML、NCNN、MNN等等。
其中 tensorflow 所对应的移动端移植框架 TensorFlow Lite。在自己这个系列记录使用 调用tfile进行神经网络预测的android的实现。
整个记录为:
Android(1) —— Android studio 开发环境搭建
Android(2) —— Android Studio找不到连接的手机
Android(3) —— 环境配置、手机端界面设计
Android(4) —— 图像分类的*.tfile的使用 Classify.java
Android(5) —— 安卓机通过相机或相册获取图片PhotoUtil.java
Android(6) —— 主函数的详解 MainActivity.java
该篇博客讲解的为:使用 Android 编写一个APP,来调用神经网络模型。来完成神经网络的预测,在这里以图像分类为例。整篇博客,有直接工程配置的操作,也有以下相关知识的记录,为了方便阅读,会在实际操作的章节开始注上【实际操作】
1 环境配置
1.1 新建工程
【实际操作】
1.2 工程文件介绍
我们以当前路径为根路径。
[1] app/libs:第三方的库文件。
[2] app/src/main/assets:应用中引用到的一些外部资源文件。图片,音频,视频等。这些资源是以原始格式保存,且只能用编程方式读取。
[3] app/src/main/java:源文件,主要 是完成java代码的编写
[4] app/src/main/res:资源文件,但与assets不同。里面的所有文件在 R.java 里面生成相应的 id,id 是该资源文件在此工程中的唯一标识符
- ./res/drawable*:存放的是工程图片的信息(默认png)。每一张需要三个版本,高/中/低 分辨率(手机分辨率有差别)
./res/layout:存放的是工程的布局文件,以 .xml 的格式保存
./res/value:存放的是工程的数据文件,以 .xml 格式保存。主要文件种类如下
a) strings.xml:存放的是自定义的字符串和数值,可手动修改,对应内部类名称–string
b) arrays.xml:用来定义数组,对应内部类名称–array
c) color.xml:用来定义颜色和颜色字符串数值,对应内部类名称–color
d) dimens.xml:用来定义尺寸数值,对应内部类名称–dimen
e) styles.xml:用来定义样式,对应内部类名称–style[5] app/src/main/AndroidManifest.xml:是整个工程的配置文件(部分属性解释附在最后)
[6] app/build.gradle:工程编译的配置
- apply plugin:声明是 Android 应用程序还是库模块;
- com.android.application 表示这是一个应用程序模块,可直接运行
- com.android.library 标识这是一个库模块,是依附别的应用程序运行
- android 闭包:配置项目构建的各种属性,compileSDKVersion 用于指定项目的变异 SDK 版本, buildToolsVersion 用户指定项目构建工具的版本。
- defaultConfig 闭包:默认配置、应用程序包名、最小sdk版本、目标sdk版本、版本号、版本名称;
- buildTypes 闭包:指定生成安装文件的配置,是否对代码进行混淆;
- signingConfigs 闭包:签名信息配置;
- sourceSets 闭包:源文件路径配置;
- lintOptions 闭包:lint 配置;
- dependencies 闭包,指定当前项目的所有以来关系,本地以来,库依赖以及远程依赖;
- repositories 闭包,仓库配置
1.3 环境配置
1.3.1 Android App中使用TF Lite
Tensorflow Lite旨在简化机器学习应用于终端设备的过程。(终端设备处于网络边缘,所以叫边缘计算),在终端设备而非服务器上计算。有以下优点:
- 低延迟、隐私性更好、不需要连接网路、功耗低
在android的app上使用tensorflow lite 有以下方式:
- 使用JCenter托管的Tensorflow Lite AAR。
在 ./app/build.gradle中添加如下内容。自己在使用这个方式时,直接就 Sync 成功了。并没有遇到其他博主说的源的问题,以及需要相应设置。(如果后期遇到,再做补充)dependencies { implementation 'org.tensorflow:tensorflow-lite:0.0.0-nightly' }
- 使用本地的TensorFlow Lite AAR
把AAR放到工程的libs目录内,然后在 ./app/build.gradle中添加如下内容。// 添加 libs 路径 repositories{ flatDir{ dirs 'libs' } } dependencies { implementation (name:'tensorflowlitenightly',ext:'aar')//添加tensorflow依赖 }
1.3.2 三方库的配置
【实际操作】
该工程是使用基于tensorflow框架的神经网络模型,进行图像分类。所以我们需要2个三方库:(1) tensorflow:用作模型预测;(2) Glide:用作图像显示等操作。流程如下(使用本地的aar文件为例):
- 备好文件tensorflowlitenightly.aar、model.tfile
- 将 tensorflow.aar 放置 ./app/libs 路径下
- 将 model.tfile 放置 ./app/src/main/assets 路径下
- 打开 ./app/build.gradle,需要修改:
- (1) 添加 libs 路径;(2) 添加三方库名;(3) 指定 model.tfile 模型不压缩
android { ...... //指定不压缩的文件格式 aaptOptions { noCompress "tflite" } } // 添加 libs 路径 repositories{ flatDir{ dirs 'libs' } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'androidx.appcompat:appcompat:1.0.2' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' // 添加 tensorflow、Glide的依赖 implementation 'com.github.bumptech.glide:glide:4.3.1' implementation (name:'tensorflowlitenightly',ext:'aar')//添加tensorflow依赖 }
1.3.3 AndroidManifest.xml 中的配置
【实际操作】
- 该应用中会使用到相机或者相册,涉及的权限有:相机权限、对SD卡读写权限。所以需要在 AndroidManifest.xml 中对相应的权限进行声明。
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.classify"> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> ......
- 另外,由于工程中会用到 FileProvider,所以需要在 AndroidManifest.xml 文件中进行声明。
<application ...... <provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.provider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider> </application>
- 上面的
android:resource="@xml/file_paths"
,表示可以对外提供共享的路径(用于存放相机拍照后,照片的保存路径)。所以编写相关信息。
在 ./app/src/main/res 路径下创建文件 ./xml/file_paths.xml。脚本内容为:<?xml version="1.0" encoding="utf-8"?> <resources> <external-path name="images" path="lite_mobile/" /> </resources>
2 界面设计
2.1 界面展示
打开文件 ./app/src/res/layout/activity_main.xml。编辑该脚本,完成应用的界面设计。
这里做一个简单展示的界面,如下图
手机端APP打开界面如下图:
2.2 脚本编写
【实际操作】
- wrap_content:wrap 翻译过来是包裹,conten是内容。那么这个就是包裹内容的意思,也就是说你的控件里面的内容有多大,这个控件就有多大。
- android:id:给控件添加一个id 这个新的id会自动生成在 R.java文件里面,就能在你的Activity里面通过这个id来引用对象了 。如果你写成是@id/aaa则是代表引用id列表里面的aaa的值。
相关api的使用这里就不做介绍了,直接记录代码内容。
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <LinearLayout android:id="@+id/btn1_ll" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:orientation="horizontal"> <!--下面两个按键横向摆放--> <Button android:id="@+id/use_photo" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="相册" /> <Button android:id="@+id/start_camera" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="拍照" /> </LinearLayout> <LinearLayout android:id="@+id/btn2_ll" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_above="@id/btn1_ll" android:orientation="horizontal"> <Button android:id="@+id/load_model" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="加载模型" /> </LinearLayout> <TextView android:id="@+id/result_text" android:layout_width="match_parent" android:layout_height="150dp" android:layout_above="@id/btn2_ll" android:hint="预测结果会在这里显示" android:inputType="textMultiLine" android:textSize="16sp" tools:ignore="TextViewEdits" /> <ImageView android:id="@+id/show_image" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_above="@id/result_text" android:layout_alignParentTop="true" /> </RelativeLayout>
3 附件
AndroidManifest.xml 部分属性介绍
- 1 manifest -> package=“com.android.helloworld”:
表示整个java应用程序的主要包名,而且是一个默认的程序名称- 2 manifest -> android:versionCode=“1”:
表示该工程生成的apk版本号,主要是用于版本升级所用,代表着app更新的次数,是int类型,以1开始,而后2、3、4…依次 往后递增,只要判断该值就能确定是否需要升级。- 3 manifest -> android:versionName=“1.0”:
这个是我们常说的版本号,其实就是显示给用户的版本名称,由三部分组成..,该值是个字符串,可以显示给用户,由1.0开始,而后可以1.1、2.0等往后递增。- 4 manifest -> android:installLocation="":
此属性是设置apk文件的安装位置,有三个选项,解释如下:
- android:installLocation=“auto”:若将此属性值设置为auto则表示让apk自动寻找安装的地方,ROM或者SDcard任意选择,适用于小型apk文件,此值为默认值
- android:installLocation=“internalOnly”:若将此属性值设置为internalOnly则表示此apk仅仅只能安装在ROM上
- android:installLocation=“preferExternal”:若将此属性值设置为preferExternal则表示此apk会直接安装在SDcard上,适用于大型apk文件,如:大型游戏
- 5 manifest -> application -> android:icon="@drawable/ic_launcher":
此属性表示应用程序的一个图片,即安装完成后所展示的图标,即logo图片- 6 manifest -> application -> android:label="@string/app_name":
此属性表示应用程序的工程的文字说明,即安装完成后所显示的名称,即程序的应用名称- 7 manifest -> application -> activity -> android:name=“com.android.helloworld.MainActivity”:
此属性表示整个应用程序的主程序的名称- 8 manifest -> application -> activity -> intent-filter:
意图过滤器,用来过滤用户的一些动作和操作
- <action android:name=“android.intent.action.MAIN” />:表示过滤的动作,设置此条信息(主要是设置字符串android.intent.action.MAIN)表示当前的程序是工程的入口程序
- <category android:name=“android.intent.category.LAUNCHER” />:表示决定应用程序是否在程序列表中显示
- 9 manifest -> uses-sdk -> android:minSdkVersion=“8”:
此属性表示最小的 SDK 版本,这个值是对应 Android 不同版本的 API Level , 如 Android 1.5 对应 3,Android 1.6 对应 4,Android 2.1 对应7,Android2.2对应8 ,Android 2.3.3 对应10等等。当用户指定这个值后,Android 系统会用这个指定的值对应的 SDK 版本去编译你的应用程序。