EasyOCR によるシーンテキスト検出・認識(英語・日本語対応)(ソースコードと実行結果)

Python開発環境,ライブラリ類

ここでは、最低限の事前準備について説明する。機械学習や深層学習を行う場合は、NVIDIA CUDA、Visual Studio、Cursorなどを追加でインストールすると便利である。これらについては別ページ https://www.kkaneko.jp/cc/dev/aiassist.htmlで詳しく解説しているので、必要に応じて参照してください。

Python 3.12 のインストール

インストール済みの場合は実行不要。

管理者権限でコマンドプロンプトを起動(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行する。管理者権限は、wingetの--scope machineオプションでシステム全体にソフトウェアをインストールするために必要である。

REM Python をシステム領域にインストール
winget install --scope machine --id Python.Python.3.12 -e --silent
REM Python のパス設定
set "PYTHON_PATH=C:\Program Files\Python312"
set "PYTHON_SCRIPTS_PATH=C:\Program Files\Python312\Scripts"
echo "%PATH%" | find /i "%PYTHON_PATH%" >nul
if errorlevel 1 setx PATH "%PATH%;%PYTHON_PATH%" /M >nul
echo "%PATH%" | find /i "%PYTHON_SCRIPTS_PATH%" >nul
if errorlevel 1 setx PATH "%PATH%;%PYTHON_SCRIPTS_PATH%" /M >nul

関連する外部ページ

Python の公式ページ: https://www.python.org/

AI エディタ Windsurf のインストール

Pythonプログラムの編集・実行には、AI エディタの利用を推奨する。ここでは,Windsurfのインストールを説明する。

管理者権限でコマンドプロンプトを起動(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行して、Windsurfをシステム全体にインストールする。管理者権限は、wingetの--scope machineオプションでシステム全体にソフトウェアをインストールするために必要となる。

winget install --scope machine Codeium.Windsurf -e --silent

関連する外部ページ

Windsurf の公式ページ: https://windsurf.com/

必要なライブラリのインストール

コマンドプロンプトを管理者として実行(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行する


pip install -U torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu126
pip install easyocr opencv-python pillow

EasyOCR によるシーンテキスト検出・認識(英語・日本語対応)プログラム

概要

本プログラムは、視覚情報から文字を認識し理解する。具体的には、カメラや動画に映る日本語文字、英語、数式を検出し、その内容をテキストデータとして抽出する。この過程では、画像中の文字領域の特定と、その領域内の文字パターンの認識という2段階の処理を行う。

主要技術

参考文献

[1] JaidedAI. (2020). EasyOCR: Ready-to-use OCR with 80+ supported languages. GitHub repository. https://github.com/JaidedAI/EasyOCR

ソースコード


# YomiTokuによる文字認識プログラム
# 特徴技術名: 文字認識(Text Recognition)
# 出典: GitHub: https://github.com/kotaro-kinoshita/yomitoku
# 特徴機能: 7000文字超の日本語文字認識(ひらがな、カタカナ、漢字)による文字認識
# 学習済みモデル: YomiToku v0.8.0 - 手書き文字認識対応の統合モデル。活字・手書き文字の両方を単一モデルで認識可能。Hugging Face Hubから自動ダウンロード
# 方式設計:
#   関連利用技術: 文字位置検知(Text Detection)、レイアウト解析(Layout Analysis)、表構造認識(Table Structure Recognition)、OpenCV(カメラ入力)、PIL(日本語テキスト描画)
#   入力と出力: 入力: 動画(ユーザは「0:動画ファイル,1:カメラ,2:サンプル動画」のメニューで選択.0:動画ファイルの場合はtkinterでファイル選択.1の場合はOpenCVでカメラが開く.2の場合はhttps://raw.githubusercontent.com/opencv/opencv/master/samples/data/vtest.aviを使用)、出力: OpenCV画面でリアルタイムに認識結果を表示.各フレームごとにprint()で処理結果を表示.プログラム終了時にprint()で表示した処理結果をresult.txtファイルに保存
#   処理手順: 1.カメラ/動画フレーム取得、2.DocumentAnalyzerによる統合解析、3.文字認識結果の抽出、4.レイアウト解析結果の取得、5.表構造の認識、6.結果の画面表示とコンソール出力
#   前処理、後処理: 前処理: BGR→RGB変換(YomiTokuはRGB形式を期待)、後処理: 日本語フォントによる認識結果の画面描画
#   追加処理: バッファサイズを1に設定してリアルタイム性を確保、認識結果を最大10ブロックに制限して画面の視認性を維持
#   調整を必要とする設定値: FONT_PATH(日本語フォントのパス、環境により要調整)、FONT_SIZE(文字サイズ、画面解像度により調整が必要)
# 将来方策: FONT_SIZE(文字サイズ)の自動調整機能。画面解像度とフレームサイズから最適な文字サイズを自動計算し、視認性を最適化する機能をプログラム内で実装可能
# その他の重要事項: VRAM 8GB以上推奨、CUDA 11.8以上必要、低解像度画像では精度低下(短辺720px以上推奨)
# 前準備:
#   pip install -U torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu126
#   pip install yomitoku opencv-python pillow numpy

import cv2
import tkinter as tk
from tkinter import filedialog
import numpy as np
from PIL import Image, ImageDraw, ImageFont
from yomitoku import DocumentAnalyzer
import sys
import io
import torch
import logging
import time
import urllib.request
from datetime import datetime

# ログレベル設定(YomiTokuのログを抑制)
logging.getLogger('yomitoku').setLevel(logging.WARNING)
logging.getLogger('yomitoku.base').setLevel(logging.WARNING)

# フォント設定
FONT_PATH = 'C:/Windows/Fonts/meiryo.ttc'  # 日本語フォントパス(環境により要調整)
FONT_SIZE = 30  # 表示文字サイズ(画面解像度により要調整)
TEXT_COLOR_RGB = (0, 255, 0)  # RGB形式での緑色

# 表示設定
TEXT_START_Y = 30      # テキスト表示開始Y座標
TEXT_LINE_HEIGHT = 40  # テキスト行間隔
MAX_DISPLAY_TEXTS = 10 # 最大表示テキスト数

# Windows文字エンコーディング設定
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', line_buffering=True)

# GPU/CPU自動選択
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f'デバイス: {str(device)}')

# グローバル変数
analyzer = DocumentAnalyzer(device=str(device))
font = ImageFont.truetype(FONT_PATH, FONT_SIZE)
frame_count = 0
results_log = []
first_run = True


def video_frame_processing(frame):
    global analyzer, font, first_run, frame_count
    current_time = time.time()
    frame_count += 1

    rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

    # YomiTokuによる解析
    try:
        result = analyzer(rgb_frame)
    except Exception as e:
        # エラー時は画面にエラーメッセージを表示
        img_pil = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
        draw = ImageDraw.Draw(img_pil)
        draw.text((10, TEXT_START_Y), "解析エラー発生", font=font, fill=(255, 0, 0))
        return cv2.cvtColor(np.array(img_pil), cv2.COLOR_RGB2BGR), f"解析エラー: {e}", current_time

    # 検出されたテキストを収集
    detected_texts = []

    if result and len(result) > 0:
        doc_schema = result[0]

        # 初回実行時のみデバッグ情報
        if first_run:
            print('\n=== DocumentAnalyzerSchemaの詳細構造 ===')
            if hasattr(doc_schema, 'paragraphs') and doc_schema.paragraphs:
                print(f'paragraphs length: {len(doc_schema.paragraphs)}')
            if hasattr(doc_schema, 'lines') and doc_schema.lines:
                print(f'lines length: {len(doc_schema.lines)}')
            if hasattr(doc_schema, 'words') and doc_schema.words:
                print(f'words length: {len(doc_schema.words)}')
            first_run = False

        # テキスト抽出(優先順位: paragraphs > lines > words)
        if hasattr(doc_schema, 'paragraphs') and doc_schema.paragraphs:
            for paragraph in doc_schema.paragraphs[:MAX_DISPLAY_TEXTS]:
                if hasattr(paragraph, 'text') and paragraph.text:
                    detected_texts.append(paragraph.text)
                elif hasattr(paragraph, 'content') and paragraph.content:
                    detected_texts.append(paragraph.content)

        if not detected_texts and hasattr(doc_schema, 'lines') and doc_schema.lines:
            for line in doc_schema.lines[:MAX_DISPLAY_TEXTS]:
                if hasattr(line, 'text') and line.text:
                    detected_texts.append(line.text)
                elif hasattr(line, 'content') and line.content:
                    detected_texts.append(line.content)

        if not detected_texts and hasattr(doc_schema, 'words') and doc_schema.words:
            for word in doc_schema.words[:MAX_DISPLAY_TEXTS]:
                if hasattr(word, 'text') and word.text:
                    detected_texts.append(word.text)
                elif hasattr(word, 'content') and word.content:
                    detected_texts.append(word.content)

    # 画面表示処理
    display_frame = frame.copy()

    if detected_texts:
        img_pil = Image.fromarray(cv2.cvtColor(display_frame, cv2.COLOR_BGR2RGB))
        draw = ImageDraw.Draw(img_pil)

        for i, text in enumerate(detected_texts):
            y_position = TEXT_START_Y + i * TEXT_LINE_HEIGHT
            draw.text((10, y_position), text, font=font, fill=TEXT_COLOR_RGB)

        display_frame = cv2.cvtColor(np.array(img_pil), cv2.COLOR_RGB2BGR)

    # 結果文字列の作成
    if detected_texts:
        result_str = f"検出: {', '.join(detected_texts[:3])}" + ("..." if len(detected_texts) > 3 else "")
    else:
        result_str = "文字未検出"

    return display_frame, result_str, current_time


# プログラム開始
print('=== YomiToku文字認識プログラム ===')
print('概要: 動画や画像から日本語文字(ひらがな・カタカナ・漢字)を認識します')
print('YomiToku DocumentAnalyzerが正常に初期化されました\n')

print("0: 動画ファイル")
print("1: カメラ")
print("2: サンプル動画")

choice = input("選択: ")

if choice == '0':
    root = tk.Tk()
    root.withdraw()
    path = filedialog.askopenfilename()
    if not path:
        exit()
    cap = cv2.VideoCapture(path)
elif choice == '1':
    cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
    if not cap.isOpened():
        cap = cv2.VideoCapture(0)
    cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
else:
    # サンプル動画ダウンロード・処理
    SAMPLE_URL = 'https://raw.githubusercontent.com/opencv/opencv/master/samples/data/vtest.avi'
    SAMPLE_FILE = 'vtest.avi'
    urllib.request.urlretrieve(SAMPLE_URL, SAMPLE_FILE)
    cap = cv2.VideoCapture(SAMPLE_FILE)

if not cap.isOpened():
    print('動画ファイル・カメラを開けませんでした')
    exit()

# メイン処理
print('\n=== 動画処理開始 ===')
print('操作方法:')
print('  q キー: プログラム終了')
try:
    while True:
        ret, frame = cap.read()
        if not ret:
            break

        MAIN_FUNC_DESC = "YomiToku文字認識"
        processed_frame, result, current_time = video_frame_processing(frame)
        cv2.imshow(MAIN_FUNC_DESC, processed_frame)
        if choice == '1':  # カメラの場合
            print(datetime.fromtimestamp(current_time).strftime("%Y-%m-%d %H:%M:%S.%f")[:-3], result)
        else:  # 動画ファイルの場合
            print(frame_count, result)
        results_log.append(result)
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
finally:
    print('\n=== プログラム終了 ===')
    cap.release()
    cv2.destroyAllWindows()
    if results_log:
        with open('result.txt', 'w', encoding='utf-8') as f:
            f.write('=== 結果 ===\n')
            f.write(f'処理フレーム数: {frame_count}\n')
            f.write(f'使用デバイス: {str(device).upper()}\n')
            if device.type == 'cuda':
                f.write(f'GPU: {torch.cuda.get_device_name(0)}\n')
            f.write('\n')
            f.write('\n'.join(results_log))
        print(f'\n処理結果をresult.txtに保存しました')