FlutterとTensorFlow Lite(TFLite)でリアルタイム物体検出をしてみる
はじめに
この記事は Flutter #1 Advent Calendar 2020 16日目の記事です。
2020年は色々と濃い1年でした。技術面から2020年を振り返ると、個人的には業務も趣味もほぼほぼFlutter漬けな1年でした。
色々なアプリを作ったのですが、まだ機械学習に関するアプリは作ったことがありませんでした。なので、アドカレを機に、次のような物体検出(Object Detection)アプリを作ってみました。
この記事では、Flutterによるリアルタイム物体検出アプリの作り方を解説します。
なお、機械学習フレームワークはTensorFlow Liteを使います。
使用ライブラリ
TensorFlow Lite(以下、TFLite)は、モバイル端末で推論を行うためのディープラーニングフレームワークです。
tflite_flutterはTFLiteのC++ APIをdart:ffiでバインドし、Flutterから利用可能にするライブラリです。
また、tflite_flutter_helperはTFLiteで画像を扱う上での前処理などを行うためのライブラリです。
ここではtflite_flutterとtflite_flutter_helperを使い、FlutterからTFLiteを利用します。
ライブラリのインストール
まず、pubspec.yamlに、このアプリで使用するライブラリ一覧を追記します。
dependencies:
flutter:
sdk: flutter
hooks_riverpod: ">=0.12.0 <1.0.0"
flutter_hooks: ">=0.14.0 <1.0.0"
tflite_flutter: ">=0.5.0 <1.0.0"
tflite_flutter_helper: ">=0.1.2 <1.0.0"
camera: ">=0.5.8 <1.0.0"
これらを追記後、次のコマンドでインストールしておきます。
flutter pub get
パッケージをインストールしたら、次はTFLiteダウンロードします。TFLiteのダウンロード方法として、まず下記スクリプトをダウンロードします。Linux、Macはinstall.shを、Windowsはinstall.batをダウンロードします。
ダウンロードしたスクリプトは、プロジェクトのルートに配置しておきます。
なお、このスクリプトではwgetを使用するため、Macを使っている場合は、次のコマンドでwgetをインストールしておきます。
brew install wget
あとは、次のようなコマンドで、TFLiteをインストールできます
# Linux、Mac
sh install.sh
# Windows
install.bat
もし、TFLiterでDelegateを使いたい場合は、sh install.sh -dやinstall.bat -dと、引数をつけてインストールします。Delegateとは、TFLiteのグラフの一部/全体をCPU以外のExcecutorへ委譲する機能です。Delegateを使用することで、推論のレイテンシ、電力効率を改善することができます。
# Delegateをサポートする場合
# Linux、Mac
sh install.sh -d
# Windows
install.bat -d
TFLiteのダウンロードを終えたら、次のリンクからモデルとラベルをダウンロードします。
coco_ssd_mobilenet_v1_1.0_quant_2018_06_29.zip
ダウンロードした coco_ssd_mobilenet_v1_1.0_quant_2018_06_29.zipを解凍し、中に含まれるdetect.tfliteとlabelmap.txtを<プロジェクト>/assets下に配置しておきます。
また、pubspec.yamlへ次を追記し、assets内のファイルを参照できるようにしておきます。
flutter:
assets:
- assets/
ここまでで、必要なファイルのダウンロードは終えました。
あとは、使用しているcameraパッケージがAPI Level 21(Android 5.0)以上を要求するので、app/build.gradleを開き、minSDKを21に変更しておきます。
android {
...
defaultConfig {
...
minSdkVersion 21
}
}
識別器の作成
早速コードの完成形を載せてしまいますが、次のような識別器を作ることで、画像ストリームから物体検出を行うことができます。
なお、識別器はtflite_flutter作者さんの次の記事を参考に作成しています。
識別器を作ったら、次はこの識別器を使って、画像ストリームから推論してみます。
画像ストリーミングに対して推論する
ここでは、cameraパッケージを使用して画像ストリームへアクセスします。
cameraパッケージでは次のようにして、画像ストリームに対して処理を行うことができます。
import 'package:camera/camera.dart';
final cameras = await availableCameras();
final cameraController = CameraController(
cameras[0],
ResolutionPreset.low,
enableAudio: false,
);
await cameraController.initialize();
cameraController.startImageStream((CameraImage cameraImage){
// ここに画像ストリームに対する処理を書く
});
cameraController.startImageStream()のコールバック関数へ推論処理を書くことで、画像ストリームから物体検出をすることができます。
ただ、Dartの実行モデルは、シングルスレッド・イベントループです。
推論処理など負荷が高い処理をMainIsolateで処理すると、イベントループが詰まり、他イベントが処理されないことから、画面が固まってしまいます。
そのため、推論処理は別のIsolateを起動して、そちらで並列処理します。
Flutterではcompute関数を使うことで、簡単に並列処理を扱うことができます。
// computeを使うことで、並列処理が可能
// 一番目の引数に、並列に処理するコールバック関数を書く
// 二番目の引数に、コールバック関数へ渡す値を書く
await compute(inference, isolateData);
// 推論処理
static Future<List<Recognition>> inference(
IsolateData isolateData) async {
var image = ImageUtils.convertYUV420ToImage(
isolateData.cameraImage,
);
if (Platform.isAndroid) {
image = image_lib.copyRotate(image, 90);
}
final classifier = Classifier(
interpreter: Interpreter.fromAddress(
isolateData.interpreterAddress,
),
labels: isolateData.labels,
);
return classifier.predict(image);
}
}
// inferenceへ渡す引数
// 引数はトップレベル関数である必要がある
// 引数にクラスのインスタンスや、staticメソッドを含むと
// Isolateへ渡すことができない
class IsolateData {
IsolateData({
this.cameraImage,
this.interpreterAddress,
this.labels,
});
final CameraImage cameraImage;
final int interpreterAddress;
final List<String> labels;
}
最後に、画像ストリームを処理する部分の、完成版コードを載せておきます。
カメラプレビューの表示
ここまではUIではなく、推論処理に関する部分を実装してきました。
最後に、次のようにUI表示部分を作り、これまでの処理と合わせることで、物体検出アプリが完成となります。
あとは、これらを実行すれば、冒頭のデモのような物体検出を行うことができます。
次のリポジトリへ、今回作ったアプリの全ソースコードを置いています。
終わりに
ということで、Flutterを使った物体検出アプリを作ってみました。作ろうと思ってから約1日で動くデモが作れるなど、Flutterは生産性が高いフレームワークだと思います。
何より書いていて楽しいので、また来年もFlutterで色々作っていきたいなと思います。
参考文献
この記事が気に入ったらサポートをしてみませんか?