1. HOME
  2. ブログ
  3. IT技術
  4. [Flutter] Firebase Cloud FunctionsとCloud Vision APIを使って画像のテキスト検出をしてみた

[Flutter] Firebase Cloud FunctionsとCloud Vision APIを使って画像のテキスト検出をしてみた

今回やったこと

Flutterアプリで画像の文字認識をしたいと思い、Firebase Cloud FunctionsとColoud Vision APIで使ってOCRアプリを作ってみました。
実際に作成したアプリとしては、カメラで撮った画像をFirebase Cloud Functionsを通してCloud Vision APIにリクエストし、検出した文字を次の画面に表示するといったものです。

使用したもの

  • Firebase Authentication(匿名認証のみ)
  • Firebase Cloud Functions
  • Cloud Vision API
  • camera(Flutterのカメラパッケージ)

デモ

環境

  • macOS: Venture 13.4
  • プロセッサ: Intel
  • Xcode: 14.3.1
  • Android Studio: Dolphin (2021.3.1)
  • Flutter SDK: 3.7.8

アプリの実装

手順

  • プロジェクトの作成
  • Firebaseの導入
  • Google Cloudの導入
  • Firebase Cloud Functions側の実装
  • Flutter側の実装
  • 動作確認

プロジェクトの作成

Flutterプロジェクトの作成をします。
私の場合、FVMでFlutter SDKのバージョンを管理しているため、以下のコマンドでプロジェクトを作成しました。

Firebaseの導入

ドキュメントを参考にFirebaseを導入していきます。

今回使用するFirebaseのプラグイン

今回使用するFirebaseのプラグインはAuthenticationとCloud Functionsです。
以下のドキュメント内容を参考に、AuthenticationとCloud Functionsを使ってCloud Vision APIを呼び出すように実装していきます。

アプリから Google Cloud API を呼び出すには、認可を処理し、API キーなどのシークレット値を保護するための中間 REST API を作成する必要があります。次に、モバイルアプリでこの中間サービスに対する認証と通信を行うためのコードを記述します。
この REST API を作成する方法の一つとして、Firebase Authentication と Functions を使用する方法があります。この方法では、Google Cloud API に対するサーバーレスのマネージド ゲートウェイが提供され、そこで認証が処理されます。このゲートウェイは、事前構築された SDK を使用してモバイルアプリから呼び出すことができます。

Firebase Auth と Functions を使用して Cloud Vision で画像内のテキストを安全に認識する(Apple プラットフォーム)

コマンドラインツールのインストール

Firebase CLIをインストールします。
Firebase CLIをインストールする方法はいくつかあるのですが、今回はnpmでインストールをしました。
npmでインストールする場合は以下のコマンドを実行します。

これにより、グローバルに使用できるfirebaseコマンドが有効になります。

次に以下のコマンドを実行し、Firebaseにログインします。

私の場合、以前ログインしたことがあり、Already logged in as 〇〇@gmail.comと表示されましたが、まだの方は流れに沿ってログインします。

Firebaseにログインできたら次に、任意のディレクトリで以下のコマンドを実行してFlutterFire CLIをインストールします。
任意のディレクトリで実行とのことなので、ホームディレクトリ(~)で以下のコマンドを実行しました。

ここまでで必要なコマンドラインツールのインストール完了です。

Firebaseの構成ファイルを作成

次のコマンドでアプリの構成ワークフローを実行します。

コマンド実行時に、既存のFirebaseプロジェクトを選択するか、Firebaseプロジェクトを新規作成する必要があります。
新規作成する場合は、一意のプロジェクトIDを入力する必要があり、すでに存在するIDだと作成が失敗してしまいます。
コンソールから新規プロジェクトを作成すると自動でIDが決定するので、コンソールで作成するのが個人的にオススメです。

上記コマンドを流れに沿って進めると、Firebaseの接続に必要なファイルがプロジェクトに追加されます。

使用するFirebaseのプラグインをインストール

FlutterアプリをFirebaseプロジェクトに接続するためのFirebase Core
ユーザー認証するためのFirebase Authentication
Cloud Vision APIを呼び出すために使用するFirebase Cloud Functions
以上の3つをFlutterプロジェクトにインストールしていきます。

以下のコマンドを実行して各プラグインをインストールします。

次に以下のコマンドでFirebaseの構成ファイルを更新します。

今回のアプリでは匿名認証を行うのため、Firebaseコンソールから匿名認証を有効化します。
コンソールのAuthenticationを開き、ネイディブのプロバイダの匿名を選択します。

トグルボタンを有効にして保存し、匿名認証を有効化します。

アプリでFirebaseを初期化

main関数でFirebase.initializeAppを定義し、Firebaseを初期化します。

Firebase Cloud Functionsの初期化

ドキュメントを参考に、以下のコマンドでFirebase Cloud Functionsを初期化します。

コマンドを実行すると、オプションの選択が求められます。
まず、既存のプロジェクトを使用するか、新規プロジェクトを使用するかなどを選択します。
今回の場合、すでにプロジェクトがあるので、既存のプロジェクトを選択し、今回のプロジェクトを選択します。
次に、Cloud Functionsで使用する言語を選択します。
JavaScriptとTypeScriptとPythonが選択でき、今回はTypeScriptを選択しました。
次にESLintを使用するかと、npm installを実行するかを聞かれたので、どちらもYを入力しました。
以上で実行が完了すると、プロジェクトにfunctionsディレクトリが作成されます。

Cloud Vision APIの有効化

Cloud Vision APIを使用するためには、Blaze料金プラン(従量課金制)にFirebaseプロジェクトをアップグレードする必要があります。
Cloud Vision APIは1000ユニット/月まで無料で使用できますが、1000ユニットを超えると1000ユニット単位で$1.5料金が発生するのでご注意ください。
※1つの画像に対してテキスト検出を行うと1ユニットがカウントされます。

Blaze料金プランにアップグレードするため、FirebaseコンソールのFirebase ML の[APIs]ページを開きます。
ページを開いたら、アップグレードボタンからBlaze料金プランへアップグレードを行います。

アップデートができたら、CloudベースのAPIを有効化のトグルが表示されるのでONにします。

次に上記手順で作成されたAPIキーのアクセスを制限します。
Cloudコンソールの認証情報ページを開きます。
ページを開いたら、キーを制限を選択し今回使用するCloud Functions APIとCloud Vision APIを選択して保存します。

以上でCloud Vision APIの有効化完了です。

Google Cloudの導入

以下のドキュメントを参考にCloud Vision APIを導入します。

gcloud CLIをインストール

プラットフォーム別にパッケージのファイルが用意されているので、自身のプラットフォームにあったファイルをダウンロードします。
ダウンロードしたファイルをホームディレクトリ(~)で開きます。

次に以下のコマンドでインストールスクリプトを実行し、gcloud CLIツールをPATHに追加します。

流れに沿ってコマンド実行が完了すると、gcloudコマンドが使用可能になります。

次に以下のコマンドでgcloud CLIを初期化します。

流れに沿ってコマンド実行が完了すると、Google Cloud SDKが使用可能になります。

Vision APIの有効化

以下のコマンドを実行し、Vision APIを有効にします。

クライアントライブラリをインストール

Cloud Vision APIのライブラリをインストールします。
今回、Firebase Cloud FunctionsではTypeScriptを使用しているので、以下のコマンドを実行しNode.jsのライブラリをインストールします。

Firebase Cloud Functions側の実装

以下のドキュメントを参考にFirebaes Cloud FunctionsでCloud Vision APIにリクエストする関数を実装していきます。

このコードでは、ImageAnnotatorClientを使用して画像のテキスト検出を行う関数を定義しています。
引数のdataでは、リクエスト時に必要な画像のデータ(Buffer)を受け取るようにします。
contextのユーザーの認証情報を使用し、ユーザーの認証がされていない場合、認証エラーを返します。
画像のテキスト検出が成功した場合、fullTextAnnotation.textで検出結果を取り出して返します。

エラーを返す時にHttpsErrorをスローしていますが、FunctionsではHttpsErrorをスローすることによりアプリ側でエラーの詳細を取得できるようになります。
HttpsErrorは引数のcodeにFunctionsのエラーコード、messageに文字列、オプションでdetails?に任意の値を設定することができます。
HttpsError以外をスローすると、エラーのcodeにinternal、messageにINTERNALが設定されたエラーが返されます。
Functionsのエラーコード

Flutter側の実装

カメラで撮影した画像のテキスト検出をし、次の画面で検出結果を表示するアプリを実装していきます。

カメラ画面の作成

以下のドキュメントを参考にカメラ画面を作成します。

まず以下のコマンドでカメラパッケージをインストールします。

次にiOSとAndroidそれぞれの設定をしていきます。
iOSではInfo.plistにNSCameraUsageDescriptionを設定します。
NSCameraUsageDescriptionにはアプリがデバイスのカメラへのアクセスを要求している理由を設定します。
設定するとカメラアクセスの許可を求めるダイアログに設定した文字が表示されます。
今回は使用しないのですが、マイクを使用する場合はNSMicrophoneUsageDescriptionも設定します。
NSMicrophoneUsageDescriptionにはアプリがデバイスのマイクへのアクセスを要求している理由を設定します。

Androidではandroid/app/build.gradleのminSdkVersionを21に設定します。

各プラットフォームの設定ができたので、カメラ画面を作成します。

このコードでは、cameraControllerの初期化とプレビューの表示をしています。
Future<void> _initializeCameraController()では、利用可能なカメラの一覧を取得し、外カメ、最高の解像度、録音OFFでCameraControllerを作成します。
Widget build(context)では、cameraControllerが初期化されている場合、縦横比を保ちながら画面最大まで拡大したプレビューを表示します。
注意点としては、高さと幅を指定する時に、画面の向きからPreviewSizeの高さと幅を切り替えているところです。
このようにしている理由は、CameraPreviewが画面の向きからPreviewSizeの向きを変えているためです。
以下はCameraPreviewの実装です。

実装を見ると、aspectRatioを画面の向きによって変えていることがわかります。
このことから、横に長いの長方形(PreviewSize)を縦画面の時には縦向きで表示し、横画面の時はそのまま横向きで表示しているような内部実装になっていることが考えられます。
ですので、それに合わせてCameraPreviewを表示する時にも、画面の向きからSizedBoxの高さと幅を切り替えています。

カメラ画面ができたので、main.dartを修正して初期画面でカメラ画面を表示するよう修正します。
今回はOcrAppというStatelessWidgetを作成し、MaterialAppのhomeでCameraScreenを定義しています。

撮影した画像のテキスト検出

以下のドキュメントを参考にテキスト検出をリクエストし、検出したテキストを表示する画面を作成します。

まず始めにテキスト検出をリクエストする部分の実装をしていきます。

このコードでは、テキスト検出をリクエストするtextDetectionという関数を実装しています。
リクエスト時には、ユーザー認証をしておく必要があるので、ユーザー認証がまだされていない場合、匿名認証をします。
ユーザー認証ができたら、Functionsで作成した関数(textDetection)を呼び出し、テキスト検出をリクエストします。

次に検出した文字を表示する画面を実装します。
今回は、読み込んだテキストを画面中央に表示する画面を作成しました。

最後にカメラ撮影をするボタンの処理を実装します。

このコードでは、画像データのテキスト検出をリクエストし、成功時に検出結果を渡して結果画面に遷移をしています。
まず_cameraController!.takePicture()でカメラ撮影をします。
次に撮影した画像をreadAsBytes()でUint8Listのデータに変換し、repository.textDetectionでテキスト検出をリクエストします。
リクエストが成功したら、Navigatorのpushで検出結果を渡して結果画面に遷移します。

動作確認

以下のコマンドを実行し、Functionsの関数をデプロイします。

デプロイが完了したら、アプリを起動して確認します。

おわりに

今回、Cloud Vision APIを使ってOCRアプリを作ってみましたが、実装していく中でカメラプレビューのレイアウト調整が難しいなと感じました。
Flutterでは、ネイティブの画面を表示することもできるらしいので、ネイティブでカメラ画面を実装してみてFlutterに表示することにもチャレンジしてみたいです。

また、テキスト検出のレスポンスは、どう扱うかが大変そうだなと思いました。
レスポンスには、ページ、ブロック、段落、単語、改行の情報が含まれるのですが、どこでブロックが分かれるかなどわからないので、欲しい情報をどう取り出すかが難しそうだなと思いました。
Vision APIはこちらで簡単に試すことができるので、気になる方は試してみてください。

書いた人はこんな人

いまむー(エンジニア)
いまむー(エンジニア)
業務ではiOS開発に携わらせていただいています。
まだまだ分からないことだらけで、日々分からないことと戦いながら仕事をしている者です。
ブログ記事は暖かい目で見ていただけるとありがたいです。

関連記事

採用情報

\ あの有名サービスに参画!? /

バックエンドエンジニア

\ クリエイティブの最前線 /

フロントエンドエンジニア

\ 世界を変える…! /

Androidエンジニア

\ みんなが使うアプリを創る /

iOSエンジニア