ゲーム中のセリフをOCRで取得したい part2
前回のあらすじ
オクトパストラベラーを英語でプレイするさなか、ふとある思いを抱く。
「ゲーム画面を見ながら、Google翻訳かけるのって面倒だな・・・」
ゲームなのに面倒、これは深刻な問題である。
ゲームに娯楽の要素が薄くなれば、プレイの中止も検討せざるをえない。
そんな現状を打破すべく、OCR(Tesseract)による自動文字情報取得システムの開発に着手、
苦労の末、プロトタイプ1が完成した!
僕の期待を背負ってプロトタイプ1は始動するも・・・
無残にも、まともに文字情報を得ることはできなかった。
前回の問題点
前回、OCRはそんなに簡単じゃないことが判明しました。
仕方がないので精度向上について考えてみます。
Tesseractのホームページをみていると、役に立ちそうな記事を発見しました。
記事の主旨を一言でまとめると、
「Tesseractに画像を入力する前に、前処理をやってくれ」
という感じになるでしょうか。
前処理とは、以下のような処理を指すようです。
- 画像の2値化
- 白背景に黒文字にする
- 不要なものを除去する(ページの境界線とか)
確かにそう言われるとなー、前回はスクショをそのままTesseractに放り投げていました。
そりゃあうまく行かない訳だw
ということで、スクショに前処理をかけてみます!!
前処理をかける
素人なりに試行錯誤したところ、精度が安定する方法を見つけることができました。
具体例を紹介します。
〇前処理をかける前
〇前処理をかけた後
もはや全然違う画像ですねw
ここまでやれば認識精度は格段に上がります!
しかしここまでやる必要はあるのか?
素人なので、ホントのところはわかりません。
ただし根拠はあります。
Tesseractはアプリケーションとして、古書のデジタル化を想定しているようです。
つまり一般的な本に対して、精度が良くなるように設計していると推測できます。
ということで、本と同じような状態に近づけると、精度向上につながるはずです!
実はこの状態でも、幾つかの文字は誤認識します。
おそらく、オクトパストラベラーのフォントが日常生活で使われていないからでしょうね。
その問題を解決するにはTesseractの再学習をやる必要がありそうなので、今回はやめておきます。
では、具体的なフローに入っていきます。
領域を特定する
抽出したい文字情報がある場所を、どうにかして抽出します。
どうにかしてというのが、素人が素人たる所以です。
理想的には、体系的かつ流用性のある方法を探るべきですが、そんなことは気にしません。
オクトパストラベラーに特化した方法でやります!
今回の例でいえば、上のスクショにおいて、表示されているメッセージには以下の特徴があります。
- メッセージの大きさは変化する
- メッセージの位置は変化する
- ほぼ一様なベージュ色
- 四角形のような形をしている(少なくとも丸や三角ではない)
大きさと位置が固定であれば、座標の直打ちで切り抜いていたところです。
仕方がないので、色や形の特徴を使うことにします。
何かいい方法はないかなーとググることしばらく、良いページを見つけました。
このページの「オブジェクト検出例(バウンディングボックス)」を使うことにします。
この方法をベースに、幾つかコード修正を加えると・・・
うまいことメッセージ部を特定することができました!
特定した領域をクロッピングし、その他の部分は破棄します。
参考までに、該当箇所のソースコードはこんな感じ。
def get_dialogue(self, img_S): # img_S is S(Saturation) element of a screenshot in HSV space img_bin = cv2.inRange(img_S, 33, 34) contours, hierarchy = cv2.findContours(img_bin, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE) for i in range(0, len(contours)): if (i == 0): max_contour = contours[i] else: if cv2.contourArea(max_contour) < cv2.contourArea(contours[i]): max_contour = contours[i] return max_contour
2値化、ノイズ除去
先ほどと同様に、どうにかしてやります。
OCRをかけるにあたってのポイントは、
- 文字はくっきりと
- 余分なノイズはなくす
となります。このバランスが結構難しいんですよ。
文字がぼやけると、文字の誤認識になります。
ノイズがあると、ノイズを文字と認識します。
ノイズが残ってしまった例を紹介します。
画像上の方に、謎の横線がいくつか残っていますよね。取り切れなかったノイズです。
これくらいいいじゃんって思うかもしまれせん。
僕はそう思っていました。
しかしTesseractは平気で誤認識してきます!
うまく回避する方法はあるのかもしれませんが、いずれにせよノイズが無いに越したことはありません。
参考までに、僕の実装例を紹介します。
ノイズ除去のため、2値化の範囲をできるだけ絞り、一部モルフォロジー変換を使っています。
また、白背景に黒字になるように色を反転しています。
#Get the position of a dialogue in the screenshot dialogue= self.get_dialogue(img_S) #Preprocessing to the dialogue x, y, w, h = cv2.boundingRect(dialogue) img_crop = img_V[y:y+h, x+3:x+w-50] img_bin = cv2.inRange(img_crop, 60, 95) img_bin[0:3] = 1 img_bin[0:40, 400:] = 1 img_inv = cv2.bitwise_not(img_bin) kernel = np.ones((2,0),np.uint8) img_inv[0:6] = cv2.morphologyEx(img_inv[0:6], cv2.MORPH_CLOSE, kernel)
プロトタイプ2、始動
そんなこんなで、前処理の仕組みを取り入れたプロタイプ2が完成しました。
メッセージの他に、「尋ねる」「精査する」コマンドにも対応させています。
簡単に結果画面だけ紹介します。
だいぶ実用的なシステムになってきました!
しかし、ところどころ文字の誤認識が見られます(I「アイ」を|「罫線」とか)。
このままだと少し不便なので、Tesseractのチューニング(再学習)が必要かな。
そのためには学習のためのデータが必要なので、とりあえずはプロトタイプ2を運用しつつ、データを集めたいと思います。
以上。