フォルダからpdfを選んで文字を青枠で囲うpythonコード(画像pdf対応可能版)

インストーラーのダウンロード

  • UB-Mannheim Tesseract Wiki にアクセスします。
  • tesseract-ocr-w64-setup-v5.x.x.xxx.exe (64bit版)をダウンロードしてください。

インストール時の注意点

  • インストーラーを起動して進めます。
  • 重要: Additional script data の選択画面で、「Japanese」関係の項目にチェックを入れてください(これがないと日本語が読めません)。
    • Japanese scriptJapanese vertical (縦書き) などがあります。
  • インストール先(パス)をメモしておいてください(通常は C:\Program Files\Tesseract-OCR です)。

pip install pytesseract pillow pymupdf

import fitz  # PyMuPDF
import pytesseract
from PIL import Image
import io
import tkinter as tk
from tkinter import filedialog
import os

# =======================================================
# 【重要】Tesseractのインストール先
# ※環境に合わせて書き換えてください
# =======================================================
pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'

def mark_exact_text_in_pdf(input_path, output_path, target_text):
    """
    画像化されたPDFから「文字単位」で位置を特定し、
    検索した文字部分だけをピンポイントで青枠で囲む関数
    """
    print("処理を開始します。文字単位の解析には時間がかかります...")
    
    # 検索語句からスペースを除去(OCRの仕様上、スペースは無視されるため合わせる)
    clean_target = target_text.replace(" ", "").replace(" ", "")
    target_len = len(clean_target)

    if target_len == 0:
        print("検索文字が空です。")
        return

    try:
        doc = fitz.open(input_path)
        total_found = 0
        
        # 拡大率(高解像度にして認識精度を上げる)
        zoom = 2.0 
        mat = fitz.Matrix(zoom, zoom)

        for page_num, page in enumerate(doc):
            print(f"{page_num + 1}ページ目を解析中...")

            # 1. ページを画像データに変換
            pix = page.get_pixmap(matrix=mat)
            img_data = pix.tobytes("png")
            img = Image.open(io.BytesIO(img_data))
            img_w, img_h = img.size

            # 2. 文字単位でOCRを実行 (image_to_boxes)
            # 返り値の形式: "char x1 y1 x2 y2 page" (1行に1文字)
            # ※注意: Tesseractのbox出力は座標原点が「左下」です
            box_data = pytesseract.image_to_boxes(img, lang='jpn+eng')

            # データをパースしてリスト化
            chars_info = []
            for line in box_data.splitlines():
                b = line.split()
                if len(b) < 6: continue
                
                char = b[0]
                # 座標変換 (左下原点 -> 左上原点)
                # x1, y1(下), x2, y2(上)
                x1 = int(b[1])
                y1_bottom = int(b[2])
                x2 = int(b[3])
                y2_top = int(b[4])

                # 画像座標(左上原点)に変換
                # boxのy1は下からの距離なので、上からの距離は H - y2
                real_y1 = img_h - y2_top
                real_y2 = img_h - y1_bottom
                
                chars_info.append({
                    "char": char,
                    "rect": fitz.Rect(x1, real_y1, x2, real_y2)
                })

            # 3. 全文字を結合した文字列を作る
            full_text_stream = "".join([c["char"] for c in chars_info])

            # 4. 文字列の中から検索語句を探す
            start_index = 0
            while True:
                # 見つかった場所のインデックスを取得
                idx = full_text_stream.find(clean_target, start_index)
                if idx == -1:
                    break  # これ以上見つからない

                # 5. 見つかった文字たちの枠を結合して一つの枠にする
                # 対象となる文字ごとのRectを取得
                target_rects = [c["rect"] for c in chars_info[idx : idx + target_len]]
                
                # 最初の文字の枠を基準に、後続の文字の枠を合体(union)させる
                union_rect = target_rects[0]
                for i in range(1, len(target_rects)):
                    union_rect |= target_rects[i]  # 枠の結合演算

                # 6. PDF上の座標に戻す(拡大率で割る)
                final_rect = fitz.Rect(
                    union_rect.x0 / zoom,
                    union_rect.y0 / zoom,
                    union_rect.x1 / zoom,
                    union_rect.y1 / zoom
                )

                # 青枠描画
                page.draw_rect(final_rect, color=(0, 0, 1), width=1.5)
                total_found += 1

                # 次の検索へ (1文字進める)
                start_index = idx + 1

        if total_found > 0:
            doc.save(output_path)
            print(f"\n完了! '{target_text}' を {total_found} 箇所囲いました。")
            print(f"保存先: {output_path}")
        else:
            print(f"\n'{target_text}' は見つかりませんでした。")
        
        doc.close()

    except Exception as e:
        print(f"\nエラーが発生しました: {e}")
        import traceback
        traceback.print_exc()

def select_pdf_file():
    root = tk.Tk()
    root.withdraw()
    file_path = filedialog.askopenfilename(
        title="PDFファイルを選択(精密検索版)",
        filetypes=[("PDFファイル", "*.pdf")]
    )
    return file_path

# --- メイン処理 ---
if __name__ == "__main__":
    input_file = select_pdf_file()

    if input_file:
        print(f"選択されたファイル: {input_file}")
        search_word = input("検索して囲みたい文字列を入力してください: ")

        if search_word:
            base_name, ext = os.path.splitext(input_file)
            output_file = f"{base_name}_exact_marked{ext}"
            
            mark_exact_text_in_pdf(input_file, output_file, search_word)
        else:
            print("文字が入力されませんでした。")
    else:
        print("ファイルが選択されませんでした。")

コメント

タイトルとURLをコピーしました