4
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

OCR結果をDeepLに投げるアプリを作ってみた【Python/JS】

Last updated at Posted at 2021-12-13

きっかけ

何て書いてあるんだ...?
sketch-1639422869518 (1).png
それは専門外な内容の英語プレゼンを聴講したある日のこと.

普段であればレジェメのPDFが配布され,テキストをコピーしてDeepLに投げることで内容を把握することができるのですが,この日はTeamsの映像でしか見ることができなかったので,専門外な内容だったことも相まって内容を理解するのに苦労しました.

「OCRしてDeepLに投げることができればいいのに...」

ということでOCR機能付きDeepLデスクトップアプリケーションを作りました.

Summary of technical topics

  • OS: Windows10
  • GUI: Eel(Python3.7)
  • OCR: Tesseract
  • Focus the window: Chrome extension (chrome.windows.update())

先行事例

作成後に調べたらより多機能なものが色々ありました.

  • Screen Translator
    DeepL以外の翻訳エンジンも利用でき,非常に多機能
  • PCOT
    日本人の方が作成されています

作ったもの

Windows10用OCR機能付きDeepLデスクトップアプリケーション

exe.png

できること

  • 基本的な挙動は本家を参考にした
    • Ctrl+C×2で選択したテキストをDeepLで翻訳
    • 任意のキーボードショートカット設定
    • 自動起動.bat作成機能付き
  • OCR
    • OCRソフトであるTesseractによる解析結果をDeepLに投げる

ペルソナ

  • テキスト化されていない英語プレゼン(動画)の聴講者
  • 英語ゲームプレイヤー(これはPCOTの方が使いやすいと思う)

技術的な話

GUI(Eel)

PythonでGUIアプリケーションを作成できるライブラリは様々存在する.

  • Tkinter
  • wxPython
  • PySimpleGUI
  • etc.

これらはそれぞれのライブラリ専用の記法で各種ウィジェットを配置していく形になっている.

ウィンドウの透明化など複雑な実装ができる点は魅力だが,ウィジェットの位置を調整するのが(CSSと比べ)大変だと実装して感じたので,HTML/CSSを利用してGUIを作成できないかと探していたところEelにたどり着いた.

EelはHTML/CSSを使ってGUIを作成できるだけでなく,Python⇔JS間で連携ができるため,Python側でキーボード入力監視やOCRといった作業を行った結果をJSに投げてウィンドウに表示するといったことが可能になる.

詳細は [Python] EelをつかってHTML/CSS/JavaScriptでGUIを構築 などの解説記事を参考にされたい.

フォルダ構造

以下紹介する各ファイル類は次のようなフォルダ構造からなる.

root
└── DeepLopenerOCR.pyw
└── assets
   └── eel.js
   └── main.html
   └── main.js
   └── main.css
DeepLopenerOCR.pyw
import eel
eel.init(assets)
eel.start("main.html")

とすることで,assetsフォルダ内のmain.htmlをウィンドウに表示できるようになる.

ちなみに*.pyでなく*.pywとすると,Pythonのコンソールなしで実行できるようになる.
GUIアプリケーションなのでこれは非表示にしたい.

Ctrl+C監視

send_clipboardは0.5秒以内にCtrl+Cが2連続で押された場合にクリップボードのテキストを翻訳する関数.また,クリップボードのテキストが前に翻訳したときと同じ場合は翻訳しない設定もしている.

DeepLopenerOCR.pyw
import time
import keyboard
import pyperclip

def send_clipboard(target_lang, api_key):
    global now, beforekeycc, beforetxt
    print("key detect")
    if(not keyboard.is_pressed("ctrl+C") and not beforekeycc):
        pass
    elif(time.time()-now < 0.5 and pyperclip.paste() != "" and pyperclip.paste() != beforekeycc:
        # ここで翻訳する
        beforetxt = pyperclip.paste()
    now = time.time()
    beforekeycc = keyboard.is_pressed("ctrl+C")

if __name__ == '__main__':
    # 諸々省略
    keyboard.add_hotkey('ctrl+C', send_clipboard,
                        args=[target_lang, api_key])
  • keyboard.add_hotkey('ctrl+C', send_clipboard,args=[target_lang, api_key])によりctrl+Cを入力した際にsend_clipboard関数が実行されるようになる

    • プログラム実行中は常に監視される
    • ほかのホットキーを追加したい場合も同様に指定できる
  • keyboard.is_pressed("ctrl+C")で入力がctrl+CであるかTrue/False

  • pyperclip.paste()でクリップボードのテキストを取得

スクリーンキャプチャ監視 & OCR

スクリーンキャプチャした際にOCRを実行する.
先述のScreen TranslatorはスクリーンキャプチャするGUIを作成しているが,自分はそこまでの労力を使いたくなかったので,クリップボードが画像に変化したことをスクリーンキャプチャされたとみなす方針にした.

したがって,Win+Shift+S以外にもChromeで画像をコピーを押した場合にも発火するようになっている.

ちなみに...
キャプチャGUIを作成するならば,画像取得は`Pillow`の`ImageGrab`,選択範囲を透明/選択範囲外を半透明のグレーにするといった実装をするには,Tkinterの`-transparentcolor`を使えばいけそうに感じた([参考](https://qiita.com/teahat/items/050b572aad0d1686370b)).

なお,クリップボードの内容が変化した際にイベントを発火させる方法が分からなかったため,1秒ごとにクリップボードの内容を取得して
「1秒前から変化している ∧ 画像データになっている」
時にOCRするような実装とした.

DeepLopenerOCR.pyw
from PIL import ImageGrab, Image
import getpass
import pyocr

def ocr(im, target, apikey):
    pyocr.tesseract.TESSERACT_CMD = r'C:\\Program Files\\Tesseract-OCR\\tesseract.exe'
    engines = pyocr.get_available_tools()
    if len(engines) == 0:
        print("Tesseract is not found")
    else:
        engine = engines[0]
        txt = engine.image_to_string(im, lang="eng")
        if(len(txt.replace("\n", "").replace(" ", "")) > 0):
            eel.show("main.html")
            eel.sleep(1)  # 読み込まれるまで待機(雑)
            eel.js_translate(txt, target, apikey)


def capture():
    global target_lang, api_key
    image = ImageGrab.grabclipboard()
    while True:
        im = ImageGrab.grabclipboard()
        if isinstance(im, Image.Image) and im != image:
            ocr(im, target_lang, api_key)
        else:
            pass
        image = ImageGrab.grabclipboard()
        eel.sleep(1)


if __name__ == '__main__':
    # 諸々省略
    spawn = eel.spawn(capture)
main.js
    eel.expose(js_translate);
    function js_translate(clipboard, target_lang, api_key) {
      // 諸々省略
      api_translate(clipboard, target_lang, api_key);
    }
main.html
<html>
    <head>
        <meta charset="UTF-8">
        <title>DeepLopenerOCR</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <!--諸々省略-->
        <script type="text/javascript" src="eel.js"></script>
        <script type="text/javascript" src="main.js">
        </script>
    </body>
</html>

capture()

  • hoge = eel.spawn(capture)とすることで簡単にマルチスレッド化できる.停止したい場合はhoge.kill()で可能.便利.
  • ImageGrab.grabclipboard()でクリップボードのオブジェクトを読み込むことができる
  • isinstance()でオブジェクトのデータ型の判定できる(=画像データかどうか) (参考)

ocr()

  • tesseractを予めインストールしておく
  • pyocr.tesseract.TESSERACT_CMD = r'C:\\Program Files\\Tesseract-OCR\\tesseract.exeでインストールしたtesseract.exeのパスを明示的に定義
  • txt = engine.image_to_string(im, lang="eng")でOCR解析を行う
    • langを指定することで解析言語を変更可能(日本語も対応可能)
  • eel.show("main.html")main.htmlを新しいウィンドウで開く
  • eel.sleep(1)
  • eel.js_translate(txt, target, apikey)
    • js_translate()main.js内で定義している関数
    • しかし,ウィンドウを開いた直後は実行できないのでeel.sleep(1)で1秒待っている(雑)

Focus the window

翻訳実行時はウィンドウが最前面に出てきてほしい.
以前はwin32gui.SetForegroundWindowにより実現できていたようだが,手元の環境では期待した挙動にならなかった(参考).

そこで,Pythonで制御することをあきらめてChrome拡張機能に着目した.
EelはデフォルトではChromeをapp-modeで起動するので拡張機能を利用できる.

キーとなる部分は以下のみ(参考).

background.js
    chrome.windows.update(
      sender.tab.windowId,
      { focused: true },
      function (tab) {}
    );

exe化

python -m eel .\DeepLopenerOCR.pyw assets --icon assets/favicon.ico
`--oneflie`と`--noconsole`の共存について
ひとつのexeファイルにまとめる`--onefile`オプションとコンソールを表示させない`.*pyw`もしくは`--noconsole`オプションの両方を選択した状態だとTesseractによる翻訳がなぜかうまくいかなかった(片方であれば問題ない). [似たようなことが言及されている記事](https://teratail.com/questions/254226)

Future Works

  • Tesseract以外のOCR(Google Vision等)を選択できるように実装
  • OCR言語変更機能
  • 任意の範囲の自動スクリーンキャプチャ機能
    • PCOTは実装済み(たぶん)
      • ゲームのテキストウィンドウを設定しておけば洋ゲーが捗りそう

まとめ

今回はじめてEelを触りましたが,HTML/CSSでウィジェットを配置できるのは楽で良かったです.
また,Python⇔JS間で連携できる点はもっと有効活用できそうに感じています.

英語プレゼンに悩んでいる方はDeepLopenerOCR,試してみてください.

4
7
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
4
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?