张新栋 · 2020年04月07日

在Android中使用TFLite c++部署

之前的文章中,我们跟大家介绍过如何使用NNAPI来加速TFLite-Android的inference(可参考使用NNAPI加速android-tflite的Mobilenet分类器)。
首发:https://zhuanlan.zhihu.com/p/71648953
作者:张新栋

不过之前介绍的文章,在进行模型加载和推断使用的是官方提供的预编译好的Jar文件,业务代码都通过Java进行编写。如果你想使用C++在native层进行业务代码的编写,你也可以参考本文的实现思路。跟之前专栏中介绍的框架一样,我们首先需要进行框架的编译 — TFLite的库函数编译。我们先下载最新版本master分支的tensorflow源码。

git clone https://github.com/tensorflow/tensorflow

在下载好源码以后,我需要进入tensorflow文件夹目录下,然后进行配置。在此之前,你还需要确保NDK和Bazel安装的正确性,本文采用的NDK为ndk-r17c,bazel采用的版本为 0.24.1。

cd tensorflow
./configure

配置完毕后,你还需要在该目录下的WORKSPACE文件中添加ndk的环境,直接在文件的末尾加入如下参数即可,

android_ndk_repository(
    name = "androidndk", # Required. Name *must* be "androidndk".
    api_level = 21,
)

本文在RK3288开发板进行验证,Android系统为5.1版本。所以为了兼容,这里采用的APP\_PLATFORM为21,对应于api\_level为21。下面,我们开始进行TFLite的模型编译。

bazel build -c opt --crosstool_top=//external:android/crosstool              \
--host_crosstool_top=@bazel_tools//tools/cpp:toolchain --cxxopt="-std=c++11" \
--fat_apk_cpu=armeabi-v7a --config=android_arm                               \
//tensorflow/lite:libtensorflowlite.so

这里面有几个参数需要注意,一个是--fat\_apk\_cpu和--config。在Android平台中一般fat\_apk\_cpu可取arm64-v8a或armeabi-v7a,config可选为android\_arm64或android\_arm。更详细的配置大家可参考/tensorflow/lite/build\_def.bzl该文件。

编译好的模型libtensorflowlite.so会保存在/tensorflow/bazel-bin/tensorflow/lite文件夹中,下面我们开始配置依赖环境,进行C++业务代码的编写。配置环境大家可参考如下mk脚本,注意将环境变量替换成你本机的环境变量。需要注意的地方,引入tensorflow的头文件路径的同时,也需要引入flatbuffer的头文件路径

LOCAL_PATH := $(call my-dir)

OpenCV_BASE = /Users/xindongzhang/armnn-tflite/OpenCV-android-sdk/
TFLITE_BASE = /Users/xindongzhang/Desktop/tflite-cpp/jni/tensorflow
FLATBUFFER_BASE = /Users/xindongzhang/armnn-tflite/flatbuffers/

include $(CLEAR_VARS)
LOCAL_MODULE := TFLITE
LOCAL_SRC_FILES := $(TFLITE_BASE)/bazel-bin/tensorflow/lite/libtensorflowlite.so
include $(PREBUILT_SHARED_LIBRARY)


include $(CLEAR_VARS)
OpenCV_INSTALL_MODULES := on
OPENCV_LIB_TYPE := STATIC
include $(OpenCV_BASE)/sdk/native/jni/OpenCV.mk
LOCAL_MODULE := tflite_ssd

LOCAL_C_INCLUDES += $(OPENCV_INCLUDE_DIR)
LOCAL_C_INCLUDES += $(TFLITE_BASE)/tensorflow
LOCAL_C_INCLUDES += $(TFLITE_BASE)
LOCAL_C_INCLUDES += $(FLATBUFFER_BASE)/include

LOCAL_SRC_FILES :=                        \
                main.cpp                  

LOCAL_LDLIBS := -landroid -llog -ldl -lz -fuse-ld=gold
LOCAL_CFLAGS   := -O2 -fvisibility=hidden -fomit-frame-pointer -fstrict-aliasing \
-ffunction-sections -fdata-sections -ffast-math -ftree-vectorize -fPIC -Ofast    \
 -ffast-math -w -std=c++14
LOCAL_CPPFLAGS := -O2 -fvisibility=hidden -fvisibility-inlines-hidden            \
-fomit-frame-pointer -fstrict-aliasing -ffunction-sections -fdata-sections       \
-ffast-math -fPIC -Ofast -ffast-math -std=c++14
LOCAL_LDFLAGS  += -Wl,--gc-sections
LOCAL_CFLAGS   += -fopenmp
LOCAL_CPPFLAGS += -fopenmp
LOCAL_LDFLAGS  += -fopenmp
LOCAL_ARM_NEON := true

APP_ALLOW_MISSING_DEPS = true

LOCAL_SHARED_LIBRARIES :=                             \
                        TFLITE             

include $(BUILD_EXECUTABLE)

接下来是C++业务代码,跟在java中的调用逻辑是一样的,先加载模型,然后进行解析;输入数据时候要根据训练时的预处理方式,进行对等的预处理操作;最后执行inference,取出结果,如有后处理,再进行相应的后处理。

    std::string model_file = "./detect.tflite";
    std::string image_file = "./322353.jpg";

    int INPUT_SIZE = 300;
    cv::Mat raw_image = cv::imread(image_file, 1);
    cv::Mat image;  
    cv::resize(raw_image, image, cv::Size(INPUT_SIZE, INPUT_SIZE));
    image.convertTo(image, CV_32FC3);
    image = (image * 2.0f / 255.0f) - 1.0f;

    std::unique_ptr<tflite::FlatBufferModel> model;
    tflite::ops::builtin::BuiltinOpResolver resolver;
    std::unique_ptr<tflite::Interpreter> interpreter;

    TfLiteTensor* input_tensor     = nullptr;
    TfLiteTensor* output_locations = nullptr;
    TfLiteTensor* output_classes   = nullptr;
    TfLiteTensor* output_scores    = nullptr;
    TfLiteTensor* num_detections   = nullptr;

    model = tflite::FlatBufferModel::BuildFromFile(model_file.c_str());
    tflite::InterpreterBuilder(*model, resolver)(&interpreter);
    interpreter->AllocateTensors();

    input_tensor = interpreter->tensor(interpreter->inputs()[0]);
    interpreter->SetNumThreads(1);

    // preprocessing
    float* dst = input_tensor->data.f;
    const int row_elems = image.cols * image.channels();
    for (int row = 0; row < image.rows; row++) {
        const uchar* row_ptr = image.ptr(row);
        for (int i = 0; i < row_elems; i++) {
            dst[i] = row_ptr[i];
        }
        dst += row_elems;
    }
    // run inference
    interpreter->Invoke();

    // get output 
    output_locations = interpreter->tensor(interpreter->outputs()[0]);
    output_classes   = interpreter->tensor(interpreter->outputs()[1]);

最后

在完成上述过程后,既可以在Android中使用C++进行TFlite模型的部署。目前TFLIte在嵌入式端的竞争力虽然不是很强,但是开发的活跃度依旧很高,推出了许多新的特性,如xla、int8、量化、fp16,以及配套的离线量化、压缩脚本,后续本专栏会持续关注TFlite在嵌入式端的进展。欢迎大家留言讨论、关注本专栏,谢谢大家。


推荐阅读

专注嵌入式端的AI算法实现,欢迎关注作者微信公众号和知乎嵌入式AI算法实现专栏

WX20200305-192544.png
更多嵌入式AI相关的技术文章请关注极术嵌入式AI专栏

推荐阅读
关注数
18789
内容数
1342
嵌入式端AI,包括AI算法在推理框架Tengine,MNN,NCNN,PaddlePaddle及相关芯片上的实现。欢迎加入微信交流群,微信号:aijishu20(备注:嵌入式)
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息