28 OCR 文字認識
OCR(Optical Character Recognition:光学式文字認識)と聞いて、多くの人が思い浮かべるのはスキャナと文書のデジタル化である。何千もの文字クラス、千変万化するフォントとレイアウト。しかし、産業用 OCR はまったくの別物である。生産ラインで読み取るのは、インクジェットプリンタが瓶のキャップに吹き付けた製造日付、レーザーが金属部品に刻印したシリアルナンバー、ドットピンマーカが銘板に打刻したロット番号である。文字セットは多くの場合数十文字(数字と少数の大文字)にすぎず、フォントはマーキング装置によって決まり完全に固定されている。しかし、撮像条件はスキャン文書よりもはるかに過酷である。曲面による反射、インク濃度のむら、ノズル詰まりによる筆画の欠落などである。「小さな文字セット、固定フォント、過酷な表面」というこの組み合わせのもとで、古典的なトレーナブルフォント(trainable font)路線は今日でも依然として主力である。現場で採取した実文字で小さな分類器(classifier)を訓練すれば、このフォントに対するロバスト性は汎用文書 OCR をはるかに凌駕する。本章では、カスタムドットマトリクスフォントを用いてこの路線を端から端まで——訓練、認識、ストレステスト、そして自らの手で破綻に追い込むまで——完全に歩き通す。図 28.1 がわれわれの「フォント」である。0–9 と A–F の計 16 文字、各 20×28 画素、インクのグレースケール値 50、背景 200 とし、0/D や 8/B のような混同しやすい対(confusable pairs)を意図的に含めている。
28.1 古典的 OCR パイプライン
古典的 OCR は「文字を読む」ことを 4 つのステップに分解する。各ステップは、前章までの基礎の上に構築されている。
- 二値化(binarization):文字の筆画を背景から分離し、前景領域を得る。これは チャプター 7 の閾値処理である。文字と背景のコントラストは通常高いため、固定閾値でも自動閾値でも対応できる。鍵となるのは極性(暗い文字か明るい文字か)を正しく選ぶことである。
- 文字セグメンテーション(character segmentation):前景領域を個々の独立した文字単位に切り出す。その中核は チャプター 23 の連結成分分析であり、そこにテキストの構造的事前知識を重ね合わせる。文字は行に並び、行内の間隔はほぼ均一で、文字の幅・高さ・面積は既知の範囲に収まる、といった知識である。
- 特徴抽出 / 正規化:各文字の小画像を統一サイズに縮小し、グレースケールまたは形状特徴を抽出する。
- 分類(classification):分類器を用いて特徴を文字ラベルに写像する。これはまさに チャプター 29 の内容である。ただしクラスは「良品/不良品」から 16 個の文字に変わるだけである。
深層学習 OCR(SDK には別途 DLOCR モジュールがある)は、検出と認識をエンドツーエンドで行い、フォントが多様で背景が複雑なシーンに適している。しかし、固定フォントかつ小文字セットのインクジェットコード読み取りにおいては、古典的路線が少数の訓練サンプルで済み、推論が高速で、失敗モードが解釈可能であるため、より軽量で制御しやすい選択肢であり続けている。本章では DLOCR は取り上げない。
一つの構造的事実に注意したい。この 4 ステップは一方通行のパイプラインであり、各ステップの出力が次ステップの入力単位となる。分類器がどれほど優れていても、セグメンテーションが渡した画像パッチに対してしか答えることができない。この一見平凡な観察は、セクション 28.5 でその真価を完全に発揮することになる。
28.2 フォントの訓練
SciVision の古典的 OCR ワークフローは「セグメンテーション—ラベリング—訓練」の三幕構成である。まず 16 文字のきれいな訓練行(図 28.1)をレンダリングし、GetCharRegion で二値化して文字の前景領域を得た後、SegCharacters で行・列構造に従って 16 個の文字小画像に切り出す。訓練行の文字並びは既知(すなわち “0123456789ABCDEF”)であるため、切り出した小画像を外接枠の中心で左から右へソートすれば、ラベルは自動的に対応する。AddSamples が 16 枚の二値小画像と 16 個のラベル文字列を一度に取り込む。
きれいなサンプル 1 行だけで訓練すると、フォントは過度に「脆く」なる。分類器はノイズも位置ずれも見たことがなく、実際のインクジェット印字に遭遇すれば容易に崩れる。そこで、軽度のデータオーギュメンテーション(data augmentation)を行う。きれいなマスターに加えて、さらに 3 行の劣化版をレンダリングする。標準偏差 \(\sigma=5\) のガウシアンノイズを重畳し、各文字を \(\pm 1\) 画素ランダムに揺らすのである。これらも同様にセグメンテーション、ラベリング、AddSamples を行う。4 行合計 \(16\times 4=64\) 個のサンプル、16 クラスとなり、最後に Train(0) を呼び出してデフォルト分類器(入力は二値小画像)を訓練する。訓練完了後、きれいな訓練行で自己検査を行うと、16 文字すべてが正しく認識された。
クラスあたりわずか 4 サンプルで機能するというのは、機械学習の直感に反するように聞こえる。しかし、その前提は極めて厳しい。フォントが完全に固定されている(クラス内変動はノイズと微小な位置ずれのみで、書体差は存在しない)こと、そしてセグメンテーションが信頼できる(分類器に渡されるのは常に 1 個の完全でセンタリングされた文字である)ことである。この 2 つの前提こそが、産業用 OCR と文書 OCR の分水嶺である。前者は現場の制御と引き換えに小サンプルを獲得し、後者は膨大なサンプルで制御不能な多様性を覆い尽くす。前提が破られた瞬間(マーキング装置の文字ダイズ交換、文字の接触・癒着など)、小サンプルフォントは即座に失効する。これこそが、続く 2 節の実験のテーマである。
28.3 認識と信頼度
フォントの訓練が完了した。次に、見たことのない文字列を読ませてみよう。テスト文字列 “8A0F31” を \(\sigma=5\) のノイズと各文字 \(\pm 1\) px のランダム揺らぎでレンダリングし(図 28.2 上段がラベリング後の結果画像)、同じ二値化—セグメンテーションパイプラインに通して 6 文字を切り出し、Recognize に渡して認識させる。結果はすべて正解であった。しかし、より注目すべきは各文字のスコア(0–100)である。
| 位置 | 真値 | 認識結果 | スコア |
|---|---|---|---|
| 0 | 8 | 8 | 100.00 |
| 1 | A | A | 100.00 |
| 2 | 0 | 0 | 100.00 |
| 3 | F | F | 94.95 |
| 4 | 3 | 3 | 100.00 |
| 5 | 1 | 1 | 87.16 |
なぜ「1」は 87.16 にとどまったのか。それは文字セット中で最も幅の狭いグリフ(わずか 11 px 幅)であり、筆画画素数が最も少ないため、正規化と縮小の後にノイズと 1 px の揺らぎが相対的に最も大きな影響を及ぼすからである。「F」の 94.95 も同様に、開いた筆画末端へのノイズの浸食に起因する。スコアの価値は「どれだけ正しかったか」ではなく、棄却(rejection)と再確認の根拠となる点にある。Recognize の minScore パラメータが受け入れ閾値を定め、閾値を下回った文字は推測値を出力せず、replaceChar で指定されたプレースホルダ(本例では “?” を渡している)を出力する。
このパラメータ対の工学的な意味は、特に強調する価値がある。低い信頼度(confidence)の推測を黙って出力するくらいなら、むしろ “?” を出力して人工再確認や再スキャンを引き起こす方がよい。誤読された製造日付が市場に流出すれば、その代償は生産ラインでの再スキャン 1 回よりもはるかに大きい。これは チャプター 26 で繰り返し強調された「黙示的失敗が最も危険な失敗である」という原則が、OCR 上に投影されたものにほかならない。87.16 と 100 の差は今日はエラーを生まなかった。しかし、それは「1」がこのフォントの中で崖に最も近い文字であることを物語っている。minScore をどこに設定し、どれだけ余裕を残すべきかは、まさにこの種のスコア分布から読み取るべきものである。
28.4 混淆と限界
フォントは \(\sigma=5\) では余裕で処理できる——では、その限界はどこにあるのか?我々は混淆ストレステストを設計した。テスト文字列 “0D8B” は、同時に2組の字形が極めて類似した文字ペアを含んでいる——0 と D(いずれも閉じたループであり、違いは角の丸みだけ)、8 と B(違いは左側が中央で括れるかどうかだけ)。ノイズは \(\sigma=10\) から 50 まで段階的に加え、各段階で独立してレンダリングと認識を 10 回繰り返す。
結果は予想に反して「急峻」であった。\(\sigma=10\)、20、30 の3段階はすべて 100% 正解であった——\(\sigma=30\) のノイズを重畳した時点で画像はすでにスノーノイズで密に覆われているのに、フォントは岩のように安定していた。初めての失敗は \(\sigma=40\) で現れた。文字ごとの正解率は 90% に低下し、しかも失敗の仕方が想定通りにはならなかった——予想された 0/D または 8/B の入れ替えではなく、D が C と認識されたのである。図 28.3 はこの初失敗の現場である。\(\sigma=40\) のノイズがちょうど D の右側の縦画を「食い潰し」、閉じたループが開かれた——分類器の目には、それが右に開いた C に見えたのである。\(\sigma=50\) 段階の失敗モードも同じであった。実験全体の 50 回の試行で、セグメンテーション失敗は一度もなかった——セグメンテーションは分類よりも本質的にロバストである。なぜなら、セグメンテーションは「ここに前景の塊がある」ことだけを必要とし、その塊が何であるかを判別する必要はないからである。
我々は 0/D、8/B の「理論的に混淆しやすいペア」を慎重に設計したが、実際の初失敗は D→C であった。教訓:失敗モードは直感に従わない。混淆行列は実測しなければならず、人間の字形類似性判断から推論することはできない——人間が似ていると感じるものを分類器は混淆するとは限らないし、人間が似ていないと感じるものでも、一画のノイズで隣接してしまうことがある。
この実験から、実行可能な結論が2つ得られる。第一に、固定フォント・小文字セットに対する古典的 OCR のロバスト性には大きな余裕がある(訓練では \(\sigma=5\) しか見ていないのに、\(\sigma=30\) まで耐えた)——正常な生産ラインの変動について心配する必要はない。第二に、フォントの評価には実測混淆分析が不可欠である。ノイズを失敗するまで加え、実際の混淆ペアを記録する——それらは予測したペアではないことが多い——そして、棄却閾値と文字セット設計(例えば、混淆しやすい文字を避けるシリアル番号ルール)のいずれも直感ではなく実測混淆に基づくべきである。
28.5 セグメンテーション:OCR のアキレス腱
混淆実験でセグメンテーションが零失敗であったのは、文字間隔が十分であったからである。では、この前提を取り除いてみよう。“8A0F31” の文字間隔を 0 まで圧縮し、隣接文字の画が直接接触するようにする——これはインクジェット印字でよく見られる文字ピッチの制御不良やインクのにじみをシミュレートする。結果を 図 28.4 に示す。最初の5文字 “8A0F3” の画が約 100 px 幅の1つの大きな連結成分に融合し、‘1’ だけが字形の右側に元々空白があるため難を逃れた。セグメンタは忠実に報告した。2個の「文字」を切り出したと。認識結果は “?1” であった——その 100 px の巨大な塊は、0.5–2 倍のスケール範囲内でフォント中のいかなる字形とも一致せず、SDK によって “?” として棄却されたのである。
この失敗が前節のノイズ失敗と本質的に異なることを理解してほしい。ノイズが D を C に変えるのは、分類層のエラーである——1文字が間違っているだけで、残りは通常通り進行し、スコアも警告を発する。一方、セグメンテーション失敗は構造層のエラーである。6文字が2個の入力ユニットになり、下流の分類器がどれほど強力であっても取り返しがつかない——分類器の任務は「このユニットはどの文字か」に答えることであり、「このユニットはそもそも文字ではない」ことはその問題空間の外にある。これこそが古典的 OCR のアキレス腱である。セグメンテーションが成功と失敗を決定し、セグメンテーションエラーは回復不能である。文書 OCR の研究史上、接触文字セグメンテーション(touching character segmentation)は最も頑固な難問の一つであり、エンドツーエンド深層学習手法の台頭は、大きな部分でまさに明示的セグメンテーションを回避するためであった。
現場での対策は、この難問を攻略することではなく、発生させないことである。
- 発生源で文字間隔を規定する:印字装置の文字間隔は設定可能である。これを工程仕様に組み込み、セグメンテーションに余裕を残す——これはいかなるアルゴリズムによる補正よりも安価である。
- セグメンテーションパラメータを文字寸法と連動させる:
SegCharactersの幅、高さ、面積の上下限は、実際の文字寸法に基づいて設定すべきである。そうすれば、100 px 幅の癒着塊は合法的な幅の範囲外に直接落ちる。 - 接触検出による警報:6文字を期待して2個しか切り出せないこと自体が強い異常信号である——文字数が一致しない場合は警報を出して再検査し、欠損した結果を正常な出力として通すことは決してない。
28.6 SciVision の実装
本章の全実験は SCIMV::SciSvOCR という単一のクラスで完結する。4 ステップの API は セクション 28.1 のパイプラインと 1 対 1 に対応する。
SCIMV::SciSvOCR ocr;
SciRegion region;
// 1) 二値化して文字の前景を抽出:手動閾値 [0,125] で暗色文字を選択
// 注意:polarity の実測セマンティクスはドキュメントと逆——暗色文字には 1 を渡す必要がある
ocr.GetCharRegion(img, roi, /*thresholdMode*/0, /*polarity*/1,
/*minGray*/0, /*maxGray*/125, 0, 3, 3, 0, ®ion);
SciImageArray binImgs, greyImgs; SciRegionArray regs;
SciROIArray lineRects, charRects; SciVarArray idx;
// 2) 文字セグメンテーション:1 行のテキスト、期待文字数 6、幅 [8,120] 高さ [14,56] 等の境界でノイズを除去
ocr.SegCharacters(region, roi, 1, 6, 10, 2, -10, 10, /*ignoreEdge*/0,
10, /*mergeMode*/1, 6, 2, 10, 0, 9999,
8, 120, 14, 56, -30, 30, 30, 9000, 0.0f, 5.0f,
&binImgs, &greyImgs, ®s, &lineRects, &charRects, &idx);
// 3) 学習:各行のセグメンテーション結果にラベルを付与して入力、4 行で計 64 サンプル、その後デフォルト分類器を学習
ocr.AddSamples(binImgs, labels); // ×4 行
ocr.Train(0); // 0 = デフォルト分類器、入力は二値の切り出し画像
// 4) 認識:minScore 未満は replaceChar("?")を出力、スコア範囲は 0-100
SciVar replaceChar("?"), result; SciVarArray scores;
Sci2DVarArray resultAll, scoresAll;
ocr.Recognize(binImgArray, /*minScore*/0, 0.5f, 2.0f, 0.5f, 2.0f,
replaceChar, &result, &scores, &resultAll, &scoresAll);主要なパラメータのうち、GetCharRegion の thresholdMode=0 は手動閾値であり、グレースケール値の区間 [0,125] と組み合わせて暗色の筆画を選択する。SegCharacters の文字の幅・高さおよび面積の上下限は、ノイズ除去の手段であると同時に セクション 28.5 で述べた接触防止の防衛線でもある。Recognize の Xscale/Yscale 区間は、認識時に各文字に対して 0.5–2 倍のスケール探索を許容する。
実測において、正直に記録すべき 3 つの落とし穴に遭遇した。
GetCharRegionの手動閾値におけるpolarityのセマンティクスはドキュメントと逆である。ドキュメントによれば暗色文字には 0 を渡すべきだが、実測では 0 を渡すと背景の連結成分が得られる——1 行 16 文字が丸ごと 538×78 の「巨大文字」1 個になり、後段のセグメンテーションは当然全滅する。暗色文字にはpolarity=1を渡し、グレースケール値の区間[0,125]と組み合わせる必要がある。SegCharactersのignoreEdge=1はエラーコード 122701003 を誤報する。文字が縁に触れていなくても発生する。0 を渡すことで回避できる。AutoSegCharacters(自動セグメンテーションのバリアント)の自動幅境界は、細い文字を取りこぼす。サンプルから推定される幅区間は [19,20] であるのに対し、‘1’ の実際の幅はわずか 11 px であり、そのままフィルタで除外される——16 文字のうち残るのは 14 文字のみとなる。文字幅のばらつきが大きいフォントでは、手動パラメータのSegCharactersを使用し、幅境界を明示的に与えるべきである(本章では [8,120] を使用)。
完全な実行可能なプロジェクトは code/ocr/ にあり、乱数シードは固定済みである。すべての数値は再現可能である。
業界事例:インクジェット日付印字の「幽霊の 8」
ある食品工場のキャップ日付印字読み取りシステムが、日付中の “3” を間欠的に “8” として読み取る事象が発生した。調査の結果、原因はプリントヘッドの軽微な詰まりと判明した。インク滴の偏向により “3” の左側の開口部に薄いインクの橋渡し筆画が生じ、字形が “8” に近づいていた。決定的な証拠はスコア曲線にあった——正常な “3” の認識スコアは 95 以上であるのに対し、故障期間中は 60–70 に低下していた。しかし、当時設定されていた minScore=50 を依然として上回っていたため、誤った結果が合格した認識としてシステムから払い出され、顧客からのクレームを受けて初めて発覚した。是正策は 3 段階で実施された。第一に minScore を 85 に引き上げた。第二に、95 未満の認識は自動再スキャンと画像保存をトリガするよう設定した。第三に、プリントヘッドの圧力とノズル状態を設備点検表に追加し、字形ドリフトを発生源で断ち切った。教訓は次の通りである。信頼度の閾値は「誤認識のコスト」から逆算して決めるべきである。可能な限り甘く設定するべきではない——閾値 50 は、システムがデフォルトで「疑わしくても通す」ことを意味し、食品に印字され消費者に向けて表示される日付にとって、このデフォルトは許容できない。
28.7 まとめ
- 産業用 OCR ≠ 文書 OCR:文字セットが小さく、フォントが固定され、対象表面の環境が過酷である。現場の制御と引き換えに少サンプルを実現する——クラスあたり 4 個の拡張サンプルで実用可能なフォントを学習できるが、その前提はフォントが固定されており、かつセグメンテーションが信頼できることである。前提が崩れればフォントは即座に失効する。
- 古典的パイプライン = 二値化 → 文字セグメンテーション → 特徴 → 分類であり、それぞれ閾値処理、連結成分解析、古典的分類器の上に構築されている。このパイプラインは一方向であり、各ステップの誤りはすべて下流が全額負担する。
- スコアは棄却と再確認の根拠であり、装飾ではない:“8A0F31” は全文字正解であったが、87.16 と 100 の差が崖に最も近い文字を示していた。
minScoreは誤認識のコストから逆算して導くべきであり、黙って推測するより “?” を出力する方がよい。 - 混同は実測しなければならない:σ 階段実験では、フォントは学習ノイズの 6 倍にあたる \(\sigma=30\) に耐えたが、\(\sigma=40\) での最初の失敗は予想外の D→C(ノイズが閉じたループを開いてしまった)であり、綿密に設計された 0/D や 8/B ではなかった——失敗モードは直感に従わない。
- セグメンテーションは OCR のアキレス腱である:文字間隔が 0 のとき、6 文字が 2 つの入力ユニットに融合し、認識結果は “?1” にまで劣化した——どれほど優れた分類器でも復元不可能である。対策はエンジニアリングの側にある。文字間隔を規定し、セグメンテーションパラメータを文字金型寸法と連動させ、文字数が一致しない場合は即座にアラームを発する。
OCR 分類器の特徴設計と学習可能フォントに関する体系的な論述については、Steger らの著書 (Steger, Ulrich, と Wiedemann 2018) を参照されたい。OCR 研究の全体像については、Mori、Suen、Yamamoto による古典的サーベイ (Mori, Suen, と Yamamoto 1992) がテンプレートマッチングから構造解析に至る発展史を体系的に整理している。本章が力点を置いた特徴抽出の段階については、Trier、Jain、Taxt がセグメンテーション済み文字に対する各種特徴手法のサーベイを行っており、今日でも参考価値を失っていない (Trier, Jain, と Taxt 1996)。さらに視野を手書き文字のオンライン・オフライン認識にまで広げるなら、Plamondon と Srihari のサーベイが公認の入門書である (Plamondon と Srihari 2000)。OCR と同じ「印刷された情報を読み取る」ファミリーに属する 1 次元バーコードおよび 2 次元コードの読み取りについては、チャプター 27 を参照されたい。




