OCRツール(pytesseract) + OpenAIで画像から文字列読み取りと解析

## はじめに
日々手作業で家計簿をつけているのですが、入力が面倒なのでレシートの写真から情報を読み取って、家計簿入力の補助をできるアプリが作れたりしないかな、というのが今回の出発点です。

OCR(光学文字認識　画像データから文字列を読み取る技術)により、レシートの写真から金額や商品名などを読み取り、AIの力で商品名などからカテゴリ（食費、光熱費など）分けをやってもらおうという作戦です。

## 1. Tesseractとpytesseractの導入
TesseractはオープンソースのOCRエンジンで、pytesseractはTesseractをpythonで利用するためのライブラリです。
それぞれインストールしていきます。

### Tesseract
macのPCのため、Homebrewでインストールを行いました。
```
brew install tesseract
```
このままでは日本語の読み取りに対応していないので、日本語の学習データを追加します。
こちらからjpn.traineddataとjpn_vert.traineddataをダウンロードします。（jpn_vertは縦書き用ですが、一応合わせてダウンロードしておきます）
https://github.com/tesseract-ocr/tessdata_best

以下のコマンドでtesseractのディレクトリ構造を確認します。
```
% brew list tesseract
/opt/homebrew/Cellar/tesseract/5.5.0/bin/ambiguous_words

（長いので省略）

/opt/homebrew/Cellar/tesseract/5.5.0/sbom.spdx.json
/opt/homebrew/Cellar/tesseract/5.5.0/share/tessdata/ (39 files)
```
/share/tessdata/となっているディレクトリのパスを確認し、ここに先ほどダウンロードしたjpn.traineddataとjpn_vert.traineddataをコピーします。

コピーできたら以下のコマンドで対応言語を確認します。jpnとjpn_vertが入っていますね。
```
% tesseract --list-langs
List of available languages in "/opt/homebrew/share/tessdata/" (5):
eng
jpn
jpn_vert
osd
snum
```

### pytesseract
続けてpytesseractをインストールします。
```
pip install pytesseract
```

これで画像ファイルから文字列の認識を行う準備が整いました。
このレシートの画像の文字列認識を試してみます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3959131/f38bb27a-1c45-be26-18a5-333297dc3bc7.jpeg" width=50%>

ソースコードは以下です。
printで読み込んだ文字列を出力します。
```python:views.py
from PIL import Image
import pytesseract

def inputImageTest():
    # 画像ファイル読み込み
    image = Image.open('画像のパス')
    text = pytesseract.image_to_string(image, lang='jpn+jpn_vert')
    print('読み込んだ文字列:\n'+ text)

```

出力結果
```
読み込んだ文字列:
中 央 自動 車道

石川 PA (下り 綿 )
中 月 本 ハイ ウェ イリ テー ル ( 株 )
TEL_ 042ー642 一 1209

2ZU24 年 11 月 30 日 ( 土 ) 9 時 U8 分

せ 淵 。 上
4514503448214
ドデカ ミン ノン \194*
小 計 \194
合計 1 点 \194
33 予 貞 ロロ ギン ロロ U
3 金光 OO
(内 課税 8% \194)
| は ピ さい \14)
```
日本語がだいぶ怪しいですね。やはり写真からなので読み取り精度は落ちるようです。
ですが一番大事なドデカミンの金額はしっかりと認識できました。


ここから必要な部分だけを抜き出していきます。
読み取った文字列から、¥（バックスラッシュ）を含む行のみ抜き出して、バックスラッシュの前の部分（商品名などカテゴリ判定用の文字列）と、後の部分（金額）に分けていきます。
```python:views.py
    # 文字列を1行ずつに分割してリストに格納
    textList = text.splitlines()

    for i in textList:
        # '\'（¥）を含む場合
        if '\\' in i:
            category_text = i.split('\\')[0]
            i = i.split('\\')[-1]
            price = ''
            for j in i:
                if j.isdecimal():
                    price += j
            print('カテゴリ判定用テキスト:' + category_text + ', 金額:' + str(price))
```
出力結果
```
カテゴリ判定用テキスト:ドデカ ミン ノン , 金額:194
カテゴリ判定用テキスト:小 計 , 金額:194
カテゴリ判定用テキスト:合計 1 点 , 金額:194
カテゴリ判定用テキスト:(内 課税 8% , 金額:194
カテゴリ判定用テキスト:| は ピ さい , 金額:14
```
小計などが入ってしまっていますが、一旦そのまま進めていきます。

## 2. OpenAIの導入
カテゴリ判定用の文字列と金額の抽出ができたので、次はOpenAIによるカテゴリ判定をやっていきます。
まずはopenaiのライブラリをインストールします。
```
pip install openai
```

次に、OpenAIのAPI利用のために、公式サイトからAPIキーを取得します。
https://openai.com/ja-JP/api/
ちなみに2024年12月現在、APIキーの発行自体は無料でできますが、利用には最低5ドル必要なようです。

カテゴリ判定用テキストを元に、OpenAIにカテゴリを判定してもらいます。
ドデカミンがドデカ ミン ノンになってますが頑張って分類してもらいましょう。
```python:views.py
import openai
openai.api_key = "[取得したキー]"

# OpenAIを利用してカテゴリ用のテキストをもとにカテゴリを設定する
def categorize(categoryText):
    categories = '光熱費, 通信費, 家賃, 食費, 娯楽費, 交通費, その他'
    res = openai.ChatCompletion.create(
        # 利用するモデルの指定
        model='gpt-3.5-turbo',
        messages=[
            # 対話の初期設定を行う
            {
                'role': 'system',
                'content': '日本語で項目名のみ返答してください。'
            },
            # カテゴリの判定を依頼する対話
            {
                'role': 'user',
                'content': categoryText + 'を' + categories + 'のいずれかの項目に分類し、項目名のみ答えてください。'
            },
        ],
    )
    print('カテゴリ:' + res.choices[0].message['content'])
    return res.choices[0].message['content']
```
カテゴリ判定用テキストを引数にcategorizeを呼び出し、OpenAIに対話形式でカテゴリを判定してもらいます。分類は光熱費, 通信費, 家賃, 食費, 娯楽費, 交通費, その他のいずれか。
応答値にOpenAIからの返答が格納されているのでそれを出力します。

各行に対して実行した結果はこちら。処理順は先ほどまでと同じで、「ドデカ ミン ノン」から順に行っています。
```
カテゴリ:食費
カテゴリ:その他
カテゴリ:食費
カテゴリ:光熱費
カテゴリ:食費
```
不正確な文字列なのに「ドデカ ミン ノン」ちゃんと食費に分類されてますね。OpenAIすごい。

***
目で見えた方が結果がわかりやすいので、簡単なWebアプリを作って、画像から読み取って解析したデータを登録、表示してみました。主題から外れるので詳しい説明は省略しますが、Djangoというフレームワークを利用しています。
ファイルを選択ボタンからレシートの画像ファイルを選択し、フォームとして画像データを連携して登録できるようにしました。

一枚目のキャプチャ画像にあるデータは、あらかじめ登録しておいたものになります。
二枚目のキャプチャがレシートから追加したデータを登録した後になります。
6行目の194円の食費が「ドデカミン」のデータで、以降は小計などのデータです。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3959131/aa7b6bc2-eb57-22cd-441e-90143e6785ae.jpeg" width=50%>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3959131/9fda9db0-98ca-591c-6547-6fccb5e7962b.jpeg" width=50%>


## おわりに
写真からの文字列読み取りは精度に難ありで、さらに店によってレシートのフォーマットが異なるため、レシートの写真から家計簿のデータを登録する、というのは実用にはまだまだ厳しいと感じました。
ただし今回使ってみて、OCR+AIによる解析は上手く活用すれば何らかの作業の自動化などに利用できそうな可能性も感じました。

## 参考
[画像から文字を抽出](https://qiita.com/i-am-misaki/items/334a31b239b3a81258e4)

[Mac端末上でpytesseractを使って画像文字認識（英語、日本語）を試してみた](https://dev.classmethod.jp/articles/ocr-on-a-mac-device-with-pytesseract/)

[ChatGPT APIの使い方【Python編 | ソースコードあり】](https://zenn.dev/umi_mori/books/chatbot-chatgpt/viewer/openai_chatgpt_api_python)