自炊のための日本語OCRサービス作ってみました


# はじめに、「自炊」って？
自分で所有する本を、スキャナなどを使ってデジタルデータとして取り込む行為のこと。
電子化してしまえば、物理的に場所をとったりせず、それ以上本が劣化したりせず、いつでも取り出せるという利点があります。

# OCRって？
OCR(Optical Character Recognition/Reader、光学文字認識)というのは、手書きや印字された「画像」などを主にテキストデータとして抽出する技術のこと。


# 本を自炊した！でもそのあとは？
自分自身が自炊したあと、いざKindleで本を読もうと思ったとき、画像だったりPDF形式であったりすると非常に読みにくいことに気づきました。
できるならmobi形式のようなKindleで読める形式にしたかったので、精度の高い日本語OCRサービスを探しました。

その中で最も精度が高かったのは**[GoogleのCloud Vision](https://cloud.google.com/vision/?hl=ja)**です。縦書きの文章もかなり高い精度で読み込んでくれたのですが、主に開発者向けに提供されたサービスなので、画像があっても気軽に使えませんでした。

そこで開発者でなくとも気軽にCloud Vision APIが使えるように、**[OCRusion(オクルージョン)](https://ocrusion.my-portfolio.site/)**を作りました。


自炊した画像をZIP形式にまとめてアップすれば、それだけで自動的にAPIを叩いてテキスト形式へと変換してくれます。

**[OCRusion | 自炊支援のための日本語OCRサービス](https://ocrusion.my-portfolio.site/)**


# OCRusionはオープンソースソフトウェアです
**[OCRusionはオープンソースソフトウェアです。](https://github.com/devinoue/OCRusion)**
すべてのソースコードを公開しています。MITライセンス。


# 主な構成
- Laravel 5.7
- Vue 2.5.7
- Vuetify 1.2.6
- Materialize 1.0

フロントはマテリアルデザイン用CSSのMaterialize.css。
動的なUIのみVue + vuetifyで構成。バックエンドはLaravelで構成しています。


# 期間
約一ヶ月。
休日の多かった９月に一気に作ったとはいえLaravelのお陰でだいぶ工数を減らせたと思います。


## Cloud Vision APIの使用制限について
無料OCRサービスを作る上で避けては通れないのは、APIの使用制限です。(2018/11/02現在、[公式サイト](https://cloud.google.com/vision/pricing?hl=ja))
細かい内容ですが、この制限を守る必要があります。

- 画像ファイルサイズは20MB以内
- JSONリクエストのオブジェクトサイズ、10MB以内
- リクエストあたりの画像数16枚以内
- 1分あたりの「リクエスト数」1,800

ここで注意すべきなのは、制限されるのは画像のファイルサイズではなく、**BASE64エンコードされた画像のファイルサイズ**だということです。
通常は元画像ファイルより約37%大きくなるので、BASE64のファイルサイズを逐一チェックしています。

```php
		$image_path="画像ファイルへのパス";
		$image = base64_encode( file_get_contents($image_path) );

		if ((strlen($image)/1000) >= 4000) {
			return "1画像当たりのファイルサイズが大きすぎます";
		}

		$requests="すべてのリクエスト";
		if ((strlen(json_encode($requests))/1000) >= 8000) {
			return "画像のファイルサイズが大きすぎます。もう少し小さくしてアップロードしてください";
		}
```

これらの割り当てに加えて、ユニットごとに料金が発生する可能性があるのでご注意してください。

- 最初の 1,000 ユニット/月 無料
- 1,001～5,000,000 ユニット/月から1.5$
- 5,000,001～20,000,000 ユニット/月 $0.60
 
といっても目が飛び出るほどの高額なものではありません。
自分のサービスの場合は先月の料金は100円以内でした。
(ユーザー数が少なすぎる……)


# リクエストを作る
リクエストはPOSTでJSON形式にして作ります。
JSON形式でOCRをするときは、fearchesの要素で"type": "TEXT_DETECTION"を選択してください。

POST https://vision.googleapis.com/v1/images:annotate?key=YOUR_API_KEY

```json
{
  "requests": [
    {
      "image": {
        "content": "/9j/7QBEUGhvdG9zaG9...base64-encoded-image-content...fXNWzvDEeYxxxzj/Coa6Bax//Z"
      },
      "features": [
        {
          "type": "TEXT_DETECTION"
        }
      ]
    }
  ]
}
```

こうやって作ったリクエストをcurlを叩きます。
今回はPHPのサンプルコードです。

```php5

		$body = [
			'requests' => $requests
		];

		// リクエスト用のJSONを作成
		$json = json_encode($body) ;
	
		// リクエストを実行
		$curl = curl_init() ;
		curl_setopt( $curl, CURLOPT_URL, "https://vision.googleapis.com/v1/images:annotate?key=" . "APIキーをここに" ) ;
		curl_setopt( $curl, CURLOPT_HEADER, true ) ; 
		curl_setopt( $curl, CURLOPT_CUSTOMREQUEST, "POST" ) ;
		curl_setopt( $curl, CURLOPT_HTTPHEADER, array( "Content-Type: application/json" ) ) ;
		curl_setopt( $curl, CURLOPT_SSL_VERIFYPEER, false ) ;
		curl_setopt( $curl, CURLOPT_RETURNTRANSFER, true ) ;
		curl_setopt( $curl, CURLOPT_TIMEOUT, 15 ) ;
		curl_setopt( $curl, CURLOPT_POSTFIELDS, $json ) ;
		$res1 = curl_exec( $curl ) ;
		$res2 = curl_getinfo( $curl ) ;

	   //エラーがあった場合には失敗とする
		 if(curl_errno($curl)) {
			return curl_errno($curl) . "Error";
		}

		curl_close( $curl ) ;
	
		// 取得したデータ
		$json = substr( $res1, $res2["header_size"] ) ;

	
		$json = mb_convert_encoding($json, 'UTF8', 'ASCII,JIS,UTF-8,EUC-JP,SJIS-WIN');
		$arr = json_decode($json,true);
		// 出力
		var_dump($arr);
```

これは文字列を抽出のための一例です。
OCRusionではRequestCloudVisionクラスを作り呼び出しています。

あとは得た情報を保存・記録すればOK。

# 同時に２つ以上のリクエストがあった場合はどうする？
一番の難所は、同時リクエストがあったときの対処法です。一秒間に複数のリクエストがあった場合、それらのアクセスは拒否されてしまいます。
そのためOCRusionでは同時接続できないよう順番待ち(Queue)テーブルを用意して、順番が空き次第APIにアクセスするよう構築されています。

今の所5分置きに`Cron`を設定して、Queueテーブルに順番ができていないか確認をしていますが、
**基本ガラガラ**なので今の所待ち時間が長いということはありません。


# 終わりに
コード自体はシンプルですが、時間がかかったのはむしろ「ガワ」(フロント部分)でした。
`Vue.js`で動的にファイル・アップロードできるようにするため勉強が大変でした。
ただDB周りや会員管理はLaravelのお陰で簡単にできました。

コードは**[GitHub](https://github.com/devinoue/OCRusion)**にアップしていますので、どうぞご確認ください。



参考・[Cloud Vision APIの使い方まとめ (サンプルコード付き)](https://syncer.jp/cloud-vision-api)
cURLによるアップロード部分を参考にさせていただきました。