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

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

1. [Kerasで簡単にMNIST数字識別モデルを作り、Pythonで確認](https://qiita.com/take-iwiw/items/796ec8560563625ace34)
2. [TensorFlowモデルに変換してPythonで使用してみる (Windows, Linux)](https://qiita.com/take-iwiw/items/80fc73ff23d8f51650f5)
3. [TensorFlowモデルに変換してCで使用してみる (Windows, Linux)](https://qiita.com/take-iwiw/items/d7cb2bd10a8bbb81cd8b)
4. [TensorFlow Liteモデルに変換してPythonで使用してみる (Windows, Linux)](https://qiita.com/take-iwiw/items/d63aa67e5c700fcea70a)
5. [TensorFlow Liteモデルに変換してCで使用してみる (Linux)](https://qiita.com/take-iwiw/items/d998df1981d46285df62)  <--- 今回の内容
6. [TensorFlow Liteモデルに変換してC++で使用してみる (Raspberry Pi)](https://qiita.com/take-iwiw/items/4d198f6203348ef7fd31)
7. [TensorFlow LiteモデルをEdge TPU上で動かしてみる (Raspberry Pi)](https://qiita.com/take-iwiw/items/6aeab468c326ecc21563) 
 

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

TensorFLow Lite用モデルは[前回](https://qiita.com/take-iwiw/items/d63aa67e5c700fcea70a)作成した`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`でダウンロードできました。

```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
```

```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/` に保存されます。

```sh:依存ライブラリのダウンロード
./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との共有フォルダで作業)だけかもしれませんが、以下のようなエラーが出ました。特に問題はありませんでした。

```txt:エラー
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のリンク設定をします。
実行時には、ライブラリパスとしてカレントディレクトリを指定しておきます。

```sh:ビルド
# 必要なファイルを確認して、
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 も参考にしてください。

```txt: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してみてください。

```sh:プロジェクト取得
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)に出来てしまいました。シンボリックリンクを作成できないことが原因のようです。どこに生成されたかは、ビルド終了時のメッセージを確認してください。

```sh:必要なファイルを用意する
# ライブラリビルドする必要があるときだけ必要
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用のインクルードパス設定をしています。
また、以前と同様に必要なリソースファイル(入力画像とモデル)のコピーもしています。

```txt: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/tensorflow/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_model や https://www.tensorflow.org/api_docs/python/tf/lite/Interpreter や https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/examples/minimal/minimal.cc を参考に、実装しました。

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

```c: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;
}
```

```sh:ビルド方法
mkdir build && cd build
cmake ..
make
```

```txt:実行結果
=== 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)スタティックライブラリの作成
[上述](https://qiita.com/take-iwiw/items/d998df1981d46285df62#%E3%83%93%E3%83%AB%E3%83%89%E3%82%BF%E3%83%BC%E3%82%B2%E3%83%83%E3%83%88) した通り、最初はスタティックライブラリをうまく作れませんでした。が、ラズパイ用のライブラリを作るために色々と調べていたら解決策が見つかりました。

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

```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を以下のように修正します

``` tensorflow/lite/tools/make/Makefile
original)
BUILD_WITH_NNAPI=true
change)
BUILD_WITH_NNAPI=false
```

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

## ビルドする
```sh
./tensorflow/lite/tools/make/build_linux.sh
```

## アプリケーションビルド例
```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.


```