【備忘】OCRサービス・エンジンの比較

# 概要
Amazon Textract、Azure AI Vision、Google Cloud Vision API、Tesseract-ocrの出力を比較していた時の備忘録です。どれを採用するか判断に迷ったときに参考になるようにまとめておきます。
すべてPythonプログラムから実行する形で実装していきます。

# 各OCRサービスについて

### Amazon Textract
- AWSのサービスの1つ
- 無料利用枠がある
（Detect Document Text API: 1,000 ページ/月）
- 日本語は対応していない

https://aws.amazon.com/jp/textract/

### Azure AI Vision
- Microsoft Azureサービスの1つ
- 画像分析（物体検出や分類、キャプション付け）、空間分析、文字認識、顔認識のサービスが使える
- Free インスタンスの場合、5,000 無料トランザクション / 月
- 日本語対応
- 
https://azure.microsoft.com/ja-jp/products/ai-services/ai-vision/#layout-container-uid189e

### Google Cloud Vision API
- Google Cloud サービスの1つ
- 画像ラベリング、光学式文字認識、不適切なコンテンツのタグ付けなどができる
- 無料枠は1,000 ユニット/月 （ユニットは画像）
（その他の料金はこちら）https://cloud.google.com/vision/pricing?hl=ja

https://cloud.google.com/vision?hl=ja

### Tesseract-ocr

- オープンソースで利用できるOCRエンジン
- 導入から実装に関してはこちらの記事が参考になりました。（本記事では環境構築は省略）

https://qiita.com/henjiganai/items/7a5e871f652b32b41a18

## 実装

#### 環境
- python 3.9.12
- VSCode 1.82.2

#### ライブラリ
```shell
pip install --upgrade google-cloud-vision
pip install boto3 
pip install pytesseract Pillow
pip install azure-cognitiveservices-vision-computervision
```

#### 環境変数
```ini:env
AZURE_ENDPOINT="リソース作成時に確認できるエンドポイント"
AZURE_SUBSCRIPTION_KEY="リソース作成時に合わせて生成されるサブスクリプションキー"
AWS_REGION='リージョン' # 今回はus-east-1を使用
AWS_ACCESS_KEY_ID='アクセスキーID'
AWS_SECRET_ACCESS_KEY='シークレットアクセスキー'
TESSERACT_PATH='tesseract.exeがインストールされた位置' # 
```
筆者のtesseractのインストール先は C:/Program Files/Tesseract-OCR/tesseract.exe でした。

<details><summary>ソース</summary>

```python:app.py
from google.cloud import vision
import boto3
import pytesseract
from PIL import Image
import json
import time
import os
from dotenv import load_dotenv
from azure.cognitiveservices.vision.computervision import ComputerVisionClient
from msrest.authentication import CognitiveServicesCredentials
from azure.cognitiveservices.vision.computervision.models import OperationStatusCodes
from azure.cognitiveservices.vision.computervision.models import ComputerVisionOcrErrorException

load_dotenv()

azure_endpoint = os.environ['AZURE_ENDPOINT']
azure_subscription_key = os.environ['AZURE_SUBSCRIPTION_KEY']

aws_region = os.environ['AWS_REGION']
aws_access_key_id = os.environ['AWS_ACCESS_KEY_ID']
aws_secret_access_key = os.environ['AWS_SECRET_ACCESS_KEY']

pytesseract.pytesseract.tesseract_cmd = os.environ['TESSERACT_PATH']

google_client = vision.ImageAnnotatorClient()
textract_client = boto3.client('textract', region_name=aws_region,aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key)
azure_client = ComputerVisionClient(endpoint=azure_endpoint, credentials=CognitiveServicesCredentials(azure_subscription_key))

def google_vision():
    with open(image_path, 'rb') as image_file:
        content = image_file.read()

    image = vision.Image(content=content)
    response = google_client.text_detection(image=image)
    texts = response.text_annotations

    print(texts[0].description)
    with open(image_path + '_google_vision_output.txt', mode='w', encoding='utf-8') as f:
        f.write(texts[0].description)

def azure_vision():
    image_data = open(image_path, "rb")
    try:
        recognize_results = azure_client.read_in_stream(image_data, language='ja',raw=True)
    except ComputerVisionOcrErrorException as e:
        print('errors:', e.response)
        raise e
    operation_location_remote = recognize_results.headers['Operation-Location']
    operation_id = operation_location_remote.split('/')[-1]

    while True:
        get_text_results = azure_client.get_read_result(operation_id)
        if get_text_results.status not in ['notStarted', 'running']:
            break
        time.sleep(1)

    if get_text_results.status == OperationStatusCodes.succeeded:
        with open(image_path + "_azure_output.txt", mode="w", errors="ignore", encoding='utf-8') as f:
            for text_result in get_text_results.analyze_result.read_results:
                for line in text_result.lines:
                    print(line.text)
                    f.write(line.text + "\n")

def aws_textract():
    with open(image_path, "rb") as image_file:
        img_data = image_file.read()

    response = textract_client.analyze_document(Document={'Bytes': img_data}, FeatureTypes=['TABLES'])

    print(response)
    with open(image_path + '_textract_response.json', mode='w')as f:
        f.write(json.dumps(response))

    with open(image_path + '_textract_output.txt', mode='w') as f:
        for item in response["Blocks"]:
            if item["BlockType"] == "LINE":
                print(item["Text"])
                f.write(item['Text'] + '\n')
                

def tesseract():
    image = Image.open(image_path)
    text = pytesseract.image_to_string(image, lang='eng')

    print(text)
    with open(image_path + '_tesseract_output.txt', mode="w", encoding='utf-8') as f:
        f.write(text)


if __name__ == '__main__':
    image_path = "image.png" # 読み込む画像のパス
    tesseract()
    google_vision()
    aws_textract()
    azure_vision()

```
</details>
上記ソースを実行すると、各サービスの処理結果がテキストで出力されます。


## 出力を比較してみる
今回は日本語を含む2種類の画像を用意して文字認識を実行してみました。
使用した画像は以前書いたUnityの記事のキャプチャ（①）と、ゲーム画面のスクショ（②）です。

①キャプチャ
ちなみに記事はこちら->https://qiita.com/tks_sakigake/items/9dfb46d7da694a0b84a9
<img width="50%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2706027/43ca110e-3a1c-2ed6-f50a-73ed2948a41c.png">

②ゲーム画面スクショ（念のため、こんな画像ですというイメージを描いて載せておきます）
<img width="30%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2706027/1a35b8b4-86d2-bf95-8bbc-662beaeddee0.png">


## 処理結果
各サービスごとの出力は以下の通りになりました。
### Amazon Textract

①の結果

```
MessageWindow.cs
public static MessageWindow instance;
// ()
private void Awake() {
if (instance -- null) {
instance - this;
} else {
Destroy(gameObject);
}
Start
Unity

```

①は日本語が全く抽出されていません。
残念ながら、Amazon Textractは英語にしか対応していないので日本語の部分は全てから文字になってしまいました。また、日本語と英語が混ざった文章では「static」や「GetComponent」といった単語は出力されていないようです。
ソースコードの方は、記号の「=」が「-」として出力されています。


<details><summary>②の結果</summary>

```
EXH
17
COMPLETE
Fly Like You
technoplanet
BEST
929 2237
BEST SCORE
9255136
+
37101
EX SCORE
4592
MAX
85.4%
BEST
4564
EX SCORE
28
EFFECTIVE RATE
CHIP
78.8%
LONGY
TSUMAMI
EARLY
S-CRITICAL
294
298
809
ERROR
18
CRITICAL
164
NEAR
94
NEAR
126
CRITICAL
133
ERROR
39
I
21
S-CRITICAL
1401
MAXIMUM CHAIN
200
CRITICAL
31
NEAR
32
ERROR
43
LATE
KAI
BLASTER GAUGE
VOLFORCE
15.140
EXTRACK
GET BONUS
--
60 PCB
PASEL-
6 PCB
25 PCB
133905PCB
START
FX-L
FX-R
+
--
TIME
PASELI: 99
CREDIT: 0

```

</details>
長くなるので折りたたみました。
こちらも同様、英語の部分のみ出力されています。
数字と英語が混ざっている場合は特に精度に影響はなさそうで、両方とも出力されています。

### Azure AI Vision

①の結果
```
解説
· シングルトン
MessageWindowクラスはシングルトンとして実装している
該当コードは以下
MessageWindow.cs
public static MessageWindow instance;
//(中略)
private void Awake( ) {
if(instance == null) {
instance = this;
} else {
Destroy(gameObject);
}
}
シングルトンはデザインパターンの一種で、シーン内に1つしか存在しないインスタンスである
自身をstaticのインスタンスとして持ち、すでにインスタンスとして存在している場合は自身を破
棄することで実現している
参考 ([Unity]シングルトンを使ってみよう)
● Start メソッド
初期化処理
Unityでの操作を減らすため、アタッチされたオブジェクトからGetComponentでオブジェクト
を取得する
(ただこうするとヒエラルキー側の変更に対応できない ... )
```
</details>
こちらは日本語もしっかり出力されています。
文章の欠落、誤抽出も一切ありません。精度としてはかなり高いように思います。

<details><summary>②の結果</summary>

```
EXH
17
COMPLETE
IKE YOU
- Fly Like You
technoplanet
-
BEST
9292237
BEST SCORE
9255136
+00037101
EX SCORE
4592
MAX 85.4%
BEST
04564
EX SCORE
+00028
EFFECTIVE RATE
778.8%
CHIP
LONG
TSUMAMI
EARL
S-CRITICAL
294
298
809
18 .
CRITICAL
164
ERROR
1 26
NEAR
94
NEAR
ERROR
2
CRITICAL
133
-
39
S-CRITICAL
1401
MAXIMUM CHAIN
200
CRITICAL
3
NEAR
321
よろしくお願いします
ERROR
43
LATE
KAI
BLASTER GAUGE
80%
2 15.140
EXTRACK
GET BONUS
ステージボーナス
60 PCB
PASELIポーナス
6 PCB
LO VAL TEX PECTED FOR
シャイニングコンプリート
25 PCB
所持PCB
133905PCB
START
FX-L
FX-R
スキップ
キャラクタービュー
TIME
+
PASELI: 99
CREDIT: 0
DIRECT CONSOLE
NEMEYS
THE
m
EX
THUNDERING SOUND BEC
THE WHOLE WORLD INTO
OLTEX
LIGHT THAT PIERCES SOUL OF CROWD.
GENERATION MUSIC GAME.

```

</details>
こちらも日本語もしっかり出力されています。
さらに、元画像だとかなり小さい文字で表示されている部分も抽出できています。
（↓のあたりの文字です。）

```
THUNDERING SOUND BEC
THE WHOLE WORLD INTO
OLTEX
LIGHT THAT PIERCES SOUL OF CROWD.
GENERATION MUSIC GAME.
```

### Google Cloud Vision API

①の結果

```
解説
●シングルトン
MessageWindowクラスはシングルトンとして実装している
該当コードは以下
MessageWindow.cs
public static MessageWindow instance;
// (中略)
private void Awake () {
}
if(instance == null) {
instance =
this;
} else {
}
Destroy(gameObject);
シングルトンはデザインパターンの一種で、シーン内に1つしか存在しないインスタンスである
自身をstaticのインスタンスとして持ち、すでにインスタンスとして存在している場合は自身を破
棄することで実現している
参考 ([Unity] シングルトンを使ってみよう)
● Start メソッド
初期化処理
Unityでの操作を減らすため、 アタッチされたオブジェクトからGetComponentでオブジェクト
を取得する
(ただこうするとヒエラルキー側の変更に対応できない....)
```
こちらも日本語が抽出できていますが、ソースが一部欠けていたり、改行が入ってしまっていたりする部分があります。
精度自体は悪くはないですが、細かい所が少し気になるかなといった感じです。

<details><summary>②の結果</summary>

```
EXH 17
Man
S-CRITICAL
CRITICAL
NEAR
ERROR
MAXIMUM CHAIN
START
CHIP Y
294
164
126
39
YOUR THUNDERING SOUND BEC
GET THE WHOLE WORLD INTO
EY VOIR
PASELI: 99
06
スキップ
よろしくお願いします
ΚΑΙ
THE
OLTEX
REON LLE
YOU
FX-L
BEST
200
Fly Like You
technoplanet
LONGY TSUMAMI
298
809
FX-R
VOLFORCE
15.140
EX SCORE 04592
21
33
9292237
UND VOLTER EXCEED
NEAR
ERROR
キャラクタービュー
EARLY
COMPLETE-
ERROR
NEAR
94
CRITICAL
133
S-CRITICAL 1401
CRITICAL
LATE
LIGHT THAT PIERCES SOUL OF CROWD,
GENERATION MUSIC GAME.
ILD BRING RISE TO A NEW LIGHT WAS BORN
181
MAX 85.4% BEST
31
32
43
80%
EXTRACK
GET BONUS
ステージボーナス
PASELIボーナス
シャイニングコンプリート
BEST SCOREY
09255136
+00037101
04564
EX SCORE +00028
EFFECTIVE RATE 78.8%
所持PCB
TIME
BLASTER GAUGE
60 PCB
6 PCB
25 PCB
133905PCB
T
CREDIT: 0
+
VOLTEX
```

</details>
こちらも抽出精度は高い感覚です（しっかり検証していないのでなんとなくです）。明らかに失敗していたり、文字列が欠けていたりすることもなさそうです。


### Tesseract-ocr

①の結果

```
MessageWindow.cs

public static MessageWindow instance;

<p)

private void Awake() {
if(instance == null) {
instance = this;
} else {

Destroy (gameObject);

Start XV y FE
RHEL:

etComponent CAF LI
eRETS


```
Tesseract-ocrも日本語対応させていないので、日本語の部分は抽出できていません。その代わり、日本語部分を何とか英語として抽出しようとしている形跡があります（Start XV y FEの部分、XVがメ、yがソ、FEがッドという感じでしょうか）。
あとは、RHEL:の部分を見ると、日本語の形から近い英単語を出力しているような動作になっている可能性も考えられます（RHEL：Red Hat Enterprise LinuxとうLinuxディストリビューションが実際にあります）。

<details><summary>②の結果</summary>

```
S-CRITICAL

=F ERROR
CRITICAL ee
CRITICAL = “9
, CRITICAL

CRITICAL
NEAR

AF—-IR—-FA
PASELIN—F2
Yrta=vIavFv—b

START
ee? eee ee 77-2
CREDIT: O

PASELI: 99

ITLILLLLLLLL LLL AA AAA AL) (f

(ALLL hhhhhhh LLL TT
NGERING SOUND BE THE LIGHT THAT PIERCES SOUL OF CROWD, y/,
OLTEX F GENERATION MUSIC GAME
me MASE IL Sree a 1) ite bm ODIKIG DICE TN ONEIIIGHTLLIAS BO


```

</details>
結構面白い抽出のされ方をしています（ITLILLLLLLLLの部分など）。
英単語として抽出できているものもいくつかありますが、半分くらいは恐らく形を見て近いものを抽出しているといった感じでしょうか。


## 総評
- 日本語対応しているGoogle Vision API、Azure AI Visionが使いやすそう
- Amazon Textractも日本語対応に期待
- Tesseract-ocrは日本語用の辞書を追加しなければいけないので構築が少々大変
（日本語でOCRする記事はこちらで実施していました。https://qiita.com/ku_a_i/items/93fdbd75edacb34ec610）
追加で学習させたりすればもっと精度がよくなる？

## 今後の展望
今回なぜこのような調査をしたかというと、リズムゲームのスコア管理にOCRが使えないかなと考えていました。
X（Twitter）などでリザルト画面をアップロードしている方を見かけて、「その画像をアップロードするだけでスコア管理出来たら楽なのでは？」と半ば思いつきで始めました。
そのうち今回よさげだったGoogleかAzureのサービスで簡単なアプリを作れたらなと思ってます。