Help us understand the problem. What is going on with this article?

Deep Learningアプリケーション開発 (5) TensorFlow Lite with C++

この記事について

機械学習、Deep Learningの専門家ではない人が、Deep Learningを応用したアプリケーションを作れるようになるのが目的です。MNIST数字識別する簡単なアプリケーションを、色々な方法で作ってみます。特に、組み込み向けアプリケーション(Edge AI)を意識しています。
モデルそのものには言及しません。数学的な話も出てきません。Deep Learningモデルをどうやって使うか(エッジ推論)、ということに重点を置いています。

  1. Kerasで簡単にMNIST数字識別モデルを作り、Pythonで確認
  2. TensorFlowモデルに変換してPythonで使用してみる (Windows, Linux)
  3. TensorFlowモデルに変換してCで使用してみる (Windows, Linux)
  4. TensorFlow Liteモデルに変換してPythonで使用してみる (Windows, Linux)
  5. TensorFlow Liteモデルに変換してCで使用してみる (Linux) <--- 今回の内容
  6. TensorFlow Liteモデルに変換してC++で使用してみる (Raspberry Pi)
  7. TensorFlow LiteモデルをEdge TPU上で動かしてみる (Raspberry Pi)

今回の内容

  • TensorFlow Lite for C++のライブラリを作る
  • TensorFlow Lite用モデルを使って、入力画像から数字識別するC++アプリケーションを作る

TensorFLow Lite用モデルは前回作成したconv_mnist.tfliteを使います
ソースコードとサンプル入力画像: https://github.com/take-iwiw/CNN_NumberDetector/tree/master/05_TensorflowLite_CPP

環境

  • OS: Windows 10 (64-bt)
  • OS(on VirtualBox): Ubuntu 16.04
  • CPU = Intel Core i7-6700@3.4GHz (物理コア=4、論理プロセッサ数=8)
  • GPU = NVIDIA GeForce GTX 1070 (← GPUは無くても大丈夫です)
  • 開発環境: bazel 0.23.1, cmake, gcc

今回は、Linux(Ubuntu)のみで動作確認しました(理由は後述)。僕の環境では、Windows10上のVirtual Boxで動かしています。

TensorFlow Lite for C++のライブラリを作る

ビルドターゲット

TensorFlow Liteに関する情報は https://www.tensorflow.org/lite/guide/get_started にあります。
基本的には、AndroidやiOSといったモバイル向けのライブラリです。また、ラズパイや組み込みLinuxボードでも使えるようです。
これら向けのビルドスクリプトが、https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/tools/make に用意されています。
Windows, Linux向けのビルドも、ここにあるスクリプトをいじれば行けるかなと思ったのですが、うまくできませんでした。 (2019/3/11追記: できるようになりました。この記事の一番最後に追記します)

代わりに、 https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/BUILD 内のターゲットに、libtensorflowlite.so というものを見つけました。今回は共有ライブラリをビルドしてみようと思います。(正しい方法かは不明。。)

ビルドツールの用意

TensorFlowのビルドには、Bazelというビルドツールが使われています。makeみたいなもののようですが、色々な言語に対応しているらしいです。

Windows

Windowsでは、https://docs.bazel.build/versions/master/install-windows.html に記載されているようにバイナリ配布されています。
Bazel自体はいいのですが、TensorFlowをビルドするにはVisual Studio 2015が必要みたいです。(2019年3月9日現在)。
僕のPCにはVS2017しか入っておらず、わざわざVS2015もインストールするのが面倒なので、今回はWindows上での検証は行わないことにします。

Linux

Linuxでは、https://docs.bazel.build/versions/master/install-ubuntu.html の手順通りにインストールするだけです。 インストーラには https://github.com/bazelbuild/bazel/releases/download/0.23.1/bazel-0.23.1-installer-linux-x86_64.sh を使用しました。

ライブラリをビルドする

以下コマンドで、TensorFlowのレポジトリをcloneします。念のため、現時点(2019年3月9日)で動作確認したバージョンにcheckoutしておきます。必要に応じてmasterのlatestにしたり、所定のバージョンにcheckoutしてください。
その後、ターゲットを指定してbazelでビルドします。
コメントアウトしていますが、label_imageというサンプルプロジェクトもありました。モデルなど、必要なファイルはdownload_models.shでダウンロードできました。

ビルドコマンド
git clone https://github.com/tensorflow/tensorflow.git
cd tensorflow             # 以後、cloneしたtensorflowディレクトリ内で全ての作業を行います。この下にさらにtensorflowというディレクトリがあるので注意(そこではないです)
git checkout 5d43d943153836f6c3bafd97d6bedbc124b99e37

bazel build '//tensorflow/lite:libtensorflowlite.so'
# bazel build '//tensorflow/lite/examples/label_image'
# tensorflow/lite/examples/ios/download_models.sh
ビルド完了
Target //tensorflow/lite:libtensorflowlite.so up-to-date:
  bazel-bin/tensorflow/lite/libtensorflowlite.so
INFO: Elapsed time: 125.840s, Critical Path: 24.11s
INFO: 151 processes: 151 local.
INFO: Build completed successfully, 158 total actions
~/tensorflow$ ls bazel-bin/tensorflow/lite/libtensorflowlite.so
bazel-bin/tensorflow/lite/libtensorflowlite.so

TensorFlow Lite用アプリケーションプログラムを手動でビルドする

方針

本当は、アプリケーションプログラムもライブラリと同様にbazelを使用してビルドすべきなのだと思います。あるいは、TensorFlow Liteと一緒にビルドしてしまうのが楽なのかもしれません。
しかし、1時間くらいBazelをいじったのですが、あまり友達になれそうにありませんでした。

ということで、先ほど作成したlibtensorflowlite.so をリンクする形で、いつも通りの方法でアプリケーションプログラムをビルドしてみようと思います。まずは普通にコマンドラインから手動でビルドしてみます。
オプション設定などは、 https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/tools/make/Makefile が参考になりました。

ヘッダファイルを集める

https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/tools/make/Makefile を見ると分かるのですが、必要なヘッダファイルが点在しています。さらに、外部のライブラリのヘッダファイルも必要になります。(ライブラリ自体はlibtensorflowlite.soビルド時に取り込まれているみたいなのですが、アプリケーションプログラムビルド時に、ヘッダファイル間の依存関係があるため、必要になります)

以下コマンドで、外部ライブラリヘッダファイルを取得します。./tensorflow/lite/tools/make/downloads/ に保存されます。

依存ライブラリのダウンロード
./tensorflow/lite/tools/make/download_dependencies.sh
ls ./tensorflow/lite/tools/make/downloads/
absl  eigen  farmhash  fft2d  flatbuffers  gemmlowp  googletest  neon_2_sse

僕の環境(VirtualBox内のWindowsとの共有フォルダで作業)だけかもしれませんが、以下のようなエラーが出ました。特に問題はありませんでした。

エラー
tar: docs/source/CONTRIBUTING.md: Cannot create symlink to ‘../../CONTRIBUTING.md’: Protocol error
tar: Exiting with failure status due to previous errors

ビルドする

先ほど作成したbazel-bin/tensorflow/lite/libtensorflowlite.soをカレントディレクトリにコピーしておきます。また、中身は後で見ますが、main.cppがアプリケーションプログラムのソースコードとして、配置しておきます。
その後、ビルドします。gccには必要なincludeパス設定(多い。。。)と、libtensorflowlite.soのリンク設定をします。
実行時には、ライブラリパスとしてカレントディレクトリを指定しておきます。

ビルド
# 必要なファイルを確認して、
cp bazel-bin/tensorflow/lite/libtensorflowlite.so .
ls libtensorflowlite.so main.cpp
libtensorflowlite.so  main.cpp

# ビルド
gcc main.cpp -I.  -I./tensorflow -I./tensorflow/lite/tools/make/downloads -I./tensorflow/lite/tools/make/downloads/eigen -I./tensorflow/lite/tools/make/downloads/absl -I./tensorflow/lite/tools/make/downloads/gemmlowp -I./tensorlow/lite/tools/make/downloads/neon_2_sse -I./tensorflow/lite/tools/make/downloads/farmhash/src -I./tensorflow/lite/tools/make/downloads/flatbuffers/include  -std=c++11 -lstdc++ -ltensorflowlite -L./

# 実行
LD_LIBRARY_PATH=. ./a.out

TensorFlow Lite用アプリケーションプログラムのcmakeプロジェクトを作る

手動でビルドすることで原理は分かったのですが、毎回面倒なのでcmakeプロジェクトで作ります。以下のようなディレクトリ構造を用意します。ソースコードなどは後から作ります。
https://github.com/take-iwiw/CNN_NumberDetector/tree/master/05_TensorflowLite_CPP も参考にしてください。

cmakeプロジェクト構成
05_TensorflowLite_CPP
├─external_libs/
│  ├─tensorflow/                <- https://github.com/tensorflow/tensorflow
│  └─tensorflow_prebuilt/
│      └─linux-cpu-x86_64/libtensorflowlite.so
├─resource/
│  └─0~9.jpg(サンプル画像)、conv_mnist.tflite
├─CMakeLists.txt
└─main.cpp

external_libs/tensorflow にhttps://github.com/tensorflow/tensorflow をcloneします。gitで管理する場合は、submoduleを使うと楽だと思います(git submodule add https://github.com/tensorflow/tensorflow external_libs/tensorflow )。

一度ビルドしたら、ライブラリをexternal_libs/tensorflow_prebuilt/linux-cpu-x86_64/libtensorflowlite.so に保存しておくと便利だと思います。自分のアプリケーションプログラムビルド時にはここに配置したライブラリをリンクします。

作成済みプロジェクトをcloneする場合

https://github.com/take-iwiw/CNN_NumberDetector/ からcloneした場合は、clone後に以下のコマンドが必要になります。また、ここではとりあえずビルドが通って使えたバージョン5d43d943153836f6c3bafd97d6bedbc124b99e37 を指定しています。もしも動かなかったらバージョンを変えたり、latestにcheckoutしてみてください。

プロジェクト取得
git clone git@github.com:take-iwiw/CNN_NumberDetector.git
cd CNN_NumberDetector
git submodule init
git submodule update

# cd 05_TensorflowLite_C/external_libs/tensorflow 
# git checkout xxx

プロジェクト内で必要なファイルを用意する

新しい環境でビルド、実行する場合には、libtensorflowlite.soのビルドが必要になります。ビルドしたらprebuiltディレクトリにコピーしておきます。
cloneして最初の状態では外部ライブラリヘッダのダウンロードが必要になります。
以下コマンドで必要なファイルを用意します。毎回実行する必要はありません。

ちなみに、ライブラリのビルドですが、こちらもおそらくVirtualBoxの共有フォルダ内で作業すると、変なところ(ホームディレクトリ下の.cache)に出来てしまいました。シンボリックリンクを作成できないことが原因のようです。どこに生成されたかは、ビルド終了時のメッセージを確認してください。

必要なファイルを用意する
# ライブラリビルドする必要があるときだけ必要
cd external_libs/tensorflow/
bazel build '//tensorflow/lite:libtensorflowlite.so'
cp /home/tak/.cache/bazel/_bazel_tak/bf687bbce172de5fd22329b67398b71f/execroot/org_tensorflow/bazel-out/k8-opt/bin/tensorflow/lite/libtensorflowlite.so ../tensorflow_prebuilt/linux-cpu-x86_64/.
cd ../../

# clone後1回だけ必要
cd external_libs/tensorflow/
./tensorflow/lite/tools/make/download_dependencies.sh
cd ../../

ソースコードなど

CMakeLists.txt

CMakeLists.txtは以下のようになります。
OpenCVの設定と、先ほど手動で指定したTensorFlow Lite用のインクルードパス設定をしています。
また、以前と同様に必要なリソースファイル(入力画像とモデル)のコピーもしています。

CMakeLists.txt
cmake_minimum_required(VERSION 2.8)
project(NumberDetector)

# Create Main project
add_executable(NumberDetector
    main.cpp
)

# For OpenCV
find_package(OpenCV REQUIRED)
if(OpenCV_FOUND)
    target_include_directories(NumberDetector PUBLIC ${OpenCV_INCLUDE_DIRS})
    target_link_libraries(NumberDetector ${OpenCV_LIBS})
endif()

# For Tensorflow Lite
target_link_libraries(NumberDetector ${PROJECT_SOURCE_DIR}/external_libs/tensorflow_prebuilt/linux-cpu-x86_64/libtensorflowlite.so)
target_include_directories(NumberDetector PUBLIC ${PROJECT_SOURCE_DIR}/external_libs/tensorflow/)
target_include_directories(NumberDetector PUBLIC ${PROJECT_SOURCE_DIR}/external_libs/tensorflow/tensorflow)
target_include_directories(NumberDetector PUBLIC ${PROJECT_SOURCE_DIR}/external_libs/tensorflow/tensorflow/lite/tools/make/downloads)
target_include_directories(NumberDetector PUBLIC ${PROJECT_SOURCE_DIR}/external_libs/tensorflow/tensorflow/lite/tools/make/downloads/eigen )
target_include_directories(NumberDetector PUBLIC ${PROJECT_SOURCE_DIR}/external_libs/tensorflow/tensorflow/lite/tools/make/downloads/absl)
target_include_directories(NumberDetector PUBLIC ${PROJECT_SOURCE_DIR}/external_libs/tensorflow/tensorflow/lite/tools/make/downloads/gemmlowp)
target_include_directories(NumberDetector PUBLIC ${PROJECT_SOURCE_DIR}/external_libs/tensorflow/tensorlow/lite/tools/make/downloads/neon_2_sse)
target_include_directories(NumberDetector PUBLIC ${PROJECT_SOURCE_DIR}/external_libs/tensorflow/tensorflow/lite/tools/make/downloads/farmhash/src)
target_include_directories(NumberDetector PUBLIC ${PROJECT_SOURCE_DIR}/external_libs/tensorflow/tensorflow/lite/tools/make/downloads/flatbuffers/include)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}  -std=c++11 -lstdc++")

# Copy resouce
file(COPY ${CMAKE_SOURCE_DIR}/resource/ DESTINATION ${PROJECT_BINARY_DIR}/resource/)
add_definitions(-DRESOURCE_DIR="${PROJECT_BINARY_DIR}/resource/")

main.cpp

main.cppは以下の通りです。
https://www.tensorflow.org/lite/guide/inference#loading_a_modelhttps://www.tensorflow.org/api_docs/python/tf/lite/Interpreterhttps://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/examples/minimal/minimal.cc を参考に、実装しました。

かなり簡単に実装できています。基本的には、TensorFlow for Cと同じ流れです(https://qiita.com/take-iwiw/items/d7cb2bd10a8bbb81cd8b )。
なお、サンプルコードでは他にも大量のincludeファイルがありました。僕の環境ではなくても問題がなかったのですが、エラーが出た場合には上記のサンプルコードを参考にincludeを追加してみてください。

main.cpp
#include <stdio.h>
#include <opencv2/opencv.hpp>
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/kernels/register.h"
#include "tensorflow/lite/model.h"
#include "tensorflow/lite/optional_debug_tools.h"

#define MODEL_FILENAME RESOURCE_DIR"conv_mnist.tflite"

#define TFLITE_MINIMAL_CHECK(x)                              \
  if (!(x)) {                                                \
    fprintf(stderr, "Error at %s:%d\n", __FILE__, __LINE__); \
    exit(1);                                                 \
  }


int main()
{
    /* read input image data */
    cv::Mat image = cv::imread(RESOURCE_DIR"4.jpg");
    cv::imshow("InputImage", image);

    /* convert to 28 x 28 grayscale image (normalized: 0 ~ 1.0) */
    cv::cvtColor(image, image, CV_BGR2GRAY);
    cv::resize(image, image, cv::Size(28, 28));
    image = ~image;
    cv::imshow("InputImage for CNN", image);
    image.convertTo(image, CV_32FC1, 1.0 / 255);

    /* tflite */
    std::unique_ptr<tflite::FlatBufferModel> model = tflite::FlatBufferModel::BuildFromFile(MODEL_FILENAME);
    TFLITE_MINIMAL_CHECK(model != nullptr);

    // Build the interpreter
    tflite::ops::builtin::BuiltinOpResolver resolver;
    tflite::InterpreterBuilder builder(*model, resolver);
    std::unique_ptr<tflite::Interpreter> interpreter;
    builder(&interpreter);
    TFLITE_MINIMAL_CHECK(interpreter != nullptr);

    // Allocate tensor buffers.
    TFLITE_MINIMAL_CHECK(interpreter->AllocateTensors() == kTfLiteOk);
    printf("=== Pre-invoke Interpreter State ===\n");
    tflite::PrintInterpreterState(interpreter.get());

    // Set data to input tensor
    float* input = interpreter->typed_input_tensor<float>(0);
    memcpy(input, image.reshape(0, 1).data, sizeof(float) * 1 * 28 * 28 * 1);

    // Run inference
    TFLITE_MINIMAL_CHECK(interpreter->Invoke() == kTfLiteOk);
    printf("\n\n=== Post-invoke Interpreter State ===\n");
    tflite::PrintInterpreterState(interpreter.get());

    // Get data from output tensor
    float* probs = interpreter->typed_output_tensor<float>(0);
    for (int i = 0; i < 10; i++) {
        printf("prob of %d: %.3f\n", i, probs[i]);
    }

    cv::waitKey(0);
    return 0;
}
ビルド方法
mkdir build && cd build
cmake ..
make
実行結果
=== Pre-invoke Interpreter State ===
Interpreter has 16 tensors and 5 nodes
~略~
Inputs: 10
Outputs: 8

Tensor   0 conv2d_1/Conv2D_bias kTfLiteFloat32   kTfLiteMmapRo         32 bytes ( 0.0 MB)  8
Tensor   1 conv2d_1/Relu        kTfLiteFloat32  kTfLiteArenaRw      25088 bytes ( 0.0 MB)  1 28 28 8
Tensor   2 conv2d_1/kernel      kTfLiteFloat32   kTfLiteMmapRo        288 bytes ( 0.0 MB)  1 3 3 8
Tensor   3 conv2d_2/Conv2D_bias kTfLiteFloat32   kTfLiteMmapRo         16 bytes ( 0.0 MB)  4
Tensor   4 conv2d_2/Relu        kTfLiteFloat32  kTfLiteArenaRw       3136 bytes ( 0.0 MB)  1 14 14 4
Tensor   5 conv2d_2/kernel      kTfLiteFloat32   kTfLiteMmapRo       1152 bytes ( 0.0 MB)  4 3 3 8
Tensor   6 dense_1/BiasAdd      kTfLiteFloat32  kTfLiteArenaRw         40 bytes ( 0.0 MB)  1 10
Tensor   7 dense_1/MatMul_bias  kTfLiteFloat32   kTfLiteMmapRo         40 bytes ( 0.0 MB)  10
Tensor   8 dense_1/Softmax      kTfLiteFloat32  kTfLiteArenaRw         40 bytes ( 0.0 MB)  1 10
Tensor   9 dense_1/kernel/transpose kTfLiteFloat32   kTfLiteMmapRo      31360 bytes ( 0.0 MB)  10 784
Tensor  10 input_1              kTfLiteFloat32  kTfLiteArenaRw       3136 bytes ( 0.0 MB)  1 28 28 1
Tensor  11 max_pooling2d_1/MaxPool kTfLiteFloat32  kTfLiteArenaRw       6272 bytes ( 0.0 MB)  1 14 14 8
Tensor  12 (null)               kTfLiteNoType  kTfLiteMemNone          0 bytes ( 0.0 MB)  (null)
Tensor  13 (null)               kTfLiteNoType  kTfLiteMemNone          0 bytes ( 0.0 MB)  (null)
Tensor  14 (null)               kTfLiteFloat32  kTfLiteArenaRw      56448 bytes ( 0.1 MB)  1 14 14 72
Tensor  15 (null)               kTfLiteFloat32 kTfLiteArenaRwPersistent       1152 bytes ( 0.0 MB)  72 4

Node   0 Operator Builtin Code   4
  Inputs: 10 2 0
  Outputs: 1
Node   1 Operator Builtin Code  17
  Inputs: 1
  Outputs: 11
Node   2 Operator Builtin Code   3
  Inputs: 11 5 3
  Outputs: 4
Node   3 Operator Builtin Code   9
  Inputs: 4 9 7
  Outputs: 6
Node   4 Operator Builtin Code  25
  Inputs: 6
  Outputs: 8
prob of 0: 0.000
prob of 1: 0.001
prob of 2: 0.013
prob of 3: 0.043
prob of 4: 0.929
prob of 5: 0.003
prob of 6: 0.001
prob of 7: 0.006
prob of 8: 0.001
prob of 9: 0.003

(追記:2019/03/11)スタティックライブラリの作成

上述 した通り、最初はスタティックライブラリをうまく作れませんでした。が、ラズパイ用のライブラリを作るために色々と調べていたら解決策が見つかりました。

ビルド用スクリプトを用意する

tensorflow/lite/tools/make/ に各「モバイル」プラットフォーム向けのビルドスクリプトが配置されています。適当な対象をベースに、PC native向けのシェルスクリプト(tensorflow/lite/tools/make/build_linux.sh) を作ります。

tensorflow/lite/tools/make/build_linux.sh
set -e

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR/../../../.."

make -j 3 -f tensorflow/lite/tools/make/Makefile TARGET=linux

Makefileを修正する

これは、現時点(2019/03/11)でのTensorFlowリポジトリ上でのミスだと思うのですが、tensorflow/lite/tools/make/Makefileを以下のように修正します

original)
BUILD_WITH_NNAPI=true
change)
BUILD_WITH_NNAPI=false

参考: https://github.com/tensorflow/tensorflow/issues/25120

ビルドする

./tensorflow/lite/tools/make/build_linux.sh

アプリケーションビルド例

g++ main_without_opencv.cpp -I.  -I./tensorflow -I./tensorflow/lite/tools/make/downloads -I./tensorflow/lite/tools/make/downloads/eigen -I./tensorflow/lite/tools/make/downloads/absl -I./tensorflow/lite/tools/make/downloads/gemmlowp -I./tensorlow/lite/tools/make/downloads/neon_2_sse -I./tensorflow/lite/tools/make/downloads/farmhash/src -I./tensorflow/lite/tools/make/downloads/flatbuffers/include -std=c++11 -lstdc++ -lm -lpthread -ltensorflow-lite -L.


Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away