[Python] 307 pyocr 文字認識によるブラウザの文字読み取り 余白増しによる精度向上

前回の続きです。

Google Chromeのurl欄で読み取れない文字列に出くわしたので、精度向上の検討を行いました。

url欄はスラッシュと文字がかなり近いため、スラッシュを避けて切り取った画像の端と文字が接近してしまいます。そこで背景と同色で一回り大きい画像に貼り付けてみました。

結果は成功でした。これで完成としたいところです。

あとは文字のフォントをサンセリフ体(日本語フォントのゴシック体に相当)ではなくセリフ体(明朝体と同種)にしたら読み取り精度がさらに向上するのでしょうが、url欄のフォントを変える方法が分かりません。

import pyautogui as gui
import pyocr
import pyocr.builders
from PIL import Image
from PIL import ImageEnhance

tools = pyocr.get_available_tools()
tool = tools[0]
print(f"tool {tool.get_name()}")

ret = gui.locateCenterOnScreen("url.png", confidence=.8)
gui.moveTo(ret) # urlへのカーソル移動を確認

# スクリーンショットの左上座標を算出
x1 = ret.x + 34
y1 = ret.y - 10
print(f"x1:{x1} y1:{y1}")

# スクリーンショットのサイズを設定
width = 200
height = 20

img = 'url_temp.png'
img2 = 'url_temp2.png'

sc = gui.screenshot(region=(x1, y1, width, height))
sc.save(img)

# 得られた画像のコントラストを1.5倍にして、縦5倍横5.5倍に拡大する
img_open = Image.open(img)
img_con = ImageEnhance.Contrast(img_open)
img_con_enh = img_con.enhance(1.5)
img_con_enh2 = img_con_enh.resize((1100,100))

# 1200*150の同色背景画像に貼り付ける
img_back = Image.open("back.png")
img_back2 = img_back.copy()
img_back2.paste(img_con_enh2,(50,25))
img_back2.save(img2)

# 画像から設定6で文字認識し文字データへ変換
txt = tool.image_to_string(
        Image.open(img2),
        lang="eng",
        builder=pyocr.builders.TextBuilder(tesseract_layout=6))

print(txt)

[Python] 306 pyocr 文字認識によるブラウザの文字読み取り

[macOS Catalina 10.15.7]

pyocrを使ってブラウザの文字を読み取りました。

今回はurl欄のurlを取得しました。この方法で動画サイトのチャンネル名などをスクリーンショットから文字データにできます。

Google Chromeのurlはドメイン名から右部分がグレイアウトしているため、コントラストを上げて文字を少し横長にしさらにtesseract_layoutの設定を調整しました。なかなか正確な文字起こしができず、時間が掛かりました。

設定が合わないと実際からは程遠い文字列になりますが、フィットする設定になると突然正しく認識します。最適解に近づいているのかどうか分かりにくいのが難点です。

様々なチャンネル名で検証した結果、コントラストは1.5倍、画像は縦横それぞれ5倍、5.5倍に拡大、tesseract_layoutは設定6にして何とか読み取れるようになりました(ディスプレイはフルHD)。あくまでも仮設定です。4Kディスプレイであれば拡大しなくて済むかもしれません。

pip install pyocr
brew install tesseract
import pyautogui as gui
import pyocr
import pyocr.builders
from PIL import Image
from PIL import ImageEnhance

tools = pyocr.get_available_tools()
tool = tools[0]
print(f"tool {tool.get_name()}")

# ドメイン名の座標を取得
ret = gui.locateCenterOnScreen("url.png", confidence=.8)
gui.moveTo(ret) # urlへのカーソル移動を確認

# スクリーンショットの左上座標を算出
x1 = ret.x + 34
y1 = ret.y - 10
print(f"x1:{x1} y1:{y1}")

# スクリーンショットのサイズを設定
width = 200
height = 20

img = 'img.png'
img_con_enh = 'img2.png'

# スクリーンショットを撮る
sc = gui.screenshot(region=(x1, y1, width, height))
sc.save(img)

# 得られた画像のコントラストを1.5倍、サイズを縦5倍横5.5倍に拡大する
img_open = Image.open(img)
img_con = ImageEnhance.Contrast(img_open)
img_con_enh_open = img_con.enhance(1.5)
img_con_enh_open2 = img_con_enh_open.resize((1100,100))
img_con_enh_open2.save(img_con_enh)

# 画像から設定6で文字認識し文字データへ変換
txt = tool.image_to_string(
        Image.open(img_con_enh),
        lang="eng",
        builder=pyocr.builders.TextBuilder(tesseract_layout=6))

print(txt)

[Python] 305 pyautogui 画面内の画像を一定時間おきにクリック 改良版

while文でより完成度を高めました。目的のアイコンが出現するまでwhile文内でループします。1分毎にチェックするので、クリック後の待機時間も設定不要です。

ネット通販の在庫チェックはselenium等を使ったクローリングよりもpyautoguiの画像認識の方が断然楽ですが、見つけ次第注文するとなると微妙です。

私自身は在庫チェックにはクローリングを利用し、注文は人力です。自動発注はやろうと思えばできるはずですが、そこは一線を引いています。

import pyautogui as gui
import time

time.sleep(2)

c = 0
while gui.locateCenterOnScreen("user_icon.png"):
    i = 0
    while not gui.locateCenterOnScreen("point_icon.png",confidence=.8):
        print(f"アイコンなし {i}分経過 {c}回クリック済み")
        time.sleep(60)
        i = i + 1
    else:
        ret = gui.locateCenterOnScreen("point_icon.png",confidence=.8)
        gui.moveTo(ret)
        gui.click()

        print(f"クリック完了 {c+1}回目")
        c = c + 1

[Python] 304 pyautogui 画面内の画像をクリックする際の精度調整

[macOS Catalina 10.15.7]

pyautoguiを使って画面内画像をクリックする際、精度を下げないと全く認識してくれないことがあります。

そのような場合はopenCV(Open Source Computer Vision Library, intelが開発)をpipでインストールし、画像認識精度(confidence)や色(白黒にする場合はgrayscale=True)を調整します。

macOSのメールアプリを起動し、各ボックスの未開封メールを開けて最後にアプリを閉じるコードを書いてみました。

このメールアプリの場合は、一度ウィンドウの空白部分をクリックしてアクティブにしないとメールボックスをクリックできませんでした。

またメールを受信するアイコンを正確に認識できず少しずれたところをクリックするため、ここだけ精度を0.9に上げています。

全体的に精度を下げており画面の状態によっては誤動作の恐れがあるので、慎重に扱うべきですね。

pyautoguiでデジタル書籍をpdf化するのも容易でしょう。すでにその種のコードは広まっているかと思いますが、kindle書籍のバックアップを取るコードを自分で書いてみたいです。

import pyautogui as gui
import time

# メールアプリを起動する
ret = gui.locateCenterOnScreen("mail.png", grayscale=True, confidence=.6)
gui.click(ret)

time.sleep(4)

# メールを受信する
ret_recieve = gui.locateCenterOnScreen("recieve.png", grayscale=True, confidence=.9)
gui.click(ret_recieve)

time.sleep(3)

box_list = ["gmail","gmail2"]

for box in box_list:
    # メールボックスを開ける
    ret2 = gui.locateCenterOnScreen(f"{box}.png", grayscale=True, confidence=.6)
    gui.moveTo(ret2)
    gui.click()

    time.sleep(1)
    
    # 画面上端にカーソルを移動し隠れているメニューバーを表示させる
    gui.moveTo(100,0)

    time.sleep(1)

    # メニューバーのメールボックスをクリックしてプルダウンさせる
    ret3 = gui.locateCenterOnScreen("メールボックス.png", grayscale=True, confidence=.6)
    gui.moveTo(ret3)
    gui.click()

    time.sleep(1)

    # 未開封メールを全て開ける
    ret4 = gui.locateCenterOnScreen("open.png", grayscale=True, confidence=.6)
    gui.click(ret4)

    time.sleep(1)

    # アプリの空白部分をクリックしてウィンドウをアクティブにする
    gui.moveTo(250,50)
    gui.click()

    time.sleep(1)

# メールアプリのアイコンを右クリックする
ret5 = gui.locateCenterOnScreen("mail.png", grayscale=True, confidence=.6)
gui.rightClick(ret5)

time.sleep(1)

# 終了をクリックする
ret6 = gui.locateCenterOnScreen("終了.png", grayscale=True, confidence=.6)
gui.click(ret6)

[Python] 303 pyautogui 画面内の画像を一定時間おきにクリック

[macOS Catalina 10.15.7]

9日ぶりの記事になります。

とあるサイトで10分おきにアイコンが出現しクリックするとポイントがもらえるので、定期的に自動クリックするコードを書きました。

事前にアイコンの画像(スクショ切り取り)を用意し、pyautoguiとPillowをpipでインストールしておく必要があります。

import pyautogui as gui
import time

for i in range(10000):
    for j in range(5): # 画像サーチを5回繰り返す
        try:
            ret = gui.locateCenterOnScreen("image.png")
            if ret == None:
                pass
            else:
                gui.click(ret) # 今回のケースではカーソル移動のみでした
                break
        except Exception as e:
            print(e)

        time.sleep(1)

    gui.click() # 画像をクリックする

    for k in range(10):
        time.sleep(60)
        print(f"{k+1}分経過")

[Python] 302 自製ライブラリpandas_exの機能追加 to_html デフォルト設定

前の記事の続きです。

自製ライブラリpandas_exのto_html関数は引数が6つもあるため、第4引数以降はデフォルト値を設定して省略可能にしました。

import pandas as pd

def to_html(df,html_name,encoding,header=False,index=False,border=1):
# header,index:True or False
# border:1(重線),2(重線)
# 第4引数(header)以降は省略可

    html_string = '''
    <html>
        <head>
            <meta charset={encoding}>
            <title></title>
        </head>
        <body>
            {table}
        </body>
    </html>
    '''

    with open(html_name,'w') as f:
        f.write(html_string.format(table=df.to_html(header=header,index=index),encoding=encoding))

    with open(html_name,'r') as f:
        html = f.read()

    if border == 1:
        html_new = html.replace('border="1"','border="1" style="border-collapse: collapse; border-color: #add8e6"')
    elif border == 2:
        html_new = html.replace('border="1"','border="1" style="border-collapse: separate; border-color: #add8e6"')

    with open(html_name,'w',encoding=encoding) as f:
        f.write(html_new)
import pandas as pd
from my_library import pandas_ex as pd_ex

df = pd.read_csv("test.csv",encoding='shift_JIS')

# 第5,6引数は省略
pd_ex.to_html(df,"test.html","UTF-8",True)

[Python] 301 自製ライブラリpandas_exの機能追加 to_html

pandasのto_htmlではtableの境界線が黒の2重線になるため、任意の色で1重線にすることもできる関数を自製ライブラリpandas_exに追加しました。

引数を増やしすぎてもかえって不便ですから、border-colorはライブラリ内で設定するようにしています。

pandasでhtmlを作成する機能は見た目に関しては最低限の内容なので、自分で機能を増やす他ないです。

import pandas as pd

def to_html(df,html_name,encoding,header,index,border):
# header,index:True or False
# border:1(重線) or 2(重線)
    html_string = '''
    <html>
        <head>
            <meta charset={encoding}>
            <title></title>
        </head>
        <body>
            {table}
        </body>
    </html>
    '''

    with open(html_name,'w') as f:
        f.write(html_string.format(table=df.to_html(header=header,index=index),encoding=encoding))

    with open(html_name,'r') as f:
        html = f.read()

    if border == 1:
        html_new = html.replace('border="1"','border="1" style="border-collapse: collapse; border-color: #add8e6"')
    elif border == 2:
        html_new = html.replace('border="1"','border="1" style="border-collapse: separate; border-color: #add8e6"')

    with open(html_name,'w',encoding=encoding) as f:
        f.write(html_new)
import pandas as pd
from my_library import pandas_ex as pd_ex

df = pd.read_csv("test.csv",encoding='shift_JIS')

# encodingはUTF-8,headerあり,2重線でhtmlファイルを作成
pd_ex.to_html(df,"test.html","UTF-8",True,False,2)

[Python] 300 自製ライブラリpandas_exの機能追加 to_csv

データフレームをワンライナーでcsvファイルに変換できるようにしました。

with文が今一つ好きになれないので作ってみました。

Pythonの記事が300回に到達しました。これからもマイペースで書いていきます。

import pandas as pd

def to_csv(df,csv_name,encoding,header,index): # header,index:True or False
    with open(csv_name,'w',encoding=encoding) as f:
        df.to_csv(f,header=header,index=index)
import pandas as pd
from my_library import pandas_ex as pd_ex

df = pd.read_csv("test.csv",encoding='shift_JIS')

<dfに何らかの処理を施す>

# encodingはUTF-8,headerありでcsvファイルを作成
pd_ex.to_csv(df,"test2.csv","UTF-8",True,False)

[Python] 299 自製ライブラリによるpandasの機能拡張

pandasで欲しい機能については自製ライブラリで機能拡張することにしました。

これをうまく利用すれば、自分の好きなように書いてcsvファイルを作ったりできそうです。

個人開発だからできることで、共同開発でこんなことしたらヒンシュクものですね。

import pandas as pd

# データフレームの列に欠損値があっても他の値を数値型変換する関数
def to_numeric_column(df,column_name):
    df[column_name] = df[column_name].fillna(0)
    df[column_name] = pd.to_numeric(df[column_name],downcast='signed')
    df[column_name] = df[column_name].replace(0,'')
    return df
from my_library import pandas_ex as pd_ex

df = pd_ex.to_numeric_column(df,'人気')

# 以前のコード
# df['人気'] = df['人気'].fillna(0)
# df['人気'] = pd.to_numeric(df['人気'],downcast='signed')
# df['人気'] = df['人気'].replace(0,'')

[Python] 298 pandasのto_numeric関数における欠損値の扱い

pandasのto_numeric関数において空白がある場合はNaNに変換されて、他のデータは指定の数値型に変換されません。

このようなことはこれまで起こらなかったのですが、競争除外馬が出たおかげでコードの不備が発覚しました。

対策として欠損値を0で埋めてからto_numeric関数を使い、あとで0を空白にreplaceしました。指定の範囲で0を使わない場合に有効です。

空白やNaNを含む列の数値型変換ができないのは惜しいところです。

df['人気'] = df['人気'].fillna(0)
df['人気'] = pd.to_numeric(df['人気'],downcast='signed')

df['人気'] = df['人気'].replace(0,'')