実践OCR:PytesseractとOpenCVで認識精度を40%から90%に向上させる方法

Python tutorial - IT technology blog
Python tutorial - IT technology blog

なぜOCRライブラリに元の画像をそのまま渡してはいけないのか?

約6ヶ月前、私は非常に困難な課題に取り組みました。それは、会社にある膨大な古い請求書をすべてデータ化するというプロジェクトでした。最初はGoogle Vision APIやAWS Textractを使って手っ取り早く解決しようと考えましたが、データセキュリティの懸念とコストの問題から方針を転換し、オープンソースのソリューションであるTesseract OCRを採用することにしました。

しかし、現実はネット上のチュートリアルほど甘くはありませんでした。単にpip install pytesseractを実行してスマートフォンの写真を読み込ませるだけでは、出力される結果の多くは文字化けしたゴミのようなデータでした。OCR(光学文字認識)は、暗闇の中で子供に文字を教えるようなものです。正しく読ませるには、明かりを灯し、眼鏡を拭き、一行ずつ指で示してあげる必要があります。そこで、極めて重要な前処理フィルタとしてOpenCVの出番となります。

以下のプロセスは、5万件以上の実データを処理した経験から導き出したものです。これにより、基本的な画像処理を行うだけで、認識精度を40%(未加工画像)から90%以上に向上させることができます。

正確な「道具」の準備

よくある間違いは、Pythonライブラリだけをインストールして、OSにTesseractエンジン本体をインストールし忘れることです。Pytesseractは、背後で動作するTesseractソフトウェアとPythonが通信するためのラッパー(仲介役)に過ぎません。

1. Tesseractエンジンのインストール

Ubuntu/Debianの場合は、以下のコマンドを実行します:

sudo apt update
sudo apt install tesseract-ocr tesseract-ocr-vie

Windowsの場合は、UB MannheimのGitHubからインストーラー(.exe)をダウンロードしてください。インストールパス(通常はC:\Program Files\Tesseract-OCR)は、後でコード内で設定するために控えておいてください。

2. サポートライブラリ

プロジェクトをクリーンに管理するために、仮想環境(venv)の使用や、Pathlibによるファイル処理コードの刷新をお勧めします:

pip install pytesseract opencv-python numpy Pillow

前処理:精度向上の鍵

私の苦い経験から言えることは、入力画像の品質が成否の80%を決定するということです。元の画像をそのまま放り込むのではなく、OpenCVを使って以下の3つの「クリーニング」ステップを実行しましょう。

ステップ1:グレースケール化とノイズ除去

カラー画像にはアルゴリズムを混乱させるノイズが多く含まれています。グレースケールに変換することで、文字と背景のコントラストに集中させることができます。

import cv2
import pytesseract

# Windows用のパス設定
# pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'

def preprocess_basic(image):
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    # 背景領域を滑らかにするためにメディアンフィルタでノイズを除去
    return cv2.medianBlur(gray, 3)

ステップ2:二値化(Thresholding)

このステップでは、画像を完全に白と黒の二色(二値画像)に変換します。私は大津の二値化(Otsu’s Thresholding)を好んで使用します。これは最適な閾値を自動的に計算してくれるため、請求書の影や照明のムラがある場合に特に効果的です。

def apply_threshold(image):
    return cv2.threshold(image, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]

ステップ3:PSMとOEMパラメータの設定

Tesseractは、PSM(Page Segmentation Mode)パラメータを通じて、さまざまな読み取りモードを提供しています。PSMの選択を誤ることは、行の飛び越しや文字欠けが発生する最大の原因です。

  • PSM 6: 画像を単一のテキストブロックとして扱う(請求書や行政書類に最適)。
  • PSM 3: 自動ページ分割(複数カラムのある新聞記事などに使用)。
  • OEM 3: デフォルトモード。最新のLSTMエンジンを組み合わせて使用。

テキストを抽出するための完全なコードは以下の通りです:

def extract_invoice_text(image_path):
    img = cv2.imread(image_path)
    processed_img = apply_threshold(preprocess_basic(img))

    # 設定:ベトナム語 + テキストブロックモード
    config = r'--oem 3 --psm 6 -l vie'
    return pytesseract.image_to_string(processed_img, config=config)

print(extract_invoice_text('sample_invoice.png'))

実運用における経験(スケールアップ)

スクリプトをデプロイして毎日数万枚の画像を処理するようになると、ローカル環境では発生しなかった課題に直面します。

1. 傾き補正(Deskewing)

手持ちで撮影された写真は、多くの場合角度が傾いています。わずか5〜10度傾いているだけで、Tesseractは上の行と下の行を誤認してしまいます。私は通常、cv2.minAreaRect関数を使用して傾斜角を求め、OCR処理の前に画像を水平方向に回転させています。

2. 機械を100%信用しない(信頼度スコア)

本番環境では、OCRの結果をチェックせずにそのままデータベースに保存してはいけません。Pydanticなどで不正データからPythonアプリを守るバリデーションの実装を検討してください。Pytesseractは、単語ごとに信頼度(0〜100%)を返すことができます。金融関連のプロジェクトでは、私は閾値を80%に設定しています。信頼度がそれ以下の単語にはフラグを立て、人間が再確認する仕組み(Human-in-the-loop)を構築しています。

# 品質管理のために詳細データを取得
data = pytesseract.image_to_data(processed_img, output_type=pytesseract.Output.DICT)
for i, text in enumerate(data['text']):
    conf = int(data['conf'][i])
    if conf > 0: # 空白をスキップ
        print(f"単語: {text} | 信頼度: {conf}%")

3. パフォーマンスの最適化

Pytesseractは、関数が呼び出されるたびに新しいプロセスを起動します。1,000枚の画像をループで処理すると、CPUが過負荷になります。マルチスレッド処理のためにconcurrent.futuresを使用するか、MultiprocessingとThreadingの使い分けを理解して最適化を図り、GPUが利用可能でより高速な処理が必要な場合はEasyOCRへの移行を検討してください。

おわりに

OCRは「導入すればすぐに動く」魔法の杖ではありません。万能なライブラリを探すよりも、入力データのクリーニングに注力してください。スクリプトをデプロイしてファイル監視を自動化し、ノイズのない、水平で鮮明な白黒画像を用意すれば、Tesseractは最大限のパフォーマンスを発揮します。デバッグの際は、IcecreamライブラリでPythonデバッグを効率化して詳細を確認するのも良いでしょう。手書き文字の認識など、より複雑な要件がある場合は、独自に構築するディープラーニングモデルや有料ソリューションの検討を始めてください。

Share: