DEV Community

Kaziu
Kaziu

Posted on

OCRで名刺を一括登録する機能をつくった話 — “1ページ複数名刺”という落とし穴

はじめに

展示会などでたまっていく紙の名刺を、CRMに手入力するのはなかなか大変です。そこで 「名刺を詰め込んだPDF(や画像)をアップロードすると、取引先・担当者データを自動で一括登録する」 機能を実務にて作りました。

最初は「OCRに投げれば終わりだろう」と思っていたのですが、実際にやってみると OCRは意外と万能ではない こと、そして どこで処理を分けるか という設計判断が肝になることに気づかされました。この記事では、その勘所と、いちばん悩んだ 「1枚のページに複数の名刺が並んでいたらどうするか」 という問題を共有します。

全体の流れ

  1. ファイル(PDF / JPEG / PNG)をアップロード
  2. サーバー側でバリデーション(形式・サイズ・ページ数)
  3. 「取り込み中」の履歴レコードをつくり、すぐにレスポンスを返す
  4. 重い処理(OCR → 構造化 → 登録/更新)はバックグラウンドジョブで実行
  5. フロントは履歴IDをポーリングして進捗を表示

ポイントは 「受付」と「処理本体」をきっぱり分ける ことです。OCRや名寄せは数十秒〜数分かかることもあるので、リクエストの中で同期的に全部やろうとするとタイムアウトします。受付APIは「ファイルを保存して履歴をつくり、ジョブを起動するところまで」で潔く終わらせます。

OCRは“2段構え”

名刺の読み取りは、実は2つの異なる処理に分かれます。

  1. 文字を読む(OCR) … 画像から文字列を抽出する。ここは Google Cloud Vision のような汎用OCRの担当。
  2. 項目に振り分ける(構造化) … 抽出したテキストのうち「どれが会社名で、どれが氏名で、どれが電話番号か」を判定する。ここは自前の項目抽出モデルの担当。
名刺画像 → ①OCR(文字を全部読む) → テキスト → ②構造化(項目に仕分け) → { 会社名, 氏名, email... }
Enter fullscreen mode Exit fullscreen mode

「OCR=構造化まで全部やってくれる」と思いがちですが、①は文字と位置を出すだけ、②はそれを項目に整理するだけ。役割が別なのです。

設計で効いたポイント

バリデーションは“中身”で判定する。
「PDFかどうか」を拡張子やContent-Typeで見ると、画像を .pdf にリネームするだけで突破されてしまいます。ファイル先頭のマジックバイトで実体を判定するのが安全です。

大きいファイルはメモリに載せない。
名刺100枚分ともなるとファイルは大きくなります。ストレージ(S3等)へ送る際は全体をメモリに読み込まず、そのままストリームで流す。サイズチェックもメタデータだけ見て、中身は読みません。

PDFのページ数チェックは軽い読み取りで。
「◯ページまで」を受付時に弾きたいだけなら、画像化ライブラリ(ImageMagick等)は重すぎます。ページ数を数えるだけならレンダリング不要で、軽量なPDFパーサでメタデータを読めば十分です。

いちばん悩んだ問題:「1ページに複数名刺」

要件を詰める中で出てきたのが、1枚のスキャン画像(PDFの1ページ)に名刺が複数枚並んでいるケースです。コピー機やフラットベッドスキャナで数枚まとめて取り込めば、当然こうなります。

OCRは“名刺単位”には分けてくれない

ここが盲点でした。汎用OCR(Vision等)は「ページ内の全文字+各文字の位置座標」は返してくれますが、「どの文字がどの名刺のものか」を論理的に分離する機能はありません(Google Cloud Vision の公式ドキュメントにも、1ページ上の複数文書を識別・分離する組み込み機能はなく、ブロックの位置を解析する独自ロジックが必要、と明記されています)。

さらに、OCRの「ブロック(文字の塊)」は レイアウト上の近接 でまとめられるため、隣り合う名刺が1つに融合したり、1枚の名刺が複数に割れたりします。「1ブロック=1名刺」にはなりません。

構造化モデルも「名刺1枚分のテキスト」を仕分ける前提なので、複数名刺のテキストが混ざったまま渡すと、「A社の会社名+B氏の氏名」が合体した誤ったデータが生まれてしまいます。

つまり「名刺ごとに切り分ける工程」を自前で挟む必要がある

OCR(全文字+座標) → 【自前】名刺ごとに切り分け → 名刺A分 → 構造化
                                            → 名刺B分 → 構造化 → …
Enter fullscreen mode Exit fullscreen mode

この切り分けの難易度は、名刺の並び方で大きく変わります。

並び方 アプローチ 難易度
固定テンプレート(常に2×3等) ページを決め打ちで等分 低(ただし全ユーザーが同じ並べ方を強制される)
整然と並ぶが枚数可変 座標の“すき間”から行・列を動的に検出 中(ルールベース。傾き・密着・空きマスに弱い)
完全にバラバラ 名刺領域を検出するAIモデルを用意 高(モデルの学習・運用が必要)

真ん中の「座標から格子を推測する」方法は、こんなイメージのルールベース処理です。

  1. すべての文字の座標を集める
  2. X座標の大きなすき間を「列の境界」とみなす
  3. Y座標の大きなすき間を「行の境界」とみなす
  4. 各マスを1名刺として、文字をグルーピングする

AIではなく幾何計算なので比較的軽い一方、傾き・名刺同士の密着・不揃いなサイズ・空きマスがあると崩れます。堅牢にするほど実データでのチューニングが必要になり、突き詰めると「AIで名刺そのものを検出する」方向に近づいていきます。

学び:仕様側で“制約”できると一気にラクになる

技術で無理にバラバラ配置へ対応するよりも、「名刺は等間隔に並べてスキャンしてください」といった入力の制約を設けるほうが、開発コストも精度リスクも大きく下がります。「どこまでの入力を許すか」はエンジニアだけで決めず、プロダクト側と早めにすり合わせるべき論点でした。

重複データの扱い(名寄せ)

一括登録では「すでに登録済みの取引先/担当者」との重複が避けられません。ここは

  • 取引先は「会社名」または「会社名+住所」で一致判定(支店を区別したいニーズもあるため住所も選べる)
  • 担当者はメールアドレスで一致判定

を事前に選ばせ、ヒットしたものは更新、しなかったものは新規登録として扱います。「重複時に既存データを名刺の値で上書きするか」もユーザーが選べるようにしました(勝手に上書きされたくないニーズがあるため)。

まとめ

  • OCRは「文字を読む」+「項目に仕分ける」の2段構え。OCRだけで構造化まで完結はしない
  • 受付と処理本体を分離し、重い処理は非同期ジョブへ。大容量ファイルはストリーム、バリデーションは中身で。
  • 最大の落とし穴は 「1ページ複数名刺」。OCRは名刺単位に分けてくれないので切り分けは自前実装が必要で、並び方次第で難易度が激変する。
  • だからこそ 「入力をどこまで制約できるか」をプロダクトと握ることが、技術選定と同じくらい重要。

“OCRに投げるだけ”に見える機能ほど、実は要件と制約の設計が肝になる——という良い事例でした。

(後日談)

さんざん「1ページに複数名刺だったらどう切り分けるか…」と唸っていたのですが、プロダクト側に確認したら——

「PDFは1ページに名刺1枚の想定です」

……でした。盛大な取り越し苦労。笑

しかし勉強にはなりました。技術で殴る前に、まず要件を疑う。良い教訓になりましたわい。

Top comments (1)

Collapse
 
topstar_ai profile image
Luis

I was particularly intrigued by the challenge of handling multiple business cards on a single page, which is a common scenario when scanning or uploading files. The fact that OCR tools like Google Cloud Vision don't provide a built-in functionality to separate text from multiple documents on the same page is a significant limitation. I appreciate how you broke down the possible approaches to tackle this issue, ranging from simple template-based division to more complex AI-powered solutions. It's also interesting to note how introducing constraints on the input side, such as requiring users to scan business cards in a specific layout, can greatly simplify the technical implementation. Have you considered exploring other machine learning-based approaches, such as object detection or image segmentation, to improve the accuracy of business card detection and text extraction in these scenarios?