かくすけのいろいろ作るブログ

かくすけの開発者ブログです。開発の他いろいろなモノづくりについて書きます。

【Amazon API Gateway】【AWS Lambda】【Ruby】【C#】Google Cloud Vision API を使って画像内の文字を取得する

こんにちは。かくすけです。
今回はGoogleのCloud Vision APIを使って画像内の文字を取得(OCR)させます。

最終的に完成させたいアプリ

kakusuke98.hatenablog.com

前回の記事

kakusuke98.hatenablog.com

前回はWindowsの画面の範囲キャプチャを取得するところまで作りました。
今回はその画像からGoogle Cloud Vision APIを使って文字取得します。

目次

  • 今回の記事で開発する内容
  • Google Cloudのプロジェクト設定
    • 認証情報の準備
  • Google Cloud Vision API を使った文字取得方法の確認
  • AWS Lambda でAPIを呼び出す
  • Amazon API Gateway の準備
  • C#からのAPI呼び出し
  • 文字認識結果

今回の記事で開発する内容

今回はいろいろなサービスを使用します。
全体像はこんな感じ

f:id:kakusuke98:20190822221049p:plain

Windows Form App】
前回作った「Windowsの画面の一部を画像として取得する」アプリです。
このアプリで取得した画像を Amazon API Gateway に送信します。

Amazon API Gateway
今回 Windows Form App とAWS Lambda の橋渡しをする役割を持ちます。
Lambda関数を別のアプリから呼び出すためにはこの Amazon API Gateway を使う必要があります。

AWS Lambda】
サーバーのことを気にせず、呼び出したいときにだけ使用できる関数を作成できます。
今回は Amazon API Gateway から受け取った画像データを Google Cloud Vision API に送り 、画像内の文字列を受け取ります。
受け取った文字列を Amazon API Gateway に受け渡します。

Google Cloud Vision API
このサービスに画像を渡すと、画像内に含まれている文字列を取得してくれます。

なぜ Windows Form App から直接 Google Cloud Vision API に画像を送らなかったかというと、将来的にサーバー側でログをとったり不正な呼び出しを制限するためです。

GoogleCloudプロジェクト設定

公式の次ページの項目「プロジェクトを設定する」を参考にGoogle Cloud Vision APIのプロジェクトを準備します。
今回はCloud Storage を使わない方式で使用するので、「Cloud Storage バケットを作成する」以降は行いません。

クイックスタート  |  Cloud Vision API ドキュメント  |  Google Cloud

プロジェクト作成→課金許可設定→Cloud Vision API の有効化
ページの通りにやればできるはず!

※この機能は課金が発生しますので使いすぎには気を付けましょう

認証情報の準備

APIでアクセスするための認証情報を準備します。
Google Cloud Platform のコンソール画面から認証情報を作成します。

Google Cloud Platform

f:id:kakusuke98:20190816000802p:plainf:id:kakusuke98:20190816001005p:plain

使用する認証方法を聞かれます。
今回はGoogle関連のサービスのユーザーデータにアクセスする必要はないので「APIキー」を使用します。
f:id:kakusuke98:20190816001449p:plain

APIキーが生成されました。このキーは絶対に外部の人に見られないように気を付けましょう!
f:id:kakusuke98:20190816001652p:plain

これでGoogleCloudプロジェクトの設定はおしまいです。

ちなみに以下の記事ではYoutubeの自分の動画一覧を取得する必要があったのでOathを使用しました。

kakusuke98.hatenablog.com

Google Cloud Vision API を使った文字取得方法の確認

実際に画像から文字取得させる方法を確認しました。

OCR機能の使用方法の説明ページ(公式)
cloud.google.com

RESTで処理を呼び出します。
今回はOAuthではなくAPI Keyでの認証なので、ヘッダーではなくURLパラメータに認証情報(key)を持たせることで認証します。上の公式ページの例はOAuth式なのでページの通りヘッダーにKayを持たせようとしてちょっとつまりました。
上のページには画像URLを渡す方法が例になっているのですが、私は画面キャプチャしたローカル画像データから文字取得させたいので↓の画像赤枠部分の「base64 エンコード文字列」で渡す方法を確認します。

f:id:kakusuke98:20190816002650p:plain

赤枠部分のリンク先がこちら
Make a Vision API request  |  Cloud Vision API Documentation  |  Google Cloud

↓のような形式でデータを送ればいいみたいですね。
f:id:kakusuke98:20190816003020p:plain

AWS Lambda でAPIを呼び出す

Lambda関数の作り方は前に書いた記事↓とほぼ同じなので、そちらを参考にされてください。

kakusuke98.hatenablog.com

今回は上で書いた通り、 Amazon API Gateway から受け取った画像データを Google Cloud Vision API に送る関数を準備しました。
今回作成した関数の内容(ruby)

require 'json'
require 'rest-client'

#--------------------------------------------
# メインメソッド
# Amazon API Gatewayから受け取った画像をGoogle Cloud Vision APIに送る
#--------------------------------------------
def google_ocr_handler(event:, context:)
  url = "https://vision.googleapis.com/v1/images:annotate?key=#{ENV['API_KEY']}"
  payload = {
    requests: [{
      image: {
        content: event['body']
      },
      features: [
        {
          type: 'TEXT_DETECTION',
          maxResults: 1
        }
      ],
      imageContext: {
        languageHints: 'en'
      }
    }]
  }
  header = {:content_type=>"text/html; charset=utf-8"}
  RestClient.post(url, payload.to_json, header) { |response, request, result|
    case response.code
    when 200
      parse_response =  JSON.parse(response)
      lambda_response_body = parse_response['responses'][0]['textAnnotations'][0]['description']
      return { statusCode: 200, body: lambda_response_body }
    else
      return { statusCode: 500, body: 'error' }
    end
  }
end

項目「認証情報の準備」で作成したAPI KeyをLambdaの環境変数(API_KEY)として登録しています。
f:id:kakusuke98:20190822223229p:plain

これが Google Cloud Vision API に送る内容です。
「content: event['body']」ここの"event['body']"は Amazon API Gateway から受け取った内容、つまり文字取得させたい画像の base64 エンコード文字列 です。
また、「languageHints: 'en'」は必須ではないのですが、画像に含まれている言語をここで指定することで認識の精度が上がるそうです。今回は英語が含まれている画像を送るので'en'と指定しています。
f:id:kakusuke98:20190822223406p:plain

ここが実際に画像を送り、文字列を取得する部分です。
ここで注意。return で返している内容がポイントです。Amazon API Gateway を使ってLambda関数を呼び出す場合には必ず「{statusCode: 三桁数字, body '文字列'}」という形でなければエラーが返ってしまいます。
今回は返したい内容が文字列だったので良かったのですが、もっと複雑な内容を返したい場合には困りそうですね。無理矢理文字列にして送って、受け取った側で分解しないといけないのかな?
f:id:kakusuke98:20190822223918p:plain

Amazon API Gateway の準備

前項目で作成したLambda関数を呼び出すための Amazon API Gateway を準備します。
ここはすみません。メモも取らずにサカサカやっちゃったのであまりまとめられません。

こちらのページを参考にさせていただいて、特に問題なく進めることができました。

qiita.com

C#からのAPI呼び出し

いよいよ最終段階!
Windows Form App から Amazon API Gateway を呼び出すメソッドです。

C#からのAPI呼び出しについてはこちらのページを参考にさせていただきました
www.ipentec.com

        // 取得した画像のテキストを抽出する(Google Vision API)
        // snap: 文字取得させたいBitmap
        private void RunOcr(Bitmap snap)
        {
            string url = string.Format("{Amazon API Gateway 呼び出し用のURL}");
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            request.Headers.Set("x-api-key", "{Amazon API Gateway 用のAPI Key}");
            request.Method = "POST";
            request.ContentType = "text/plain";

            // Bitmap(snap)をbyteに変換
            System.IO.MemoryStream memory_stream = new System.IO.MemoryStream();
            snap.Save(memory_stream, System.Drawing.Imaging.ImageFormat.Bmp);
            byte[] imageBytes = memory_stream.ToArray();
            string base64String = Convert.ToBase64String(imageBytes);
            request.SendChunked = true; 

            try
            {
                // API Gateway を呼び出す
                using (StreamWriter writer = new StreamWriter(request.GetRequestStream()))
                {
                    writer.WriteLine(base64String);
                }
                var response = (HttpWebResponse)request.GetResponse();
                var responseString = new StreamReader(response.GetResponseStream()).ReadToEnd();
                System.Diagnostics.Trace.WriteLine(responseString);  // 返ってきた文字列を Visual Studio のコンソールに出力する
            }
            catch (WebException e)
            {
                System.Diagnostics.Trace.WriteLine(e);
            }
        }

このメソッドを呼び出すことで画像内に含まれる文字列を取得できます。
引数として文字取得したいBitmap画像要素を渡します。

ここでBitmapをBase64に変換しています。
f:id:kakusuke98:20190822225809p:plain

ここが実際の通信部分。
うまくいってもいかなくてもとりあえず返ってきた内容をVisual Studioのコンソールに出力します。
f:id:kakusuke98:20190822225900p:plain

文字認識結果

今回開発した機能を実際に使ってみました。
このアプリはゲーム画面翻訳するために作っているのでゲーム画面を使わせていただきました。気になっていたけど日本語対応していないので今までプレイできていなかったゲーム「The Wolf's Bite」の画面に含まれる英語を取得します。
作成したWindows Form Appで以下ゲーム画面の赤枠部分を画像として取得し、APIに送ってみました。
f:id:kakusuke98:20190822230724p:plain

そして結果は・・・
f:id:kakusuke98:20190822231213p:plain

完璧!!!!

このゲームはテキスト表示がはっきりしているものうまくいった理由だと思いますが、ここまで完璧だと嬉しくなります!
いろいろなゲーム画面で試さないとですね。

次回は今回取得した英文を Google Translate API で日本語に翻訳します。
長文になりましたがお読みくださりありがとうございました!