snovaのブログ

主にプログラミングやデジタルコンテンツについて書きます。最近はPython, Flutter, VRに興味があります。

Flutterから利用できるFirebaseサービスをカウンターアプリで実践(Firebase ML編)

はじめに

Flutterから利用できるFirebaseの機能をカウンターアプリに実装し、内容をまとめたシリーズの第11回目です。

今回はFirebaseを使った機械学習モデルの推論についてです。

目次

シリーズの内容

回数 内容 リンク
第0回 準備編 ブログ
第1回 Analytics ブログ
第2回 Firebase Crashlytics ブログ
第3回 Firebase Performance Monitoring ブログ
第4回 Firebase Remote Config ブログ
第5回 Firebase Authentication ブログ
第6回 Cloud Firestore ブログ
第7回 Firebase Realtime Database ブログ
第8回 Cloud Storage for Firebase ブログ
第9回 Firebase Cloud Messaging ブログ
第10回 Firebase In-App Messaging ブログ
第11回 Firebase ML イマココ
第12回 Cloud Functions for Firebase ブログ
第13回 Firebase Hosting ブログ
第14回 Firebaseのその他のサービス ブログ

開発環境

項目 内容
PC Macbook Air(M1)
Flutter 3.0.1
Firebase 11.0.1
FlutterFire 0.2.2+2
デバッグバイス Android 12(APIレベル31), Chrome

準備

準備編が完了出来ているものとします。

snova301.hatenablog.com

FirebaseとFlutterで使用できる機械学習の推論

FlutterとFirebaseを使った機械学習の推論には、オンデバイスで推論する方法と、クラウド上で推論する方法があります。

オンデバイスの推論でFirebaseを使うというのは、カスタムTensorFlowLiteモデルFirebase MLで配信してローカルで推論することです。 実際の推論にはtflite_flutter, ML Kitなどを使用します。 カスタマイズされたTFモデルをFirebaseで配信するメリットはユーザーがアプリをアップデートせずに最新のモデルを使用できるという点です。

クラウドの推論でFirebase(Google Cloud)を使うというのは、Cloud Vision AICloud Natural Languageなどで推論するということです。 現在のところ、Flutter向けにはAPIが提供されていないので、各OS向けのAPIを組み合わせる必要があります。

なお、上記以外にも、他サービスの機械学習モデルを使う方法があります。

FirebaseによるカスタムTensorflowLiteモデル配信の導入方法

自分で学習させたTFのカスタムモデルをTFLiteに変換したファイルを用意します。

今回は画像認識のタスクを実行するため、TensorFlow HubからImagenetの画像分類学習モデルを取得しました。 このとき、ライセンスとダウンロードするファイルの種類に気をつけてください。

ファイルを用意できたら、Firebase ConsoleMachine Learningからモデルをデプロイします。

公式では推論にtflite_fluttertfliteが推奨されていましたが、私の開発環境ではtflite_flutter等をインポートした状態でアプリをビルド出来なかったので、ML Kitで実験しました。

ML KitでTFLiteのカスタムモデルを使用できるタスクには、Image LabelingまたはObject Detection and Trackingがあります。 今回はImage Labelingの推論をしますので、google_ml_kitgoogle_mlkit_image_labelingも導入します。

dependencies:
  google_ml_kit: ^0.11.0
  google_mlkit_image_labeling: ^0.3.0

Firebase MLからモデルのダウンロードは、google_mlkit_image_labelingパッケージに入っているFirebaseImageLabelerModelManagerを使用します。

final bool response =
    await FirebaseImageLabelerModelManager().downloadModel(modelname);
final options = FirebaseLabelerOption(
        confidenceThreshold: 0.5, modelName: modelname, maxCount: 3);
_imageLabeler = ImageLabeler(options: options);

image_picker等で写真のパスがわかれば、基本的なラベル推論に必要なコードは2行です。

final InputImage inputImage = InputImage.fromFilePath(path);
final List labels = await _imageLabeler.processImage(inputImage);

推論された結果からラベルを取り出します。

String labelText = '';
for (final label in labels) {
  labelText += '\nLabel: ${label.label}';
}

アプリで確認してみます。 推論は出来ているみたいです。 ただ、思っていた結果とは違ったので、改善の余地がありそうです。

コード全文

前回からの変更点です。

  • Firebase MLを実装
  • ml_pageページを追加
  • main.dartml_pageページに遷移するボタン追加
  • その他、コードの変更

ml_page.dart

/// Flutter
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:image_picker/image_picker.dart';
import 'package:google_mlkit_image_labeling/google_mlkit_image_labeling.dart';

/// Firebase
import 'package:firebase_ml_model_downloader/firebase_ml_model_downloader.dart';

/// Providerの初期化
final imageStateProvider = StateProvider<File?>((ref) => null);
final textStateProvider = StateProvider<String?>((ref) => null);

class MLPage extends ConsumerStatefulWidget {
  const MLPage({Key? key}) : super(key: key);

  @override
  MLPageState createState() => MLPageState();
}

class MLPageState extends ConsumerState<MLPage> {
  /// 画像ラベル分類問題の定義
  late ImageLabeler _imageLabeler;
  ImagePicker? _imagePicker;

  /// ImageLabelerの初期化
  @override
  void initState() {
    super.initState();
    _initializeLabeler();
    _imagePicker = ImagePicker();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('画像からラベル分類'),
      ),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: <Widget>[
          /// 画像が存在すれば画像を表示
          /// そうでなければ画像アイコンを表示
          ref.watch(imageStateProvider) != null
              ? SizedBox(
                  height: 400,
                  width: 400,
                  child: Stack(
                    fit: StackFit.expand,
                    children: <Widget>[
                      Image.file(ref.watch(imageStateProvider)!),
                    ],
                  ),
                )
              : const Icon(
                  Icons.image,
                  size: 200,
                ),
          ElevatedButton(
            child: const Text('写真を選択'),
            onPressed: () => _getImage(ImageSource.gallery),
          ),

          Container(
            padding: const EdgeInsets.all(10),
            child: Text(ref.watch(imageStateProvider) == null
                ? ''
                : ref.watch(textStateProvider) ?? ''),
          ),
        ],
      ),
    );
  }

  /// ラベラーの初期化
  void _initializeLabeler() async {
    /// モデルの名前はFirebase MLにアップロードした名前
    const modelname = 'Image';

    /// FirebaseからML kitに学習モデルを読込ませるため、FirebaseImageLabelerModelManagerを使う
    /// 参考 : https://pub.dev/documentation/google_mlkit_image_labeling/latest/
    final bool response =
        await FirebaseImageLabelerModelManager().downloadModel(modelname);
    print(response);

    /// ラベラーのオプションを設定し、読込
    final options = FirebaseLabelerOption(
        confidenceThreshold: 0.5, modelName: modelname, maxCount: 3);
    _imageLabeler = ImageLabeler(options: options);
  }

  /// 画像を選択し推論を実行
  Future _getImage(ImageSource source) async {
    /// 画像選択
    final pickedFile = await _imagePicker?.pickImage(source: source);

    /// 有効な画像が選択できたら推論
    if (pickedFile != null) {
      /// ファイルのパスを取得
      final path = pickedFile.path;
      ref.read(imageStateProvider.state).state = File(path);
      final inputImage = InputImage.fromFilePath(path);

      /// ラベル分類推論実行
      final labels = await _imageLabeler.processImage(inputImage);

      /// ラベルの分類が成功した場合、ラベルのテキストを生成
      if (inputImage.inputImageData?.size != null &&
          inputImage.inputImageData?.imageRotation != null) {
      } else {
        String labelText = '';
        for (final label in labels) {
          labelText += '\nLabel: ${label.label}';
        }
        ref.read(textStateProvider.state).state = labelText;
      }
    }
  }
}

GitHubのページを貼ります。

github.com

Google Play and the Google Play logo are trademarks of Google LLC.