ゲーム中のセリフをOCRで取得したい
オクトパストラベラー(Steam版)絶賛プレイ中です!
以前の記事を挙げてから一ヶ月が経過、8人中5人の2章をクリアしました。
進捗が遅いように見えますが、それは英語でプレイしているせいです。
人に話しかける
→ちょっと何言ってるかわからない
→グーグルで単語を調べる
→グーグルで熟語を調べる
→グーグルで全文翻訳をかける
と繰り返しているので、めちゃくちゃ時間がかかります。数日プレイして、一度も街の外に出ないことはざらです。ゲームとしてのテンポは最悪ですが、それでもオクトパストラベラーは面白いです!
というのも、ただの村人にさえバックグラウンドが用意されていて、
- 多額の借金を背負って、本当の自分を偽りながらお金を稼ぐ踊り子
- 貧民あがりで、貴族出身の同僚に対抗意識を抱く兵士
- 悪そうな人を見つけては、雪玉を投げつける女の子
などなど、色々な人がいます。
単に話しかけるだけでは「ここは始まりの町です」レベルのことしか言わない人でも、深く探ると「宿屋の経営に行き詰っているため、夫にリフォームするように訴えている女性」ということが判明します。
村人にもそれぞれの人生があるんやなぁ、と干渉にひたりながらプレイしています。
OCRで翻訳作業を効率化
※ここからの話はSteamでゲームをしていることが前提になります。
さて、上記のプレイスタイルにあって、面倒に感じていることがあります。
英文の意味を調べるには、ブラウザ上で英文を打つ必要があるということです。
- 現状の作業
ゲーム画面を見ながら、英文を打ち、ブラウザに張り付ける
これを、
- 変更後の作業
キーを押したら、英文がブラウザに張り付けられる
という風に変更できたら、多少は楽になります。
そこで、以下のようなシステムを考えてみました。
青色の箱はプロセスを、橙色の箱はファイルを表します。
システムの説明をデータの流れる順に行います。
- SteamでF12キーを押すと、スクリーンショットを保存します(標準機能)。
- Pythonは、スクリーンショットの保存を確認すると、Tesseractを呼び出します。
- Tesseractは、OCRを行うためのツールで、スクリーンショット内に含まれる文字情報を取り出します。
- Pythonは、Tesseractから文字情報を受け取り、整形した後、json形式で保存します。
- Webサーバはjsonファイルを読み取り、Webページを動的生成します。
・・・といっても、どんなシステムかわかりにくいですよね。
システムの入力と出力だけでも具体的に紹介します。
入力はSteam画面のスクリーンショットです。
出力はブラウザ上の文字になります。
やりたいことは、Steam上のセリフをブラウザ上で表示するだけです。
後はコピペすれば、Google翻訳にかけられるでしょ?
このシステムで肝になるのがOCRと呼ばれる技術です。
OCRとは、Optical character recognitionの略で、日本語では光学文字認識と訳されます。活字の文書をイメージスキャナで画像化し、パソコン上で画像から文字情報を抽出する、といった使い方が起源のようです。スクリーンショットの場合、もはや光学要素はないのですが、OCRを応用することは 可能です。
OCRを実現するためのツールとして、僕はTesseractを採用しました。Googleが開発元で、オープンソースソフトウエア(Apache License 2.0)です。
正直、OCRについても、Tesseractについても、僕は全く詳しくないですw
今回はOCRを試すことができればよいので、今後必要になれば勉強することにします。
プロトタイプ1の開発
プロトタイプ1と銘打って、とりあえずシステム全体として動くものを作ります。
Steamの設定
スクリーンショットの保存方法を変更します。
保存先をプログラムで扱いやすい場所(僕は D:\steam_ss にしました)、保存形式を非圧縮にします。非圧縮にする理由は、圧縮すると画像内の文字が滲んで、OCRの精度に悪影響がありそうだからです(※あくまで素人意見です)。
具体的な手順は、以下ページをご参考に。
Tesseractのセットアップ
インストール&Pythonから呼び出せるようにします。
僕の環境はWindowsなので、以下ページを参考にインストールしました。
Tesseract OCR をWindowsにインストールする方法 | ガンマソフト株式会社
僕はWindows 64bit、v5.0.0α版のインストーラにしました。インストール完了後、システム環境変数を編集し、tesseract.exeにパスを通します。
Pythonコードの作成
ネット上に落ちているコードを集めて、適当に組み合わせます。
なにせPythonも詳しくはないので、とりあえず動けばOKで作ります。
簡単にコードの紹介をすると、
- watchdogクラスを使って、フォルダの監視を行います。つまり、スクリーンショットが保存されたことを検出し、OCRを行うハンドラを呼び出します。
- subprocessクラスを使って、Tesseractを実行します。Tesseractの文字情報の出力先はstdoutとし、さらにstdoutをsubprocessの返り値にリダイレクトしています。この仕組みにより、Python内でTesseractによる結果をバイナリデータ列として扱えます。
- collectionsクラスおよびjsonクラスを使って、文字情報をjson形式で保存します。
- jsonの保存先は、python実行場所と同一階層にあるocrというフォルダにしています。
という感じでしょうか。コード全体は以下のようになります。
import sys import os import time import subprocess import json import collections from watchdog.observers import Observer from watchdog.events import PatternMatchingEventHandler class screenshotEventHandler(PatternMatchingEventHandler): text_index = 0 def __init__(self, patterns=['*.png'], ignore_patterns=[], ignore_directories=True, case_sensitive=False): super().__init__(patterns, ignore_patterns, ignore_directories, case_sensitive) def on_any_event(self, event): if (event.event_type == "modified"): print("Chaptured a screenshot No." + str(self.text_index)) # Read lines from Steam's screenshot using OCR mycmd = 'tesseract.exe ' + event.src_path + ' stdout --dpi 96' tesseract_out = subprocess.check_output(mycmd, shell=True).decode() person = '' msg = '' for i in tesseract_out.splitlines(): if len(i) > 3: if (len(person) == 0): person = i else: msg = msg + i + " " # Write lines in JSON format lines = collections.OrderedDict() lines["person"] = person lines["msg"] = msg fn = './ocr/lines' + str(self.text_index) + '.json' self.text_index += 1 fw = open(fn, 'w') json.dump(lines, fw, indent=4) if __name__ == "__main__": evh = screenshotEventHandler() obs = Observer() obs.schedule(evh, 'D:/steam_ss', recursive=True) print("Start OCR") obs.start() try: while True: time.sleep(1) except KeyboardInterrupt: obs.unschedule_all() obs.stop() obs.join() print("Finish OCR")出力するjsonは以下のようになります。
{ "person": "Coachman", "msg": "If you're planning a journey ..." }
Webサーバ側のコード
なんのへんてつもないhtml + phpで作ります。
これも素人なのでクオリティはお察し、動けばいいやで作ります。
ちなみにWebサーバはApatche2を使っています。
まずはhtmlから。
<!DOCTYPE html> <html> <head> <meta charset='utf-8'> <title>OCR Test</title> </head> <body> <h2>Octopath Traveller's dialogue</h2> <?php include('./proto1.php'); ?> </body> </html>
ついでphp。
<?php $dir_ocr = "./ocr"; if (is_dir($dir_ocr)) { if ($dh = opendir($dir_ocr)) { while (($file = readdir($dh)) !== false) { $dot_pos = strpos($file, '.json'); if(strlen($file) > 2) { $filepath = $dir_ocr."/".$file; $json = file_get_contents($filepath); $arr = json_decode($json,true); echo "<h3>$arr[person]</h3>\n"; echo "<p>$arr[msg]</p>\n"; echo "<br/>\n"; } } closedir($dh); } } ?>
プロトタイプ1、始動
一通り仕組みが完成したところで、動かしてみます!
わくわく。
まず、ターミナルソフトでpythonを実行します。
無限ループするので、そのまま放置しておきます(画面上でpython_sl.exeとなっているのは、python.exeを実行すると、Microsoft純正のpythonのダウンロードを勧められるため。僕はAnacondaのpythonにシンボリックリンクを張って使っています)。
ゲームをプレイします。
人々との会話を楽しむさなか、英語の意味がわからないタイミングで、F12を押します。
スクリーンショットの保存を検出すると、自動的にOCRを実行し、jsonファイルを生成します。
ターミナル上では、その旨を簡潔に表示します。
適当に4人ほどに試した後、先ほど作成したWebページを開きます。
人々のセリフを正しく取得できてい・・・ないですね。
やっぱ適当に作るとこうなるよなーーーー
今回はとりあえず動いたということで、改善は次回以降にします!
以上。