画像認識 API で OCR アプリを実装してみた

Ateam Lifestyle x cyma Advent Calendar 2018 の 9 日目は、知的好奇心が満たされればアプリを仕上げなくても満足してしまいがちなエンジニア、cyma の @okoshi が担当いたします。

最近、AI に詳しい友人と話をしていたところ画像認識技術が話題になり、Microsoft Azure の Computer Vision というサービスで画像認識を簡単に実現できるということを知りました。
そこで、どれだけ簡単に、実用性が見込めるアプリを作ることができるのか挑戦してみました。
早速「[Computer Vision API を利用した画像処理 | Microsoft Azure](https://azure.microsoft.com/ja-jp/services/cognitive-services/computer-vision/)」を読んでみました。機能がいくつかあってどれに取り組むか迷いましたが、実用性があって簡単に実現できそうな機能は OCR かなと思ったので、とりあえず OCR を作ってみました。

# 想定する読者

Swift で iPhone アプリを開発することができる方で、画像認識アプリの開発に興味がある方。

# ご注意

本ページでは、すべてのプログラムを記載しません。GitHub 上に公開しますので、後述のハイパーリンクから GitHub 上にある Xcode プロジェクトをごらんください。GitHub 上のプログラムソースには Bundle ID や API キーなど、筆者個人のための情報は除いた状態にしてありますのでそのままでは動きません。試してみたい方はご自分で埋め込んでお楽しみください。

# 意識したこと

- すばやく簡単に作れること・・・簡単に実現できそうと思ってはじめたのにそうでなかったら意味ないよねってことで。サーバーを用意する必要がないのもポイント。
- 手軽に読み取れること・・・作るのは楽でも簡単に使えなければ意味がないので入力デバイスはスマホにしました。私は iPhone ユーザーですので iPhone で実装します。
- 読み込んだ文字は編集しやすいこと・・・どのような OCR でも 100% 正確に読み取れるとは限らないので、読み取れなかった箇所はすぐに修正できる状態できれば嬉しいと思い、Google スプレッドシートに出力して編集しやすいようにしました。

先述の通り実用性を見込んでいて、入力デバイスがスマホなので、紛失時に備えてデータを端末内に残さないために出力先を端末外にするという意味もあります。

# 使用するサービス

- [Computer Vision API](https://docs.microsoft.com/ja-jp/azure/cognitive-services/computer-vision/home)
- [Google App Script](https://developers.google.com/apps-script/)
- [Google スプレッドシート](https://www.google.com/intl/ja_jp/sheets/about/)

# サービスの構成とデータの流れのイメージ

![ocrdemo.png](https://qiita-image-store.s3.amazonaws.com/0/64294/1fdb37eb-e3af-0a39-4c01-b9bd36b556e1.png)

<dl>
  <dt>(1) 写真撮影</dt>
  <dd>作る OCR アプリで写真を撮ります。</dd>
  <dt>(2) 画像送信</dt>
  <dd>OCR アプリ写真撮影後、即座に Computer Vision API に画像を送信します。</dd>
  <dt>(3) テキスト受信</dt>
  <dd>OCR アプリは Computer Vision API から送られてくる写真から読み取ったテキストを受信します。</dd>
  <dt>(4) テキスト送信</dt>
  <dd>OCR アプリは Google App Script のエンドポイントに受信したテキストを送信します。</dd>
  <dt>(5) 書き込み</dt>
  <dd>Google App Script は受け取ったテキストを Google スプレッドシートのセル A:1 に書き込みます。</dd>
</dl>

# できたもの

![ocr-ss.png](https://qiita-image-store.s3.amazonaws.com/0/64294/93269607-f794-61c2-41e7-5e1c65ce86af.png)

Google スプレッドシート A:1 に、カメラで読み取った文字列を表示しています。手書きの文字がセルに表示されているのがわかると思います。

# 作り方

ここでは、要点となる部分をかいつまんで解説します。

## Computer Vision API の アカウントを作成

ここははじめて触る方も多いと思うので、ちょっと詳しめにスクリーンショットを用意しました。

- [こちら](https://azure.microsoft.com/ja-jp/services/cognitive-services/computer-vision/)からアカウントを作りましょう。無料アカウントでかまいません。
- Cognitive Services から Computer Vision のエンドポイントを作成します。
<img width="428" alt="スクリーンショット 2018-12-05 1.59.09.png" src="https://qiita-image-store.s3.amazonaws.com/0/64294/25e5af48-ab4b-12f0-284a-4ae8d10ee79a.png">
<img width="869" alt="スクリーンショット 2018-12-05 2.00.09.png" src="https://qiita-image-store.s3.amazonaws.com/0/64294/0b799ad9-f26b-eb25-a1d6-459daa5a5317.png">
<img width="590" alt="スクリーンショット 2018-12-05 2.01.02.png" src="https://qiita-image-store.s3.amazonaws.com/0/64294/a656179a-b7bb-28fe-6320-843a644d1e08.png">
<img width="576" alt="スクリーンショット 2018-12-05 2.02.04.png" src="https://qiita-image-store.s3.amazonaws.com/0/64294/c8a64ffc-30a1-02ce-5226-a27b81e56b47.png">
<img width="845" alt="スクリーンショット 2018-12-05 2.06.22.png" src="https://qiita-image-store.s3.amazonaws.com/0/64294/08b6863e-eeaf-e0dd-4004-c48cb8796374.png">
- Keys ハイパーリンクのページで表示される Reference Key をメモします。KEY 1をコピーしましょう。
<img width="843" alt="スクリーンショット 2018-12-05 2.05.28.png" src="https://qiita-image-store.s3.amazonaws.com/0/64294/0d5332cb-a48f-1715-c02a-6cb6384b70a8.png">
<img width="481" alt="スクリーンショット 2018-12-05 2.08.27.png" src="https://qiita-image-store.s3.amazonaws.com/0/64294/efd6e920-6516-d354-2f52-eea7cce22bba.png">

## Google App Script から Google スプレッドシートを操作する Web アプリケーションを作成

最初にスプレッドシートを作成し、スクリプトエディタに以下のプログラムをコピペしてください。
YOUR-SPREADSHEET-URL-DOCUMENT-ID の部分は、テキストを出力するスプレッドシートの URL の一部です。
そして、メニューの“公開”から、“Webアプリケーションとして導入...”を選択し、公開してください。
このとき、アプリケーションにアクセスできるユーザーは、“全員（匿名ユーザーを含む）”にしましょう。

```JavaScript
function recordText(text) {
  var sheet = SpreadsheetApp.openById("YOUR-SPREADSHEET-URL-DOCUMENT-ID").getSheetByName("シート1")
  sheet.getRange(1, 1).setValue(text)
}

function doPost(e) {
  recordText(e.parameter.text);
  return ContentService.createTextOutput({result: true}).setMimeType(ContentService.MimeType.JSON)
}
```

## 写真撮影アプリを作成

### 撮影した画像を Computer Vision API に送信する

Computer Vision API は Web API なのでネットワーク通信を行います。流れるデータの形式は JSON です。

ネットワーク通信を取り扱うためのフレームワークに Alamofire を、 JSON を取り扱うためのフレームワークに SwiftyJSON を使用しました。Carthage などのパッケージマネージャーを使えば簡単に導入できますね。

その他、カメラデバイスを取り扱うために標準フレームワークの AVFoundation を使います。

AVCapturePhotoCaptureDelegate を実装したコントローラーに photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) を実装します。
ファンクション内で引数 photo から imageData を取得し、それを、Computer Vision API のエンドポイントに送信しています。

ソース中の INPUT-YOUR-REFERENCE-KEY となっている部分は、 Microsoft Azure の管理画面 “ダッシュボード > Cognitive Services > OCRDemo - Quick start > Manage keys” でメモした Reference Key に変更してください。

先ほどメモしたエンドポイントのサーバー名はプログラム中に書き込んでありますので変更の必要はありません。もし、日本以外のリージョンを選択した場合は変更してください。

```swift
    func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
        if let imageData = photo.fileDataRepresentation() {
            readText(imageData)
        }
    }
    
    fileprivate func readText(_ imageData: Data) {
        Alamofire.upload(imageData,
                         to: "https://japaneast.api.cognitive.microsoft.com/vision/v1.0/ocr?language=ja&detectOrientation=true",
                         method: .post,
                         headers: [
                            "Content-Type": "application/octet-stream",
                            "Ocp-Apim-Subscription-Key": "INPUT-YOUR-REFERENCE-KEY"
            ]).validate(statusCode: 200...226)
            .responseJSON { response in self.readTextResponse(response: response)
        }
    }
```

### Google App Script から Google スプレッドシートにテキストを書き込み

Google App Script のエンドポイントの URL を変数 url に代入して、そのエンドポイントにテキストを送信します。
プログラム中のURLを見るとわかりますが、YOUR-GENERATED-URLと書いてある部分は書き換えが必要な部分です。Google App Script のエンドポイントの URL に書き換えましょう。
プログラム中の JSON 構造体は SwiftyJSON が提供するものです。

```swift
    fileprivate func readTextResponse(response: DataResponse<Any>) {
        guard let result = response.result.value else {
            statusLabel.title = "テキスト化失敗"
            return
        }
        statusLabel.title = "テキスト化完了"

        var text: String = ""
        let json = JSON(result)
        json["regions"].forEach { (_, region) in
            region["lines"].forEach { (_, line) in
                line["words"].forEach { (_, word) in
                    text.append(word["text"].string!)
                }
                text.append("\n")
            }
        }
        
        print(text)
        
        Alamofire.request("https://script.google.com/macros/s/YOUR-GENERATED-URL/exec",
                          method: .post,
                          parameters: ["text": text],
                          encoding: URLEncoding(destination: .methodDependent))
            .validate(statusCode: 200...226)
            .response { _ in self.statusLabel.title = "Google スプレッドシートに転送完了" }
    }
```

# プログラムの場所

https://github.com/okoshi-f/TinyOCR

# 総括

いかがでしたでしょうか。ちょっと iPhone アプリや Swift の知識は必要かもしれませんが、簡単に OCR が作れました。掲載しているプログラムは一部ではありますが、それでも 5〜6 割ほど掲載しているのでその規模感はイメージしていただけるのではないでしょうか。

これだけのソースでこれだけのことができるで、良いアイデアが浮かんだら簡単に実現できそうです。
それに業務利用への応用も考えられます。例えば、取引先との間で使用する伝票を読み取って電子化したり、ホワイトボードに書いた文章を議事録として文字に起こすといったことができたりするかもしれません。

Computer Vision のページを読まれた方はお気づきかもしれませんが、撮影しなくてもカメラを向けただけでプレビュー画面に文字に表示する機能も公開されています。現在は英語のみのプレビュー版ということで使ってみることはしませんでしたが、今後対応されていくでしょうから楽しみです。

今回は Microsoft Azure のサービスを使用しましたが、Google Cloud Platform など別のクラウドのサービスにも同じような機能をもったものが存在するので興味を持たれた方は是非挑戦してみてください。

----

Ateam Lifestyle x cyma Advent Calendar 2018、明日は @ryosuketter に書いてもらう予定です。お楽しみに！

エイチームグループでは、一緒に働けるチャレンジ精神旺盛な仲間を募集しています。興味を持たれた方はぜひエイチームグループ採用サイトを御覧ください。
https://www.a-tm.co.jp/recruit/