mokajima.com

Cloud Vision API を使って手書き文字をテキストデータ化する

はじめに

勉強メモを A4 コピー用紙に書くことが多かったのですが、正直場所を取るし、意外と見直さないし…という状態だったため、OCR(Optical Character Recognition)でテキストデータ化を試してみることにしました。

やったこと

Google Cloud Platform(以下、GCP)の Cloud Vision API を使い、OCR を行う Node.js スクリプトを TypeScript で書きました。コードはこちらです。

https://github.com/mokajima/vision-api-demo

使い方

$ GOOGLE_APPLICATION_CREDENTIALS=./serviceAccount.json ts-node index.ts -f './image.jpg'
$ GOOGLE_APPLICATION_CREDENTIALS=./serviceAccount.json node index.js -f './image.jpg'

ts-node index.tsnode index.js を実行します。スクリプトの実行に成功すると、実行結果が result.txt というファイル名で出力されます。

環境変数 GOOGLE_APPLICATION_CREDENTIALS に GCP のサービス アカウントキーが含まれる JSON ファイルのパスを指定します。GCP のサービス アカウントキーの取得方法については後述します。

-ffileName の略で、OCR を行う画像ファイルのパスを指定します。

GCP のサービス アカウントキーの取得

GCP のサービス アカウントキーを取得するには、GCP のプロジェクトを作成する必要があります。GCP コンソールのヘッダーの「プロジェクトの選択」をクリックし、「プロジェクトの選択」ダイアログを表示します。

select-project.png

「プロジェクトの選択」ダイアログの右上の「新しいプロジェクト」をクリックし、「新しいプロジェクト」ページに遷移します。

to-new-project.png

「新しいプロジェクト」ページで「作成」をクリックします。「プロジェクト名」「場所」は初期値のままで問題ありません。この記事では「プロジェクト名」を「vision-api-demo」としました。

new-project.png

プロジェクトの作成が完了すると、右上に通知が表示されます。「プロジェクト『vision-api-demo』を作成」の下の「プロジェクトを選択」クリックし、プロジェクトのダッシュボードに遷移します。

notification.png

次に、GCP コンソールのヘッダーの検索バーに「Cloud Vision API」と入力します。検索結果の「MARKETPLACE」>「Cloud Vision API」を クリックし、Cloud Vision API のページに遷移します。

to-cloud-vision-api.png

Cloud Vision API のページの「有効にする」クリックし、Cloud Vision API を有効化します。

activate-cloud-vision-api.png

次に、ナビゲーションメニューの「IAM と管理」>「サービス アカウント」をクリックします。

to-service-accounts.png

「プロジェクト『vision-api-demo』のサービス アカウント」の上の「サービス アカウントを作成」をクリックします。

create-service-account.png

「サービス アカウントの詳細」ページで「サービス アカウント名」と「サービス アカウント ID」を入力し、「作成して続行」をクリックします。この記事では「サービス アカウント名」を「サービス アカウント」としました。

service-account-detail.png

「このサービス アカウントにプロジェクトへのアクセスを許可する (省略可)」で「続行」をクリックします。

allowing-service-account-to-access.png

「ユーザーにこのサービス アカウントへのアクセスを許可 (省略可)」で「完了」をクリックします。

allowing-user-to-access.png

サービス アカウントの作成が完了すると、「プロジェクト『vision-api-demo』のサービス アカウント」ページに遷移します。作成したサービスアカウント ID(「メール」カラムの値)をクリックします。

projects-service-account.png

「詳細」タブの内容が表示されます。「キー」タブをクリックします。

detail.png

「鍵を追加」>「新しい鍵を作成」をクリックします。

key.png

「『サービス アカウント』の秘密鍵の作成」ダイアログで「キーのタイプ」>「JSON」を選択します。「作成」をクリックすると、秘密鍵をダウンロードします。この JSON ファイルがサービス アカウントキーで、使い方serviceAccount.json です。

create-service-account-key.png

コード

import { ImageAnnotatorClient } from '@google-cloud/vision'
import { program } from 'commander' // (1)
import * as fs from 'fs'

// Creates a client
const client = new ImageAnnotatorClient()

program // (2)
  .requiredOption('-f, --fileName <fileName>')
  .action(async () => { // (3)
    const { fileName } = program.opts() // (4)

    try {
      const [{ fullTextAnnotation }] =
        await client.documentTextDetection(fileName) // (5)

      fs.writeFile( // (6)
        './result.txt',
        fullTextAnnotation?.text ?? '',
        (error) => {
          if (error) {
            throw error
          }
        },
      )
    } catch (error) {
      console.error(error)
    }
  })

program.parse(process.argv) // (7)

(1) Commander.js

Commander.js は Node.js で Command Line Interface(以下、CLI)を作成するためのライブラリです。

(2) program

commander からインポートした programCommand クラスのインスタンスです。commander から Command クラスをインポートし、new Command() することも可能です。1

requiredOption() はスクリプト実行時に必須オプションを渡せるようにするためのメソッドです。requiredOption() の第1引数はフラグ名です。-f, --fileName <fileName> のように フラグ名 <任意の名前> とすることで、オプションが真偽値以外の値を取ることができます。なお、オプションの値は program.opts() で取得可能ですが、<任意の名前>program.opts() の返り値に影響しません。そのため、.requiredOption('-f, --fileName <fileName>').requiredOption('-f, --fileName <value>') のようにすることも可能です。

(3) action

action() に CLI 実行時の処理を記述しています。

(4) program.opts()

program.opts()fileName を取得しています。

(5) client.documentTextDetection()

client.documentTextDetection() で OCR を実行します。レスポンスは配列で解決する Promise で、型定義は Promise<[protoTypes.google.cloud.vision.v1.IAnnotateImageResponse]> となっています。IAnnotateImageResponse の型定義は次の通りです。

/** Properties of an AnnotateImageResponse. */
interface IAnnotateImageResponse {
  /** AnnotateImageResponse faceAnnotations */
  faceAnnotations?: google.cloud.vision.v1.IFaceAnnotation[] | null;

  /** AnnotateImageResponse landmarkAnnotations */
  landmarkAnnotations?: google.cloud.vision.v1.IEntityAnnotation[] | null;

  /** AnnotateImageResponse logoAnnotations */
  logoAnnotations?: google.cloud.vision.v1.IEntityAnnotation[] | null;
  /** AnnotateImageResponse labelAnnotations */
  labelAnnotations?: google.cloud.vision.v1.IEntityAnnotation[] | null;

  /** AnnotateImageResponse localizedObjectAnnotations */
  localizedObjectAnnotations?:
    | google.cloud.vision.v1.ILocalizedObjectAnnotation[]
    | null;

  /** AnnotateImageResponse textAnnotations */
  textAnnotations?: google.cloud.vision.v1.IEntityAnnotation[] | null;

  /** AnnotateImageResponse fullTextAnnotation */
  fullTextAnnotation?: google.cloud.vision.v1.ITextAnnotation | null;

  /** AnnotateImageResponse safeSearchAnnotation */
  safeSearchAnnotation?: google.cloud.vision.v1.ISafeSearchAnnotation | null;

  /** AnnotateImageResponse imagePropertiesAnnotation */
  imagePropertiesAnnotation?: google.cloud.vision.v1.IImageProperties | null;

  /** AnnotateImageResponse cropHintsAnnotation */
  cropHintsAnnotation?: google.cloud.vision.v1.ICropHintsAnnotation | null;

  /** AnnotateImageResponse webDetection */
  webDetection?: google.cloud.vision.v1.IWebDetection | null;

  /** AnnotateImageResponse productSearchResults */
  productSearchResults?: google.cloud.vision.v1.IProductSearchResults | null;

  /** AnnotateImageResponse error */
  error?: google.rpc.IStatus | null;

  /** AnnotateImageResponse context */
  context?: google.cloud.vision.v1.IImageAnnotationContext | null;
}

今回は fullTextAnnotation プロパティから実行結果のテキストを取得しています。fullTextAnnotation プロパティは pagestext をプロパティに持つオブジェクトです。

https://googleapis.dev/nodejs/vision/latest/google.cloud.vision.v1.ITextAnnotation.html

(6) fs.writeFile()

実行結果を fs.writeFile()result.txt として出力しています。

(7) program.parse(process.argv)

program.parse(process.argv) は引数を処理し、オプションによって使われなかった引数を program.args 配列に残します。たとえば、次のコマンドを実行した場合、program.args['foo', 'bar'] となります。

$ GOOGLE_APPLICATION_CREDENTIALS=./serviceAccount.json ts-node index.ts -f './image.jpg' foo bar

実験

3枚の画像で OCR を実行してみました。

実験1: 英語の手書き文字

公式ドキュメントの画像を試した結果はこちらです。

Google Cloud
Platform

実験2: 日本語の手書き文字

文章を青空文庫から引用し、実験用の画像を用意しました。

pollanno-square.jpg

この画像を試した結果はこちらです。

あのイーハトーヴオのすきとおった風,
夏でも底に冷たさをもつ青いそら
うつくしい森で飾られたモリーオ市
郊外のぎらぎらひかる草の波

実験3: 英語と日本語の混ざった手書き文字

文章をCloud Vision のドキュメント  |  Cloud Vision API  |  Google Cloudから引用し、実験用の画像を用意しました。

cloud-vision-api.jpg

この画像を試した結果はこちらです。

Cloud Visionを使用すると開発する
アプリケーションの中で簡単に画像検出機
能を統合できま. この機能の例としては、
画像ラベリング,顔やランドマークの検出
1 光学文字認識 LOCR), 石のなコンテンツ
へのタグ付けがあります。

実験1、2の画像は正しく認識できていましたが、実験3の画像では正しく認識できない箇所がちらほらある結果となりました。英語と日本語が混ざることで精度が落ちるのかどうかは不明です。実験1、2の画像と比べて実験3の画像のテキストの方が複雑な字形の文字が多いため、精度に差が出た可能性もありそうです。書き方の癖の有無によっても精度が大きく異なりそうですね。

おわりに

Cloud Vision API を使って手書き文字のテキストデータ化を試してみました。実験の後、実際の勉強メモの画像でも試してみましたが、実験3と同じような結果でした。勉強メモは自分が読める程度の綺麗さで書いていることも多いため、OCR でデータ化するのはなかなか難しいのかもしれませんね。今回は手書き文字のテキストデータ化自体を目的として Cloud Vision API を使ってみましたが、何かしらのアプリケーションの中で使うのも楽しそうです。

参考