スクリーンショットや写真の中の文字を、テキストとして取り出したい——いわゆる OCR(光学文字認識)です。
私の場合は「画面上の特定領域をキャプチャして、そこに表示されている文字を読み取る」自動化ツールを作るために必要になりました。OCR は Google の Cloud Vision のようなクラウド API を使う手もありますが、無料・オフライン・ローカル完結でやりたいなら、オープンソースの OCR エンジン Tesseract が定番です。
この記事では、Python から Tesseract を使って日本語の文字認識を行い、認識した文字列と画面上の座標を取得するところまでをコード付きで紹介します。
準備1: Tesseract-OCR 本体をインストールする
Tesseract は Python のライブラリではなく、独立した OCR エンジン(exe)です。まず本体をインストールします。
私は Windows を使用しているので、UB Mannheim 版の Windows インストーラーをダウンロードしてインストールしました。
このときのポイントは1つだけ。インストーラーの言語データ選択画面で「Japanese」にチェックを入れることです。デフォルトでは英語しか入らないため、後から日本語を認識しようとすると失敗します(入れ忘れた場合は再インストールするか、jpn.traineddata を tessdata フォルダに追加すれば OK です)。
インストール先は既定で C:\Program Files\Tesseract-OCR\ になります。以降このパスを前提にします。
準備2: Python 側のパッケージを入れる
Python から Tesseract を呼び出すラッパーとして pyocr を使います。画像の扱いに OpenCV と Pillow も使うので、まとめてインストールします。
pip install pyocr opencv-python pillow numpy
私は Visual Studio 2022 の Python 環境で作業していますが、環境ウィンドウからパッケージ検索で追加しても同じです。
Tesseract を Python から認識させる
pyocr は環境変数 PATH から Tesseract を探します。PATH に登録していない場合でも、コード側で指定できます。
import os
import pyocr
# 環境変数「PATH」にTesseract-OCRのパスを設定。
# Windowsの環境変数に設定している場合は不要。
path = 'C:\\Program Files\\Tesseract-OCR\\'
os.environ['PATH'] = os.environ['PATH'] + path
# pyocrにTesseractを指定する。
pyocr.tesseract.TESSERACT_CMD = r'C:\Program Files\Tesseract-OCR\tesseract.exe'
tools = pyocr.get_available_tools()
if len(tools) == 0:
print("No OCR tool found")
exit(1)
tool = tools[0]
print(tool.get_available_languages())
実行して ['eng', 'jpn', 'osd'] のように jpn が含まれていれば準備完了です。ここに jpn がない場合は日本語の言語データが入っていません(準備1参照)。
準備3: OpenCV ⇔ PIL の画像変換関数
ここで1つ落とし穴があります。pyocr が受け付けるのは PIL(Pillow)形式の画像ですが、画面キャプチャや画像加工では OpenCV(numpy 配列・BGR 順)を使うことが多く、この2つは色の並び順が違うため、そのまま渡すと色がおかしくなり認識精度が落ちます。
相互変換の関数を用意しておきます。
import cv2
import numpy as np
from PIL import Image
class Converter(object):
""" OpenCV(BGR) と PIL(RGB) の相互変換 """
def cv2pil(image):
""" OpenCV -> PIL """
new_image = image.copy()
if new_image.ndim == 2: # モノクロ
pass
elif new_image.shape[2] == 3: # カラー
new_image = cv2.cvtColor(new_image, cv2.COLOR_BGR2RGB)
elif new_image.shape[2] == 4: # 透過
new_image = cv2.cvtColor(new_image, cv2.COLOR_BGRA2RGBA)
new_image = Image.fromarray(new_image)
return new_image
def pil2cv(image):
""" PIL -> OpenCV """
new_image = np.array(image, dtype=np.uint8)
if new_image.ndim == 2: # モノクロ
pass
elif new_image.shape[2] == 3: # カラー
new_image = cv2.cvtColor(new_image, cv2.COLOR_RGB2BGR)
elif new_image.shape[2] == 4: # 透過
new_image = cv2.cvtColor(new_image, cv2.COLOR_RGBA2BGRA)
return new_image
文字を認識する
いよいよ本題です。OpenCV 画像(image)から日本語テキストを行単位で抽出します。
result = ""
# 画像の文字を抽出(imageはOpenCV形式の画像)
temp_image = Converter.cv2pil(image)
builder = pyocr.builders.LineBoxBuilder(tesseract_layout=6)
lineboxes = tool.image_to_string(temp_image, lang="jpn", builder=builder)
for line in lineboxes:
print(line.content.replace(" ", "")) # 認識した文字列
print(line.position) # 画面上の座標 ((左上x,y), (右下x,y))
result += line.content.replace(" ", "")
result += "\r\n"
短いコードですが、3つほど押さえておきたいポイントがあります。
builder で「何を取り出すか」が変わる
pyocr は builder の指定によって出力形式が変わります。
| builder | 取得できるもの |
|---|---|
TextBuilder | 認識した文字列全体(座標なし) |
WordBoxBuilder | 単語ごとの文字列+座標 |
LineBoxBuilder | 行ごとの文字列+座標 |
文字列だけ欲しいなら TextBuilder が手軽です。私は「認識した文字が画面のどこにあるか」まで必要だった(見つけた文字の位置をクリックしたい)ので、行単位で座標が取れる LineBoxBuilder を使いました。line.position に ((左上x, 左上y), (右下x, 右下y)) のタプルが入ります。
tesseract_layout(ページセグメンテーションモード)
tesseract_layout=6 は「画像を単一の均一なテキストブロックとみなす」モードです。この値(PSM: Page Segmentation Mode)は画像の性質に合わせて変えると精度が変わります。よく使うのは次の3つです。
6: 単一のテキストブロック(デフォルトで迷ったらこれ)7: 1行だけの画像11: 文字がまばらに散っている画像
日本語 OCR の定番トラブル:謎のスペース
replace(" ", "") を入れているのは、Tesseract が日本語文字列の単語の間に半角スペースを挟んでくるためです。「これ は テスト です」のような結果になるので、日本語のみの文書ならスペースを全部除去してしまうのが手軽です。ただし英数字混じりの文書だと Visual Studio → VisualStudio になってしまうので、用途に応じて調整してください。
おまけ:認識結果を目視デバッグする
認識した行の位置に赤枠を描いて確認するコードです。座標が正しく取れているかを目で確かめられるので、精度チューニングのときに重宝します。
# debug view
for line in lineboxes:
cv2.rectangle(image, line.position[0], line.position[1], (0, 0, 255), 2) # image自体に上書きするので注意!!
cv2.imshow("Image", image)
cv2.waitKey(5000)
cv2.destroyAllWindows()
cv2.rectangle は元画像を直接書き換えるので、元画像を後で使う場合は image.copy() に描画してください。
認識精度を上げるコツ
Tesseract は画像の状態に精度が大きく左右されます。認識がうまくいかないときは、OCR にかける前の前処理を見直すと改善することが多いです。
- 拡大する: 文字が小さい画像は
cv2.resizeで2〜3倍に拡大するだけで精度が上がる - 二値化する:
cv2.thresholdで白黒2値にしてコントラストを上げる - グレースケール化: カラー情報が邪魔をするケースでは
cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) - 言語指定を絞る: 英数字しかないと分かっているなら
lang="eng"の方が高精度
まとめ
- Tesseract は本体(exe)と Python ラッパー(pyocr)の2段構え。本体インストール時に日本語データを忘れずに
- pyocr に渡す画像は PIL 形式。OpenCV 画像は BGR→RGB 変換が必要
- 座標も欲しいなら
LineBoxBuilder、文字列だけならTextBuilder - 日本語の認識結果にはスペースが混入するので除去する
- 精度が出ないときは拡大・二値化などの前処理を試す
ちなみに「コードを書かずに画面の文字をサッと取り出したい」だけなら、PowerToys の Text Extractor という選択肢もあります。以前記事にしているので、用途に合わせて使い分けてみてください。
