Flutterアプリに機械学習を組み込む！

# 概要

最近LLMが注目されてますが、物体検出や文字検出といった手法も便利&簡単に使えるよ！ということを紹介したいと思います。

- 最近アプリ作り始めたけど、機械学習とか難しくて使える気がしない
- 機械学習使ったアプリ作りたいけど、触れたことないしな〜

みたいな人に見ていただけると嬉しいです。
本記事では既存の学習済みモデルを利用する方法を紹介しているので、「モデルの検出精度を高めたい！」といった方は他の記事・本をおすすめします。

本記事では、Flutter を使用した機械学習の利用について、サンプルと共に紹介します。

# モバイルアプリにおける機械学習利用の流れ

モバイルアプリでの機械学習利用は、大まかに以下の流れで進みます。

```mermaid
graph TB
    A(データを集める) --> B(軽量モデルを使って分析)
    B --> C(分析結果を画面に反映する)
```

**1. データを集める**
センサーやカメラなどのデバイスを通じて取得される数値や画像が使用されます。また、ユーザーの行動履歴も入力データとして利用されることがあります。

**2. 軽量モデルを使って分析**
モバイルアプリ用に最適化された機械学習モデルを利用して、入力データから出力を推論します。モバイルデバイスの計算リソースには限りがあるため、TensorFlow Lite や PyTorch Mobile で最適化されたモデルを用意する必要があります。これらのモデルはサーバー上で訓練された後、圧縮や最適化を経てモバイルデバイス用に変換されます。

**3. 分析結果を画面に反映する**
モデルによる推論結果を UI を通じてユーザーにフィードバックします。

# ML Kit

本記事で利用では Google の[ML Kit](https://developers.google.com/ml-kit?hl=ja)を利用します。Google の ML Kit は、モバイル開発者が Android や iOS アプリに機械学習機能を簡単に統合できるように設計されたソフトウェアキットです。このキットには、テキスト認識、顔検出、画像ラベリング、オブジェクト検出とトラッキングなど、多くの事前トレーニングされた機械学習モデルが含まれています。

ML Kit には以下のような特徴があります。

| 特徴                         | 説明                                                                                                                        |
| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| 事前トレーニングされたモデル | 画像分析や言語理解など、多様なタスクを実行するための事前トレーニングされたモデルが利用可能です。                            |
| カスタムモデルの統合         | 自分の機械学習モデルをアップロードし、ML Kit と統合することで、特定のニーズに合わせたカスタマイズが可能です。               |
| デバイス上での実行           | ML Kit の処理がデバイス上で実行されるため、リアルタイムなユースケースが高速になります。また、オフライン時にも利用可能です。 |

# 実装

[google_ml_kit](https://github.com/flutter-ml/google_ml_kit_flutter) を利用した物体検出の例を紹介します。
ここでは、google_ml_kit の example から基本的な部分を抽出し、最低限動作する形を目指します。

最初に完成系をお見せします！

麦茶をトラッキングできていることがわかると思います。
![obj_mugicha.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2396718/7bcd1aeb-b9dd-8b00-190f-1c2da4c80518.gif)

今回紹介する物体検出システムの流れは以下のようになります。

```mermaid
graph TB
    A(カメラからフレームデータを取得する) --> B(google_ml_kitを使って分析)
    B --> C(検出したオブジェクトの位置をプレビューに重ねる)
```

### 1. カメラからフレームデータを取得する

まず[camera](https://pub.dev/packages/camera)を追加します。
権限、minSdkVersion の設定もお忘れなく！

[camera](https://pub.dev/packages/camera)にはフレームデータを扱うための[startImageStream](https://pub.dev/documentation/camera/latest/camera/CameraController/startImageStream.html)が定義されています。あらかじめ初期化を済ませておいた CameraController に対して、startImageStream を呼び出すことでフレーム画像のストリーミングが開始されます。画像は CameraImage として渡されるので、google_ml_kit の InputImage に変換しておきます。型の変換は本題から外れてくるので、[公式の example](https://github.com/flutter-ml/google_ml_kit_flutter/blob/2687e385d8ba89bdebf8f7ed0cb67374f053f875/packages/example/lib/vision_detector_views/camera_view.dart#L327)をご覧ください。

```dart
// cameraControllerの初期化は事前に済ませておく
cameraController.startImageStream((CameraImage image) async {
    // CameraImageをInputImageに変換
    final InputImage? inputImage = _inputImageFromCameraImage(image);

    if (inputImage == null) return;
    // 物体検出APIを呼び出す
    // 後で定義していく
    processImage(inputImage);
});
```

### 2. google_ml_kit を使って分析

取得したフレームデータを使って物体検出していきます。

まずは ObjectDetector のインスタンスを作成し、フレームデータを引数として processImage 関数を呼び出します。オリジナルのモデルを使用しない場合これで完了です。オリジナルのモデルを使わない場合は、これで以上です！こんな楽に物体検出ができるなんで Google 様様ですね。今回はフレームデータを連続して処理したいので、mode に DetectionMode.stream を指定しました。

classifyObjects と multipleObjects を true にすることで、検出したオブジェクトのクラス分類したり、複数オブジェクトの同時検出を可能です。

```dart
final options = ObjectDetectorOptions(
    mode: DetectionMode.stream,
    classifyObjects: false,
    multipleObjects: false,
);
ObjectDetector objectDetector = ObjectDetector(options: options);
final objects = await objectDetector.processImage(inputImage);
```

自前のモデルを使用することもできます。ML Kit と互換性のある TensorFlow Lite モデルを利用できます。

```dart
// モデルのパスを取得
// getModelPathは自前で実装する必要がある
final modelPath = await getModelPath('assets/ml/object_labeler.tflite');

// オプションの指定方法が変わる
final options = LocalObjectDetectorOptions(
    mode: DetectionMode.stream,
    modelPath: modelPath,
    classifyObjects: classifyObjects,
    multipleObjects: classifyObjects,
);
final objectDetector = ObjectDetector(options: options);
final List<DetectedObject> objects = await objectDetector.processImage(inputImage);
```

検出結果は DetectedObject 形式で取得されます。

### 3. 検出したオブジェクトの位置をプレビューに重ねる

検出された物体を四角形で囲み、強調表示します。[CameraPreview](https://pub.dev/documentation/camera/latest/camera/CameraPreview-class.html)には、プレビューのオーバーレイウィジェットを表示するための child プロパティがあります。

CameraPreview の child に渡したウィジェットは ValueListenableBuilder の child に渡され、プレビューが更新されるたびにオーバーレイがリビルドされることなく表示されます。

オーバーレイの表示位置は、DetectedObject が持つ boundingBox プロパティを利用して計算します。具体的な実装は省略しますが、CustomPainter を継承した ObjectDetectorPainter クラスを作成し、CameraPreview の child に渡しています。

```dart
final objects = await objectDetector.processImage(inputImage);

if (inputImage.metadata?.size != null &&
    inputImage.metadata?.rotation != null) {
    final painter = ObjectDetectorPainter(
        objects,
        inputImage.metadata!.size,
    );
    customPaint = CustomPaint(painter: painter);
}
```

# まとめ

今回紹介したコードの完成系は GitHub にアップロードしています。
https://github.com/yukisakai1225/object_detection_example

物体検出はハードルが高そうに聞こえますが、モデルさえ用意できればアプリへは手軽に組み込むことができます。LLM に限らず、他の機械学習関連技術もぜひ使ってみてください！

いい機会があれば次回はモバイル用モデル作成についてでも書いてみたいと思います！