▍AI Dev Lab
TextPluck

TextPluck ができるまで — tesseract で画像から文字を抜く

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

·decision改善·stage公開中

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本目として、これを採用しました。

#見た目の方針

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

  • 深いミッドナイトブルー (#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 の精度限界を補う上で大きい。

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

#2. worker をキャッシュ

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

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

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

#3. スキャンビーム演出

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

css
.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 を実際に動かす。

この開発ログは TextPluck をどう作ったかの記録です。 読み終わったらそのままサービス本体へ戻って、 実物で価値を確かめてください。

[ ./related_logs ]

関連する開発ログ

all logs →
ShadowTwin

立体パズルの別解を作る:同じ影と最少の積み木

ShadowTwinの開発記録。3方向の図から別々の最少配置を2つ探す仕組み、5問の独立全探索、スマホの横並び比較、保存・再編集と入力検査を解説します。実照明の計算や能力診断ではありません。

read log →
SlitCard

スリットアニメーションを作る:4コマの合成と透明原稿の設計

SlitCardの開発記録。4コマの帯を交互に合成し、すき間と黒帯で別のコマを見せる仕組みを解説。同縮尺の台紙・透明シートSVG、50mm確認線、手動プレビュー・初期非表示、再編集と実出力検査を整理します。

read log →
WindowLight

日当たりシミュレーションを作る:窓の光を床へ投影する幾何モデル

WindowLightの開発記録。太陽位置近似から窓の4隅を床へ投影し、凸多角形を部屋で切り取る設計を解説。国立天文台の16時刻との限定照合、大気差・低高度・実際の採光との違い、SVGと設定の再編集を整理します。

read log →
GridLights

電源構成の比較を作る:割合と実量を100個の電球で読み解く

電源構成比較GridLightsの開発記録。12か国の年次発電データを最大剰余法で100個の電球に変換し、割合とTWhを比較する設計を解説。欠損を0補完しない7分類、予想と実値、出典付きSVGと実データ検証を整理します。

read log →
SoundLoom

音のお絵かきを作る:実WAVから絵を測り直す設計

音のお絵かきの開発記録。描画を正弦波のPCMとWAVへ変換し、保存音声の周波数をGoertzel法で測って比較する仕組みを解説。音量・測定窓・量子化の限界と、編集・離脱時の古い結果を抑止する検証を整理します。

read log →
MazeLoom

宝探し迷路を作る:寄り道まで含めた最短ルートの探索

宝探し迷路の開発記録。通路を木として生成し、現在地と3つの宝の取得状態を組み合わせて最短ルートを探す仕組みを解説。問題コードの版固定、画面と解答付きPDFの一致、手数と印刷の検証を整理します。

read log →