【Python】Pyocrを使用して受付機を考えてみた

connpassを使用しイベントを実施してみることにしました
受付などできない時間帯に変わりをしてくれるものが作れないかPythonを学び始めたこともあり試しに作ってみることにしました

簡単に実装イメージ、connpassの受付票を読み取って、スプレッドシートと照らし合わせOKなら通知出す形です

![スクリーンショット 2024-07-21 22.54.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276326/c818acc4-3753-b5f5-3879-cf450bc7bc56.png)

## 実装準備

### 読み取りを実装準備

関連ライブラリをインストール
```
!pip install pyocr
!pip install opencv-python
```

また日本語対応のためTesseractをインストール
今回windows使っていたのでこちらのサイトを参考に64bit版のTesseractをインストール

https://gammasoft.jp/blog/tesseract-ocr-install-on-windows/

tesseractがうまく読み取れなかったのでパス指定し設定しました
```
import cv2
import pyocr
from PIL import Image
import pyocr.builders

pyocr.tesseract.TESSERACT_CMD = r'C:\Program Files\Tesseract-OCR\tesseract.exe'
```
### googleスプレッドシート連携

スプレッドシートの連携には、Google Cloud Platformの設定が必要です

設定に関しては、こちらのサイトを参考に行いました、手順こちらに書くと長くなってしまいそうなので、Google Cloud Platformの設定は、別でまた記載するようにします

https://zenn.dev/yamagishihrd/articles/2022-09_01-google-spreadsheet-with-python


設定終わったら、必要なライブラリをインストール
```
pip install --upgrade pip
pip install google
pip install gspread
```

googleスプレッドシートとgoogleドライブのスコープを設定します

```
# スプレッドシートの設定
scopes = [
'https://www.googleapis.com/auth/spreadsheets',
'https://www.googleapis.com/auth/drive'
]

credentials = Credentials.from_service_account_file(
key_file,
scopes=scopes
)
```

## 実装

実装の流れは、
[openCVでカメラ起動 > 文字読み取り > スプレッドシート照合 > 成功したらアラートと成功音]
という流れで作成しました

### カメラ起動 > 文字読み取り

関連モジュールを読ませ、カメラの指定や、ウィンドウサイズを設定します
```
import cv2
import pyocr
from PIL import Image
import pyocr.builders

pyocr.tesseract.TESSERACT_CMD = r'C:\Program Files\Tesseract-OCR\tesseract.exe'

tools = pyocr.get_available_tools()
print(tools)
tool = tools[0]

#使用するカメラを指定
cap = cv2.VideoCapture(0)

#希望のセッティングにしてみる （ウィンドウサイズ指定）
cap.set(cv2.CAP_PROP_FPS,60); 
cap.set(cv2.CAP_PROP_FRAME_WIDTH,1280);
cap.set(cv2.CAP_PROP_FRAME_HEIGHT,720);
```

起動から文字読み取りまで
OCRには画像化したもの渡すため一部を切り取り画像化したものをtool.image_to_stringに渡し画面内の文字を取得します。

```
def OcrTest():
    while True:
        ret, frame = cap.read()

        Height, Width = frame.shape[:2]
        img = cv2.resize(frame, (int(Width), int(Height)))

        ocr(img, Width, Height)

        cv2.imshow('Ocr Test', img)

        if cv2.waitKey(100) & 0xFF == ord('q'):
            break

    cap.release()
    cv2.destroyAllWindows()

def ocr(img, Width, Height):
    dst = img[100:Height-200, 100:Width-200]  # OCRで読みたい領域を切り出す
    PIL_Image = Image.fromarray(dst)
    text = tool.image_to_string(
        PIL_Image,
        lang='jpn', #日本語指定
        builder=pyocr.builders.TextBuilder()
    )

    if text != "":
        list = text.split()
        if list:
            # スプレッドシートと照合
            googleSheet(list)
```

### スプレッドシート照合

先にgspreadやスコープや、使用するスプレッドシートを読み込ませます

```
# スプレッドシートの設定
key_file = "XXXXX.json"　# 認証鍵

# スコープの設定
scopes = [
'https://www.googleapis.com/auth/spreadsheets',
'https://www.googleapis.com/auth/drive'
]

credentials = Credentials.from_service_account_file(
key_file,
scopes=scopes
)

gc = gspread.authorize(credentials)


# 対象のスプレッドシート
spreadsheet_url = "https://docs.google.com/spreadsheets/d/XXXXXX"
spreadsheet = gc.open_by_url(spreadsheet_url)
```

スプレッドシート内、OCRから読み取った文字列検索
対象文字を含むセルが見つかったら、出席と書き込みます

```
def googleSheet(text):
    worksheet = spreadsheet.sheet1
    all_vals = spreadsheet.sheet1.get_all_values()
    
    terget_row = 8 #書き込み対象列番号
    match_row = []
    cal = 1
    for cols in all_vals:
        row = 0
        for val in cols:
            for txt in text:
                if(val == txt):
                    match_row = [cal,row]
                    break

            row += 1
            
        cal += 1
    
    #文字検索
    if(match_row):
        # 対象セルに出席と書き込み
        worksheet.update_cell(match_row[0], terget_row, '出席')

        # 受付通知アラートへ
        showMessage("受付完了","info",3000)
```

### 成功通知

受付が成功した旨を音と、アラートでお知らせします

音の通知には、playsoundというライブラリを使用しました

```
pip install playsound
```

アラートに関しては、tkinterとPythonに入っているライブラリをそのまま使用します

```
def showMessage(message, type='info', timeout=2500):
    import tkinter as tk
    from tkinter import messagebox as msgb

    root = tk.Tk()
    root.withdraw()
    try:
        # root.after(timeout, root.destroy)
        if type == 'info':

            # 成功音
            playsound("SAMPLE.mp3")

            # アラートが自動消えるタイマー
            root.after(timeout, root.destroy)

            # アラート表示
            msgb.showinfo('Info', message, master=root)
        elif type == 'warning':
            msgb.showwarning('Warning', message, master=root)
        elif type == 'error':
            msgb.showerror('Error', message, master=root)
    except:
        pass
```

一連の流れは以上。
実際に作成した全体のコードはこちらのocr_reception_machine.pyに

https://github.com/merika427/py_ocr_reception_machine/blob/main/ocr_reception_machine.py

これにて、スプレッドシートに受付書き込め通知まで出すことができました！


#### 追記

connpassの受付票にQRコードが表示されるようになりました
QRコードを読み取るようにすると精度が上がるので、調整しました

https://help.connpass.com/participants/event-checkin#participants-event-checkin-qr

cv2のQRCodeDetectorを使用します


```
# QRコードのデコーダーを準備
qr_detector = cv2.QRCodeDetector()

while True:
    ret, frame = cap.read()
    if not ret:
        print("カメラからの映像が取得できませんでした。")
        break

    # QRコードをデコード
    data, bbox, _ = qr_detector.detectAndDecode(frame)
    if data:
        # 読み取ったデータを確認
        print(data)
```

読み取った内容は、受付番号が含まれているURLになっており、イベント管理者がコンパスにログインした端末でQRコードを読み取りアクセスすると受付状態になります

ただし、今回は、連続して読み取り可能にするため読み取ったURLの受付番号を切り取り、
受付表スプレッドシートを参照し受付を可能にすることに

```
url = data
match = re.search(r'/([^/]+)/$', url)
if match:
    # 末尾にある受付番号を取得
    result = match.group(1)

    # 以前作ったスプレッドシートマッチ用のメソッドgoogleSheetに番号を渡す
    if(googleSheet([result])):
        print("受付成功")
```

これでQRコードを使う形に変更できました

実際に作成した全体のコードはocr_reception_machine.py_qr.pyになります

https://github.com/merika427/py_ocr_reception_machine/blob/main/ocr_reception_machine.py