宝くじの番号をOCRで一括確認

<img width=A%><img width=100% src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3784222/f920c76e-3c56-75b0-ea7d-4ea0fd880818.png">

これは、CANVAの生成AIに描いていただいたものです。
なかなかよいですね。

# はじめに

前回の投稿では、OCRとOpenAIを比較して、認識精度の比較をしてみました。
その結果は、下記投稿記事でご確認いただければと思っております。

https://qiita.com/ogi_kimura/items/f6b8e3426349767e8f7b

　今回は、宝くじ券の番号をOCRで認識させるプログラムを紹介します。私事で恐縮なのですが、先日「宝くじ記念くじ」を150枚買ったのですが、券を1つ1つ確認すると歳のせいか手がカサカサになり、紙で指が切れて血が出てしまいました。
　OCRを使って当選した券を瞬時に見分けられないか、ということで、宝くじ券番号を一括で大量に読み込んで、当選した宝くじ券を判定するプログラムを作成しました。「そんなこと、券売所の機械で店員さんに確認してもらえばいいのに。」と思う方もいるかと思いますが、そこはご愛敬ということで・・・

# OCRライブラリ
 Windowsでも利用できる`tesseract`を適用することにします。
 これについてのダウンロード方法やインストール方法については、先述した前回の投稿記事を参照ください。

 https://qiita.com/ogi_kimura/items/f6b8e3426349767e8f7b

# 宝くじの画像
 宝くじ券の番号の部分だけを画像として作成しました。

![画像一覧.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3784222/2819f349-d628-990f-86ad-d163e9f15216.jpeg)

こんな感じです。大きな金額が当選している抽選券は一切ありません。
200円の当選が1つあります。（`1.jpg`）
また、あまりに外ればかりで面白くないので、`11.jpg`では、少し加工をしました。下3桁を「124」にしたので、4等(1万円)当選とOCRが認識するかもしれません。
さすがに150枚の撮影はしんどいので、11枚のみをピックアップしてみました。

また、先日の抽選結果も併せて記載します。

![抽選結果.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3784222/237a5f28-8ca7-0e7e-6e4e-104ab69d1632.jpeg)

# プログラム
では、Pythonのプログラムを以下に示します。

```python:
import sys
import pytesseract
from PIL import Image
import pandas as pd
import re
import os
import glob

kekkas = [
     {'number':'108354', 'name':'1等 or 1等組違い賞', 'money': 15000000},
     {'number':'108353', 'name':'1等前後賞', 'money': 25000000},
     {'number':'108355', 'name':'1等前後賞', 'money': 25000000},
     {'number':'116950', 'name':'2等', 'money': 100000},
     {'number':'127099', 'name':'2等', 'money': 100000},
     {'number':'8309', 'name':'特別賞', 'money': 30000},
     {'number':'6449', 'name':'特別賞', 'money': 30000},
     {'number':'7541', 'name':'特別賞', 'money': 30000},
     {'number':'0395', 'name':'3等', 'money': 50000},
     {'number':'124', 'name':'4等', 'money': 10000},
     {'number':'77', 'name':'5等', 'money': 2000},
     {'number':'5', 'name':'6等', 'money': 200},
]

def image_to_text(image_path):
    # 画像を読み込む
    img = Image.open(image_path)

    # TesseractでOCRを実行
    custom_config = r'--oem 1 --psm 6'
    text = pytesseract.image_to_string(img, config=custom_config, lang='jpn')

    return text

if __name__ == "__main__":
    image_path = 'C:/Users/ogiki/Desktop/data'
    filepath_list = glob.glob(os.path.join(image_path, "**/*.*"), recursive=True)

    amount = 0
    for file in filepath_list: 
        base, ext = os.path.splitext(file)

        if ext == '.jpg':
            text = image_to_text(file)
            rows = text.split('\n')     # 念のため複数行の文章だと仮定
            text = text.replace('\n','')
            print(f"{file}  {text}")
            for kekka in kekkas:
                regex_len= 6-len(kekka['number'])
                if regex_len == 0:
                    regex = kekka['number']
                else:
                    regex = fr"[0-9]{{{regex_len}}}{kekka['number']}"

                if re.match(regex, str(rows[0])):
                    print(f"★当選★ --- 番号：{text}  等賞：{kekka['name']} 金額：{kekka['money']}")
                    amount = amount + kekka['money']
                    break
```
　まずは`kekkas`というディクショナリを作成しました。（結果の複数形で`kekkas`はダメなネーミングですね）
その中には「番号」と「（等級）名前」それから「（当選）金額」としています。

　次に`image_to_text`という関数がありますが、画像ファイルのパスをインプットとして、認識した文字をアウトプットするようにしました。

　それから「main」と続きますが、特定のフォルダ内のjpeg形式のファイルを繰り返し読み込みます。

　当選番号の桁数は等級によって異なりますので、ちょっとややこしいのですが、以下のようなプログラムにしました。そうすることで、例えば「1等は、6桁すべてが合致した場合のみ当選と判断し」、「6等は、下1桁のみが合致した場合で当選と判断する」というようにできます。

```python
for kekka in kekkas:
    regex_len= 6-len(kekka['number'])
    if regex_len == 0:
        regex = kekka['number']
    else:
        regex = fr"[0-9]{{{regex_len}}}{kekka['number']}"
```

最後に合致した場合は、「★当選★・・・・・」を標準出力します。

```python
print(f"★当選★ --- 番号：{text}  等賞：{kekka['name']} 金額：{kekka['money']}")
```


ここまでがプログラムの流れなのですが、OCRの精度を確認するため、OCRが認識した文字を標準出力に出力させることとしました。

```python
print(f"{file}  {text}")
```

# プログラム実行

ではプログラムを実行してみます。

```dos
C:/Users/ogiki/Desktop/data\1.jpg  012345
　　　★当選★ --- 番号：012345  等賞：6等 金額：200
C:/Users/ogiki/Desktop/data\10.jpg  12027テ7
C:/Users/ogiki/Desktop/data\11.jpg  120194
C:/Users/ogiki/Desktop/data\2.jpg  .140570
C:/Users/ogiki/Desktop/data\3.jpg  Su9\2う
C:/Users/ogiki/Desktop/data\4.jpg  1&0570)
C:/Users/ogiki/Desktop/data\5.jpg  イプ5プラ7
C:/Users/ogiki/Desktop/data\6.jpg  104561
C:/Users/ogiki/Desktop/data\7.jpg  0979エ
C:/Users/ogiki/Desktop/data\8.jpg  ュ78プ57
C:/Users/ogiki/Desktop/data\9.jpg  12027テ7
```

うーん、ほとんど上手に読み込めてませんねぇ。`1.jpg`と`6.jpg`だけが正しく認識されていました。11打数2安打、1割8分2厘・・・スタメン落ちですな。


# おわりに
今回は、緩い感じで記事を書いてみました。ですので、結果も緩～い感じになっちゃいました。
私の前回の投稿記事でもお伝えさせていただきましたが、`gpt-4o`を使って画像認識をするとまた違った結果になるかもしれません。

今日はここまでとします。

# 追記（psmの変更）
あまりに精度が悪かったので、`psm`の値を1～13まで変更して再度実行しました。
そうすると「8」の時に以下の様になりました。

```dosbatch
C:/Users/ogiki/Desktop/data\1.jpg  012345
　　★当選★ --- 番号：012345  等賞：6等 金額：200
C:/Users/ogiki/Desktop/data\10.jpg  120277
　　★当選★ --- 番号：120277  等賞：5等 金額：2000
C:/Users/ogiki/Desktop/data\11.jpg  』20194
C:/Users/ogiki/Desktop/data\2.jpg  140570
C:/Users/ogiki/Desktop/data\3.jpg  nu9125
C:/Users/ogiki/Desktop/data\4.jpg  1&0570)
C:/Users/ogiki/Desktop/data\5.jpg  ゴフ5プラ7
C:/Users/ogiki/Desktop/data\6.jpg  104561
C:/Users/ogiki/Desktop/data\7.jpg  0979エ
C:/Users/ogiki/Desktop/data\8.jpg  78757
C:/Users/ogiki/Desktop/data\9.jpg  120277
　　★当選★ --- 番号：120277  等賞：5等 金額：2000
```

5つ正解しました。という事で数字だけを読み取る場合は`psm`が「8」に設定することをおすすめします。