未来の自分へ知識貯金

ゆーきのエンジニアブログ

約6分 PythonVisual Studioプログラミング

【Python】画像から文字列を認識する(OCR)【コード】

【Python】画像から文字列を認識する(OCR)【コード】

スクリーンショットや写真の中の文字を、テキストとして取り出したい——いわゆる 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 StudioVisualStudio になってしまうので、用途に応じて調整してください。

おまけ:認識結果を目視デバッグする

認識した行の位置に赤枠を描いて確認するコードです。座標が正しく取れているかを目で確かめられるので、精度チューニングのときに重宝します。

# 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 という選択肢もあります。以前記事にしているので、用途に合わせて使い分けてみてください。

この記事をシェアする

ゆーき
ゆーき

プログラマー経験10年以上。好きなプログラム言語はC#。使える言語はC++, Python他。好きな開発環境はVisual Studio。

プロフィールを見る →