今回は、入力の手間を減らす補助機能として、OCR(文字認識)を使った領収書読み取り機能を実装しました。
「あったらいいな」という軽い気持ちで始めたのですが、実際にやってみると想像以上に学びが多く、これは共有しておきたいなと思いました。
本記事では、
• 実際にやったこと
• 実装中に躓いたポイント
• それに対してどう対処したか
を、これからOCRを触る人にも分かるように整理しています。
今回やったこと(OCR実装内容)
クライアント側OCRの実装
今回は Tesseract.js を使い、ブラウザ上でOCRを完結させる構成にしました。
主な実装内容は以下です。
領収書画像から
- 金額
- 日付
を自動抽出
駐車場代・備品ページで
- 画像アップロード時にOCRを自動実行
読み取り中は
- プログレスバーを表示して待ち時間を可視化
イメージとしては、
「写真を置いたら、横でAIが一文字ずつ読んでいる進捗が見える」
そんな仕組みです。
……と言いつつも、あまりに高い期待を持ってしまうと、正直がっかりする可能性はあります。
躓いたこと(OCRあるある)
OCRは「画像を渡せば正しく読んでくれる」ほど甘くないんですね。とほほ。
今回、特に問題になったのは次の点です。
| 問題 | 原因 |
|---|---|
| matchAllでエラーが出る | 非グローバル正規表現を渡していた |
| 金額が「120」になる | 電話番号(0120-xx-xxxx)を誤検出 |
| 金額が「96402」になる | 取引番号を金額と誤認 |
| 日付が取れない | OCRが途中にスペースを挿入 |
| 一部領収書が読めない | 低コントラスト・特殊フォント |
中学生向けに例えると、
OCRは「文章を読める人」ではなく、
「形を見て文字っぽいものを拾う人」
という感じでした。
そのため、**電話番号も金額も同じ「数字のかたまり」**として見えてしまいます。
実際に行った対処方法
① matchAllをやめて安全なループに変更
while ((match = pattern.exec(text)) !== null) {
// 抽出処理
}
正規表現の扱いを明示的にし、
「なぜか動かない」という状況を避けるようにしました。
② 電話番号・取引番号を事前に除外
数字を探す前に、明らかに金額ではないものを先に消します。
normalizedText = normalizedText
.replace(/0120[\s\-ー一]*\d{2,4}[\s\-ー一]*\d{2,4}/g, ' ')
.replace(/\d{5,}[\s\-ー一]+\d{3,}/g, ' ')
.replace(/\d{8,}/g, ' ');
これは、
砂の中から金を探す前に、大きな石をどかす
作業に近いです。
③ 日付のスペース揺れに対応
OCRは、勝手にスペースを入れることがあります。
例:
2026 年 2 月 6 日
これに対応するため、空白を許容した正規表現に変更しました。
/(\d{4})\s*年\s*(\d{1,2})\s*月\s*(\d{1,2})\s*日/
まとめ(結論)
今回のOCR実装から得られた結論です。
成功点
印刷品質の良い領収書は、かなり高精度で読み取れる
限界
低コントラスト・特殊フォントはTesseract.jsでは厳しい
運用方針
- OCRはあくまで補助機能
- 読めない場合は手動入力に切り替える設計が現実的
OCRは「魔法」ではありませんが、正しく期待値を設定すれば、とても便利な道具だと感じました。
もっと高精度を求めるなら、別のツール導入を検討するのも選択肢ですね。
誰かの参考になれば幸いです。
