0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Python】講義動画の画面から文字をコピペしたい!

Posted at

はじめに

 筆者が受けている講義のなかで、学期末になると授業動画を消される講義があり、内容を記録しておく必要がありました。写真を撮るというのも手なのですが、ワード内での検索がしたいので、文字にしたかったというのが動機です。筆者はタイピング速度が爆速なので、人力で何とかなっていましたが、ツールを使った方が、誤字の修正込みでも少し速かったため紹介します。一般的なタイピング速度の方なら、使わない理由がないくらい効率化できると思います。

方針

  1. pyautoguiで画面全体の画像データを取得し、pillowで変換してtkinter上に表示
  2. tkinter上で領域を指定し、画像データとして取得
  3. OpenCVを用いて、画像データに2値化などの前処理をする
  4. tesseractを用いて領域内の文字を検出
  5. 検出した文字を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に変えてください。

main.bat
py main.pyw
main.vbs
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でクリップポボードに保存

ごこのような方針でプログラムを作成し、作成したプログラムにショートカットキーを割り当
てるごことで、講義を見ながらバッと使えるようにします。

最後に

 ここまで読んで下さりありがとうございました。結構いろんな技術を使ったのですが、それぞれに対応したモジュールを活用することで、割と簡単に作ることができました。今後の展望としては、精度向上であったり、スライドが切り替わったのを検知して自動化などが挙げられます。特に、精度は高ければ高いだけいいですから、追及する価値はありそうです。

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?