TensorFlow Lite for MicrocontrollersをESP32で試す 続編

概要

2年ほど前に雑誌で紹介したいので、PlatformIOで動かし方を書いてほしいと依頼され、PlatformIOの導入部分書くの面倒だなと思ってArduinoライブラリに移植しました。さすがに2年放置していたので最新バージョンを元に更新しなおしてみました。

TensorFlow Lite for Microcontrollersとは?

TensorFlowはGoogleが開発した機械学習向けのプラットフォームです。パソコンなどで動くのでもう少し軽量化したのが携帯などのモバイル端末向けがTensorFlow Liteです。そして、さらに軽量化したのがTensorFlow Lite for Microcontrollersになります。

TensorFlow Lite for MicrocontrollersはSTM32などのArm向けが主なのですが、なぜかESP32にも対応しています。Wio Terminalとか、Sony Spresenseにも対応していますね。比較的軽量なので、32ビットのプラットフォームでは移植し易いみたいです。

ただし、ちょっと動かすのが面倒です。2年前はTensorFlow本体の中に組み込まれていました。その後にTensorFlow Liteが別リポジトリに切り出されてしまいました。現在みたらESP32は更にEspressifが管理されているリポジトリに切り出されています。

もともとSTM32とかが主で、ESP32はちょっと扱いが悪かったのでEspressifの自主管理になったのかもしれません。

移植方法

TensorFlow Lite for Microcontrollersは一般的なC++ 11で組まれているために比較的移植性が高いです。ただしArduinoとの相性が悪いところがあります。

  • 拡張子が.cc
  • include pathが結構指定してある
  • menuconfigを使ってCONFIGを変更している

まず拡張子がccなのでArduinoではプログラムファイルとして認識しません。そして、Arduinoだとinclude pathが指定できないのでinclude文を書き換えて相対Pathに変更する必要があります。あとはmenuconfigで何個か設定を指定しないといけないのですがArduinoでは利用できませんので、単なるdefine文に変更が必要になります。

実は2年前は突貫で作ったので全部手で調整していました。ちょっとした手間なのですが数時間あれば調整が終わり、実際の端末を使ってサンプルを書く作業が主でした。しかしながら、毎回手で書き換えるとなると更新頻度が減ってしまいます。そこで今回は自動で書き換えるスクリプトを作ってみました。

できあがったスクリプトが上記になります。我ながらひどいスクリプトが完成しました!

# rename
#rename -v 's/\.cc/\.cpp/' *.cc
#rename -v 's/\.cc/\.cpp/' */*.cc
#rename -v 's/\.cc/\.cpp/' */*/*.cc
#rename -v 's/\.cc/\.cpp/' */*/*/*.cc
#rename -v 's/\.cc/\.cpp/' */*/*/*/*.cc
#rename -v 's/\.cc/\.cpp/' */*/*/*/*/*.cc
#rename -v 's/\.cc/\.cpp/' */*/*/*/*/*/*.cc
#rename -v 's/\.cc/\.cpp/' */*/*/*/*/*/*/*.cc
#rename -v 's/\.cc/\.cpp/' */*/*/*/*/*/*/*/*.cc
#rename -v 's/\.cc/\.cpp/' */*/*/*/*/*/*/*/*/*.cc
#rename -v 's/\.cc/\.cpp/' */*/*/*/*/*/*/*/*/*/*.cc
rename -v .cc .cpp *.cc
rename -v .cc .cpp */*.cc
rename -v .cc .cpp */*/*.cc
rename -v .cc .cpp */*/*/*.cc
rename -v .cc .cpp */*/*/*/*.cc
rename -v .cc .cpp */*/*/*/*/*.cc
rename -v .cc .cpp */*/*/*/*/*/*.cc
rename -v .cc .cpp */*/*/*/*/*/*/*.cc
rename -v .cc .cpp */*/*/*/*/*/*/*/*.cc
rename -v .cc .cpp */*/*/*/*/*/*/*/*/*.cc
rename -v .cc .cpp */*/*/*/*/*/*/*/*/*/*.cc

上記がファイル名の変更です。CentOSとUbuntuで引数の取り方が違います。とりあえずありそうな階層はベタで全部書き換える流儀です!

find ./ -name '*.h' -type f | xargs sed -i 's/\"flatbuffers/\"third_party\/flatbuffers/g'
find ./ -name '*.cpp' -type f | xargs sed -i 's/\"flatbuffers/\"third_party\/flatbuffers/g'

include path対策です。上記みたいにflatbuffers配下のものはthird_partyフォルダ以下に保存しているので全部書き換えます。この処理はWSL上のUbuntuで動かすと非常に重かったですが、VM上のUbuntuだと軽かったです。でもGitHubにコミットする環境はCentOSなのでrenameが、、、

sed -i -e "1i #define CONFIG_I2C_BUS_DYNAMIC_CONFIG y" tflite-micro-esp-examples/components/bus/i2c_bus.c
sed -i -e "2i #define CONFIG_I2C_MS_TO_WAIT 200" tflite-micro-esp-examples/components/bus/i2c_bus.c

CONFIG対策で、とりあえず1行目にdefine文を追記しています。本当はコメントの下がいいのですが、今後行数変わったらいやなので先頭です。

# examples/hello_world
mv tflite-micro-esp-examples/examples/hello_world/main/* tflite-micro-esp-examples/examples/hello_world
rm -rfv tflite-micro-esp-examples/examples/hello_world/main
rm -rfv tflite-micro-esp-examples/examples/hello_world/main.cpp
rm -rfv tflite-micro-esp-examples/examples/hello_world/*.txt
rm -rfv tflite-micro-esp-examples/examples/hello_world/sdkconfig.defaults
mv tflite-micro-esp-examples/examples/hello_world/main_functions.cpp tflite-micro-esp-examples/examples/hello_world/hello_world.ino
echo "" >tflite-micro-esp-examples/examples/hello_world/main_functions.h
sed -i -e "1i #include <TensorFlowLite_ESP32.h>" tflite-micro-esp-examples/examples/hello_world/hello_world.ino

スケッチ例の変換です。構造が変わるのとESP-IDFで必要なmain.cppを消して、不要になるmain_functions.hを空にしています。そしてTensorFlowLite_ESP32.hを先頭で読み込むことでTensorFlowLite_ESP32ライブラリを使うことを認識させています。

移植に困ったのが実は液晶対応です。カメラを使うperson_detectionは液晶付きのモデルもあるので、画面表示もありました。ESP-IDF対応なので、有名な描画ライブラリがなくEspressifがベタ書きしたと思われるライブラリが入っています。ここのコードが結構依存性があってかなり苦労しています。そして実際にはLovyanGFXとかを使うので、私はたぶん使わないコードです。

まとめ

とりあえずオリジナルを極力壊さないように移植しています。ですがちゃんと動くかの確認はできていません。センサーからのデータをきれいに入れる必要があるので、動かすのちょっと大変なんですよね。とりあえず最新版のESP32ボードマネージャーでエラーなくビルドできるところまで確認して、リリースしました。

これから実際のボードでの実験と、モデルのトレーニングとかも検証できればなと思っています。。。

hello_worldを動かしてみましたが、赤が正弦波なのでそれなりに動いている気がします。青はX軸なのでこの値に対応するY軸が赤だと思います。ただデフォルトの出力だと上記の1000分の1の振幅なのでちょっとわかりにくかったです。

x_value: 1.5707957*2^0, y_value: 1.0420598*2^0


x_value: 1.8849551*2^0, y_value: 1.9146791*2^-1


x_value: 1.0995567*2^1, y_value: 1.6435742*2^-1


x_value: 1.2566366*2^1, y_value: 1.0674761*2^-1


x_value: 1.4137159*2^1, y_value: 1.8977352*2^-3

上記みたいな出力がデフォルトでした。んー、ぱっと見わからない。。。

あとESP32-S3は専用命令が増えたので、機械学習系がものすごく早くなっています。しかしながらビルド時にESP32とESP32-S3を指定する方式で、Arduinoだとコードに手を入れる必要があったので今回はS3用の最適化は使っていません。

コメント