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

Deep Learningアプリケーション開発 (6) TensorFlow Lite with C++ on Raspberry Pi

この記事について

機械学習、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) <--- 今回の内容

今回の内容

  • TensorFlow Lite for C++のライブラリ(RaspberryPi用)を作る
  • 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)
  • CPU = Intel Core i7-6700@3.4GHz (物理コア=4、論理プロセッサ数=8)
  • OS(on VirtualBox): Ubuntu 16.04
    • 開発環境: bazel 0.23.1, cmake, gcc
  • Raspberry Pi 2 Model B (2018-11-13-raspbian-stretch)

全体の方針

まず、PC Linux(Ubuntu)上で、ラズパイ用のTensorFlow Lite for C++ ライブラリをクロスビルドします。
そして、作成したTensorFlowLiteライブラリを使用するアプリケーションを作成します。ビルドは、クロスビルドとネイティブビルドの両方を試してみます。

TensorFlow Lite for C++のライブラリ(RaspberryPi用)を作る

ビルドする

基本的には、https://www.tensorflow.org/lite/guide/build_rpi に書かれている通りにやればOKです。Bazelのインストールをしておく必要があるかもしれません。
PC Linux(Ubuntu)上で、以下コマンドを実行します。
が、2点注意があるので先に↓の注意事項を確認してください。

tfliteライブラリのクロスビルド(onUbuntu)
sudo apt-get update
sudo apt-get install crossbuild-essential-armhf
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_rpi_lib.sh
ls tensorflow/lite/tools/make/gen/rpi_armv7l/lib/libtensorflow-lite.a

生成物は tensorflow/lite/tools/make/gen/rpi_armv7l/lib/libtensorflow-lite.a になります。
※上記コマンドでは念のため、僕の方で動作確認できたバージョンを指定しています。本当はタグが打たれているv1.13.1あたりを指定すべきなのだと思いますが、後述のMakefileの問題を直すのが少し面倒になるため、中途半端ですがこのバージョンを指定することにしました。

注意事項1

VirtualBox上の共有フォルダで作業は行わないようにしてください。
download_dependencies.sh実行中にシンボリックリンク作成エラーが出てしまいます。
困ったことに、ライブラリ自体は作成されてしまいます。が、これを使用したアプリケーションをビルドしようとすると、以下のようなエラーが出てリンクに失敗します。先のコマンドではホームディレクトリにcdしてから作業しています。

./libtensorflow-lite.a(spectrogram.o): In function `tflite::internal::Spectrogram::ProcessCoreFFT()':
spectrogram.cc:(.text+0x1d2): undefined reference to `rdft'

注意事項2

これは、TensorFlow側の問題だと思うのですが、現時点(2019年3月10日)では、Android用のNNAPIを使用する設定になってしまっています。そのため、これもライブラリ自体の作成はできるのですが、アプリケーションをビルドしようとすると、以下のようなエラーが出てリンクに失敗します。

undefined reference to `NnApiImplementation()'

tensorflow/lite/tools/make/Makefile を以下のように編集します。これは、単純にMakefileの条件設定のif文の問題だと思うので、そのうち修正されると思います。

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

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

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

PC上でクロスビルドしてみる (コマンドライン)

最初にgit cloneした、tensorflowディレクトリで作業します。
まず、先ほど作成したlibtensorflow-lite.aを作業ディレクトリにコピーしておきます。また、TensorFlowLiteだけを使用するサンプルコード(OpenCV未使用)を用意しておきます(https://github.com/take-iwiw/CNN_NumberDetector/blob/master/05_TensorflowLite_CPP/main_without_opencv.cpp )。ソースコードは、前回作成したPC Linux用のコードと全く同じものが使用できます。

以下のようなコマンドでコンパイルが出来ます。pthreadが必要みたいでした。

アプリケーションのクロスビルド(onUbuntu)
cd ~/tensorflow
cp tensorflow/lite/tools/make/gen/rpi_armv7l/lib/libtensorflow-lite.a .

# サンプルコードをダウンロード
wget https://raw.githubusercontent.com/take-iwiw/CNN_NumberDetector/master/05_TensorflowLite_CPP/main_without_opencv.cpp

# ビルド
arm-linux-gnueabihf-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. 

# ラズパイにアップロード (IPアドレスは適宜置き換えてください)
scp ./a.out pi@192.168.1.88:/home/pi/a_cross.out
wget https://github.com/take-iwiw/CNN_NumberDetector/raw/master/05_TensorflowLite_CPP/resource/conv_mnist.tflite
scp conv_mnist.tflite pi@192.168.1.88:/home/pi/.

ラズパイ上で、./a.out で実行できます。結果は次のネイティブビルド版と同様なので省略します。

ラズパイ上でネイティブビルドしてみる (コマンドライン)

OpenCV等を使用したプロジェクトの場合、クロスビルドだと色々と設定が面倒なので、ラズパイ上でネイティブビルドしてみます。とはいえ、使用するコンパイラが変わっただけです。
ラズパイ上でも、tensorflowリポジトリをcloneして、依存ファイルをダウンロードしておきます。これはビルド時にヘッダファイルが必要なためです。また、先ほど作成したlibtensorflow-lite.aをコピーしておきます。

PCからラズパイにライブラリをコピーしておく(onUbuntu)
scp tensorflow/lite/tools/make/gen/rpi_armv7l/lib/libtensorflow-lite.a pi@192.168.1.88:/home/pi/.
# ラズパイのIPアドレスは適宜置き換えてください
アプリケーションのネイティブビルド(onラズパイ)
# ビルドに必要なヘッダ取得
git clone https://github.com/tensorflow/tensorflow.git
cd tensorflow
git checkout 5d43d943153836f6c3bafd97d6bedbc124b99e37
./tensorflow/lite/tools/make/download_dependencies.sh

# PCからアップロードしたライブラリをコピーしておく
cp ~/libtensorflow-lite.a .

# サンプルコードとモデルをダウンロード
wget https://raw.githubusercontent.com/take-iwiw/CNN_NumberDetector/master/05_TensorflowLite_CPP/main_without_opencv.cpp
wget https://github.com/take-iwiw/CNN_NumberDetector/raw/master/05_TensorflowLite_CPP/resource/conv_mnist.tflite


# ビルド
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. 

# 実行
./a.out
実行結果
pi@raspberrypi:~/tensorflow $ ./a.out 
=== Pre-invoke Interpreter State ===
~略~
=== Post-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.020
prob of 1: 0.504
prob of 2: 0.140
prob of 3: 0.020
prob of 4: 0.006
prob of 5: 0.026
prob of 6: 0.021
prob of 7: 0.220
prob of 8: 0.012
prob of 9: 0.033

入力がオール0の値なので、結果に意味はありません。

クロスプラットフォーム対応なプロジェクトにする

今回作成したアプリケーション用プログラムでは、ソースコードは、PC用とラズパイ用で全く同じものが使えました。
また、ラズパイ用ビルドにおいて、クロスビルドとネイティブビルドの違いはtoolchainの違いだけです(arm-linux-gnueabihf-g++ or g++)。

これらを簡単に切り替えられるようなcmakeプロジェクトを作ってみました。PC Linux(Ubuntu)、ラズパイ用ネイティブビルド、ラズパイ用クロスビルドに対応しています。
https://github.com/take-iwiw/CNN_NumberDetector/tree/master/05_TensorflowLite_CPP
トップのCMakeLists.txtはこんな感じです。

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

# Switch build target
set(BUILD_TARGET PC CACHE STRING "Build target?")
set(LINK_ONLY OFF CACHE BOOL "Use pre-built library?")

# Common build settings
include(cmakes/common.cmake)

# Build settings for each target
if(${BUILD_TARGET} STREQUAL PC)
    if(WIN32)
        message(FATAL_ERROR "[BUILD] PC Windows is not supported")
    else()
        message("[BUILD] PC Linux")
        include(cmakes/PC_LINUX.cmake)
    endif()
elseif(${BUILD_TARGET} STREQUAL RASPI_NATIVE)
    message("[BUILD] Raspberry Pi Native")
    include(cmakes/RASPI_NATIVE.cmake)
elseif(${BUILD_TARGET} STREQUAL RASPI_CROSS)
    message("[BUILD] Raspberry Pi Cross")
    include(cmakes/RASPI_CROSS.cmake)
else()
    message(FATAL_ERROR "[BUILD] Invalid target")
endif()

# Create Main project
add_executable(NumberDetector
    main.cpp
    # main_without_opencv.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
if((${BUILD_TARGET} STREQUAL PC) AND UNIX)
    # use shared library
    # target_link_libraries(NumberDetector ${CMAKE_SOURCE_DIR}/external_libs/tensorflow_prebuilt/linux-cpu-x86_64/libtensorflowlite.so)
    # file(COPY ${CMAKE_SOURCE_DIR}/external_libs/tensorflow_prebuilt/linux-cpu-x86_64/libtensorflowlite.so DESTINATION ${PROJECT_BINARY_DIR}/)
    # use static library
    target_link_libraries(NumberDetector ${CMAKE_SOURCE_DIR}/external_libs/tensorflow_prebuilt_static/linux-cpu-x86_64/libtensorflow-lite.a)
elseif((${BUILD_TARGET} STREQUAL RASPI_NATIVE) OR (${BUILD_TARGET} STREQUAL RASPI_CROSS))
    target_link_libraries(NumberDetector ${CMAKE_SOURCE_DIR}/external_libs/tensorflow_prebuilt_static/rpi_armv7l/libtensorflow-lite.a)
else()
    message(FATAL_ERROR "[BUILD] Invalid target")
endif()
target_include_directories(NumberDetector PUBLIC ${CMAKE_SOURCE_DIR}/external_libs/tensorflow/)
target_include_directories(NumberDetector PUBLIC ${CMAKE_SOURCE_DIR}/external_libs/tensorflow/tensorflow)
target_include_directories(NumberDetector PUBLIC ${CMAKE_SOURCE_DIR}/external_libs/tensorflow/tensorflow/lite/tools/make/downloads)
target_include_directories(NumberDetector PUBLIC ${CMAKE_SOURCE_DIR}/external_libs/tensorflow/tensorflow/lite/tools/make/downloads/eigen )
target_include_directories(NumberDetector PUBLIC ${CMAKE_SOURCE_DIR}/external_libs/tensorflow/tensorflow/lite/tools/make/downloads/absl)
target_include_directories(NumberDetector PUBLIC ${CMAKE_SOURCE_DIR}/external_libs/tensorflow/tensorflow/lite/tools/make/downloads/gemmlowp)
target_include_directories(NumberDetector PUBLIC ${CMAKE_SOURCE_DIR}/external_libs/tensorflow/tensorlow/lite/tools/make/downloads/neon_2_sse)
target_include_directories(NumberDetector PUBLIC ${CMAKE_SOURCE_DIR}/external_libs/tensorflow/tensorflow/lite/tools/make/downloads/farmhash/src)
target_include_directories(NumberDetector PUBLIC ${CMAKE_SOURCE_DIR}/external_libs/tensorflow/tensorflow/lite/tools/make/downloads/flatbuffers/include)

# Copy resouce
file(COPY ${CMAKE_SOURCE_DIR}/resource/ DESTINATION ${PROJECT_BINARY_DIR}/resource/)
add_definitions(-DRESOURCE_DIR="${PROJECT_BINARY_DIR}/resource/")
ビルド方法
cd 05_TensorflowLite_CPP/
mkdir build && cd build
cmake ..        # PC Linux native
# cmake .. -DBUILD_TARGET=RASPI_CROSS       # Cross compile for Raspberry Pi on PC Linux
# cmake .. -DBUILD_TARGET=RASPI_NATIVE  # Native compile on Raspberry Pi
make
Why do not you register as a user and use Qiita more conveniently?
  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