Flutter×Firebase(MachineLearning)で画像認識(OCR/オブジェクトのラベリング)をやってみる

---
# :book: Flutterの記事を整理し本にしました :book:
https://zenn.dev/kazutxt/books/flutter_practice_introduction

* <font color="red"><b>本稿の記事を含む様々な記事を体系的に整理し本にまとめました</b></font>
* 今後はこちらを最新化するため、<font color="red"><b>最新情報はこちらをご確認ください</b></font>
* 25万文字を超える超大作になっています!!

---

## まとめ記事
* [Flutter系の記事のまとめ](https://qiita.com/kazutxt/items/524acb1c134015682ccf)

# はじめに
* VisionAPIを使ったことがあったので、それと親しいFirebaseのMLをFlutterと組み合わせて動かしてみました。

# 概要

Firebase MachineLearning(以下ML)は機械学習の画像認識APIを用いて、判定やクラス分け、ラベリングなどを行う環境を提供します。
自前で学習やモデル作成を行えば、より幅広い機械学習の識別が実現できます。

# 有効化手順
MLの有効化は、Firebaseの左側のメニューの**MachineLearning**を選択し、始めるをクリックします。

![pic1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/ad0a5272-f8f4-2be3-0252-7e98326221b7.png)


機械学習には、以下の3種類があります。

* APIs:GCPのVisionAPIのように、画像認識を基本にテキスト認識や、ラベリング、顔検知などを行えます
* Custom:TensorFlowなどのtfliteの学習モデルを使って処理を行えます
* AutoML:CloudAutoMLでGCP上での機械学習と連携することができます。
me

以後は、事前のモデル作成が不要なAPIsについて解説していきます

APIsには、いくつかの種類が用意されていますので、使いたい目的に合わせて使用するAPIを選択します。

![pic2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/5848a98b-6f10-4703-45d4-b6438a0253e8.png)


処理には、デバイスで行うオンデバイスと、クラウドで行う2種類があります。

APIによって利用できる形態や機能に制約がありますので、ご注意ください。

クラウドベースを行う際には、プランを`Blaze`にアップグレードする必要があります。

なお、今回解説するテキスト認識の日本語は、クラウドでないと使えないため、プランをアップグレードしています。

![pic3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/5fec9b15-ef5b-eec9-89d4-6ef3174c2a04.png)

# 準備

```yaml:pubspec.yaml
  firebase_core: ^1.1.0
  firebase_ml_vision: ^0.12.0+1
  image_picker: ^0.7.4
```

MLを使うためのパッケージが`firebase_ml_vision`と`firebase_core`になります。
`image_picker`は画像やカメラを選択するために使っています。


# 実装

デバイス内の画面を選択してテキスト認識(OCR)をするものと、カメラを使って取った写真のラベリングを行うものの2種類を実装しています。
どちらもクラウド側で処理を行っています。

```dart:main.dart
import 'dart:io';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_ml_vision/firebase_ml_vision.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();

  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Flutter Demo'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  Text _text;
  Image _image;

  Future<void> _ocr() async {
    PickedFile pickerFile =
        await ImagePicker().getImage(source: ImageSource.gallery);

    final File imageFile = File(pickerFile.path);
    final FirebaseVisionImage visionImage =
        FirebaseVisionImage.fromFile(imageFile);
    TextRecognizer textRecognizer =
        FirebaseVision.instance.cloudTextRecognizer();
    final VisionText visionText =
        await textRecognizer.processImage(visionImage);

    String text = visionText.text;
    for (TextBlock block in visionText.blocks) {
      print(block.text);
    }

    // 画面に反映
    setState(() {
      _text = Text(text);
      _image = Image.file(File(pickerFile.path));
    });

    // リソースの開放
    textRecognizer.close();
  }

  Future<void> _labeling() async {
    PickedFile pickerFile =
        await ImagePicker().getImage(source: ImageSource.camera);

    final File imageFile = File(pickerFile.path);
    final FirebaseVisionImage visionImage =
        FirebaseVisionImage.fromFile(imageFile);

    ImageLabeler labeler = FirebaseVision.instance.imageLabeler();
    List<ImageLabel> labels = await labeler.processImage(visionImage);
    String text = "";
    for (ImageLabel label in labels) {
      print(label.text);
      text += label.text + " ";
      print(label.confidence);
    }

    // 画面に反映
    setState(() {
      _text = Text(text);
      _image = Image.file(File(pickerFile.path));
    });

    // リソースの開放
    labeler.close();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              if (_text != null) _text,
              if (_image != null) SafeArea(child: _image),
            ],
          ),
        ),
        floatingActionButton:
            Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
          FloatingActionButton(onPressed: _ocr, child: Icon(Icons.photo_album)),
          FloatingActionButton(
              onPressed: _labeling, child: Icon(Icons.photo_camera))
        ]));
  }
}
```


まず、firebaseを利用するために以下の2行で初期化処理をしています。
`WidgetsFlutterBinding.ensureInitialized();`
`await Firebase.initializeApp();`

テキスト認識用とラベリング用のボタンがあり、それぞれが`_ocr`と`_labeling`に対応しています。

基本的な流れは同じですが、使用するAPIによって使用するインスタンスが異なります。

## テキスト認識(OCR)×写真ギャラリー

まず、ImagePickerを使って、ギャラリーからOCRで文字を読み取りたい画像を選択します。

その後、`VisionImage`と`TextRecognizer`のインスタンスを作成し、`TextRecognizer`に画像を与えて解析を行います。
そして、結果からtextで文字や詳細情報を取得します。

最後に、画面に表示させたあとリソースを開放しています。


## オブジェクトラベリング×カメラ

こちらも、まず、ImagePickerを使って、ギャラリーからOCRで文字を読み取りたい画像を選択します。

その後、`VisionImage`と`ImageLabeler`のインスタンスを作成し、`ImageLabeler`に画像を与えて解析を行います。
そして、結果(Labelのリスト)から認識されたラベルテキストを取得します。

最後に、画面に表示させたあとリソースを開放しています。


# 動作イメージ

ギャラリーのアイコンをタップした後、ストレージから本書の帯を選択したときの例

## テキスト認識(OCR)×写真ギャラリー
![pic4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/db0ce8ee-67a3-4aa0-2de7-7b612e57ad42.png)


```sh:result.sh
I/flutter (14560): Flutter実践入門
I/flutter (14560): 環境構築からAndroid/iOS/Webに
I/flutter (14560): 広告を付けて同時にリリース!!
```

コンソール及び画面に、写真から識別した文字が、1字の誤字もなく高い精度で変換されていることが分かります。


## オブジェクトラベリング×カメラ

カメラのアイコンをタップした後、時計を撮影したときの例

![pic5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/51d5a330-c75d-cfb9-3ac2-c783ce6758a7.png)


```sh:result.sh
I/flutter (18593): Clock
I/flutter (18593): 0.8892788887023926
```

写真が約90%の確率で時計であると認識されています。
今回は、時計のみの識別ですが、写真のとり方によってはいくつかの選択肢が提示されたりします。
また、複数のオブジェクトが写っている場合は、それぞれに識別が行われます。