はじめに
国立国会図書館(NDL)が公開しているNDLOCR-LiteはPythonで実装されていますが、モデルはONNX形式で提供されているため、他の言語からも利用できます。
今回はこのNDLOCR-LiteのONNXモデルを使って.NET/C#で古典籍OCRを行なうサンプルを作成してみたので、その内容について記述します。
概要
ソースコード
今回作成したサンプルの完全なコードは以下のリポジトリにあります。
以降の解説はこのソースコードに沿って説明していきます。
実行環境
| 項目 | 概要 |
|---|---|
| OS | Windows 11 |
| 言語/ランタイム | C#/.NET 10 |
| 推論エンジン | ONNX Runtime |
| レイアウト検出モデル | DEIM-S |
| 文字認識モデル | PARSeq |
使用ライブラリ
| ライブラリ | 用途 |
|---|---|
| Microsoft.ML.OnnxRuntime.DirectML | ONNX推論エンジン |
| SkiaSharp | 画像の読み込み、リサイズ、回転等の画像処理 |
| YamlDotNet | 検出クラス定義、文字セットの読み込み |
処理フロー
アプリケーションは以下の2段階のパイプラインで処理を行います。
- レイアウト検出: DEIM-Sモデルで画像中のテキスト行領域を検出し、バウンディングボックスを取得
- 文字認識: 検出した各テキスト行領域を切り出し、PARSeqモデルで文字を認識
Ocr名前空間のクラス構成
OCR処理の中核となるクラスはNdlOcrExample.Ocr名前空間に配置しています。
| クラス | 分類 |
|---|---|
| DeimDetector | レイアウト検出 |
| ParseqRecognizer | 文字認識 |
| PixelNormalizer | 画像前処理(共通) |
| DetectResult | 検出結果データ |
- DeimDetector: 入力画像の前処理、DEIM-Sモデルによる推論、信頼度フィルタリング・NMSを含む後処理を実施して、テキスト行のバウンディングボックスを返します
- ParseqRecognizer: 切り出されたテキスト行画像の前処理、PARSeqモデルによる推論、logitsからのGreedy Decodingを実施してテキストを返します
- PixelNormalizer: 画像のピクセルデータをONNXモデル入力用のfloatテンソルに変換するクラスです
- DetectResult: クラス名、信頼度、バウンディングボックスの座標(X, Y, Width, Height)を保持するデータクラスです
レイアウト検出 (DEIM)
レイアウト検出にはDEIM-SのONNXモデルを使用しています。
| 項目 | 値 |
|---|---|
| モデル | deim-s-1024x1024.onnx |
| クラス定義 | ndl.yaml |
| 入力サイズ | 1024x1024 RGB |
| 出力 | クラスID、バウンディングボックス座標、信頼度スコア |
| 検出クラス数 | 17種類(うちテキスト行関連6種類を使用) |
モデルは17種類のレイアウト要素を認識できますが、本サンプルではテキスト行に関するクラス(line_main、line_caption、line_ad、line_note、line_note_tochu、line_title)のみをフィルタリングして使用しています。
モデルの初期化
NDLOCR-LiteのDEIM-Sモデルは2つの入力を持っています。
| 入力 | 型 | 形状 |
|---|---|---|
| images | float32 | [1, 3, 1024, 1024] |
| orig_target_sizes | int64 | [1, 2] |
imagesは正規化済みのRGB画像テンソル、orig_target_sizesは入力画像のサイズ(高さ, 幅)です。
InputMetadataの1つ目のエントリからは入力名(images)と画像テンソルの形状を取得し、そこからモデルが期待する入力サイズ(1024x1024)を読み取ります。
2つ目のエントリからは画像サイズ入力の名前(orig_target_sizes)を取得します。
これらの入力名はsession.Run()に渡すNamedOnnxValueの名前として使用します。
前処理
入力画像はアスペクト比を維持したまま1024x1024にリサイズし、余白部分は黒で埋めます。
ピクセル値の正規化にはImageNetの統計値を使用し、RGBA8888形式からRGBチャネル毎のplanar形式のfloatテンソルに変換します。
ピクセル正規化処理はPixelNormalizerクラスでSIMDを使った形で実装しています。
前処理の結果、以下の2つのテンソルを作成してONNXランタイムに渡します。
| テンソル | 型 | 形状 |
|---|---|---|
| 画像テンソル | DenseTensor<float> | [1, 3, 1024, 1024] |
| サイズテンソル | DenseTensor<long> | [1, 2] |
画像テンソルは正規化済みのRGB画像で、RGBの各チャネルがplanar配置されています。
サイズテンソルにはモデルの入力サイズ[1024, 1024]を格納します。
後処理
推論結果からテキスト行の検出結果を取得する後処理では、以下のフィルタリングを行います。
- 信頼度フィルタリング: 閾値(デフォルト0.5)以下の検出を除外
-
クラスフィルタリング:
line_で始まるクラス名のみを対象とする - 座標変換: モデル空間(1024x1024)の座標をパディング前の元画像の座標系に変換し、画像範囲外の座標をクリッピング
- NMS (Non-Maximum Suppression): 重複する検出結果を除去
NMS
信頼度の高い順にソートし、IoU(Intersection over Union)が閾値(0.2)以上の検出結果を抑制することで、同一のテキスト行に対する重複検出を除去します。
文字認識 (PARSeq)
文字認識にはPARSeqのONNXモデルを使用しています。
| 項目 | 値 |
|---|---|
| モデル | parseq-ndl-16x768-100-tiny-165epoch-tegaki2.onnx |
| 文字セット定義 | NDLmoji.yaml |
| 入力サイズ | 16x768 BGR |
| 最大認識文字数 | 100文字 |
| 文字セット | 約27,000文字(ひらがな、カタカナ、漢字、英数字、記号等) |
モデルの初期化
PARSeqモデルの入力は1つです。
| 入力 | 型 | 形状 |
|---|---|---|
| input | float32 | [1, 3, 16, 768] |
正規化済みのBGR画像テンソルです。
InputMetadataから入力名(input)とテンソルの形状を取得し、モデルが期待する入力サイズ(16x768)を読み取ります。
前処理
レイアウト検出で得られたバウンディングボックスを元画像からテキスト行領域として切り出し、PARSeqモデルの入力用に前処理を行います。
前処理の結果、以下のテンソルを作成してONNXランタイムに渡します。
| テンソル | 型 | 形状 |
|---|---|---|
| 画像テンソル | DenseTensor<float> | [1, 3, 16, 768] |
BGRのチャネル順でplanar配置され[-1, 1]の範囲に正規化された画像テンソルになります。
DEIM-SとはRGB/BGRのチャネル順と正規化方法が異なります。
デコード
推論結果のlogits(各タイムステップ×約27,000クラスのスコア)からGreedy Decodingで文字列を生成します。
各タイムステップでargmax(最大スコアのインデックス)を取得し、文字セットにマッピングします。
実行結果
次のような画像に対して読み取りを実施してみた時の結果を示します。
> NdlOcrExample -f input.jpg
Device: CPU
Loading configuration files...
Configuration files loaded (charset: 7145 chars, 97ms)
Initializing detection model...
Detection model initialized (1436ms)
Initializing recognition model...
Recognition model initialized (100-char model, 2128ms)
Loading image: input.jpg
Image loaded (size: 240 x 350, 30ms)
Running layout detection...
Layout detection complete: 5 text lines detected (486ms)
Running character recognition...
Character recognition complete: 5 lines recognized (264ms)
Total processing time: 4445ms
========== OCR Results ==========
[line_title] score=0.806 pos=(50,208) size=139x31
WARNING
[line_main] score=0.788 pos=(49,272) size=143x15
state of emergency
[line_title] score=0.781 pos=(15,293) size=216x52
TOTI
[line_title] score=0.776 pos=(10,76) size=222x110
会議
[line_main] score=0.674 pos=(69,250) size=101x16
緊急事態発生
うさコメ
NDLOCR-LiteのモデルはONNX形式で提供されているので、.NETでも簡単にOCR処理を実現できました( ˙ω˙)
以下の記事でも顔認識に使用していますが、Microsoft.ML.OnnxRuntimeを使えば.NETアプリケーションに推論機能を簡単に導入でき、今回はそれをOCR用途に適用した形になります。
