Tesseract、Vision API、Document AIでのOCR比較

## 概要
カスタムGPTやその他のLLMにテキストデータを読み込ませる機会が増えそうなので、画像データのテキスト化の選択肢を検討します。

## 利用する環境
- Google Colab
- Google Drive
- ChatGPT4（コード生成）

※ここで紹介するPythonコードはすべてChatGPT-4が出力したものです。私は指示や確認のみを行い、コードは書いていません。

### Googleドライブのマウント
今回は画像ファイルやテキストファイルをGoogleドライブに格納するので、事前にマウントしておきます。

```python
from google.colab import drive
drive.mount('/content/drive')
```



## 使用する画像

画像は何でもよかったのですが手元にふるさと納税の案内があったので、これをスキャンしてPNG形式で使用しています。

![image1 2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147689/90035767-f6cd-f1a5-85c0-7514ef37d26e.png)

## 1) TesseractでのOCR

まずはオープンソースのTesseractで試してみます。

```shell
!sudo apt install tesseract-ocr-jpn
!pip install pytesseract
```

```python
import pytesseract
from PIL import Image
import os

def extract_text_from_images(input_directory, output_directory, lang='jpn'):
    if not os.path.exists(output_directory):
        os.makedirs(output_directory)

    for filename in os.listdir(input_directory):
        if filename.lower().endswith(('.png', '.jpg', '.jpeg', '.tiff', '.bmp', '.gif')):
            image_path = os.path.join(input_directory, filename)
            # 画像からテキストを抽出
            text = pytesseract.image_to_string(Image.open(image_path), lang=lang)

            # テキストファイル名を生成
            text_file_name = os.path.splitext(filename)[0] + '.txt'
            text_file_path = os.path.join(output_directory, text_file_name)

            # 抽出したテキストをファイルに保存
            with open(text_file_path, 'w', encoding='utf-8') as file:
                file.write(text)

# 画像が含まれるディレクトリのパス
input_directory = '/content/drive/My Drive/experiment/image/'

# テキストを保存するディレクトリのパス
output_directory = '/content/drive/My Drive/experiment/text/'

# テキストの抽出と保存
extract_text_from_images(input_directory, output_directory)
```

### 出力結果

```
くワンストップ特例を申請する皆様ヘへ>
 
確定申告をする方や6団体以上にワンストッツ提
例を申請する方などは、特例が適用されません。
 
2で しても適用されない場合
・ 医療費控除の申告などのため、確定申告をした、又は住民税の申
告をした
四還仙9昌Eワンム22陽東
・ 寄附した翌年の 1 月 1 日の住所地が申請書に記載された市町村で
なくなったにもかかわらず、変更の届出がされていない
※ ワンストップ特例を申請した後で、市外へ転居するなど申請書の記載事項に変更があ
る場合には、寄附した翌年の 1 月 10 日までに那識市役所に選け出れば符例が適用され
ます。
```

「ワンストップ特例」が「ワンストッツ提例」と誤認識されているなど、全体的に誤字が多く、このままでは厳しそうです。
黒帯・黒文字の「ご注意ください」の文字は、コントラストが低くて認識できなかったようです。

## 2) 画像を前処理＆TesseractでのOCR

認識精度向上のために、ChatGPTに提案されたOpenCVを用いた前処理を実行してみます。

グレースケール変換、ノイズ除去、二値化、高解像度化などの処理を行います。

```python
import pytesseract
from PIL import Image
import os
import cv2

def preprocess_image(image_path):
    # OpenCVを使用して画像を読み込み
    image = cv2.imread(image_path)

    # グレースケールに変換
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

    # ノイズの除去
    denoised = cv2.fastNlMeansDenoising(gray, None, 30, 7, 21)

    # 二値化
    _, binary = cv2.threshold(denoised, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)

    # 解像度を2倍に調整
    resized = cv2.resize(binary, None, fx=2, fy=2, interpolation=cv2.INTER_CUBIC)

    return resized

def extract_text_from_images(input_directory, output_directory, lang='jpn'):
    if not os.path.exists(output_directory):
        os.makedirs(output_directory)

    for filename in os.listdir(input_directory):
        if filename.lower().endswith(('.png', '.jpg', '.jpeg', '.tiff', '.bmp', '.gif')):
            image_path = os.path.join(input_directory, filename)

            # 画像を前処理
            processed_image = preprocess_image(image_path)

            # PIL形式に変換してOCR実行
            text = pytesseract.image_to_string(Image.fromarray(processed_image), lang=lang)

            # テキストファイル名を生成
            text_file_name = os.path.splitext(filename)[0] + '.txt'
            text_file_path = os.path.join(output_directory, text_file_name)

            # 抽出したテキストをファイルに保存
            with open(text_file_path, 'w', encoding='utf-8') as file:
                file.write(text)

# 画像が含まれるディレクトリのパス
input_directory = '/content/drive/My Drive/experiment/image/'

# テキストを保存するディレクトリのパス
output_directory = '/content/drive/My Drive/experiment/text/'

# テキストの抽出と保存
extract_text_from_images(input_directory, output_directory)

```

### 出力結果

```
くワンストップ特例を申請する皆様ヘ>
 
確定申告をする方や6国体以上にワンストップ翌
例を申請する方などは、特例が適用されません。
 
ワンストップ特例を申請しても適用されない場合
・ 医療費控除の申告などのため、確定申告をした、又は住民税の申
告をした
・ 6団体以上にワンストップ特例を申請した
・ 寄附した翌年の1月 1 日の住所地が申請書に記載された市町村で
なくなったにもかかわらず、変更の届出がされていない
※ ワンストップ特例を申請した後で、貼外へ転居するなど申請書の記載事項に変更かあ
る場合には.寄附した翌年の 1 月 I0 日までじ邪麺市役所に届け出れば特例が適用され
ます。
```

前処理を施しても誤字が多く存在しています。「ご注意ください」の文字も認識できていません。

## 3) Cloud Vision APIでのOCR

Cloud Vision APIのText Detectionを試してみます。

画像1枚ごとにAPIをコールするため、枚数に応じて費用が発生しますが、私の場合800枚の画像認識で100円以下の費用感でした。

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

Vision APIには「TEXT_DETECTION」と「DOCUMENT_TEXT_DETECTION」の2種類がありますが、ここでは後者を利用します。

```shell
pip install --upgrade google-cloud-vision
```

```python
from google.colab import userdata
from google.cloud import vision
import io
import os

os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = userdata.get('GOOGLE_APPLICATION_CREDENTIALS')

def detect_text(path):
    """ファイル内のテキストを検出します。"""
    client = vision.ImageAnnotatorClient()

    with io.open(path, 'rb') as image_file:
        content = image_file.read()

    image = vision.Image(content=content)
    response = client.document_text_detection(image=image)
    texts = response.text_annotations

    return texts[0].description if texts else ''

def extract_text_from_images(input_directory, output_directory):
    if not os.path.exists(output_directory):
        os.makedirs(output_directory)

    for filename in os.listdir(input_directory):
        if filename.lower().endswith(('.png', '.jpg', '.jpeg', '.tiff', '.bmp', '.gif')):
            image_path = os.path.join(input_directory, filename)
            text = detect_text(image_path)

            text_file_name = os.path.splitext(filename)[0] + '.txt'
            text_file_path = os.path.join(output_directory, text_file_name)

            with open(text_file_path, 'w', encoding='utf-8') as file:
                file.write(text)

# 画像が含まれるディレクトリのパス
input_directory = '/content/drive/My Drive/experiment/image/'

# テキストを保存するディレクトリのパス
output_directory = '/content/drive/My Drive/experiment/text/'

extract_text_from_images(input_directory, output_directory)
```

### 出力結果

```
確定申告をする方や6団体以上にワンストップ特
例を申請する方などは、 特例が適用されません。
<ワンストップ特例を申請する皆様へ>
【ご注意ください】
ワンストップ特例を申請しても適用されない場合
医療費控除の申告などのため、 確定申告をした、 又は住民税の申
告をした
6団体以上にワンストップ特例を申請した
寄附した翌年の1月1日の住所地が申請書に記載された市町村で
なくなったにもかかわらず、変更の届出がされていない
※ ワンストップ特例を申請した後で、 市外へ転居するなど申請書の記載事項に変更があ
る場合には、寄附した翌年の1月10日までに那覇市役所に届け出れば特例が適用され
ます。
```

黒帯の文字を含め、すべての文字が正確に認識されました。


## 4) Document AIでのOCR

Document AIでは画像を直接アップロードして試してみます。

![スクリーンショット 2024-01-13 13.33.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147689/58c7ddc2-52c1-e2e4-9224-19fe048fa5ad.png)

Vison API同様に誤字なく正しく認識できていました。

## おまけ：spaCyで改行位置を整える

OCRでスキャンしたテキストは元のレイアウトを再現しているため、単語が途中で改行されることがあります。

そのため自然言語処理のGiNZAとja_ginza_electraモデルを用いて文章の改行位置を整えます。

```shell
pip install -U ginza ja_ginza_electra
```

```python
import os
import spacy
nlp = spacy.load('ja_ginza_electra')

# 読み込みディレクトリと書き出しディレクトリのパス
input_directory_path = '/content/drive/My Drive/experiment/text/'
output_directory_path = '/content/drive/My Drive/experiment/fix_text/'

# 入力ディレクトリ内のすべてのテキストファイルを処理
for filename in os.listdir(input_directory_path):
    if filename.endswith('.txt'):  # テキストファイルのみを対象
        input_file_path = os.path.join(input_directory_path, filename)

        # ファイルを読み込み
        with open(input_file_path, 'r', encoding='utf-8') as file:
            text = file.read()

        # 改行を除去
        text = text.replace('\n', '')

        # nlp関数にテキストを渡す
        doc = nlp(text)
        processed_text = '\n'.join(sent.text for sent in doc.sents)

        # 処理したテキストを新しいファイルに書き出す
        output_file_path = os.path.join(output_directory_path, filename)
        with open(output_file_path, 'w', encoding='utf-8') as file:
            file.write(processed_text)
```

### 出力結果

```
確定申告をする方や6団体以上にワンストップ特例を申請する方などは、 特例が適用されません。
<ワンストップ特例を申請する皆様へ>
【ご注意ください】
ワンストップ特例を申請しても適用されない場合
医療費控除の申告などのため、 確定申告をした、 又は住民税の申告をした6団体以上にワンストップ特例を申請した
寄附した翌年の1月1日の住所地が申請書に記載された市町村でなくなったにもかかわらず、変更の届出がされていない
※ ワンストップ特例を申請した後で、 市外へ転居するなど申請書の記載事項に変更がある場合には、寄附した翌年の1月10日までに那覇市役所に届け出れば特例が適用されます。
```

ばっちり自然な改行位置になりました。


## おわりに
OCRライブラリの精度が低い場合にはCloud Vision APIやDocument AIを用いることで、より高い認識精度を得ることができるかもしれません。