1. 概要
mruby + TensorFlow Liteで画像分類する - Qiita の続き。さらにRaspberry Pi上のmrubyで、Cloud AutoML Vision Edge と組み合わせて動かせるようにする。
画像を通常のAutoML Vision APIに送りつけると、遅延が大きく、月1000枚以上は費用もかかるが、AutoML Vision Edgeと組み合わせることで、Tensorflow Liteモデル作成はAutoMLに任せ、Raspberry Pi側で高速に分類し放題の環境を作ることができる。
2. クロスコンパイル
セルフビルドはつらいので、できるだけクロスコンパイルする方針とする。以下、ホストマシンのプロンプトは[host]、ターゲットのRaspberry Piのプロンプトは[rpi]で記載する。
環境 | OS |
---|---|
ホスト | WSL / Ubuntu XX |
Raspberry Pi | Rasbian Strech |
2.1 準備
ホストマシンにARMのクロスコンパイル環境をインストールする。
[host] $ apt install -y crossbuild-essential-armhf
2.2 Tensorflow lite
tensorflow直下でbuild_rpi_lib.shを実行すると、tensorflow/lite/tools/make/gen/rpi_armv7l/lib/libtensorflow-lite.aができる。
[host] $ tensorflow/lite/tools/make/build_rpi_lib.sh
さらに、C experimental APIライブラリをビルドするために、tensorflow/lite/experimental/cに以下のMakefileを作る。
SRCS = c_api.cc c_api_experimental.cc
OBJS = $(subst .cc,.o,$(subst .cxx,.o,$(subst .cpp,.o,$(SRCS))))
CXXFLAGS = -DTF_COMPILE_LIBRARY -I$(TENSORFLOW_ROOT) -I$(TENSORFLOW_ROOT)/tensorflow/lite/tools/make/downloads/flatbuffers/include -fPIC
TARGET = libtensorflowlite_c
OS_ARCH = rpi_armv7l
TARGET_SHARED := $(TARGET).so
LDFLAGS += -L$(TENSORFLOW_ROOT)/tensorflow/lite/tools/make/gen/$(OS_ARCH)/lib
LIBS = -ltensorflow-lite
TARGET_TOOLCHAIN_PREFIX := arm-linux-gnueabihf-
CXX := ${TARGET_TOOLCHAIN_PREFIX}g++
CC := ${TARGET_TOOLCHAIN_PREFIX}gcc
AR := ${TARGET_TOOLCHAIN_PREFIX}ar
.SUFFIXES: .cpp .cxx .o
all : $(TARGET_SHARED)
$(TARGET_SHARED) : $(OBJS)
mkdir -p $(OS_ARCH); \
$(CXX) -shared -o $@ $(OBJS) $(LDFLAGS) $(LIBS)
.cxx.o :
$(CXX) -std=c++14 -c $(CXXFLAGS) -I. $< -o $@
.cpp.o :
$(CXX) -std=c++14 -c $(CXXFLAGS) -I. $< -o $@
clean :
rm -f *.o $(TARGET_SHARED)
このディレクトリでTENSORFLOW_ROOTを指定してMakeを実行するとrpi_armv7l/libtensorflowlite_c.soができる。
(x86のバイナリと重ならないように、今回は違うディレクトリに出力した)
libtensorflowlite_c.so はmrubyのコンパイル用に/usr/arm-linux-gnueabihf/libにコピーしておく。
[host] tensorflow/lite/experimental/c $ export TENSORFLOW_ROOT=.../tensorflow # 適宜修正
[host] tensorflow/lite/experimental/c $ Make -f Makefile.arm
[host] tensorflow/lite/experimental/c $ cp rpi_armv7l/libtensorflowlite_c.so /usr/arm-linux-gnueabihf/lib
2.3 GD
GDは依存ライブラリが多くクロスコンパイルは大変そうなので、Raspberry Piの実機から持ってくることにする。(もしいい方法があれば教えてください)
まずは、Raspberry Pi側でGDをインストールする。
[rpi] $ sudo apt-get install libgd3 libgd-dev
lddでlibgdが依存するライブラリを調べる。
[rpi] $ ldd /usr/lib/arm-linux-gnueabihf/libgd.so.3
linux-vdso.so.1 (0x7ebc3000)
/usr/lib/arm-linux-gnueabihf/libarmmem.so (0x76ea1000)
libm.so.6 => /lib/arm-linux-gnueabihf/libm.so.6 (0x76e12000)
libz.so.1 => /lib/arm-linux-gnueabihf/libz.so.1 (0x76deb000)
libpng16.so.16 => /usr/lib/arm-linux-gnueabihf/libpng16.so.16 (0x76db1000)
libfontconfig.so.1 => /usr/lib/arm-linux-gnueabihf/libfontconfig.so.1 (0x76d6e000)
libfreetype.so.6 => /usr/lib/arm-linux-gnueabihf/libfreetype.so.6 (0x76cd3000)
libjpeg.so.62 => /usr/lib/arm-linux-gnueabihf/libjpeg.so.62 (0x76c8d000)
libXpm.so.4 => /usr/lib/arm-linux-gnueabihf/libXpm.so.4 (0x76c6e000)
libX11.so.6 => /usr/lib/arm-linux-gnueabihf/libX11.so.6 (0x76b4b000)
libtiff.so.5 => /usr/lib/arm-linux-gnueabihf/libtiff.so.5 (0x76ad1000)
libwebp.so.6 => /usr/lib/arm-linux-gnueabihf/libwebp.so.6 (0x76a73000)
libc.so.6 => /lib/arm-linux-gnueabihf/libc.so.6 (0x76934000)
/lib/ld-linux-armhf.so.3 (0x76f1e000)
libexpat.so.1 => /lib/arm-linux-gnueabihf/libexpat.so.1 (0x76902000)
libpthread.so.0 => /lib/arm-linux-gnueabihf/libpthread.so.0 (0x768d9000)
libxcb.so.1 => /usr/lib/arm-linux-gnueabihf/libxcb.so.1 (0x768aa000)
libdl.so.2 => /lib/arm-linux-gnueabihf/libdl.so.2 (0x76897000)
liblzma.so.5 => /lib/arm-linux-gnueabihf/liblzma.so.5 (0x76866000)
libjbig.so.0 => /usr/lib/arm-linux-gnueabihf/libjbig.so.0 (0x76849000)
libgcc_s.so.1 => /lib/arm-linux-gnueabihf/libgcc_s.so.1 (0x7681c000)
libXau.so.6 => /usr/lib/arm-linux-gnueabihf/libXau.so.6 (0x76f3f000)
libXdmcp.so.6 => /usr/lib/arm-linux-gnueabihf/libXdmcp.so.6 (0x76805000)
libbsd.so.0 => /lib/arm-linux-gnueabihf/libbsd.so.0 (0x767dc000)
こんな感じで依存するライブラリを適当なディレクトリに集め、libgd.so.3と手元になさそうなlibc, libm以外のライブラリをホスト環境の/usr/arm-linux-gnueabihf/libにコピーする。
require 'fileutils'
dest = "."
`ldd /usr/lib/arm-linux-gnueabihf/libgd.so.3`.each_line do |l|
next if l !~ /=>/
t1, t2, path, addr = l.split
puts path
FileUtils.cp(path, dest)
end
2.4 mruby
build_config.rbを修正してクロスコンパイルの設定を行う。CrossBuild.newの引数に指定した名前でbuild/以下にディレクトリが作成される。この場合は build/rpi/bin以下に mruby, mirbができる。
build_config.rbの前半にあるホスト向けのMRuby::Build.newとかはコンパイルの時間がかかるだけなので削除してしまってもよい。
mruby-tfliteはmattnさんが修正を反映してくれたので、今回はGitHub直接でいけるはず。
MRuby::CrossBuild.new('rpi') do |conf|
toolchain :gcc
conf.cc.command = 'arm-linux-gnueabihf-gcc'
conf.cxx.command = 'arm-linux-gnueabihf-g++'
conf.linker.command = 'arm-linux-gnueabihf-gcc'
conf.linker.flags = '-L/usr/arm-linux-gnueabihf/lib'
conf.archiver.command = 'arm-linux-gnueabihf-ar'
conf.gembox 'default'
conf.gem :github => 'qtkmz/mruby-gd'
conf.gem :github => 'mattn/mruby-tflite'
end
[host] mruby $ export TENSORFLOW_ROOT=.../tensorflow # 適宜修正
[host] mruby $ ./minirake
これで build/rpi/bin以下に mrubyとmirbができるので、Raspberry Piにバイナリを送る。
画像とTF liteのモデルもRaspberry Piに送って前回のスクリプトを動かしてみた。Mobilenetのモデル(約4MB)で対して計測してみると、最速でモデル読み込みが10msec以内、画像読み込みが370msec、推論が300msec程度だった。ただ、速度はあまり安定せず、倍ぐらいかかる場合もあるようだ。
自前で作った約50MBのモデルでは推論は650msec程度だった。
[rpi ]$ time ./mruby tf_test.rb
w: 224
h 224
ch: 3
131 255 flamingo
real 0m0.930s
user 0m0.908s
sys 0m0.022s
Raspberry Piでmruby/mirbを実行した際に以下のようなエラーが出てしまったら、コンパイルしたマシンの/usr/arm-linux-gnueabihf/libからRaspberry Piの適当なディレクトリにlim.so.6を持ってきて、LD_LIBRARYにそのディレクトリを指定してmruby/mirbを実行するようにする。
mruby/mirb: /lib/arm-linux-gnueabihf/libm.so.6: version `GLIBC_2.27' not found (required by .../libtensorflowlite_c.so)
3. AutoML Vision Edge
@shinkoizumi0033 さんの GCP Cloud AutoML Vision Edgeを使ってカスタムモデルを作ってエッジデバイスで動かす話(Firebase ML Kit) - Qiita にある通りにtfliteのモデルを作成する。
AutoML Vison Edgeを使うと、画像サイズは224x224固定らしいので、そのサイズで学習用の画像を用意しておく。
(今回は以前使った学習用の画像を再利用したのでサイズは適当)
Model TypeでEdgeを選ぶ。Optimize model forはBest trade-offにしてみた。Higher accuracyにする必要はないと思う。node hour budgetはしれっと2 node hoursを推奨されるが、無料枠を使いたいなら1に修正する。
毎月最初のモデル 10 個については、アカウントごとに 1 ノード時間まで無料です
学習が完了すると、dict.txt, model.tflite, tflite_metadata.jsonの3ファイルが生成される。
{
"batchSize": 1,
"imageChannels": 3,
"imageHeight": 224,
"imageWidth": 224,
"inferenceType": "QUANTIZED_UINT8",
"inputTensor": "image",
"inputType": "QUANTIZED_UINT8",
"outputTensor": "scores",
"supportedTfVersions": [
"1.10",
"1.11",
"1.12",
"1.13"
]
}
以下のようなスクリプトで実行させる。
model = TfLite::Model.from_file 'model.tflite'
interpreter = TfLite::Interpreter.new(model)
interpreter.allocate_tensors
input = interpreter.input_tensor(0)
expected_width = input.dim(1) # 224
expected_height = input.dim(2) # 224
expected_channel = input.dim(3) # 3
img = Array.new(expected_height * expected_width * expected_channel, 0)
im = GD::Image.new_from_png_file ARGV[0]
w = im.width
h = im.height
if expected_width != w || expected_height != h
puts "ERROR: image size mismatch"
return
end
(0...w).each do |x|
(0...h).each do |y|
offset = (y * w + x ) * expected_channel
img[offset] = im.red(im.get_pixel(x, y))
img[offset + 1] = im.green(im.get_pixel(x, y))
img[offset + 2] = im.blue(im.get_pixel(x, y))
end
end
im.destroy
output = interpreter.output_tensor(0)
input = interpreter.input_tensor(0)
input.data = img
interpreter.invoke
labels = File.read('dict.txt').lines.map {|x| x.strip}
output.data.each_with_index do |v, i|
puts "#{i} #{v} #{labels[i]}" if v > 5
end
こちらも時間を計測してみると、モデルサイズ約3MBで、Mobilenetの画像分類より若干程度早い程度だった。
これでAutoMLで作成したモデルでの推論環境が完成。Edge TPU を使うほどの性能は不要だが、Pythonでは重いという場合があればぜひどうぞ(あるのか?)。