使用Tensorflow Lite创建一个Android AI应用

261 篇文章 10 订阅
39 篇文章 2 订阅

目录

下一步


在这里,我们使用TensorFlow Lite解释器检查图像并产生其输出。

这是将神经网络与Android上的TensorFlow Lite结合使用的系列文章中的第三篇。在本系列的第2部分中,我们最后从预训练的模型制作了TensorFlow Lite模型。在这一部分中,我们将创建一个Android应用程序并将该模型导入其中。您将需要上一部分(yolo.tflite)中创建的.tflite文件。

应用程序流程如下:

  1. 选择图像进行分析。
  2. 调整图像大小以匹配所使用的TensorFlow Lite模型的要求。
  3. 输入缓冲区由图像构成。
  4. 具有可选委托的TensorFlow Lite解释器被实例化。
    1. GPU代表将在图形硬件上运行一些计算。
    2. NNAPI委托(Android 8.1和更高版本)可以在GPUDSP或神经处理单元(NPU)上运行。
  5. 实例化输出缓冲区。
  6. 解释器针对输入运行模型,并将结果放入输出中。

应用程序的源位图可以通过多种不同方式获取:可以从文件系统加载,从设备的摄像头获取,从网络下载或通过其他方式获取。只要图像可以作为位图加载,那么此处显示的其余代码都可以轻松地适用于此。在此示例程序中,我所有的源图像来自图片选择器。

使用空活动模板创建一个新的Android应用程序。创建应用程序之后,我们需要为应用程序执行一些配置步骤。首先,我们将添加对TensorFlow的引用,以便该应用程序具有必要的库,用于将TensorFlow LiteTensorFlow Lite委托用于CPUNPU。打开应用程序的build.gradle并将以下内容添加到依赖项部分:

implementation 'org.tensorflow:tensorflow-lite:0.0.0-nightly'
implementation 'org.tensorflow:tensorflow-lite-gpu:0.0.0-nightly'
implementation 'org.tensorflow:tensorflow-lite-support:0.0.0-nightly'

保存更改并构建应用程序。如果收到有关所需NDK版本不存在的错误,请参阅本系列的第1部分。如果应用程序生成,则对build.gradle进行其他更改。在“Android”部分中,必须添加一个设置以指示Android Studio不压缩.tflite文件。在文件的android部分中,添加以下行:

aaptOptions {
	noCompress "tflite"
}

.tflite文件将进入项目的assets文件夹中。该文件夹在新项目中不存在。您可以在apps/src/main中的项目中创建文件夹。

将文件yolo.tflite复制到assets文件夹。

我们将从允许用户选择图像并显示它的应用程序开始。为此,activity_main.xml文件仅需要几个元素:用于激活图像选择器的按钮和图像视图。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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">


   <ImageView

       android:id="@+id/selectedImageView"

       android:layout_width="match_parent"

       android:layout_height="match_parent"

       android:layout_marginBottom="64dp"

       app:layout_constraintBottom_toBottomOf="parent"

       app:layout_constraintEnd_toEndOf="parent"

       app:layout_constraintStart_toStartOf="parent"

       app:layout_constraintTop_toTopOf="parent" />

   <Button

       android:id="@+id/selectImageButton"

       android:text="@string/button_select_image"

       android:layout_width="wrap_content"

       android:layout_height="wrap_content"

       android:layout_marginTop="8dp"

       app:layout_constraintEnd_toEndOf="parent"

       app:layout_constraintStart_toStartOf="parent"

       app:layout_constraintTop_toBottomOf="@+id/selectedImageView" />
</androidx.constraintlayout.widget.ConstraintLayout>

在文件MainActivity.java中,我们添加执行代码。要从此代码引用图像视图,请添加一个名为selectedImageView的字段。

ImageView selectedImageView;

onCreate()中,在setContentView()后面添加一行代码,将加载的ImageView的实例分配给selectedImageView

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);
   selectedImageView = findViewById(R.id.selectedImageView);
}

在布局中定义的按钮将触发打开图像选择器的函数。

final int SELECT_PICTURE = 1;
public void onSelectImageButtonClicked(View view) {
   Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
   intent.setType("image/*");
   Intent chooser = Intent.createChooser(intent, "Choose a Picture");
   startActivityForResult(chooser, SELECT_PICTURE);
}

当用户激活此函数时,系统的图像选择器将打开。用户选择图像后,控制权返回给应用程序。要检索选择,活动必须实现方法onActivityResult()。所选图像的URI在传递给此方法的数据对象内。

public void onActivityResult (int reqCode, int resultCode, Intent data) {
   super.onActivityResult(reqCode, resultCode, data);
   if (resultCode == RESULT_OK) {
       if (reqCode == SELECT_PICTURE) {
           Uri selectedUri = data.getData();
           String fileString = selectedUri.getPath();
           selectedImageView.setImageURI(selectedUri);
       }
   }
}

activity_main.xml中定义的按钮尚未附加到任何代码。将以下行添加到按钮的定义:

android:onClick="onSelectImageButtonClicked"

如果立即运行该应用程序,您将看到它能够加载和显示图像。我们想将此图像通过TensorFlow解释器传递。让我们为此准备好代码。YOLO算法有多种实现方式。我在这里使用的那个期望图像被分为13列和13行。此网格内的每个单位均为32x32像素。输入图像将为416x416像素(13 * 32 = 416)。这些值在添加到MainActivity.java的常量中表示。还添加了一个常量,用于保存要加载的.tflite文件的名称,以及一个用于保存TensorFlow Lite解释器的变量。

final String TF_MODEL_NAME = "yolov4.tflite";
final int IMAGE_SEGMENT_ROWS = 13;
final int IMAGE_SEGMENT_COLS = 13;
final int IMAGE_SEGMENT_WIDTH = 32;
final int IMAGE_SEGMENT_HEIGHT = 32;
final int IMAGE_WIDTH = IMAGE_SEGMENT_COLS * IMAGE_SEGMENT_WIDTH; //416
final int IMAGE_HEIGHT = IMAGE_SEGMENT_ROWS * IMAGE_SEGMENT_HEIGHT; //416

Interpreter tfLiteInterpreter;

有几种调整图像大小的选项。我将使用TensorFlow ImageProcessorImageProcessor带有我们要应用于图像的操作列表。当给定TensorImage时,ImageProcessor将在图像上执行这些操作,并返回一个新的TensorImage准备进行进一步处理。

void processImage(Bitmap sourceImage) {
   ImageProcessor imageProcessor =
           new ImageProcessor.Builder()
                   .add(new ResizeOp(IMAGE_HEIGHT, IMAGE_WIDTH, ResizeOp.ResizeMethod.BILINEAR))
                   .build();
   TensorImage tImage = new TensorImage(DataType.FLOAT32);
   tImage.load(sourceImage);
   tImage = imageProcessor.process(tImage);
   //...
}

我们需要使用我们的模型初始化TensorFlow Lite解释器,以便它可以处理该图像。Interpreter类的构造函数接受一个包含模型的字节缓冲区和一个包含要应用于Interpreter实例的选项的对象。对于这些选项,我们添加了一个GPU委托和一个NNAPI委托。如果设备具有用于加速某些操作的兼容硬件,则在运行TF解释器时将使用该硬件。

void prepareInterpreter() throws IOException {
   if(tfLiteInterpreter == null) {
       GpuDelegate gpuDelegage = new GpuDelegate();
       Interpreter.Options options = new Interpreter.Options();
       options.addDelegate(gpuDelegage);
       //Only add the NNAPI delegate of this were build for Android P or later.
       if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
           NnApiDelegate nnApiDelegate = new NnApiDelegate();
           options.addDelegate(nnApiDelegate);
       }
       MappedByteBuffer tfLiteModel = FileUtil.loadMappedFile(this, TF_MODEL_NAME);
       tfLiteInterpreter = new Interpreter(tfLiteModel, options);
   }
}

您可能还记得张量通常表示为数组。在processImage函数中,创建了一些缓冲区以接收来自网络模型的输出。

float[][][][][] buf0 = new float[1][52][52][3][85];
float[][][][][] buf1 = new float[1][26][26][3][85];
float[][][][][] buf2 = new float[1][13][13][3][85];

这些多维数组一开始可能看起来有些吓人。如何处理这些数组中的数据?

让我们关注三个数组中的最后一个。某些网络模型可以同时处理感兴趣的数据集的多个实例。您可能还记得,该算法将416x416像素的图像分为13行和13列。数组的第二维和第三维用于图像的行和列。在这些网格的每个网格中,该算法最多可以为在特定网格位置识别的对象检测3个边界框。尺寸3的第四个维度适用于这些边界框中的每一个。最后一个维度是85个元素。列表中的前四项用于定义边界框坐标(xy,宽度,高度)。此列表中的第五个元素是介于01之间的值,表示框是对象匹配项的置信度。

对于我在这里使用的YOLO的实现,最多可以识别80种类型的对象。YOLO还有其他实现可以检测其他一些元素。有时,您可以通过最后一个维度中的元素数量来猜测网络所标识的项目数量。但是,不要依赖于此。要知道哪个位置代表哪些对象,您需要查阅所用网络的文档。这些值的含义和解释将在本系列的下一部分中详细讨论。

这些值打包在一个HashMap并传递到TensorFlow Lite解释器。

//...
HashMap<Integer, Object> outputBuffers = new HashMap<Integer, Object>();
outputBuffers.put(0, buf0);
outputBuffers.put(1, buf1);
outputBuffers.put(2, buf2);

tfLiteInterpreter.runForMultipleInputsOutputs(new Object[]{tImage.getBuffer()}, outputBuffers);

执行YOLO神经网络的行是runForMultipleInputsOutputs()。如果只有一个输入和输出,则将使用名为run()的函数的调用。结果存储在第二个参数中传递的数组中。

网络运行并产生输出,但是要使这些输出有用或有意义,我们需要知道如何解释它们。为了测试,我使用了这张图片。

挖掘一个输出数组,我得到了一系列数字。让我们检查一下前四个。

00: 0.32  01: 0.46   02: 0.71   03: 0.46

前四个数字是用于匹配以及宽度和高度的XY坐标。值从-11缩放。必须将它们调整为0416才能将它们转换为像素尺寸。

数组的其余大多数值均为0.0。对于我正在寻找非零值的结果,在位置19遇到

12: 0.00   13: 0.00   14: 0.00   15: 0.00
16: 0.00   17: 0.00   18: 0.00   19: 0.91
20: 0.00   21: 0.00   22: 0.00   23: 0.00

数组的整个元素为85个元素,但是在这种情况下,其余的值也为零,因此省略。从位置5开始的值是该神经网络可以识别的每类物品的置信度。如果从值位置减去5,我们将得到对象类的索引。对于使该值与对象匹配的类列表,我们将查看位置1419-5 = 14)。

00: person        01: bicycle       02: car          03: motorbike
04: airplane      05: bus           06: train        07: truck
08: boat          09: traffic light 10: fire hydrant 11: stop sign
12: parking meter 13: bench         14: bird         15: cat
16: dog           17: horse         18: sheep

在这种情况下,程序已识别出一只鸟。

下一步

现在,我们已经成功地在图像上运行了模型,是时候对模型的输出进行一些有趣的操作了。继续阅读下一篇文章,以了解如何解释这些结果并为其创建可视化效果。

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值