【Flutter】firebase_ml_visionでリアルタイムOCRを実装する

【Flutter】firebase_ml_visionでリアルタイムOCRを実装するアイキャッチ 技術メモ
本多
本多

ハマらなかったら、おそらく30分くらいで作れると思います。精度が良くてびびっています。

今回作るもの
リアルタイムOCRのサンプル

はじめに、前提など

前提1:Flutterの環境OK

この記事にたどり着く人なら、大丈夫だと思いますが、Flutterの環境構築が完了していることを前提としています。

もし、まだの人は以下の記事を参考に、環境構築を済ませておきましょう。

前提2:iOSのみ動作確認しています

iOSの実機でのみ動作確認しています(Android版はまた別の設定が必要です)。

ちなみに、Android端末でビルドしようとしたら、上手くいかず沼にハマっています。誰か助けてください。

firebase_ml_visionでリアルタイムOCRを実装する手順

1.必要な3つのパッケージをインストール

必要なパッケージは「camera」「camera_platform_interface」と「firebase_ml_vision」の3つです。以下のようにpubspec.yamlファイルに追記して、Pub getします。

dependencies:
  flutter:
    sdk: flutter


  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.1
  camera: ^0.6.3
  camera_platform_interface: ">=1.0.4 <1.1.0"
  firebase_ml_vision: ^0.9.10

「camera_platform_interface: “>=1.0.4 <1.1.0″」を追加する理由は、cameraパッケージの0.6.3などを取り込む場合、これを追加しないと、リアルタイムでカメラの映像を取り込めないバグがあります。(以下にIssueのリンクを貼りました)

2.podfileに必要な情報を記載

まず、プロジェクトルート>ios>Podfileを開きます。

次に、冒頭2行目のコメントアウトを外して、以下のようにします。

platform :ios, '9.0'

最後に、target ‘Runner’ do の後に、1行追記します。

target 'Runner' do
  pod 'Firebase/MLVisionTextModel'  ←追加

  use_frameworks!
  use_modular_headers!

  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end

ちなみに、今回はOCRなので、テキスト認識用のモデルライブラリをインストールするようにしましたが、使いたい機能によって、以下をインストールしても良いです。

pod 'Firebase/MLVisionBarcodeModel'
pod 'Firebase/MLVisionFaceModel'
pod 'Firebase/MLVisionLabelModel'
pod 'Firebase/MLVisionTextModel'

3.Firebaseにアプリを追加しておく【重要】

詳しくは説明しませんが、Firebaseのプロジェクトを作成して、以下のように、アプリを追加しておきましょう。

firebaseでアプリを追加する画面

そして、ダウンロードした設定ファイル「GoogleService-Info.plist」を以下のようにxcodeから格納します。(ディレクトリに格納するだけではダメな時があった。)

GoogleService-InfoをRunner配下に格納する
Runner>Runner配下に格納する
本多
本多

この辺でビルドしておきますか。iOS実機デバッグでビルドしてみます。

4.コードを書いていく

コードを書いていきます。分かりやすいようにStatefulWidgetで書いていきます。

まずは、以下のように雛形を作っておきましょう。

import 'package:flutter/material.dart';

class RealtimeOcrCamera extends StatefulWidget {

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

class _RealtimeOcrCameraState extends State<RealtimeOcrCamera> {
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(),
    );
  }
}
また・・・

main.dartのほうで、RealtimeOcrCameraクラスに遷移するようにしておきましょう。

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.blue,
      ),
      home: RealtimeOcrCamera(), /// ★ここ変更!
    );
  }
}

1.必要な変数を定義

必要な変数は以下の3つです。コピペしておきましょう。

class _RealtimeOcrCameraState extends State<RealtimeOcrCamera> {

  /// OCRした文字
  var ocrText = '';

  /// カメラコントローラー
  CameraController cameraController;

  /// テキスト検出処理の途中かどうか
  bool detecting = false;

2.UIを書いていく

適当にUIを書きます。これもコピペでOKです。CameraPreviewの使い方はcameraパッケージのページにいい感じに書いてあります。

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Stack(
          alignment: Alignment.bottomCenter,
          children: [
            /// カメラコントローラーの初期化が終わっていたらCameraPreviewを表示
            (cameraController != null && cameraController.value.isInitialized) ?
            AspectRatio(
                aspectRatio:
                cameraController.value.aspectRatio,
                child: CameraPreview(cameraController)) : 
            Container(),
            
            /// 適当にOCRテキストを表示
            Positioned(
              bottom: 100,
              child: Text(
                ocrText,
                style: TextStyle(
                  color: Colors.greenAccent,
                  fontSize: 40.0,
                ),
              ),
            ),
          ],
        )
    );
  }

3.カメラのInitialize処理をする

ここでは、カメラの映像をリアルタイムに表示させるようにします。

画面が生成されたタイミング(initState()内)に処理を書いていきます。

まず、カメラの使用許可を取れるようにする

iOSでカメラ使用の許可画面を出せるように「ios>Runner>info.plist」ファイルに以下の4行を追記します。

<key>NSCameraUsageDescription</key>
<string>Can I use the camera please?</string>
<key>NSMicrophoneUsageDescription</key>
<string>Can I use the mic please?</string>
こんな感じ
	</array>
	<key>UIViewControllerBasedStatusBarAppearance</key>
	<false/>
	<key>NSCameraUsageDescription</key>
    <string>Can I use the camera please?</string>
    <key>NSMicrophoneUsageDescription</key>
    <string>Can I use the mic please?</string>
</dict>
使用できるカメラを取得して、Initializeする

ここからは画面が生成されたタイミングでカメラの初期化をするようにしてあげましょう。

  @override
  void initState() {
    super.initState();

    _initializeCamera();
    
  }

  /// カメラのInitialize処理などを行う関数
  Future<void> _initializeCamera() async {

    // 使用可能なカメラを取得して、カメラコントローラーのインスタンス生成
    final cameras = await availableCameras();
    cameraController = CameraController(cameras[0], ResolutionPreset.high, enableAudio: false);

    // カメラコントローラーの初期化
    await cameraController.initialize();

    // カメラの映像の取り込み開始
    await cameraController.startImageStream(_detectText);
  }

  /// テキストを検出する関数
  _detectText(CameraImage availableImage) async {

  }
本多
本多

あんまり好きじゃない書き方だけど、まぁ動くし、いいか。ちなみに、この状態でビルドしても画面更新(setState())してないので、Initializeが終わったら、▶ボタン押してあげるとカメラの映像が見えます。

4.カメラから取り込んだ映像にOCRを実行する

最後に、カメラで取り込んだ情報を基にテキスト認識をさせます。

上記 _detectText() の中身を以下のように書き換えます。

  /// テキストを検出する関数
  _detectText(CameraImage availableImage) async {
    // 前呼ばれたテキスト検出処理が終わってない場合は、即return
    if (detecting) {
      return;
    }

    // 検出中フラグを立てておく
    detecting = true;

    // スキャンしたイメージをFirebaseMLで使える形式に変換する
    final metadata = FirebaseVisionImageMetadata(
      rawFormat: availableImage.format.raw,
      size: Size(availableImage.width.toDouble(), availableImage.height.toDouble(),),
      planeData: availableImage.planes.map((currentPlane) => FirebaseVisionImagePlaneMetadata(
        bytesPerRow: currentPlane.bytesPerRow,
        height: currentPlane.height,
        width: currentPlane.width,
      ),).toList(),
    );

    final visionImage = FirebaseVisionImage.fromBytes(
      availableImage.planes.first.bytes,
      metadata,
    );

    // テキストを検出する
    final textRecognizer = FirebaseVision.instance.textRecognizer();
    final visionText = await textRecognizer.processImage(visionImage);

    // 検出中フラグを下げる
    detecting = false;

    setState(() {
      ocrText = visionText.text;
    });
  }

全ソースコードは以下に格納してあります。

5.実機デバッグしてみる

では、実機でデバッグしてみましょう。

記事の冒頭のようなアプリができるかと思います。

※テキストのレイアウトはいい感じにしてください。

まとめ

お疲れさまでした!

もし、不明点や不備があれば、気軽にコメントいただけると助かります。

ここまで、読んでいただきありがとうございます。

コメント

  1. […] […]

タイトルとURLをコピーしました