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



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

 

はじめに

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

qiita.com

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

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

qiita.com

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

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枚のみをピックアップしてみました。

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

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」に設定することをおすすめします。