如何在 (Android Studio) NDK (C / C++ API) 中 运行 进行 Tensorflow-Lite 推理?

How to run a Tensorflow-Lite inference in (Android Studio) NDK (C / C++ API)?

信息

推理时间不是很好,所以现在我想在 Android 的 NDK 中使用 TFL。

所以我在 Android Studio 的 NDK 中构建了 Java 应用程序的精确副本,现在我试图在项目中包含 TFL 库。我按照 TensorFlow-Lite's Android guide 并在本地构建了 TFL 库(并获得了一个 AAR 文件),并将该库包含在 Android Studio 的 NDK 项目中。

现在我试图在我的 C++ 文件中使用 TFL 库,方法是在代码中尝试 #include 它,但我收到一条错误消息:cannot find tensorflow(或我使用的任何其他名称我正在尝试使用,根据我在 CMakeLists.txt 文件中给它的名称)。

文件

应用build.gradle:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.3"

    defaultConfig {
        applicationId "com.ndk.tflite"
        minSdkVersion 28
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

        externalNativeBuild {
            cmake {
                cppFlags ""
            }
        }

        ndk {
            abiFilters 'arm64-v8a'
        }

    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

    // tf lite
    aaptOptions {
        noCompress "tflite"
    }

    externalNativeBuild {
        cmake {
            path "src/main/cpp/CMakeLists.txt"
            version "3.10.2"
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

    // tflite build
    compile(name:'tensorflow-lite', ext:'aar')

}

项目build.gradle:

buildscript {

    repositories {
        google()
        jcenter()

    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.6.2'

    }
}

allprojects {
    repositories {
        google()
        jcenter()

        // native tflite
        flatDir {
            dirs 'libs'
        }

    }

}


task clean(type: Delete) {
    delete rootProject.buildDir
}

CMakeLists.txt:

cmake_minimum_required(VERSION 3.4.1)

add_library( # Sets the name of the library.
             native-lib

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             native-lib.cpp )

add_library( # Sets the name of the library.
        tensorflow-lite

        # Sets the library as a shared library.
        SHARED

        # Provides a relative path to your source file(s).
        native-lib.cpp )

find_library( # Sets the name of the path variable.
              log-lib

              # Specifies the name of the NDK library that
              # you want CMake to locate.
              log )


target_link_libraries( # Specifies the target library.
                       native-lib tensorflow-lite

                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )

native-lib.cpp:

#include <jni.h>
#include <string>

#include "tensorflow"

extern "C" JNIEXPORT jstring JNICALL
Java_com_xvu_f32c_1jni_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

class FlatBufferModel {
    // Build a model based on a file. Return a nullptr in case of failure.
    static std::unique_ptr<FlatBufferModel> BuildFromFile(
            const char* filename,
            ErrorReporter* error_reporter);

    // Build a model based on a pre-loaded flatbuffer. The caller retains
    // ownership of the buffer and should keep it alive until the returned object
    // is destroyed. Return a nullptr in case of failure.
    static std::unique_ptr<FlatBufferModel> BuildFromBuffer(
            const char* buffer,
            size_t buffer_size,
            ErrorReporter* error_reporter);
};

进度

我也尝试遵循这些:

但就我而言,我使用 Bazel 来构建 TFL 库。

尝试构建 (label_image) 的分类演示,我设法构建它并 adb push 到我的设备,但是在尝试 运行 时出现以下错误:

ERROR: Could not open './mobilenet_quant_v1_224.tflite'.
Failed to mmap model ./mobilenet_quant_v1_224.tflite

尝试编译 TFL,我在 tensorflow/tensorflow/lite/BUILD 中添加了 cc_binary(在 label_image example 之后):

cc_binary(
    name = "native-lib",
    srcs = [
        "native-lib.cpp",
    ],
    linkopts = tflite_experimental_runtime_linkopts() + select({
        "//tensorflow:android": [
            "-pie",
            "-lm",
        ],
        "//conditions:default": [],
    }),
    deps = [
        "//tensorflow/lite/c:common",
        "//tensorflow/lite:framework",
        "//tensorflow/lite:string_util",
        "//tensorflow/lite/delegates/nnapi:nnapi_delegate",
        "//tensorflow/lite/kernels:builtin_ops",
        "//tensorflow/lite/profiling:profiler",
        "//tensorflow/lite/tools/evaluation:utils",
    ] + select({
        "//tensorflow:android": [
            "//tensorflow/lite/delegates/gpu:delegate",
        ],
        "//tensorflow:android_arm64": [
            "//tensorflow/lite/delegates/gpu:delegate",
        ],
        "//conditions:default": [],
    }),
)

并尝试为 x86_64 构建它,然后 arm64-v8a 我收到错误消息:cc_toolchain_suite rule @local_config_cc//:toolchain: cc_toolchain_suite '@local_config_cc//:toolchain' does not contain a toolchain for cpu 'x86_64'.

检查第 47 行中的 external/local_config_cc/BUILD(其中提供了错误):

cc_toolchain_suite(
    name = "toolchain",
    toolchains = {
        "k8|compiler": ":cc-compiler-k8",
        "k8": ":cc-compiler-k8",
        "armeabi-v7a|compiler": ":cc-compiler-armeabi-v7a",
        "armeabi-v7a": ":cc-compiler-armeabi-v7a",
    },
)

这是仅有的 2 个 cc_toolchain。在存储库中搜索 "cc-compiler-" 我只找到了“aarch64”,我认为它适用于 64 位 ARM,但 "x86_64" 没有。虽然有 "x64_windows",但我在 Linux.

尝试像这样使用 aarch64 构建:

bazel build -c opt --fat_apk_cpu=aarch64 --cpu=aarch64 --host_crosstool_top=@bazel_tools//tools/cpp:toolchain //tensorflow/lite/java:tensorflow-lite

导致错误:

ERROR: /.../external/local_config_cc/BUILD:47:1: in cc_toolchain_suite rule @local_config_cc//:toolchain: cc_toolchain_suite '@local_config_cc//:toolchain' does not contain a toolchain for cpu 'aarch64'

使用 Android Studio 中的库:

我能够通过更改构建配置中的 soname 并使用 CMakeLists.txt 中的完整路径来为 x86_64 体系结构构建库。这导致 .so 共享库。另外 - 我能够使用 TFLite Docker 容器为 arm64-v8a 构建库,通过调整 aarch64_makefile.inc 文件,但我没有更改任何构建选项,并让 build_aarch64_lib.sh 无论它构建什么。这导致了一个 .a 静态库。

所以现在我有两个 TFLite 库,但我仍然无法使用它们(例如,我不能 #include "..." 任何东西)。

尝试构建项目时,仅使用 x86_64 可以正常工作,但尝试包含 arm64-v8a 库会导致忍者错误:'.../libtensorflow-lite.a', needed by '.../app/build/intermediates/cmake/debug/obj/armeabi-v7a/libnative-lib.so', missing and no known rule to make it.

不同的方法 - build/compile 源文件 Gradle:

  1. 我在 Android Studio
  2. 中创建了一个原生 C++ 项目
  3. 我从 Tensorflow 的 lite 目录中获取了基本的 C/C++ 源文件和 headers,并在 app/src/main/cpp 中创建了一个类似的结构,其中包括(A) tensorflow、(B) absl 和 (C) flatbuffers 文件
  4. 我将所有 tensorflow header 文件中的 #include "tensorflow/... 行更改为相对路径,以便编译器可以找到它们。
  5. 在应用程序的 build.gradle 中,我为 .tflite 文件添加了 no-compression 行:aaptOptions { noCompress "tflite" }
  6. 我在应用程序中添加了一个 assets 目录
  7. native-lib.cpp中我添加了some example code from the TFLite website
  8. 尝试使用包含的源文件构建项目(构建目标是 arm64-v8a)。

我得到一个错误:

/path/to/Android/Sdk/ndk/20.0.5594570/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/c++/v1/memory:2339: error: undefined reference to 'tflite::impl::Interpreter::~Interpreter()'

<memory>中,第2339行是"delete __ptr;"行:

_LIBCPP_INLINE_VISIBILITY void operator()(_Tp* __ptr) const _NOEXCEPT {
    static_assert(sizeof(_Tp) > 0,
                  "default_delete can not delete incomplete type");
    static_assert(!is_void<_Tp>::value,
                  "default_delete can not delete incomplete type");
    delete __ptr;
  }

问题

如何在 Android Studio 中包含 TFLite 库,以便 运行 来自 NDK 的 TFL 推理?

或者 - 我如何使用 gradle(目前使用 cmake)构建和编译源文件?

我通过以下方式将 Native TFL 与 C-API 结合使用:

设置:

  1. 下载最新版本TensorFlow Lite AAR file
  2. 将下载的 .arr 文件的文件类型更改为 .zip 并解压文件以获取共享库(.so 文件)
  3. TFL repository
  4. 中的 c 目录下载所有 header 文件
  5. 在 Android Studio 中创建一个 Android C++ 应用程序
  6. app/src/main 中创建一个 jni 目录(New -> Folder -> JNI Folder)并在 sub-directories 中创建架构它(例如 arm64-v8ax86_64
  7. 将所有 header 文件放入 jni 目录(架构目录旁边),并将共享库放入架构 directory/ies
  8. 打开 CMakeLists.txt 文件并包含 TFL 库的 add_library 节,set_target_properties 节中的共享库路径和 header 中的include_directories 节(见下文,在注释部分)
  9. 同步Gradle

用法:

native-lib.cpp中包括header,例如:

#include "../jni/c_api.h"
#include "../jni/common.h"
#include "../jni/builtin_ops.h"

TFL函数可以直接调用,例如:

TfLiteModel * model = TfLiteModelCreateFromFile(full_path);
TfLiteInterpreter * interpreter = TfLiteInterpreterCreate(model);
TfLiteInterpreterAllocateTensors(interpreter);
TfLiteTensor * input_tensor =
            TfLiteInterpreterGetInputTensor(interpreter, 0);
const TfLiteTensor * output_tensor =
            TfLiteInterpreterGetOutputTensor(interpreter, 0);
TfLiteStatus from_status = TfLiteTensorCopyFromBuffer(
            input_tensor,
            input_data,
            TfLiteTensorByteSize(input_tensor));
TfLiteStatus interpreter_invoke_status = TfLiteInterpreterInvoke(interpreter);
TfLiteStatus to_status = TfLiteTensorCopyToBuffer(
            output_tensor,
            output_data,
            TfLiteTensorByteSize(output_tensor));

备注:

  • 在此设置中使用了 SDK 版本 29
  • cmake环境也包括cppFlags "-frtti -fexceptions"

CMakeLists.txt 示例:

set(JNI_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../jni)
add_library(tflite-lib SHARED IMPORTED)
set_target_properties(tflite-lib
        PROPERTIES IMPORTED_LOCATION
        ${JNI_DIR}/${ANDROID_ABI}/libtfl.so)
include_directories( ${JNI_DIR} )
target_link_libraries(
        native-lib
        tflite-lib
        ...)

我也曾为 Android 构建 TF Lite C++ APIs 而苦苦挣扎。幸运的是,我设法让它发挥作用。

问题是我们需要在 运行 执行 bazel build ... 命令之前配置 Bazel 构建过程。 TF Lite Android 快速入门指南没有提到它。

分步指南 (https://github.com/cuongvng/TF-Lite-Cpp-API-for-Android):

git clone https://github.com/tensorflow/tensorflow
cd ./tensorflow/
  • 步骤 3:配置 Android 构建 在 运行 执行 bazel build ... 命令之前,您需要配置构建过程。通过执行
./configure

configure 文件位于 tensorflow 目录的根目录下,您在第 2 步 cd 进入该目录。 现在你必须在命令行输入一些配置:

$ ./configure
You have bazel 3.7.2-homebrew installed.
Please specify the location of python. [Default is /Library/Developer/CommandLineTools/usr/bin/python3]: /Users/cuongvng/opt/miniconda3/envs/style-transfer-tf-lite/bin/python

首先是python的位置,因为./configure执行的是.configure.py文件。 选择安装了Numpy的位置,否则后面的构建会失败。 这里我指向conda环境的python可执行文件。

接下来,

Found possible Python library paths:
  /Users/cuongvng/opt/miniconda3/envs/style-transfer-tf-lite/lib/python3.7/site-packages
Please input the desired Python library path to use.  Default is [/Users/cuongvng/opt/miniconda3/envs/style-transfer-tf-lite/lib/python3.7/site-packages]

我按 Enter 使用默认站点包,其中包含构建 TF 所需的库。

接下来,

Do you wish to build TensorFlow with ROCm support? [y/N]: N
No ROCm support will be enabled for TensorFlow.

Do you wish to build TensorFlow with CUDA support? [y/N]: N
No CUDA support will be enabled for TensorFlow.

Do you wish to download a fresh release of clang? (Experimental) [y/N]: N
Clang will not be downloaded.

Please specify optimization flags to use during compilation when bazel option "--config=opt" is specified [Default is -Wno-sign-compare]: 

如上所示键入,在最后一行键入 Enter。 然后它会询问您是否为 Android 构建配置 ./WORKSPACE,键入 y 添加配置。

Would you like to interactively configure ./WORKSPACE for Android builds? [y/N]: y
Searching for NDK and SDK installations.

Please specify the home path of the Android NDK to use. [Default is /Users/cuongvng/library/Android/Sdk/ndk-bundle]: /Users/cuongvng/Library/Android/sdk/ndk/21.1.6352462

这是 Android NDK(版本 21.1.6352462)在我本地机器上的主路径。 注意,当你ls路径时,它必须包含platforms,例如:

$ ls /Users/cuongvng/Library/Android/sdk/ndk/21.1.6352462
CHANGELOG.md      build             ndk-stack         prebuilt          source.properties wrap.sh
NOTICE            meta              ndk-which         python-packages   sources
NOTICE.toolchain  ndk-build         package.xml       shader-tools      sysroot
README.md         ndk-gdb           platforms         simpleperf        toolchains

现在我忽略生成的警告,然后选择最低 NDK API 级别

WARNING: The NDK version in /Users/cuongvng/Library/Android/sdk/ndk/21.1.6352462 is 21, which is not supported by Bazel (officially supported versions: [10, 11, 12, 13, 14, 15, 16, 17, 18]). Please use another version. Compiling Android targets may result in confusing errors.

Please specify the (min) Android NDK API level to use. [Available levels: ['16', '17', '18', '19', '21', '22', '23', '24', '26', '27', '28', '29']] [Default is 21]: 29

下一个

Please specify the home path of the Android SDK to use. [Default is /Users/cuongvng/library/Android/Sdk]: /Users/cuongvng/Library/Android/sdk

Please specify the Android SDK API level to use. [Available levels: ['28', '29', '30']] [Default is 30]: 30

Please specify an Android build tools version to use. [Available versions: ['29.0.2', '29.0.3', '30.0.3', '31.0.0-rc1']] [Default is 31.0.0-rc1]: 30.0.3

这就是 Android 构建配置的全部内容。为以后出现的所有问题选择 N

  • 第 4 步:构建共享库 (.so) 现在您可以 运行 bazel build 命令为您的目标架构生成库:
bazel build -c opt --config=android_arm //tensorflow/lite:libtensorflowlite.so
# or
bazel build -c opt --config=android_arm64 //tensorflow/lite:libtensorflowlite.so

它应该没有错误。 生成的库将保存在 ./bazel-bin/tensorflow/lite/libtensorflowlite.so.