はじめに
毎年この時期になって焦って始める「医療費の領収書の集計」。
しかも、集計した結果が「控除額未満」だったりするともうガッカリ。
という訳で、写真をフォルダに置いておけば、自動で医療機関名と金額を集計して、合計額を計算してくれるアプリを作ろうと思いつきました。
そこで問題になるのが、医療機関や調剤薬局の領収書から、精度良く医療機関の名称や金額を拾ってくること。
まずは第一段階として、罫線だらけの日本の帳票に、どれくらい今どきのOCRが対応できるのか、以下4系統を同一データで比較するパイプラインを作成して検証してみることにしました。
-
paddle(cuda版)https://pypi.org/project/paddlepaddle-gpu/ -
deepseekhttps://github.com/deepseek-ai/DeepSeek-OCRすべてローカル実行で、GPU(CUDA)版があるものはGPU版を使用。対象画像はクリニックと調剤薬局の領収書2枚で実験しました。
検証用リポジトリ
この記事のゴール
- 実際にどのOCRが通るか
- Windows環境でハマる依存ポイント
- 現時点の精度傾向
-
DeepSeek-OCRローカル推論を既存アダプタにどう組み込んだか
検証環境
- OS: Windows 11
- CPU: Intel Core i9 10900
- RAM: 32GB
- GPU: NVIDIA GeForce RTX 3070 8GB
- Python: 3.12
検証対象の画像
- クリニックの領収書
- 調剤薬局の領収書
※ プライバシー満載の画像のため、サンプルの掲載は控えます
※ スマホカメラで雑に撮影した、傾きあり、照明暗め、影の映り込みあり画像
※ 調剤薬局の領収書には処方箋発行医療機関名が掲載されているため、文字認識がうまくいかないと、後段の「医療機関・調剤薬局名抽出」ロジックの精度に影響が大きい
実装の前提
OCR を抽象化し、app.main から --ocr-engine を切り替えられるようにしました。
-
ocr/factory.pyでエンジン生成 -
ocr/*_adapter.pyで実装差分を吸収 - 正規化後は同一の抽出・判定パイプラインへ投入
- OCR抽出後の文字列に対して、推定ロジックより医療機関名称や日付・金額を抽出
実装の詳細
- 前処理としてOpenCVで画像からドキュメント範囲の矩形切り出し処理を入れる
- 各ライブラリについてはCUDAで動くものはCUDA、それ以外のものはCPU処理
- OCR認識後、「医療機関名、日付、金額」の抽出処理を行い、その結果を自動判定
-
AUTO_ACCEPT: 抽出OK -
REVIEW_REQUIRED: 要確認 -
REJECTED: 抽出不可
-
先に結論
- 実運用候補としては現状
yomitokuが最も安定 -
tesseractは今回データでは精度不足 -
paddle-gpuは環境調整で動くが、現時点では抽出精度が弱い -
DeepSeek-OCRは Windowsでもローカル推論できるが、請求書抽出としてはまだREVIEW_REQUIREDが多い
モデル別の結果
画像1: clinic_receipt.jpg
| モデル | 判定 | confidence | 文書分類 | OCR行数 |
|---|---|---|---|---|
| tesseract | REJECTED | 0.0668 | unknown | 15 |
| yomitoku | REVIEW_REQUIRED | 0.6773 | clinic_or_hospital | 110 |
| paddle-gpu | REJECTED | 0.1405 | unknown | 23 |
| deepseek-local | REVIEW_REQUIRED | 0.4796 | clinic_or_hospital | 53 |
画像2: pharmacy_receipt.jpg
| モデル | 判定 | confidence | 文書分類 | OCR行数 |
|---|---|---|---|---|
| tesseract | REJECTED | 0.2847 | unknown | 29 |
| yomitoku | AUTO_ACCEPT | 0.7344 | pharmacy | 127 |
| paddle-gpu | REVIEW_REQUIRED | 0.5660 | unknown | 16 |
| deepseek-local | REVIEW_REQUIRED | 0.4100 | unknown | 162 |
2画像平均(参考)
| モデル | 平均confidence | AUTO_ACCEPT / REVIEW_REQUIRED / REJECTED |
|---|---|---|
| yomitoku | 0.7058 | 1 / 1 / 0 |
| deepseek-local | 0.4448 | 0 / 2 / 0 |
| paddle-gpu | 0.3533 | 0 / 1 / 1 |
| tesseract | 0.1758 | 0 / 0 / 2 |
ハマりどころと対処
1. Paddle GPUの依存不整合
発生した問題:
-
paddleocrとpaddlepaddle-gpuの組み合わせでAPI差異 -
numpyABI不整合(numpy 2.x)
対応:
-
numpy==1.26.4に固定 -
ocr/paddle_adapter.pyを修正し、predict()が無い場合はocr()にフォールバック
2. DeepSeek-OCRの公式手順をWindowsでそのまま使えない
発生した問題:
-
vLLMの配布wheelが Linux向けで Windows 非対応 -
flash-attnの導入が困難 - 文字コード由来の
cp932例外
対応:
-
transformers経路に切り替え -
_attn_implementation='eager'で推論 -
PYTHONIOENCODING=utf-8で実行
DeepSeekローカル経路の組み込み内容
deepseek_adapter.py を API/Local の2バックエンド対応にしました。
変更点
-
backend: api | localを追加 -
local時はtorch + transformersでdeepseek-ai/DeepSeek-OCRを直接推論 -
result.mmdを読み込み、既存フォーマットへ変換 -
factory/config/README/testsも追従
設定例(config.yaml)
ocr:
engines:
deepseek:
enabled: true
backend: local
model_name: deepseek-ai/DeepSeek-OCR
local_prompt: "<image>\nFree OCR."
local_base_size: 512
local_image_size: 512
local_crop_mode: false
local_device: cuda
local_dtype: bfloat16
local_attn_impl: eager
実行コマンド例
Paddle GPU(例)
.\.venv-py312-paddle-gpu\Scripts\python.exe -m app.main extract `
--config config.yaml `
--image data/samples/clinic_receipt.jpg `
--household-id household_demo `
--ocr-engine paddle `
--output data/outputs/clinic_paddle_gpu_test.json
DeepSeek Local(例)
$env:PYTHONIOENCODING="utf-8"
.\.venv-py312-deepseek-local\Scripts\python.exe -m app.main extract `
--config config.yaml `
--image data/samples/pharmacy_receipt.jpg `
--household-id household_demo `
--ocr-engine deepseek `
--output data/outputs/pharmacy_deepseek_local.json
まとめ
- Windows + RTX 3070 でも、複数OCRエンジンの横比較は十分可能
- ただし導入コストは
tesseract < yomitoku < paddle-gpu < deepseek-local - 現時点の品質は
yomitokuが最も実用寄り -
DeepSeek-OCRはローカル統合できたので、今後は抽出器側(施設名/日付/金額)のルール最適化が効きそう
次にやると良さそうなこと
-
deepseek-local出力向けの後処理ルール追加(特に表OCRのノイズ除去) -
paddleのモデルバージョン固定を再調整して再評価 - 2枚だけでなく、帳票バリエーションを増やして再ベンチマーク