Google Coral Edge TPU in C++

# 注意
本記事の内容は、現時点(2019/10/27)では、既に古いです。役に立ちません。
Edge TPUのコードは、https://github.com/google-coral/edgetpu に移行されました。
2019 Septemberアップデートで公式でもC++サポートをちゃんとしてくれるようになりました。
~~ただ、まだゴチャゴチャしている状況のようなので、落ち着いたらまとめてみようかと思います。~~

## 追記 2019/11/27
基本的には、https://github.com/google-coral/edgetpu/blob/master/src/cpp/examples/Makefile のコメント通りに進めれば大丈夫です。記事にするまでもなかったです。

MobileNetで識別を行う、簡単なプロジェクトのひな形です。ご参考までにどうぞ。
https://github.com/iwatake2222/EdgeTPU_CPP



**繰り返しになりますが、以下の内容は現時点(2019/10/27)では不要です**

# この記事について
Jetson Nano上で、Google Coral Edge TPUをC++から使用します。
MobileNet v2のclassificationを動かして、オウムが識別出来ることを確認します。

サンプルコードはbazelを用いてビルドするようになっていました。
本記事ではCMakeからビルドできるようにします。これによって、自分のプロジェクト内で使いやすくします。


## 情報源
- https://coral.withgoogle.com/docs/edgetpu/api-cpp/
- https://coral.googlesource.com/edgetpu-native/+/refs/heads/release-chef


## 環境
- ホストPC
	- Jetson Nanoへのターミナル操作を行う
	- Windows 10 64-bit
	- MSYS (ssh用)
- ライブラリビルド用PC 
	- `libtensorflow-lite.a` ビルド用
	- Ubuntu 16.04 on VirtualBox on Windows 10
- ターゲットボード
	- Jetson Nano
	- jetson-nano-sd-r32.1-2019-03-18.img
	- Google Coral Edge TPU接続
		- ドライバのインストールのようなことは不要

## 使用するモデルと入力画像
- https://coral.withgoogle.com/models/
	- https://dl.google.com/coral/canned_models/mobilenet_v2_1.0_224_quant_edgetpu.tflite
	- http://download.tensorflow.org/models/tflite_11_05_08/mobilenet_v2_1.0_224_quant.tgz
	- https://dl.google.com/coral/canned_models/imagenet_labels.txt
- https://coral.withgoogle.com/static/docs/images/parrot.jpg

入力画像(オウム)に対して、オウム=89 と識別できればOK。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214268/b55420f4-8fbe-33ce-497e-25db52fe38de.png)


# 事前準備
## TensorFlow Liteライブラリのビルド
https://qiita.com/iwatake2222/items/4d198f6203348ef7fd31#tensorflow-lite-for-cのライブラリraspberrypi用を作る
の記事を参考に、Jetson Nano (aarch64) 用のTensorFlow Liteライブラリを作ります。

上記記事はRaspberry Pi (armv7)用でしたが、今回はJetson (aarch64)用に作ります。
生成物(`libtensorflow-lite.a` )は後で使うので、適当にコピーしておいてください。

```sh:tfliteライブラリのクロスビルドforAARRCH64(onUbuntu)
sudo apt-get update
sudo apt-get install crossbuild-essential-armhf
sudo apt install -y crossbuild-essential-arm64
cd ~/
git clone https://github.com/tensorflow/tensorflow.git
cd tensorflow
git checkout 5d43d943153836f6c3bafd97d6bedbc124b99e37
./tensorflow/lite/tools/make/download_dependencies.sh
nano tensorflow/lite/tools/make/Makefile
./tensorflow/lite/tools/make/build_aarch64_lib.sh 
ls tensorflow/lite/tools/make/gen/aarch64_armv8-a/lib/libtensorflow-lite.a 
```

### 作るのが面倒な場合
ここに一式取り揃えておきます。
https://github.com/iwatake2222/CNN_NumberDetector/tree/master/08_TensorflowLite_TPU_CPP/external_libs/tensorflow_prebuilt_static


## Jetson Nano上で必要なライブラリのインストール
```sh:JetsonNanoターミナル
sudo apt install -y curl wget cmake
sudo apt install -y libc6-dev libc++-dev libc++abi-dev
sudo apt install -y libusb-1.0-0 
```

# プロジェクトの作成
説明のため分けて記載しますが、以下「コマンド1」～「コマン４」までをつなげて実行すれば動くはずです。

## edgetpuライブラリとサンプルコードの取得
```sh:コマンド1(onJetsonNano)
# Create a project directory
cd ~/
mkdir project_tpu_c

# Clone and prepare edgetpu library
cd ~/project_tpu_c
git clone https://coral.googlesource.com/edgetpu-native
cd edgetpu-native
git checkout release-chef
# git checkout ffc5c11cf7bb1f3f7cfaaf6cfa9882ec82c0bf7e
git submodule init && git submodule update
# note: tensorflow commit id is e91d746e0bf0c1c6faa0c7281466acad8b7290fd
cd tensorflow/tensorflow/lite/tools/make/
bash ./download_dependencies.sh
```

Jetson Nanoの端末上で、上記コマンド実行によって、
- プロジェクト用ディレクトリの作成 (project_tpu_c)
- edgetpuライブラリとサンプルコードの取得
- tensorflowライブラリと必要な外部ライブラリの取得
を行います。

edgetpu-nativeリポジトリではmasterブランチではなく、release-chefブランチを使っているようです。
念のため動作確認したcommit idもコメントアウトして書いておくので、何か変だと思ったら試してみてください。

edgetpu-nativeからtensorflow liteを使用しています。これは、git submoduleで管理されているので、更新取得します。
また、外部依存ライブラリのヘッダファイルが必要になるため、ダウンロードを行います。

実際に自分のプロジェクトをgitで管理する場合には、edgetpu-nativeもgit cloneではなく、git submoduleで管理した方が良いと思います。ここでは簡単のためgit cloneしています。

## 自分のプロジェクトを作る
```sh:コマンド2(onJetsonNano)
# Prepare my project
cd ~/project_tpu_c

## Download tensorflow lite library (or copy library you generated)
wget https://github.com/iwatake2222/CNN_NumberDetector/raw/master/08_TensorflowLite_TPU_CPP/external_libs/tensorflow_prebuilt_static/aarch64/libtensorflow-lite.a

## Copy edge tpu utility code
cp edgetpu-native/edgetpu/cpp/examples/utils.* .
nano utils.cc
# fix this:
# #include "edgetpu/cpp/examples/utils.h"
# ->
# #include "utils.h"

## edit code
nano CMakeLists.txt
nano main.cpp

```

いよいよ自分のプロジェクトを作っていきます。
まず、最初に作成した`tensorflow-lite.a` をコピーします。上記コマンドではダウンロードしています。
次に、edgetpuライブラリを使いやすくするutilityコードをサンプルからコピーします。
`utils.cc` がヘッダファイルを読むときのパス指定がよろしくないので修正します(一番最初の行)。

その後、`CMakeLists.txt` と`main.cpp` を以下のように作成します。(実際にはホストPCでコーディングしてssh転送しました)
ソースコードに関しては、コード内のコメントをご参照ください。なお、簡単のためエラー処理は全て省略しています。

```txt:CMakeLists.txt
cmake_minimum_required(VERSION 2.8)
project(EdgeTPU_C)

# Compile options
set(CMAKE_C_FLAGS "-Wall -pthread ")
set(CMAKE_C_FLAGS_DEBUG "-g -O0")
set(CMAKE_C_FLAGS_RELEASE "-O3")
set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -std=c++11  -lstdc++")
set(CMAKE_CXX_FLAGS_DEBUG ${CMAKE_C_FLAGS_DEBUG})
set(CMAKE_CXX_FLAGS_RELEASE ${CMAKE_C_FLAGS_RELEASE})

set(CMAKE_BUILD_TYPE release)
# set(CMAKE_BUILD_TYPE debug)

# Create Main project
add_executable(EdgeTPU_C
	main.cpp
	utils.cc
)

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

# For Tensorflow Lite and Edge TPU
target_link_libraries(EdgeTPU_C ${CMAKE_SOURCE_DIR}/edgetpu-native/libedgetpu/libedgetpu_arm64.so)
target_link_libraries(EdgeTPU_C ${CMAKE_SOURCE_DIR}/libtensorflow-lite.a)

target_include_directories(EdgeTPU_C PUBLIC ${CMAKE_SOURCE_DIR}/edgetpu-native/libedgetpu/)
target_include_directories(EdgeTPU_C PUBLIC ${CMAKE_SOURCE_DIR}/edgetpu-native/tensorflow/)
target_include_directories(EdgeTPU_C PUBLIC ${CMAKE_SOURCE_DIR}/edgetpu-native/tensorflow/tensorflow/lite/tools/make/downloads/flatbuffers/include)
```

```c:main.cpp
#include <stdio.h>
#include <string.h>
#include <chrono>
#include <opencv2/opencv.hpp>

#include "edgetpu.h"
#include "utils.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/model.h"

/*** Model parameters ***/
#define MODEL_FILENAME "mobilenet_v2_1.0_224_quant_edgetpu.tflite"
// #define MODEL_FILENAME "mobilenet_v2_1.0_224_quant.tflite"
#define MODEL_WIDTH 224
#define MODEL_HEIGHT 224
#define MODEL_CHANNEL 3


int main()
{
	/*** Create interpreter ***/
	/* read model */
	std::unique_ptr<tflite::FlatBufferModel> model = tflite::FlatBufferModel::BuildFromFile(MODEL_FILENAME);
	/* initialize edgetpu_context */
	edgetpu::EdgeTpuContext* edgetpu_context = edgetpu::EdgeTpuManager::GetSingleton()->NewEdgeTpuContext().release();
	/* create interpreter */
	std::unique_ptr<tflite::Interpreter> interpreter = coral::BuildEdgeTpuInterpreter(*model, edgetpu_context);

	/*** Read input image data ***/
	cv::Mat inputImage = cv::imread("parrot.jpg");
	cv::cvtColor(inputImage, inputImage, CV_BGR2RGB);
	cv::resize(inputImage, inputImage, cv::Size(MODEL_WIDTH, MODEL_HEIGHT));
	std::vector<uint8_t> inputData(inputImage.data, inputImage.data + (inputImage.cols * inputImage.rows * inputImage.elemSize()));

	/*** Run inference ***/
	const auto& result = coral::RunInference(inputData, interpreter.get());

	/*** Retrieve result ***/
	// for (int i = 0; i < result.size(); i++) printf("%5d: %f\n", i, result[i]);
	auto it_a = std::max_element(result.begin(), result.end());
	printf("Max index: %ld (%.3f)\n", std::distance(result.begin(), it_a), *it_a);

	/*** Measure calculation time ***/
	const auto& t0 = std::chrono::steady_clock::now();
	for (int i = 0; i < 100; i++) {
		coral::RunInference(inputData, interpreter.get());
	}
	const auto& t1 = std::chrono::steady_clock::now();
	std::chrono::duration<double> timeSpan = t1 - t0;
	printf("Calculation time = %f [sec]\n", timeSpan.count() / 100);
	
	return 0;
}
```


## ビルド・実行する
```sh:コマンド3(onJetsonNano)
mkdir build && cd build
cmake ..
make -j2
```

```sh:コマンド4(onJetsonNano)
wget https://dl.google.com/coral/canned_models/mobilenet_v2_1.0_224_quant_edgetpu.tflite
wget http://download.tensorflow.org/models/tflite_11_05_08/mobilenet_v2_1.0_224_quant.tgz
tar xfz mobilenet_v2_1.0_224_quant.tgz
wget https://coral.withgoogle.com/static/docs/images/parrot.jpg

cp ../edgetpu-native/libedgetpu/libedgetpu_arm64.so libedgetpu.so.1

sudo LD_LIBRARY_PATH=./ ./EdgeTPU_C
```

最初のコマンドでビルドします。
次のコマンドでは、まず必要なモデルや画像をダウンロードします。
また、edgetpuのライブラリは共有ライブラリ(so形式)なので、コピーしておきます。
なぜか、`libedgetpu.so.1` という名前で読もうとしていたので名前を揃えました。これは、`/usr/local/lib` などにsoファイルをコピーするなら不要です。

最後に実行します。この時、カレントディレクトリからもライブラリを読むように、`LD_LIBRARY_PATH` を指定しています。

もう一つ重要な点として、sudoで実行しています。
これがないと、以下のようなエラーが出ます。
(横着してsudo実行していますが、`plugdev` グループにユーザ登録するのが正しい解決方法です。)
(TPUを使わない場合(TPU用にコンパイルしていないモデル使用時)は、sudoが無くても実行できますが、なぜかめちゃくちゃ遅くなります(`edgetpu::EdgeTpuManager::GetSingleton()->NewEdgeTpuContext().release()` で数十秒かかった))

```txt:実行時エラー
ERROR: Failed to retrieve TPU context.
ERROR: Node number 0 (edgetpu-custom-op) failed to prepare.

Failed to allocate tensors.
Segmentation fault (core dumped)

```

## 実行結果
```txt:実行結果
# w/ TPU: mobilenet_v2_1.0_224_quant_edgetpu.tflite 使用
jetson@jetson:~/project_tpu_c/build$ sudo LD_LIBRARY_PATH=./ ./EdgeTPU_C
Max index: 89 (0.996)
Calculation time = 0.0026456 [sec]

# w/o TPU: mobilenet_v2_1.0_224_quant.tflite 使用
jetson@jetson:~/project_tpu_c/build$ sudo LD_LIBRARY_PATH=./ ./EdgeTPU_C
Max index: 89 (17.207)
Calculation time = 0.088618 [sec]
```

実行結果は上記の通りです。
入力画像はオウムの画像だったのですが、どちらも正しい識別番号(89=macaw) になっています。(https://dl.google.com/coral/canned_models/imagenet_labels.txt )


処理時間に関しては、TPUを使うことで約33倍に高速化されています。

以前Pythonで試したとき (https://qiita.com/iwatake2222/items/e8e29babc7e60d983093#速度比較 )
は、2.5msecだったので、今回のC++バージョンでも、速度はほぼ同じでした。

# Raspberry Pi ではどうなの
試したけど無理だった。。。

ビルドまでは成功したのですが、実行時にsegmentation faultで死にます。

https://coral.googlesource.com/edgetpu-native/+/refs/heads/release-chef/tools/Dockerfile.16.04
にもRaspberry Pi上だとlibc++/libc++abiのバグでexceptionが発生すると書いてありました。
ただ、ここに書いてある回避策を試したのですが、ダメでした。念のため、Dockerファイルに記載されている設定を参考に、clangでもビルドしてみたのですがダメでした。
オフィシャル(?)の手順通り、Docker上でbazelを使ってクロスビルドしたら大丈夫なのかもしれません。

```sh:ラズパイで試したコマンド残骸
sudo apt-get install libunwind8 
sudo apt-get install libunwind-dev
wget http://ftp.us.debian.org/debian/pool/main/l/llvm-toolchain-7/libc++-7-dev_7.0.1-8_armhf.deb
dpkg-deb -R libc++-7-dev_7.0.1-8_armhf.deb ~/tmp
wget http://ftp.us.debian.org/debian/pool/main/l/llvm-toolchain-7/libc++abi-7-dev_7.0.1-8_armhf.deb
dpkg-deb -R libc++abi-7-dev_7.0.1-8_armhf.deb ~/tmp2
sudo cp -v ~/tmp/usr/lib/llvm-7/lib/libc++.a /usr/lib/arm-linux-gnueabihf
sudo cp -v ~/tmp2/usr/lib/llvm-7/lib/libc++abi.a /usr/lib/arm-linux-gnueabihf
```


ラズパイでもビルドまでは可能にしたプロジェクトを以下に置いておきます。
どなたか解決できたら教えてください。

https://github.com/iwatake2222/CNN_NumberDetector/tree/master/08_TensorflowLite_TPU_CPP