最近一个项目需要使用Tensorflow lite, 官网上的解释又特别简单,主要给了一个例子,但是这个例子和官网的解释又不一样。。。。这里简单记录下操作方法。

添加依赖

某些加载的方法,依赖并不支持。

在自己的build.grandle的依赖中添加:

implementation 'org.tensorflow:tensorflow-lite:1.15.0'
    implementation 'org.tensorflow:proto:1.15.0'

模型转化

对于keras或者用model建立的模型,有直接的转化函数,对于sess建立的模型(计算图),有很多方法不再适用。我尝试了一种是可以使用的:

  • 先转化为saved model
  • 通过saved model 转化为tflite file

转化为saved model:

tf.saved_model.simple_save(
            sess,
            "%s/trained model/flows" % (savedir),
            inputs={"input": input},
            outputs={"flows": flows}
        )

注意:其中的inputs和outputs必须是张量,否则会提醒你array没有name属性。

inputs就是feed_dict中那些place holder,outputs可以填输出的张量,也可以直接填操作本身。

转为tflite file:

import tensorflow as tf

converter = tf.lite.TFLiteConverter.from_saved_model(path)
tflite_model = converter.convert()
open(path+"/converted_model.tflite", "wb").write(tflite_model)

至此得到了tflite文件

模型加载

注意点:
1: 读取文件时需要申请权限
2: 对于Interpreter的构造函数,只有MappedByteBuffer那个可以使用,用File的那个会报错。

直接把生成的文件放在“我的手机”中,如果是虚拟机,则是storage/emulated/0文件夹下,然后使用

Environment.getExternalStorageDirectory().getPath();

后面加上文件名称,可以得到该文件的路径。

文件加载函数:

public MappedByteBuffer getFile(String fileName) throws IOException {
        File f = new File(fileName);
        FileInputStream in = new FileInputStream(f);
        FileChannel channel = in.getChannel();
        return channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
    }

把该函数返回的作为Interpreter的输入,可以生成一个解释器。

权限申请:

在manifest中增加:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

对于Android11以下的版本,需要额外增加:

android:requestLegacyExternalStorage="true"

申请权限:

// Storage Permissions
private static final int REQUEST_EXTERNAL_STORAGE = 1;
private static String[] PERMISSIONS_STORAGE = {
        Manifest.permission.READ_EXTERNAL_STORAGE,
        Manifest.permission.WRITE_EXTERNAL_STORAGE
};

public static void verifyStoragePermissions(Activity activity) {
    // Check if we have write permission
    int permission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE);

    if (permission != PackageManager.PERMISSION_GRANTED) {
        // We don't have permission so prompt the user
        ActivityCompat.requestPermissions(
                activity,
                PERMISSIONS_STORAGE,
                REQUEST_EXTERNAL_STORAGE
        );
    }
}

推理

有两个函数可以使用:

单输入输出

interpreter.run(inputs, outputs);

inputs和ouputs都是高维数组,比如正常的都是:

float[][][][] inputs = new float[1][h][w][3];

注意不管是输入还是输出,数组申请空间时候必须把每一个维度都确定,否则可能导致拷贝出错或者维度出错。

比如:

Caused by: java.lang.IllegalStateException: Internal error: Unexpected failure when preparing tensor allocations: tensorflow/lite/kernels/concatenation.cc:74 t->dims->data[d] != t0->dims->data[d] (1 != 2)
Node number 9 (CONCATENATION) failed to prepare.

输入维度错误,或者没有给定输入的维度

ouputs也是多维数组,和inputs相同。

多输入

interpreter.runForMultipleInputsOutputs([Objects], outputs)

第一个参数是一个数组,里面是输入的张量。我猜测顺序和convert函数中命名的字典序有关。如果不放心的话,可以使用:

Tensor a = interpreter.getInputTensor(0);

然后看一下copyShape是否符合。

因此,如果张量是4维的,那么第一个参数是一个5维的数组。

outputs是一个Map,可以声明如下:

Map outputs = HashMap<Interger,Objects>();
outputs.put(0,Objects);

其他

将Bitmap转为float数组:

public Bitmap toBitmap(float[][][] a){
        int[] colors = new int[h_res*w_res];
        for(int i = 0; i < w_res; i++){
            for(int j = 0; j < h_res; j++){
                int r = (int)(a[j][i][0]*255) & 0xff;
                int g = (int)(a[j][i][1]*255) & 0xff;
                int b = (int)(a[j][i][2]*255) & 0xff;
                colors[i*h_res+j] = 0xff000000 | (r<<16) | (g<<8) | b;
            }
        }
        return Bitmap.createBitmap(colors, 0, w_res, w_res, h_res, bitmaps.get(0).getConfig());
    }

Bitmap转float

public float[][][] toFloatArray(Bitmap a){
        // 获取图片宽度和高度
        int width = a.getWidth();   // 图片宽度
        int height = a.getHeight();  //图片高度
        int channel = 3; // 3个通道
        int r = 0;
        int g = 1;
        int b = 2;
        int[] data = new int[width*height];
        a.getPixels(data, 0,width,0,0,width,height);
        // 将二维数组转换为三维数组
        float[][][] rgb4DArray = new float[width][height][channel]; // 图像数量*宽度*高度*通道数


        for(int i = 0; i < width; i++) {
            for (int j = 0; j < height; j++) {
                rgb4DArray[j][i][r] = ((data[i*height + j] & 0xff0000) >> 16)/255.0f;
                rgb4DArray[j][i][g] = ((data[i*height + j] & 0xff00) >> 8)/255.0f;
                rgb4DArray[j][i][b] = (data[i*height + j] & 0xff)/255.0f;
            }
        }
        return rgb4DArray;
    }

reference


https://stackoverflow.com/questions/23527767/open-failed-eacces-permission-denied

https://tensorflow.google.cn/lite/guide/inference

https://www.jianshu.com/p/83cfd9571158