DeepSeek-OCR によるPDF・画像OCR(ソースコードと説明と利用ガイド)

【概要説明】 [PDF], [パワーポイント]


元画像

画像からの読み取り結果

プログラム利用ガイド

1. このプログラムの利用シーン

PDFドキュメントやスキャン画像から文字情報を抽出し、テキストに変換するためのツールである。論文、レポート、スキャン文書、画像内の文字列など、幅広い文書タイプに対応する。

2. 主な機能

3. 基本的な使い方

事前準備

以下のパッケージを事前にインストールする必要がある:

実行手順

  1. プログラムを起動する。モデルのロードには数秒から数十秒を要する。
  2. モード選択画面で1〜5の数字を入力し、Enterキーを押す。初回利用時はBase(3)を推奨する。
  3. ファイル選択ダイアログが表示されるため、処理したいPDFまたは画像ファイルを選択する。複数ファイルの選択も可能である。
  4. 処理が自動的に開始され、各ファイルのOCR結果がコンソールに表示される。
  5. 全ファイルの処理完了後、処理統計情報が表示される。

4. 便利な機能

DeepSeek-OCR の公式プロンプト

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 transformers==4.46.3 tokenizers==0.20.3 einops addict easydict gitpython PyMuPDF

flash-attnはプリビルドwhlをダウンロードしてインストール(環境に合わせて選択)。参考ページ:https://github.com/mjun0812/flash-attention-prebuild-wheels/releases


pip install https://github.com/mjun0812/flash-attention-prebuild-wheels/releases/download/v0.4.15/flash_attn-2.8.3+cu126torch2.9-cp312-cp312-win_amd64.whl

プログラムコード

1. 概要

このプログラムは、PDFファイルおよび画像ファイルから文字情報を抽出するOCRアプリケーションである。DeepSeek-OCRモデル,Free OCR プロンプトを使用。複数ファイルの処理に対応し、GUIでファイルを選択できる。

2. 主要技術

Context Optical Compression

DeepSeek-OCRが採用する視覚圧縮技術である[1]。テキストを画像として扱い、ビジョンエンコーダで少数のビジョントークンに圧縮することで、従来のテキストトークン表現と比較して7〜20倍のトークン削減を実現する。Fox benchmarkでは、10倍圧縮時に97%の精度を維持することが報告されている[1]。

DeepEncoder + MoEデコーダ構造

DeepEncoderは約380Mパラメータを持つ視覚エンコーダで、SAM(Segment Anything Model)[2]とCLIP(Contrastive Language-Image Pre-training)[3]の2つのコンポーネントを組み合わせている。SAMはウィンドウアテンションで局所的な知覚を行い、CLIPは密なアテンションで大域的な理解を行う[1]。両者の間には16倍の畳み込み圧縮層があり、ビジョントークン数を削減する。デコーダはDeepSeek3B-MoEモデルで、約570Mの活性パラメータを持つ[1]。

3. 技術的特徴

4. 実装の特色

PDFと画像の両形式に対応し、以下の機能を実装する:

5. 参考文献

[1] DeepSeek-AI. (2025). DeepSeek-OCR: Contexts Optical Compression. GitHub. https://github.com/deepseek-ai/DeepSeek-OCR

[2] Kirillov, A., et al. (2023). Segment Anything. arXiv:2304.02643. https://arxiv.org/abs/2304.02643

[3] Radford, A., et al. (2021). Learning Transferable Visual Models From Natural Language Supervision. arXiv:2103.00020. https://github.com/openai/CLIP

ソースコード


"""
DeepSeek-OCR による画像・PDF OCRアプリケーション
画像ファイルまたはPDFを選択してOCR処理を実行します

事前インストールが必要なパッケージ (Windows):
pip install transformers==4.46.3 tokenizers==0.20.3 einops addict easydict gitpython PyMuPDF
pip install https://github.com/mjun0812/flash-attention-prebuild-wheels/releases/download/v0.4.15/flash_attn-2.8.3+cu126torch2.9-cp312-cp312-win_amd64.whl

flash-attnはプリビルドwhlをダウンロードしてインストール(環境に合わせて選択):
https://github.com/mjun0812/flash-attention-prebuild-wheels/releases
"""
import warnings
warnings.filterwarnings('ignore')

import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
os.environ['TF_ENABLE_ONEDNN_OPTS'] = '0'
os.environ['TRANSFORMERS_VERBOSITY'] = 'error'

import sys
import logging

logging.getLogger('transformers').setLevel(logging.CRITICAL)
logging.getLogger('transformers.generation').setLevel(logging.CRITICAL)
logging.getLogger('transformers.modeling_utils').setLevel(logging.CRITICAL)

import tkinter as tk
from tkinter import filedialog
from transformers import AutoModel, AutoTokenizer
import torch
from pathlib import Path
import fitz
import tempfile
import shutil
from io import StringIO

SUPPORTED_IMAGE_FORMATS = ['.png', '.jpg', '.jpeg', '.bmp', '.tiff']
PDF_ZOOM_FACTOR = 150 / 72

MODE_CONFIGS = {
    "1": {"base_size": 512, "image_size": 512, "crop_mode": False, "name": "Tiny", "tokens": "64"},
    "2": {"base_size": 640, "image_size": 640, "crop_mode": False, "name": "Small", "tokens": "100"},
    "3": {"base_size": 1024, "image_size": 1024, "crop_mode": False, "name": "Base", "tokens": "256"},
    "4": {"base_size": 1280, "image_size": 1280, "crop_mode": False, "name": "Large", "tokens": "400"},
    "5": {"base_size": 1024, "image_size": 640, "crop_mode": True, "name": "Gundam", "tokens": "n×100+256 (動的)"}
}

class TeeOutput:
    """標準出力を複数のストリームに分配"""
    def __init__(self, *streams):
        self.streams = streams
    def write(self, data):
        for stream in self.streams:
            stream.write(data)
    def flush(self):
        for stream in self.streams:
            stream.flush()

class SuppressOutput:
    """出力を抑制"""
    def write(self, data):
        pass
    def flush(self):
        pass

def print_separator():
    print("=" * 60)

def run_ocr_inference(image_path, config, model, tokenizer, prompt):
    old_stdout = sys.stdout
    old_stderr = sys.stderr
    captured_out = StringIO()
    sys.stdout = TeeOutput(old_stdout, captured_out)
    sys.stderr = SuppressOutput()

    temp_output_dir = tempfile.mkdtemp()

    try:
        model.infer(
            tokenizer,
            prompt=prompt,
            image_file=image_path,
            output_path=temp_output_dir,
            base_size=config['base_size'],
            image_size=config['image_size'],
            crop_mode=config['crop_mode'],
            save_results=True,
            test_compress=False
        )
    finally:
        sys.stdout = old_stdout
        sys.stderr = old_stderr

    output = captured_out.getvalue()

    filtered_lines = []
    for line in output.split('\n'):
        if line.startswith('BASE:') or line.startswith('PATCHES:') or line.startswith('====='):
            continue
        if line.strip():
            filtered_lines.append(line)

    result_path = os.path.join(temp_output_dir, "result.md")
    result_text = None
    if os.path.exists(result_path):
        with open(result_path, 'r', encoding='utf-8') as f:
            result_text = f.read()

    shutil.rmtree(temp_output_dir, ignore_errors=True)

    return result_text if result_text else '\n'.join(filtered_lines)

def execute_ocr(image_path, filename_base, config, model, tokenizer, original_path):
    print(f"  処理中(Free OCRモード - 高精度)...")
    prompt_text = "<image>\nFree OCR."

    result_text = run_ocr_inference(image_path, config, model, tokenizer, prompt_text)

    return {
        'filename': filename_base,
        'text': result_text,
        'original': original_path
    }

def process_file(file_path, config, model, tokenizer):
    file_ext = Path(file_path).suffix.lower()

    if file_ext in SUPPORTED_IMAGE_FORMATS:
        result_dict = execute_ocr(
            file_path,
            Path(file_path).name,
            config,
            model,
            tokenizer,
            file_path
        )
        return [result_dict]
    elif file_ext == '.pdf':
        doc = fitz.open(file_path)
        total_pages = len(doc)
        print(f"  ページ数: {total_pages}")
        results = []

        for page_num in range(total_pages):
            page = doc[page_num]
            mat = fitz.Matrix(PDF_ZOOM_FACTOR, PDF_ZOOM_FACTOR)
            pix = page.get_pixmap(matrix=mat)

            tmp_path = None
            try:
                with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as tmp_file:
                    tmp_path = tmp_file.name
                pix.save(tmp_path)

                filename_base = f"{Path(file_path).stem}_page{page_num + 1}.png"
                result_dict = execute_ocr(
                    tmp_path,
                    filename_base,
                    config,
                    model,
                    tokenizer,
                    file_path
                )
                result_dict['filename'] = f"{Path(file_path).name} Page {page_num + 1}/{total_pages}"

                results.append(result_dict)
            finally:
                if tmp_path and os.path.exists(tmp_path):
                    os.unlink(tmp_path)

        doc.close()
        return results
    else:
        print(f"未対応形式: {file_path}")
        return []

def select_files_dialog():
    root = tk.Tk()
    root.withdraw()
    root.update()
    user_files = filedialog.askopenfilenames(
        title="ファイルを選択してください(複数選択可)",
        filetypes=[
            ("対応ファイル", "*.pdf *.png *.jpg *.jpeg *.bmp *.tiff"),
            ("すべてのファイル", "*.*")
        ]
    )
    root.destroy()
    return user_files

print_separator()
print("DeepSeek-OCR: 画像・PDFからMarkdown変換")
print("\n【モデル選択】")
print("1. Tiny (512×512, 64トークン) - 最速")
print("2. Small (640×640, 100トークン) - 高速")
print("3. Base (1024×1024, 256トークン) - 推奨・バランス型")
print("4. Large (1280×1280, 400トークン) - 高精度")
print("5. Gundam (動的解像度) - 最高精度")

choice = input("\nモード選択 (1-5, デフォルト=3): ").strip() or "3"
config = MODE_CONFIGS.get(choice, MODE_CONFIGS["3"])
print(f"\n選択: {config['name']}モード")

print("\n【モデルについて】")
print("""DeepSeek-OCRは2つのコンポーネントで構成:
  1. DeepEncoder: 画像を圧縮しビジョントークンに変換(SAM+CLIP構造)
  2. DeepSeek3B-MoE: ビジョントークンからテキストを復元(570M活性パラメータ)
  特徴: 7-20倍トークン削減、97%精度で10倍圧縮達成(Fox benchmark)""")

print("\nモデルロード中...")
if not torch.cuda.is_available():
    print_separator()
    print("【エラー】NVIDIA GPUが検出されませんでした")
    print("DeepSeek-OCRはNVIDIA GPU専用モデルです")
    print_separator()
    raise RuntimeError("GPU required for DeepSeek-OCR")

print(f"デバイス: cuda")

tokenizer = AutoTokenizer.from_pretrained('deepseek-ai/DeepSeek-OCR', trust_remote_code=True)
model = AutoModel.from_pretrained(
    'deepseek-ai/DeepSeek-OCR',
    _attn_implementation='flash_attention_2',
    trust_remote_code=True,
    use_safetensors=True
).eval().cuda().to(torch.bfloat16)
print("ロード完了\n")

print()
print_separator()
print("【ファイル選択】")
print_separator()
print("複数の画像・PDFファイルをアップロード可能です")
print("対応形式: PDF, PNG, JPG, JPEG, BMP, TIFF")
print(f"\nOCR方式: Free OCRモード(高精度、座標なし、斜め文字対応)\n")

user_files = select_files_dialog()

if user_files:
    all_results = []

    for file_path in user_files:
        print(f"\n処理中: {file_path}")
        file_results = process_file(file_path, config, model, tokenizer)
        all_results.extend(file_results)

    print()
    print_separator()
    print("【OCR結果一覧】")
    print_separator()
    for result in all_results:
        print(f"\n--- {result['filename']} ---")
        print(f"元のファイル: {result['original']}")
        print()
        print(result['text'])
        print_separator()

    print("\n【検証データ】")
    total_files = len(user_files)
    total_pages = len(all_results)
    print(f"  処理ファイル数: {total_files}")
    print(f"  総ページ/画像数: {total_pages}")
    print(f"  モード: {config['name']} (base_size={config['base_size']}, image_size={config['image_size']}, crop_mode={config['crop_mode']})")
    print(f"  ビジョントークン数: {config['tokens']}")
else:
    print("ファイル未選択")

print("\n処理完了")

実験・研究スキルの基礎:パソコンで学ぶOCR実験

1. 実験・研究のスキル構成要素

実験や研究を行うには、以下の5つの構成要素を理解する必要がある。

1.1 実験用データ

このプログラムではPDFファイルと画像ファイルが実験用データである。

1.2 実験計画

何を明らかにするために実験を行うのかを決める。

計画例:

1.3 プログラム

実験を実施するためのツールである。このプログラムはDeepSeek-OCRモデルとTkinterのファイル選択機能を使う。

1.4 プログラムの機能

このプログラムは解像度モードでOCR処理を制御する。

入力パラメータ:

出力情報:

処理の動作:

1.5 検証(結果の確認と考察)

プログラムの実行結果を観察し、モードの影響を考察する。

基本認識:

観察のポイント:

2. 間違いの原因と対処方法

2.1 プログラムのミス(人為的エラー)

プログラムがエラーで停止する

モデルのダウンロードに時間がかかる

2.2 期待と異なる結果が出る場合

認識精度が低い

正しく認識されない

数式や特殊記号が正しく認識されない

3. 実験レポートのサンプル

文書タイプ別の最適モード選択

実験目的:

異なるタイプの文書(レポート、論文、新聞)に対して、精度と処理時間のバランスが最も良いモードを探す。

実験計画:

3種類の文書を用意し、全5つのモードで処理を行い、認識精度と処理時間を記録する。

実験方法:

プログラムを実行し、各モードを選択しながら以下の基準で評価する:

実験結果(レポート文書の例):

モード 解像度 文字認識率 レイアウト再現性 処理時間(秒/ページ) 総合評価
Tiny 512×512 xxxx xxxx xxxx xxxx
Small 640×640 xxxx xxxx xxxx xxxx
Base 1024×1024 xxxx xxxx xxxx xxxx
Large 1280×1280 xxxx xxxx xxxx xxxx
Gundam 動的 xxxx xxxx xxxx xxxx

考察:

結論:

(例文)本実験のレポート文書においては、Smallモードが最もバランスの取れた設定であった。処理速度を優先する場合はTiny、高い精度が必要な場合はBase、表や図を多く含む複雑な文書や学術論文にはGundamが適切である。文書の種類と目的に応じてモードを選択する必要性が確認できた。