Tex, python, illusrator, VPSの学生ノート

latotex-blog

Python

DroidcamとOpenCVを使い年賀状の当選番号を半自動でチェックする

投稿日:







毎年50枚くらい年賀状を作っているのですが、当選番号を確認するのがめんどくさくなって来たので、Pythonで自動化できないか試してみました。(タイトルが半自動となっている理由はPythonスクリプトをみれば分かります)

自動化の過程は、年賀状を画像で保存しその画像からOCRで数字を読み取り、当選番号と一致していれば「win the prize!!」と表示します。

リアルタイムで画像で保存するため、WEBカメラが必要です。といってもWEBカメラは値段が高く、コロナウイルス流行で需要が高いため入手ができない・・・・・そのため、この記事ではスマホをWEBカメラの代わりにするために、Droidcamを使用しています。

作業環境とライブラリーのバージョンは以下の通りです。
・windows 10
・python:3.6.13
・conda version : 4.10.1
・opencv:4.5.1
・ffmpeg:4.3.1
・tesseract:4.1.1
・pyocr:0.5
・スマートフォン(Android)

今回の記事は長くなりそうなので、リンクのリストを作りました。上記のライブラリーのインストールが既にできている方は以下のリンクからPythonコードに飛んでください。

スポンサーリンク

必要なものをインストール

私はAnacondaを使っているので、Anaconda以外でPythonを使用している方は各自インストールをしてください。インストールするものはtesseract、pyocr、opencv、ffmpeg、Droidcamです。

tesseract、pyocrのインストール

まずは仮想環境を作ります。(ocr_yearcardは適当につけた環境名)pythonバージョンは3.6にすることに注意してください。

$ conda create -n ocr_yearcard python=3.6

なぜ3.6にしないといけないかというと、Pythonバージョンが3.6以上だとpyocrがインストールできないからです。間違ってPython3.8でインストールすると以下のような表示が出ます。

(ocr_yearcard) C:\Users\user>conda install -c brianjmcguirk pyocr
Collecting package metadata (current_repodata.json): done
Solving environment: failed with initial frozen solve. Retrying with flexible solve.
Solving environment: failed with repodata from current_repodata.json, will retry with next repodata source.
Collecting package metadata (repodata.json): done
Solving environment: failed with initial frozen solve. Retrying with flexible solve.
Solving environment: \
Found conflicts! Looking for incompatible packages.
This can take several minutes.  Press CTRL-C to abort.
failed

UnsatisfiableError: The following specifications were found
to be incompatible with the existing python installation in your environment:

Specifications:

- pyocr -> python[version=	'>=3.6,<3.7.0a0']

Your python: python=3.8

If python is on the left-most side of the chain, that 's the version you 've asked for.
When python appears to the right, that indicates that the thing on the left is somehow
not available for the python version you are constrained to. Note that conda will not
change your python version to a different minor version unless you explicitly specify
that.

Pythonのバージョン指定を間違えなければ、後はインストールするだけです!

$ conda install -c conda-forge tesseract
$ conda install -c brianjmcguirk pyocr

opencv、ffmpegのインストール

OpenCVで動画を使うときはI/Oが有効になっていなければいけません。しかし、conda-forgeリポジトリで提供されているopencvは有効になっているので、こちらをインストールします。(conda install opencvはNG)
また、既にconda-forgeリポジトリでOpencvをインストールしていても、ffmpegがインストールされていないと動画を扱えない可能性があるので「conda list ffmpeg」で確認した方がよいです。

$ conda install -c conda-forge opencv ffmpeg

Droidcamのインストール

長くなるので、別の記事にしました。以下のリンクカードをクリックしてください。

Pythonスクリプト

ここまでインストール続きでしたが、ここでようやくPythonを動かせます(*^▽^*)

画像からハガキの部分を抜き出す方法は以下のサイト様のコードを使用しました。
素晴らしい記事をありがとうございます!

しかし、ハガキを抜き出すには少し撮影を工夫する必要があります。
・画像はハガキが縦長に写るように撮影する
・テーブルクロス等は使わず、無地の上にハガキを載せた状態で撮影する
この2点を守れば、ハガキの形が上手く取り出せるはずです。

その他、参考・引用させていただいたサイト様については、コメントに書いていますので、
適宜参照ください。

from PIL import Image
import pyocr
import pyocr.builders
import cv2
import numpy as np
import itertools
import os
import glob
import re
import time
import datetime


#はがきの画像から右下の番号の場所だけを切り取る
#引用元:https://haitenaipants.hatenablog.com/entry/2018/08/17/011916
def cut_img(file_path):

    cut_img = np.array(Image.open(file_path))
    gray = cv2.cvtColor(cut_img, cv2.COLOR_BGR2GRAY)
    ret,thresh = cv2.threshold(gray,128,255,cv2.THRESH_BINARY)
    contours, hierarchy = cv2.findContours(thresh , cv2.RETR_TREE, cv2.RETR_LIST)

    menseki=[ ]

    for i in range(0, len(contours)):
        menseki.append([contours[i],cv2.contourArea(contours[i])])

    menseki.sort(key=lambda x: x[1], reverse=True)

    epsilon = 0.1*cv2.arcLength(menseki[0][0],True)
    approx = cv2.approxPolyDP(menseki[0][0],epsilon,True)

    approx=approx.tolist()

    left = sorted(approx,key=lambda x:x[0]) [:2]
    right = sorted(approx,key=lambda x:x[0]) [2:]

    left_down= sorted(left,key=lambda x:x[0][1]) [0]
    left_up= sorted(left,key=lambda x:x[0][1]) [1]

    right_down= sorted(right,key=lambda x:x[0][1]) [0]
    right_up= sorted(right,key=lambda x:x[0][1]) [1]

    perspective1 = np.float32([left_down,right_down,right_up,left_up])
    perspective2 = np.float32([[0, 0],[1378, 0],[1378, 2039],[0, 2039]])

    psp_matrix = cv2.getPerspectiveTransform(perspective1,perspective2)
    img_psp = cv2.warpPerspective(cut_img, psp_matrix,(1378,2039))

    img_psp = img_psp[1850 : 1950, 900:1300]

    return img_psp


#はがきの右下の番号を読み取る
#https://qiita.com/d_m/items/4690aa9f03bb13bf1d21 にあるスクリプトから少し改編
def ocr(img):

    # ツール読み込み
    tools = pyocr.get_available_tools()
    tool = tools[0]

    # 画像読み込み
    img_org = Image.fromarray(img)

    # OCR
    max_medals = tool.image_to_string(img_org, lang='eng', builder=pyocr.builders.DigitBuilder(tesseract_layout=8))

    # 数値以外の文字を除去
    max_medals = re.sub(r'\D', '', max_medals)

    return max_medals


#読み取った番号が当選番号と合っているかチェックする
def judge_num(pic_folder, nu_1, nu_2, nu_3):

    i = pic_folder
    ocr_file = cut_img(i)

    try:
        test = ocr(ocr_file)[-6]
        nu = ocr(ocr_file)

        if nu == nu_1:
            print(f'win the first prize!!, {os.path.basename(i)}')
        elif nu[-4:] == nu_2:
            print(f'win the second prize!!, {os.path.basename(i)}')
        elif nu[-2:] in nu_3:
            print(f'win the third_prize!!, {os.path.basename(i)}')
        else:
            print(nu)

    except IndexError:
            print(f'can not OCR, {os.path.basename(i)}')



#Droid camからリアルタイムで画像を保存する
#https://note.nkmk.me/python-opencv-camera-to-still-image/ にあるスクリプトから少し改編
def save_frame_camera_key(device_num, base_path, nu_1, nu_2, nu_3, ext='jpg', delay=1, window_name='frame'):

    #PCの設定でカメラがオフになっていないか確認しておくこと!
    cap = cv2.VideoCapture(device_num)

    #実行しても何もでない場合はこれがFalseになっているから
    if not cap.isOpened():
        return

    os.makedirs(base_path, exist_ok=True)

    #保存されるファイルの名前はpicから始まり、撮った枚数と日時が含まれる
    add_path = os.path.join(base_path, 'pic')
    save_time = datetime.datetime.now().strftime('%Y-%m-%d_%H%M-%S')

    n = 0
    while True:
        ret, frame = cap.read()

        save_path = '{}_{}_{}.{}'.format(add_path, n, save_time, ext)
        cv2.imshow(window_name, cv2.rotate(frame, cv2.ROTATE_90_CLOCKWISE))

        key = cv2.waitKey(delay) & 0xFF

        if key == ord('l'):
            cv2.imwrite(save_path, frame)
            judge_num(save_path, nu_1, nu_2, nu_3)
            n += 1

        #qを押すと撮影終了
        elif key == ord('q'):
            break

    cv2.destroyWindow(window_name)


if __name__ == '__main__':

    #当選番号
    first_prize = '757462'
    second_prize = '6335'
    third_prize = ['60', '58', '50']

    #画像を入れるフォルダーを作成
    dir = os.path.dirname(__file__)
    tag = 'camera_capture_2'
    output_path = os.path.join(dir, tag)

    save_frame_camera_key(1, output_path, first_prize, second_prize, third_prize)

結果:画像の解像度が低くて読み取れませんでした・・!

上記のPythonスクリプトから、実際に年賀状を読み取ってみましたが上手く行きませんでした。そこでDroidcamを使わず、スマホのデフォルトのカメラから撮った画像を使ったところ上手く読み取れたので解像度が原因ではないかと思います。Droidcamの有料版を使うのも1つの手かもしれません。

参考・引用させていただいたサイト様

tesseract、pyocrのインストール
https://qiita.com/kamome885/items/d048ad10c4bf7f56c748

conda-forgeリポジトリのopencvとffmpegについて
https://rc30-popo.hatenablog.com/entry/2019/08/17/134844

OpenCVでリアルタイム撮影をしてキーボードを押下したタイミングで保存する方法
https://note.nkmk.me/python-opencv-camera-to-still-image/

OCRの手順
https://qiita.com/d_m/items/4690aa9f03bb13bf1d21


-Python
-, ,

Copyright© latotex-blog , 2021 All Rights Reserved Powered by STINGER.