ai-lab.org
TextPluck

TextPluck ができるまで — ブラウザ内tesseractで画像から文字を抜く設計

tesseract.js + WASM SIMD でブラウザ完結のOCRを実装したWebサービスの設計記録。日本語OCRの落とし穴、UI上の工夫、スキャンビーム演出までを解説します。

判断: 改善

TextPluck ができるまで — ブラウザ内tesseractで画像から文字を抜く設計

TextPluck は、画像内の文字を アップロードせずに 読み取るWebサービスです。 tesseract.js を WebAssembly でブラウザ内に呼び込み、 日本語・英語・中国語・韓国語の OCR をすべてクライアント側で完結させました。 voice-scribe (音声→テキスト) と対をなす、画像→テキストの軸として置いています。

なぜこの形にしたか

OCR は需要が広い割に、 「無料のオンラインOCR」 サイトの多くは画像を自社サーバーに送る前提です。 レシート、契約書、本人確認書類、スクリーンショット、子供のノートの写真。 これらを得体の知れないサイトにアップロードするのは正直やりたくない。

tesseract.js は LSTM ベースのOCRエンジン tesseract を WebAssembly でブラウザに持ち込むライブラリで、 SIMD 対応の WASM が普通に動く今、 実用速度で OCR を完結させられます。 voice-scribe (音声) → clip-cast (動画) → bg-snap (画像背景透過) と続く ラボの「ブラウザ完結メディア処理」 路線の4本目として、これを採用しました。

visual direction

フォレンジック・ドキュメントスキャナー。

  • 深いミッドナイトブルー (#060a14) を背景に、コバルト (#5b8def) を主アクセント
  • 上下に流れるシアン (#7ed8ff) のスキャンビームを画像領域に常時表示 (OCR 中はもっと速く)
  • voice-scribe (mint cyan / 縦オシロ) / clip-cast (amber / 横タイムライン) / bg-snap (violet / フォトスタジオ) と意図的に温度・形・モチーフをずらしてある
  • スキャンライン演出は ヒーロー背景にも薄く入れて、サービス全体に「読み取っている感」を出す

実装の見どころ

1. 言語切替で再OCRをトリガ

OCR は言語選択で結果が劇的に変わります。 日本語と英語が混ざった画像で eng だけを使うと日本語部分が全部 garbage に なります。

実装では言語チップ (jpn / jpn+eng / eng / chi_sim / kor) を上に置いて、 クリックしたら即時に同じ画像で再OCR する形にしました。 ユーザーが結果を見ながらリトライできるのは tesseract の精度限界を補う上で大きい。

const chip = (l: OcrLang) => (
  <button onClick={() => rerunWithLang(l)} ...>{label(l)}</button>
)

2. worker をキャッシュ

createWorker は初回 ~3-5秒かかります (WASM ロード + 言語モデルDL)。 言語ごとにキャッシュして再利用しています。

const workerCache = new Map<OcrLang, Worker>()
// 2回目以降は cached.recognize(img) だけで済む

これで「言語チップを切り替えるたびに再OCR」が現実的な速度になります。

3. スキャンビーム演出

OCR 実行中は画像領域にシアンの水平ビームを keyframes で上下に走らせる:

.scanBeam {
  background: linear-gradient(180deg, transparent 45%,
    rgba(126,216,255,0.9) 50%, transparent 55%);
  background-size: 100% 100%;
  animation: tp-scan 1.8s linear infinite;
  mix-blend-mode: screen;
}
@keyframes tp-scan {
  0% { background-position: 0 -100%; }
  100% { background-position: 0 200%; }
}

mix-blend-mode: screen で下の画像に明色合成されて、 古典的なスキャナーの読み取り光が現実っぽく走ります。 「何かが起きている」 という感覚をはっきり出すための演出です。

4. 信頼度を一緒に出す

OCR は確実に間違えるので、結果と一緒に confidence を表示しました。 80% 未満なら別言語で試すか、画像を撮り直すかの判断材料になります。 単に結果だけ出して放置するより、 ユーザーが次の手を打ちやすくなります。

5. dynamic import + ssr:false

tesseract.js も VoiceScribe / ClipCast / BGSnap と同様、 client component 専用です。 Next.js 16 では Server Component 側から ssr:false を付けられないので、 loader を別ファイルに切り出して dynamic(() => import(...), { ssr: false }) で囲んでいます。

苦労したところ

  • tesseract.js の logger callback は型が緩く、 status string の中身がバージョン依存。 ステップ判定は includes("recogni") などの文字列マッチで凌いだ。
  • 日本語モデルの初回ロード時間。 ~5-15MB だがネット越しなので3-8秒程度かかる。 progress を見せて 「読み込み → 認識」 の2段階で UI に反映する。
  • 手書き文字は厳しい。 印刷物中心のユースケースであることを FAQ で明記し、 将来 TrOCR への切替を予告した。

今後の拡張

  • 複数画像のバッチ処理: 1枚ずつではなく、複数ファイルをキューで連続OCR
  • PDF ページ単位の読み込み: PDF をクライアント側でレンダリングして各ページ OCR
  • TrOCR / PaddleOCR への切替: 精度重視モードを追加 (重量級モデルだが日本語も強い)
  • 座標 (bbox) 付き結果のエクスポート: hOCR / ALTO 形式での書き出し
  • clip-cast との連携: 動画フレームから抽出 → テキスト書き起こし
  • bg-snap との連携: 背景透過してから OCR、というワークフロー

このサービスから言える事

ラボの「ブラウザ完結メディア処理」が4本そろいました:

  • voice-scribe (音声 → テキスト)
  • clip-cast (動画 → トリミング)
  • bg-snap (画像 → 透過)
  • text-pluck (画像 → テキスト)

メディア処理の代表的なケースは、サーバーに上げる必要が無くなった、 ということを4方向から実証しています。 「無料SaaSにアップロードする」 という UX の標準仕様そのものを更新するべきタイミングが来ています。

Next Action

読んだあとに、そのまま使って確かめる。

この開発ログは TextPluck をどう育てているかの記録です。読んだらそのままサービス本体へ戻って、価値を確かめられるようにしています。

Keep Reading, Keep Trying

読むだけで終わらせない主力サービス

改善ログで方向を理解したあとに、そのまま使って価値を確かめやすい主力を並べています。

hack-sim主力

HackSim

/hack-sim

1分で始められるSNS向けハッキング体験シミュレーター

· エンタメ· 公開中· 48 pv
prompt-stock主力

PromptStock

/prompt-stock

使うだけで終わらせず、改善して育てるためのプロンプト実用品ストック

· 改善· 改善して伸ばす· 20 pv

More Logs

次に読みたい改善ログ

記事一覧へ