はじめに
筆者が受けている講義のなかで、学期末になると授業動画を消される講義があり、内容を記録しておく必要がありました。写真を撮るというのも手なのですが、ワード内での検索がしたいので、文字にしたかったというのが動機です。筆者はタイピング速度が爆速なので、人力で何とかなっていましたが、ツールを使った方が、誤字の修正込みでも少し速かったため紹介します。一般的なタイピング速度の方なら、使わない理由がないくらい効率化できると思います。
方針
-
pyautogui
で画面全体の画像データを取得し、pillow
で変換してtkinter
上に表示 -
tkinter
上で領域を指定し、画像データとして取得 -
OpenCV
を用いて、画像データに2値化などの前処理をする -
tesseract
を用いて領域内の文字を検出 - 検出した文字を
pyperclip
でクリップボードに保存
このような方針でプログラムを作成し、作成したプログラムにショートカットキーを割り当てることで、講義を見ながらパッと使えるようにします。
準備
以下の記事を参考に、tesseract
のインストールとパスの設定をしてください
以下のモジュールを使うので、足りないものがあればpipしてください
pip install pytesseract opencv-python pyautogui pyperclip pillow numpy
コード
tkinter
上で領域を選択する部分については、以下の記事のコードを使わせていただきました。
import tkinter, sys, os
import pyautogui
import numpy as np
import cv2
import pytesseract
import pyperclip
from PIL import Image, ImageTk
# ドラッグ開始した時のイベント - - - - - - - - - - - - - - - - - - - - - - - - - -
def start_point_get(event):
global start_x, start_y # グローバル変数に書き込みを行なうため宣言
canvas1.delete("rect1") # すでに"rect1"タグの図形があれば削除
# canvas1上に四角形を描画(rectangleは矩形の意味)
canvas1.create_rectangle(event.x,
event.y,
event.x + 1,
event.y + 1,
outline="red",
width = 3,
tag="rect1")
# グローバル変数に座標を格納
start_x, start_y = event.x, event.y
# ドラッグ中のイベント - - - - - - - - - - - - - - - - - - - - - - - - - -
def rect_drawing(event):
# ドラッグ中のマウスポインタが領域外に出た時の処理
if event.x < 0:
end_x = 0
else:
end_x = min(img_resized.width, event.x)
if event.y < 0:
end_y = 0
else:
end_y = min(img_resized.height, event.y)
# "rect1"タグの画像を再描画
canvas1.coords("rect1", start_x, start_y, end_x, end_y)
# ドラッグを離したときのイベント - - - - - - - - - - - - - - - - - - - - - - - - - -
def release_action(event):
global region # グローバル変数に書き込みを行なうため宣言
# "rect1"タグの画像の座標を元の縮尺に戻して取得
start_x, start_y, end_x, end_y = [
round(n * RESIZE_RETIO) for n in canvas1.coords("rect1")
]
# 画面領域のスクリーンショット取得(画像を保存せずに処理)
region = [start_x, start_y, end_x, end_y] # 選択した領域の座標
def press_enter(event):
extract_text(*region)
# 選択した領域の座標をファイルに保存
with open("region.txt", "w") as f:
f.write(",".join(map(str, region)))
sys.exit()
def extract_text(x1, y1, x2, y2):
screenshot = pyautogui.screenshot(region=(x1, y1, x2-x1, y2-y1))
image = prepare_image(screenshot)
# OCRで文字を抽出
text = pytesseract.image_to_string(image, lang='jpn')
text = text.replace(" ", "")
# クリップボードへコピー
pyperclip.copy(text)
pyautogui.alert("抽出した文字をクリップボードへコピーしました。画像は保存されません。")
print(text)
def prepare_image(img):
# スクリーンショットを保存せずに処理
img_arr = np.array(img) # numpy配列に変換
img_data = cv2.cvtColor(img_arr, cv2.COLOR_RGB2BGR) # OpenCV形式に変換
# グレースケール化
gray_img = cv2.cvtColor(img_data, cv2.COLOR_BGR2GRAY)
# ノイズ除去
denoised_img = cv2.bilateralFilter(gray_img, 9, 75, 75)
# 二値化処理
_, binary_img = cv2.threshold(denoised_img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
return binary_img
# メイン処理 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
if __name__ == "__main__":
RESIZE_RETIO = 1 # 縮小倍率の規定
if os.path.exists("region.txt"):
with open("region.txt", "r") as f:
region = list(map(int, f.read().strip().split(",")))
else:
region = [100, 100, 200, 200]
# 表示する画像の取得(スクリーンショット)
img = pyautogui.screenshot()
# 好みのサイズに画像リサイズ
img_resized = img.resize(size=(int(img.width / RESIZE_RETIO),
int(img.height / RESIZE_RETIO)),
resample=Image.BILINEAR)
root = tkinter.Tk()
root.attributes("-fullscreen", True) # tkinterウィンドウをフルスクリーンに設定(リサイズするなら要らない)
root.attributes("-topmost", True) # tkinterウィンドウを常に最前面に表示
# tkinterで表示できるように画像変換
img_tk = ImageTk.PhotoImage(img_resized)
# Canvasウィジェットの描画
canvas1 = tkinter.Canvas(root,
bg="black",
width=img_resized.width,
height=img_resized.height)
# Canvasウィジェットに取得した画像を描画
canvas1.create_image(0, 0, image=img_tk, anchor=tkinter.NW)
canvas1.config(cursor="cross")
# canvas1上に四角形を描画(rectangleは矩形の意味)
canvas1.create_rectangle(region[0],
region[1],
region[2],
region[3],
outline="red",
width = 3,
tag="rect1")
# Canvasウィジェットを配置し、各種イベントを設定
canvas1.pack()
canvas1.bind("<ButtonPress-1>", start_point_get)
canvas1.bind("<Button1-Motion>", rect_drawing)
canvas1.bind("<ButtonRelease-1>", release_action)
root.bind("<Return>", press_enter) # EnterキーでOCR実行
root.bind("<Escape>", sys.exit) # Escapeキーで終了
root.mainloop()
一応リサイズできるようにしてありますが、筆者は画面サイズそのままのウィンドウを出して使っています。そのためフルスクリーン化もしていますが、小さいウィンドウで作業したい方は、フルスクリーン化している部分は削除してください。
ドラッグで範囲選択を行い、エンターで決定すると、文字を抽出するとともに、テキストファイルに座標を保存します。講義動画から文字を抜き出す場合、毎回同じ範囲を指定すると思うので、2回目以降は起動したらエンターを押すだけで良いようにしています。もちろん抜き出す範囲を変えたい時は、再度選択すればOKです。
ショートカットキーを割り当てる
こちらはWindows向けの手順になっています
まず、作業用フォルダを作成し、上のコードをコピペしたファイルを用意してください。(仮にmain.pyw
とします)
テキストファイルを2つ作成し、それぞれ以下の内容をコピペし、拡張子をbatとvbsに変えてください。
py main.pyw
Set ws = CreateObject("Wscript.Shell")
ws.run "cmd /c main.bat", vbhide
main.bvs
を右クリックして、ショートカットを作成します。作成したショートカットを右クリックしてプロパティから好きなキーを割り当てます。あとはデスクトップに配置すれば完了です。
この時点でショートカットが効かない場合、他のショートカットと競合していないか確認したうえで、再起動するなどしてみてください。筆者は再起動したら解決しました。また、設定のゲームからゲームモードをオフにすると良いかもしれません。
もしくはデスクトップではなくC:\ProgramData\Microsoft\Windows\Start Menu\Programsの下にフォルダを作って、その中に配置してみるとか。(配置したら、もう一回ショートカットキーを割り当て直す)
精度について
この記事の「はじめに」と「方針」の項目に対して使ったところ以下の結果になりました。縦横がきれいに整列している文章に対しては、かなりの精度で抽出ができるのですが、レイアウトが凝っているほどミスが増えます。とはいえ、実用的な範囲には収まっている印象です。
結果を開く
はしじめに
筆者が受けている講義のなかで、学期末になると授業動画を消される講義があり、内容を
記録しておく必要がありました。写真を撮るというのも手なのですが、ワード内での検索が
したいので、文字にしたかったというのが動機です。筆者はタイピング速度が爆速なので、
人力で何とかなっていましたが、ツールを使った方が、誤字の修正込みでも少し速かったた
め紹介します。一般的なタイビング速度の方なら、使わない理由がないくらい効率化できる
と思います。
方針
①.pyautoguiで画面全体の画像データを取得し、pillowで変換してtkinter上に表示
②.tkinter上で領域を指定し、画像データとして取得
③.opencvを用いて、画像データに②値化などの前処理をする
④.tesseractを用いて領域内の文字を検出
⑤.検出した文字をpypercluipでクリップポボードに保存
ごこのような方針でプログラムを作成し、作成したプログラムにショートカットキーを割り当
てるごことで、講義を見ながらバッと使えるようにします。
最後に
ここまで読んで下さりありがとうございました。結構いろんな技術を使ったのですが、それぞれに対応したモジュールを活用することで、割と簡単に作ることができました。今後の展望としては、精度向上であったり、スライドが切り替わったのを検知して自動化などが挙げられます。特に、精度は高ければ高いだけいいですから、追及する価値はありそうです。
参考