在安卓平台使用tensorflow
最近更新 2017.6.7
添加了app预览如下,更详细请到github
分类预览1:
拍照分类:
程序启动:
自动分类为相册:
最近更新 2017.3.7
注意
最近有几个小伙伴询问移动端平台怎样使用自己的模型以及其他相关问题,我对部分问题做了总结,相关的内容在另外一篇博客中,点这里查看。另外大家要使用自己的模型请直接到上述博客中查看,本博客的所用的tf版本太旧,移植自己的模型可能会出现问题。
另外我做的用0.12版本tf做了一个图片分类相册应用(android),tf部分已经可以正常使用了,大家可以到我的github查看
前言
最近在Android平台上需要用到图片分类的技术,tensorflow是谷歌发布的一个开源的机器学习的框架,可以在pc端以及移动端使用。虽然官方提供了一个Android的demo我也成功安装到手机上运行了,但是对于之前没有接触过安卓开发的我来说,将tensorflow移植到我的项目并且在android studio上继续开发仍然很困难…。走了好多条弯路后,我终于成功将它运行到我写的app上了,下面分享一下怎样将tensorflow移植到android上。
开始
首先我们先从github克隆tensorflow的demo,需要注意的是这个demo不是官方的demo,而且也不是基于最新的0.12版本,只是0.10版本。克隆这个的原因是这个项目已经将官方的demo移植android studio上了,不用我再配置编译。克隆后从studio打开,建立虚拟机模拟运行
问题1:
无法运行报错:INSTALL_FAILED_NO_MATCHING_ABIS: Failed to extract native libraries, res=-113
原因是安卓模拟器是x86构架的,然而这个项目中使用的tensorflow的动态链接库是arm的,因此无法在这个模拟机中模拟。首先我想到该怎样编译一个x86的动态链接库,然而不好意思,我折腾了一天也没成功。后来用在studio中新建了一款arm的模拟器,虽然能跑了,但是实在是太慢了,可是我又没有android手机,所以只能这样用了。不过虽然这个虚拟机在开机的时候需要将近10分钟,但是开机之后速度会快一点,虽然赶不上x86的模拟器,但是对于小任务量的任务也能凑合用
解决方案:新建一个arm的模拟器,开机时间很长,开机后速度还能忍;有能力的可以自行编译x86的动态链接库
分析源码
在Android中 native修饰的就是使用动态链接库中的接口,对于这个图片分类的demo,看了这写java代码,我们可以找到tensorflow的3个接口如下
// load the tensorflow
public native int initializeTensorFlow(
AssetManager assetManager,
String model,
String labels,
int numClasses,
int inputSize,
int imageMean,
float imageStd,
String inputName,
String outputName);
// classify the image by input the bitmap
private native String classifyImageBmp(Bitmap bitmap);
// classify the image by input the rgb
private native String classifyImageRgb(int[] output, int width, int height);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
因此我们只需要学会使用这三个函数就能够将tensorflow移植我们的项目中了,对了,下面这条语句是载入动态链接库
static {
System.loadLibrary("tensorflow_demo");
}
- 1
- 2
- 3
我们到TensorFlowImageListener中找到了这几个函数的使用,因此,在使用时我们首先需要创建TensorFlowClassifier对象
private final TensorFlowClassifier tensorflow = new TensorFlowClassifier();
- 1
然后我们需要载入tensorflow模型,载入时需要以下几个参数,注意我在TensorFlowImageClassifier中已经将我暂时不需要的参数删除了
private static final int NUM_CLASSES = 1001;
private static final int INPUT_SIZE = 224;
private static final int IMAGE_MEAN = 117;
private static final float IMAGE_STD = 1;
private static final String INPUT_NAME = "input:0";
private static final String OUTPUT_NAME = "output:0";
private static final String MODEL_FILE = "file:///android_asset/tensorflow_inception_graph.pb";
private static final String LABEL_FILE =
"file:///android_asset/imagenet_comp_graph_label_strings.txt";
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
不要忘了载入模型的第一个参数是assetManager,这个参数表示模型训练的数据结果(pb&&txt)文件的位置,如果为空的话会报出异常,上面的几个参数,基本看一下名字就知道是啥了,比如输入的名,图片的大小224*224等。模型初始化完成后就要对图片分类了,我们可以使用private native String classifyImageBmp(Bitmap bitmap);直接传入图片的bitmap位图,并且将图片大小调整为INPUT_SIZE即可
问题2:
应用在载入模型过程中闪退
看看你的assets目录位置对吗,也就是第一个参数,这个错了是无法载入模型的哦
在项目中使用ternsorflow
我直接将上面的demo中的主Activity清空,重新写了一个Activity,这个应用打开手机相册中的一张照片,然后将图片显示在界面上并且在最上面显示这个物品最可能的名字(使用谷歌的训练数据),这个项目依赖上面demo中的TensorflowClassifier类
主Activity代码
package org.tensorflow.demo;
import java.io.FileNotFoundException;
import java.util.List;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.Intent;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
public class CameraActivity extends Activity {
/** Called when the activity is first created. */
private static final String MODEL_FILE = "file:///android_asset/tensorflow_inception_graph.pb";
private static final String LABEL_FILE = "file:///android_asset/imagenet_comp_graph_label_strings.txt";
private static final int NUM_CLASSES = 1001;
private static final int INPUT_SIZE = 224;
private static final int IMAGE_MEAN = 117;
private static final float IMAGE_STD = 1;
private static final String INPUT_NAME = "input:0";
private static final String OUTPUT_NAME = "output:0";
private final TensorFlowClassifier tensorflow = new TensorFlowClassifier();
private TextView mResultText;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_camera);
// test1 load tensorflow
final AssetManager assetManager = getAssets();
tensorflow.initializeTensorFlow(
assetManager, MODEL_FILE, LABEL_FILE, NUM_CLASSES, INPUT_SIZE, IMAGE_MEAN, IMAGE_STD,
INPUT_NAME, OUTPUT_NAME);
// test1 end
Button button = (Button)findViewById(R.id.b01);
button.setText("选择图片");
button.setOnClickListener(new Button.OnClickListener(){
@Override
public void onClick(View v) {
Intent intent = new Intent();
/* 开启Pictures画面Type设定为image */
intent.setType("image/*");
/* 使用Intent.ACTION_GET_CONTENT这个Action */
intent.setAction(Intent.ACTION_GET_CONTENT);
/* 取得相片后返回本画面 */
startActivityForResult(intent, 1);
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK) {
Uri uri = data.getData();
Log.e("uri", uri.toString());
ContentResolver cr = this.getContentResolver();
try {
Bitmap bitmap = BitmapFactory.decodeStream(cr.openInputStream(uri));
dealPics(bitmap);
} catch (FileNotFoundException e) {
Log.e("Exception", e.getMessage(),e);
}
}
super.onActivityResult(requestCode, resultCode, data);
}
private void dealPics(Bitmap bitmap) {
ImageView imageView = (ImageView) findViewById(R.id.iv01);
/* 将Bitmap设定到ImageView */
int width = bitmap.getWidth();
int height = bitmap.getHeight();
System.out.println(width + "&&" + height);
float scaleWidth = ((float)INPUT_SIZE) / width;
float scaleHeight = ((float) INPUT_SIZE) / height;
Matrix matrix = new Matrix();
matrix.postScale(scaleWidth, scaleHeight);
Bitmap newbm = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);
imageView.setImageBitmap(newbm);
final List<Classifier.Recognition> results = tensorflow.recognizeImage(newbm);
for (final Classifier.Recognition result : results) {
System.out.println("Result: " + result.getTitle());
}
mResultText = (TextView)findViewById(R.id.t01);
mResultText.setText("Detected = " + results.get(0).getTitle());
System.out.println(newbm.getWidth() + "&&" + newbm.getHeight());
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:id="@+id/t01"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="hello"
/>
<Button
android:id="@+id/b01"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<ImageView
android:id="@+id/iv01"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
其他
如果仅仅使用tensorflow进行图片分类,那个现在这3个借口基本就可以满足你的需要,建议在模拟时使用真机模拟,因为arm模拟器太慢。可以用tensorflow训练自己的数据集,并且根据自己的需求来处理