Google Cloud Vision APIで文字認識を試してみる

# はじめに

[オープンストリーム Advent Calendar 2018](https://qiita.com/advent-calendar/2018/opst)の12日目です。

「AWS絡みで何か書きます」と宣言しておいて、これから書くのは事もあろうに**Google Cloud Platform**の記事という。
元々書こうと思っていたネタが、思ったよりどん詰まってしまい、可作業時間的に無理だと思ったのでネタを変えることにしました。

今回のテーマに挙げたのは、「OCR(光学式文字認識)」です。画像から文字を認識する、アレです。
ちょっとした思いつきで、OCRを使って作ってみようかな、と思ったアプリがあるため、前段階としてOCRを検証してみることにしました。

ということで、GCPの`Cloud Vision API`を使って、OCRがどのレベルまで文字を識別してくれるのか試してみることにしました。

ちなみに、GCP経験少ないので、もっと楽なやり方あればご教授ください。

# 前準備

AWSと同様に、サービスを検索出来たのでそこからCloud Visionを探します。
<img width="277" alt="スクリーンショット 2018-12-08 12.10.21のコピー.png" src="https://qiita-image-store.s3.amazonaws.com/0/159578/6579777a-e757-7c2c-dfcc-c1495618da1e.png">

自分なりに学習させて使うことも出来るらしいのですが、試してみるだけなので、右側「Use the pre-trained model」を選択。
<img width="525" alt="スクリーンショット 2018-12-08 11.36.27のコピー.png" src="https://qiita-image-store.s3.amazonaws.com/0/159578/a25166e0-b55c-0c63-505b-d0e83bca5463.png">

「有効にする」。
<img width="380" alt="スクリーンショット 2018-12-08 11.38.35のコピー.png" src="https://qiita-image-store.s3.amazonaws.com/0/159578/8ee59030-bcb8-b245-5205-086c297da9d3.png">

AWSのIAM的な認証情報を作らないといけないようなので、
<img width="1151" alt="スクリーンショット 2018-12-08 11.38.56のコピー.png" src="https://qiita-image-store.s3.amazonaws.com/0/159578/9604c4a5-da5e-cde8-3c2f-f8942fbaa0c2.png">

よしなに作ります。
App Engineでやるとどうやらいろいろ楽だったらしいのですが、画像も含めて一旦ローカルで完結させたいので「いいえ」にしておきました。
<img width="468" alt="スクリーンショット 2018-12-08 12.05.24のコピー.png" src="https://qiita-image-store.s3.amazonaws.com/0/159578/68664063-2a4b-59e2-bde1-8c30616c3f35.png">

ここまで終わると、認証情報を記したjsonファイルがダウンロード出来るので、忘れずにダウンロードしておきます。

# PHPで書いてみた

私が一番使い慣れてるのがPHPなので、PHPで書いてみます。
PHPから使用するためのクライアントライブラリがGoogleから提供されているので、それを使用してみます。
https://cloud.google.com/vision/docs/libraries#client-libraries-install-php

↑で記述されている通りに書いてみましょう。

## composerでクライアントを導入

composerで入れます。
まっさらから始めてるので、マニュアル通りにやる前に `composer init` を追加。

```bash
$ composer init
$ composer require google/cloud-vision
```

## 認証周り

環境変数に先程のjsonのパスを書くと、そこから認証情報を読み込むようなので、大人しく従います。

```bash
$ export GOOGLE_APPLICATION_CREDENTIALS="さっき落としてきたJSONのフルパス"
```

準備はこれだけ。

## コード

ドキュメントを参考に、とりあえず書いてみました。
https://googleapis.github.io/google-cloud-php/#/docs/google-cloud/v0.88.0/vision/image

```php
<?php
require __DIR__ . '/vendor/autoload.php';
use Google\Cloud\Vision\VisionClient;

if (!isset($argv[1])) exit("argv1 is required.\n");
if (!file_exists($argv[1])) exit($argv[1] . " does not exists.\n");

$vision = new VisionClient();

$resource = file_get_contents($argv[1]);
$image = $vision->image($resource, ['TEXT_DETECTION']);
$annotation = $vision->annotate($image);

$txt = $annotation->fullText()->text();

var_dump($txt);
```

上記のコードで、1枚写真を読み込んでみます。

# 例1: 駅の標識

まずは先日京都に行った際に撮った、京都駅奈良線ホームの駅標識で試してみます。
![IMG_6506.jpg](https://qiita-image-store.s3.amazonaws.com/0/159578/a54833c8-8f55-d218-d5b2-e977e3adf8de.jpeg)
この写真で先のプログラムを実行してみた結果はコレです。

```
KensMBP-Late2017:CloudAPITest saken649$ php -f cloudvision.php ./img/IMG_6506.JPG
string(566) "UR
きょうとKyoto 교토
とうふくじ
Töfukuji
도후쿠지东福寺
京都駅構内で案内
Kyoto Station Map
for Töfukuji, Inari, Uji, Nara
土·休日
立
エスカレ-タ-
나想算所
エレベーター 1を コインロッカ
&多機能お手洗
-喫煙ルーム
园
Aee塞
八条東口. 하치조 동쪽 출구
Hachijo East Exitハ条东ロ
,
20
all 6:50-23:00
みどりの窓口(本日分)
Ogen
243840
,
21⑥10
226
ハ条東ロ
島内.
厦
22 23
寵患
012345678901
56789
52
40 49 49 49 49 49 49-54-52
49鄂37
37
37鄂39跖. 40-40
49
3
鹵?
合tin
"
```

**めっちゃ拾ってんな。** 思った以上に細かい文字をたくさん拾ってますね。
駅名の下に、ちらっと駅の構内図や、時刻表が写っているのですが、そこの文字も細かく拾ってるようです。

- 細かく拾ってるのは良いけど**肝心の駅名はどうしたよ**
    - デカデカと書いてあるのに`京都`を拾わない:thinking: 
- 英数字はかなり細かく拾ってる上、精度も高そう
    - 英語については誤検出は無さそう (`Kyoto Station Map`, `for Töfukuji, Inari, Uji, Nara` etc...)
    - ひたすら数字が検出されてるのは、時刻表の数字をかなりちゃんと拾ってるからっぽい
- 日本語よりハングルと簡体字の方が検出率高い件
    - kanashimi

# 例2: 例1から情報量を落としてみる

写ってる文字の量が多過ぎるのがいけないのかもしれません。
写ってる文字の量を減らしてみましょう。
![IMG_6506_mini.jpeg](https://qiita-image-store.s3.amazonaws.com/0/159578/81de2386-1052-952e-986d-0d104f4608bd.jpeg)
実行結果はこちら。

```
KensMBP-Late2017:CloudAPITest saken649$ php -f cloudvision.php ./img/IMG_6506_mini.jpeg
string(85) "UR京都
显
きょうとKyoto 교토
とうふくじ
Tofukuji
도후쿠지东福寺
"
```

- 今度はちゃんと駅名も拾った
    - やっぱり拾わないといけない文字量が多過ぎると結果に影響するのかもしれない
- JRが`UR`になるのは、まあ仕方ない
    - `TEXT_DETECTION`で検証しているが、`LOGO_DETECTION`だったら？
- `京`の誤検出と思しき`显`を除いて、ほぼ問題なく認識出来ている

一番検出して欲しいものに専念させると、比較的いい結果になるんじゃないか、と思える結果になっています。

## 例2.5: JRのロゴを認識出来るか

ところで例2では、JRのロゴを`UR`と誤検出していました。
ちゃんとした文字ではないので、これは仕方ないです。

ところがCloud Vision APIには、 `LOGO_DETECTION`という、企業ロゴに特化したモードがあるようなので、これ使ったらどんな結果になるのか、見てみようと思います。
もはや文字認識ではないので、OCRとは話が逸れてしまいますが。

`$vision->image()`以降を以下のようにして、同じ画像で試してみます。

```php
$image = $vision->image($resource, ['LOGO_DETECTION']);
$annotation = $vision->annotate($image);

$txt = $annotation->logos();
if (isset($txt[0])) {
    $txt = $txt[0]->description();
}

var_dump($txt);
```

```
KensMBP-Late2017:CloudAPITest saken649$ php -f cloudvision_logo.php ./img/IMG_6506_mini.jpeg
NULL
```

:thinking: 

では、ロゴだけ切り抜いたらどうなるでしょうか。
![IMG_6506_mini_onlyjr.jpeg](https://qiita-image-store.s3.amazonaws.com/0/159578/c7b044ca-3013-4fd6-c66c-14f74b1aa51a.jpeg)

```
KensMBP-Late2017:CloudAPITest saken649$ php -f cloudvision_logo.php ./img/IMG_6506_mini_onlyjr.jpeg
NULL
```
:sob:

![Google_G_Logo-TA-1024x768.jpg](https://qiita-image-store.s3.amazonaws.com/0/159578/3454ceac-9c57-ed61-5556-8a3d45f6c398.jpeg)

```
KensMBP-Late2017:CloudAPITest saken649$ php -f cloudvision_logo.php ./img/Google_G_Logo-TA-1024x768.jpg
string(11) "Google logo"
```

![twitter_logo.png](https://qiita-image-store.s3.amazonaws.com/0/159578/d72a5136-88cb-54c9-ef1e-156fb7c49900.png)

```
KensMBP-Late2017:CloudAPITest saken649$ php -f cloudvision_logo.php ./img/twitter_logo.png
string(7) "Twitter"
```

残念ながらJRは検出出来ませんでしたが、アメリカの大手企業とかはちゃんと検出出来るので、使い道は普通にありそうです。

# 例3: 領収書

定番ですが、自分でも結果を見てみたかったのでやってみます。
![IMG_6741.JPG](https://qiita-image-store.s3.amazonaws.com/0/159578/500f4df1-d6c1-e2ee-c2be-3478af1b8cb3.jpeg)

```
KensMBP-Late2017:CloudAPITest saken649$ php -f cloudvision.php ./img/IMG_6741.JPG
string(263) "option
様
領取年月日p.20 18.-3.1
¥ 1 , 550
〔クレジット扱11]
購入商品 JR乗車券類JR
(4
東日本旅客鉄道株式会社
品川駅
品川駅FC-1発行 50159-01
tickets
印紙税申告納
付につき渋谷
1税務署承認済
8
七
令民領
"
```

- **例1で`京都`が拾えなかったのは何だったのか**というくらい、こちらは結構良い感じに検出している
    - JRのロゴと被ってる日付が惜しいくらいで、必要最低限の日付・金額はちゃんと押さえてる
    - 日付は、光の加減でいくらでもどうにかなりそう
- 文字の濃さとか、情報量の均一さがある程度影響している？
    - 先の例と比べると、日本語オンリーな上、文字の大きさも割と均一

# 例4: 領収書その2

レシート形式の普通の領収書でもやってみます。[^1]
[^1]: 例1の京都に行った日の夕食←
![IMG_6740.JPG](https://qiita-image-store.s3.amazonaws.com/0/159578/c27602f9-371b-64b2-c17f-2878afbeacb5.jpeg)

```
KensMBP-Late2017:CloudAPITest saken649$ php -f cloudvision.php ./img/IMG_6740.JPG
string(452) "御計算書|
command
:
京都ヨドバシ店
075-343-2266
京都市下京区烏丸通七城下る
東塩小路町590-2京都ヨドバシ6F
2018年11月
4日(日)21時40分000101
¥1.380外
牛カツ京玉膳130g
伝票No. 123 テーブ1No.
¥1.380
¥110
¥1,490
1, 5 0 0
¥110)
¥10
小計額
外税
合計
現計
(消費税
等
お金勺
合計点数
0 1扱1
No.5513 1名
ぜひご意見をお聞かせください!
アンケートはこちらから
"
```

- びっくりするぐらい綺麗に検出してる。これはすごい:tada: 
    - そうすると、なんでさっきの`京都`は検出出来なかったのかが、やっぱり気になる:thinking: 
- `文字の濃さとか、情報量の均一さがある程度影響している？`という仮説は、意外とあるかもしれない

# ドキュメント検出も試してみる

ここまでは、テキスト検出でしたが、Cloud Vision APIには`DOCUMENT_TEXT_DETECTION`という、もう少し検出精度が高いけど、お値段も高い、というものも存在しています。
これもせっかくなのでやってみます。
宇治の甘味処、中村藤吉本店の店前にある、建物についての看板がちょうど良い例になりました。

![IMG_6510_e.JPG](https://qiita-image-store.s3.amazonaws.com/0/159578/24901a82-2db1-88d7-ca17-03aa02eb77fb.jpeg)

```diff
- $image = $vision->image($resource, ['TEXT_DETECTION']);
+ $image = $vision->image($resource, ['DOCUMENT_TEXT_DETECTION']);
```

まず、`TEXT_DETECTION`のまま実行した結果がこちら。

```
KensMBP-Late2017:CloudAPITest saken649$ php -f cloudvision.php ./img/IMG_6510_e.JPG
string(823) "国選定重要文化的景観
重要構成堊素
中村藤吉本店
中村藤吉家は安政元年(一八五四)
創業の茶商です。現在の表屋は明治
中期の建物で、玄関から右側がお茶
を製造する焙炉場、左側が主屋でし
た。奥には中庭が造られ、茶室や土
蔵が建てられています。大正期
製造が機械化されたのに伴い、奥に
新.is tEH-陽 et芬、表s焙炉場は
事務所やお茶の詰め場に変化し、改
されながら使われてきました。この
ように中村藤吉本店は、近代におけ
る宇治茶製造の歴史を良くとどめる
代表的な建物群として、国選定重
要文化的景観「宇治の文化的景観」
の重要構成要素に指定されています。
11
宇治市
平成二十六年三月
"
```

`DOCUMENT_TEXT_DETECTION`に切り替えた結果がこちら。

```
KensMBP-Late2017:CloudAPITest saken649$ php -f cloudvision_dt.php ./img/IMG_6510_e.JPG
string(838) "国選定重要文化的景觀 重要構成要素
中村藤吉本店
中村藤吉家は安政元年(一八五四)
創業の茶商です。現在の表は明治
中期の建物で、玄関から右側がお茶
を製造する焙炉場、左側が主屋でし
た。奥には中度が造られ、茶室やエ
満が建てられています。大正期に茶
製造が機械化されたのに伴い、奥に
「新しい茶工場が建ち、表の焙炉場は
事務所やお茶の詰め場に変化し、次
修されながら使われてきました。この
ように中村藤吉本店は、近代におけ
る宇治茶製造の歴史を良くとどめる
代表的な建物群として、国選定重
要文化的景観「宇治の文化的景観」
の重要構成要素に指定されています。
宇治市
平成二十六年三月
"
```

```diff
- 重要構成堊素
+ 重要構成要素
```

とか

```diff
- 新.is tEH-陽 et芬、表s焙炉場は
+ 「新しい茶工場が建ち、表の焙炉場は
```

この辺りがだいぶ違います。
文字の検出精度がさらに高くなってますね。

# 試してみての感想

話にはすごいと聞いていましたが、レシートとかこんなに綺麗に検出出来るのか！というのは少しびっくりでした。
例1において**京都**が拾えなかった例のように、得意不得意はありそう[^2]ですが、用途がハマればうまく使えそうです。
[^2]: Qiitaには画像が載せられないので載せていませんが、例えばアニメの画像の中の文字列など、うまく拾えないものはありました。

また、冒頭で述べたように、今回は「pre-trained model」を使用して、この結果でした。
自身で学習パターンを作ると思しき、左側でうまいことチューニングしていくとまた結果が変わるのかもしれません。
<img width="525" alt="スクリーンショット 2018-12-08 11.36.27のコピー.png" src="https://qiita-image-store.s3.amazonaws.com/0/159578/a25166e0-b55c-0c63-505b-d0e83bca5463.png">

# 困ったこと

## クライアントが2種類ある

- https://googleapis.github.io/google-cloud-php/#/docs/google-cloud/v0.88.0/vision/image
- https://googleapis.github.io/google-cloud-php/#/docs/google-cloud/v0.88.0/vision/v1/imageannotatorclient

**なんか書き方が2種類ある**:thinking: 
PHPクライアントのページに書いてあるのはV1の方でした。
わざわざ `V1` て付けて、ドキュメントのメニューも `V1`で括ってるからにはそっちが古いんだろう、と勝手に推測しているのですが、この辺どっちが正しいんでしょう？
PHPクライアントのページの内容が古いんだと信じたい。

とりあえず今回はV1ではない、前者の方を参考にして書きました。

# 備考

画像認識に使用した例として掲載している写真は、Qiita掲載にあたって、サイズを適当に縮小した上でjpgで保存し直していますので、同じ写真で試されても同じ結果にはなりません。
iPadで撮った写真デカ過ぎ。。予めご了承ください。