NET MauiでOCRを実行する方法
このガイドでは、 .NET開発者がLEADTOOLS OCRからIronOCRへの完全な移行を行う手順を説明します。 本書では、 NuGetパッケージの置き換えから完全なコード移行まで、あらゆるステップを網羅しており、LEADTOOLSの初期化手順、マルチネームスペース構造、ファイルベースのライセンス展開モデルによって最も影響を受けるパターンについて、移行前と移行後の例を示しています。
LEADTOOLS OCRから移行する理由
LEADTOOLS OCRは、1990年にまで遡る歴史を持つEnterprise画像処理プラットフォームに組み込まれており、そのAPIもその系譜を反映している。 最小限の動作構成には、4つのNuGetパッケージ、4つの名前空間、既知のパスにデプロイされた2つの物理ライセンスファイル、エンジンの前に初期化されるコーデックレイヤー、3つのオプションからのエンジンタイプの選択、およびランタイムバイナリをメモリにロードするブロッキング起動呼び出しが必要です。 それら全ては、文字が一つも認識される前に実行される。 IronOCRに移行するチームは、そのレイヤー全体を排除できます。つまり、パッケージ1つ、名前空間1つ、ライセンスキーを設定するための行1つだけで済みます。
コンテナ内でのファイルベースのライセンス展開における問題。 LEADTOOLSはアプリケーションを実行するすべてのマシンで特定のパスに読み取り可能な2つの物理ファイルLEADTOOLS.LIC.KEYを必要とします。 Dockerコンテナでは、これらのファイルはイメージに組み込まれる(レイヤー履歴で公開される)か、実行時にマウントされる(あらゆるオーケストレーション環境でボリューム調整が必要となる)かのいずれかでなければなりません。 Azure FunctionsとAWS Lambdaには、回避策を用いない限り、ライセンスファイルをデプロイする仕組みがありません。 CI/CD パイプラインでは、起動呼び出しで使用された正確なパスにファイルが存在する必要があります。存在しない場合、アプリケーションは単一のドキュメントを処理する前にエラーをスローします。 IronOCRは、両方のファイルを環境変数、Kubernetesシークレット、またはAzure Key Vault参照に適合する文字列に置き換えます。
一つのタスクに対して4つのパッケージ。 作動するLEADTOOLS OCRプロジェクトには、最低限Leadtools.Forms.DocumentWritersが必要です。 パスワードで保護されたPDFサポートは、購入したバンドルに含まれていない可能性のある追加のLeadtools.Pdfモジュールを必要とします。 各パッケージは、すべて存在し、互換性があり、バージョンも一致している必要があります。 IronOCRは1つのNuGetパッケージとして提供されます。 ネイティブPDF入力、検索可能なPDF出力、前処理、バーコード読み取りなど、すべての機能が含まれています。
エンジンライフサイクルがメンテナンスの課題を作る。 LEADTOOLSは、OCRエンジンをDispose()の前に行われる必要があり、さもなくばアプリケーションがエラーを起こすためです。 LEADTOOLSバッチプロセッサの生産環境における実装では、ドキュメントチャンクごとにGC.Collect() / RasterImageインスタンスの蓄積の補償のためです。 IronOCRは標準のusingブロックを使用します。 OcrInputは、唯一廃棄が必要なオブジェクトです。
運用環境でバンドルに関する混乱が発生。LEADTOOLSは価格を公表していません。 ドキュメントイメージングSDK(OCRを含むティア)の費用は、開発者1人あたり年間3,000ドルから8,000ドルと見積もられています。 OCRモジュールをPDFモジュールなしで購入したチームは、パスワード保護されたドキュメントに対するRasterSupport.IsLocked()が本番環境でtrueを返したときに、ギャップを発見します。 OmniPageエンジンの精度向上版を利用するには、LEADTOOLSの購入とは別に、Kofaxのライセンス契約が必要です。 IronOCRは、各ティアのすべての機能のライセンスを提供します。 二次的なベンダーとの関係はなく、暗号化された文書用の独立したモジュールもありません。
初期化コストがコールドスタートに影響。 engine.Startup()はランタイムファイルをメモリにロードするブロッキング呼び出しです。 コールドスタート時のレイテンシが重要なサーバーレス環境(Azure Functions、AWS Lambdaなど)では、認識処理が開始される前に500~2000ミリ秒のブロッキング初期化が発生することは構造的な問題である。 IronOCRは遅延初期化を使用します。 IronTesseractインスタンスは最初の使用時に初期化され、その後の同一プロセス内の呼び出しでは初期化コストが全く発生しません。
基本的な問題
LEADTOOLSでは、文字を認識する前に、4つの名前空間、4つのNuGetパッケージ、および必須の起動手順が必要です。
// LEADTOOLS: Four namespaces, four packages, six steps before recognition
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;
using Leadtools.Forms.DocumentWriters;
RasterSupport.SetLicense(licPath, File.ReadAllText(keyPath)); // Step 1: two files on disk
var codecs = new RasterCodecs(); // Step 2: codec layer
var engine = OcrEngineManager.CreateEngine(OcrEngineType.LEAD); // Step 3: engine factory
engine.Startup(codecs, null, null, runtimePath); // Step 4: blocking startup
// ... still need to create document, add page, call Recognize(), extract text
// IronOCR: One namespace, one package, one line
using IronOcr;
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var text = new IronTesseract().Read("document.jpg").Text;
IronOCRとLEADTOOLS OCR:機能比較
以下の表は、2つのライブラリ間の機能を直接対応付けたものです。
| フィーチャー | リードツールズOCR | IronOCR |
|---|---|---|
| 必要なNuGetパッケージ | 最低4ページ(PDFの場合はそれ以上) | 1 (IronOcr) |
| ライセンスメカニズム | .LIC + .LIC.KEY ファイルペア | 文字列キー |
| ライセンス展開 | すべての生産機械上のファイル | 環境変数または設定 |
| 価格設定モデル | 開発者1人あたり年間3,000ドル~15,000ドル以上(推定) | $999–$2,399 一回限りの永続的 |
| エンジン初期化 | ランタイムパスを使った手動のStartup() | 自動、怠惰 |
| エンジン停止 | 前Shutdown() | 不要 |
| コーデック層 | すべての画像読み込みにRasterCodecsが必要 | 不要 |
| PDF入力 | ページごとのラスタライズループ | ネイティブLoadPdf() |
| パスワードで保護されたPDF | 別個のLeadtools.Pdfモジュールが必要 | 組み込みのPasswordパラメータ |
| 検索可能なPDF出力 | DocumentWriter + PdfDocumentOptions + document.Save() | result.SaveAsSearchablePdf() |
| 前処理 | 別のコマンドクラス (DeskewCommand, DespeckleCommand など) | OcrInput上の組み込みフィルターメソッド |
| 複数ページTIFF | CodecsLoadByteOrderとの手動フレーム反復 | input.LoadImageFrames() |
| 構造化された出力 | ページおよびゾーンレベル | ページ、段落、行、単語、文字の座標 |
| 信頼度スコア | OcrPageRecognizeStatus列挙型 | パーセンテージとしてのresult.Confidence |
| バーコード読み取り | LEADTOOLSバーコードモジュール(別売) | 組み込み (ReadBarCodes = true) |
| スレッドセーフ。 | 慎重な管理が必要 | フル (各スレッドに1つのIronTesseract) |
| 対応言語 | 60~120(エンジンによる) | NuGet言語パッケージ経由で125以上 |
| 言語展開 | tessdata files or engine-bundled files | 言語ごとのNuGetパッケージ |
| クロスプラットフォーム | プラットフォームごとのランタイム構成に対応 | Windows、Linux、macOS、Docker、Azure、AWS |
| Dockerデプロイメント | .KEYファイルはマウントまたはベイクする必要があります | 標準のdotnet publish |
| 商業サポート | はい | はい |
クイックスタート:LEADTOOLS OCRからIronOCRへの移行
ステップ 1: NuGet パッケージを置き換える
LEADTOOLSパッケージをすべて削除してください。
dotnet remove package Leadtools
dotnet remove package Leadtools.Ocr
dotnet remove package Leadtools.Codecs
dotnet remove package Leadtools.Forms.DocumentWriters
dotnet remove package Leadtools.Pdf
NuGetからIronOCRをインストールしてください。
ステップ 2: 名前空間の更新
すべてのLEADTOOLSusingディレクティブを単一のIronOCRインポートに置き換えます:
// Before (LEADTOOLS)
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;
using Leadtools.Forms.DocumentWriters;
// After (IronOCR)
using IronOcr;
ステップ 3: ライセンスの初期化
.LIC / .LIC.KEYファイル参照をすべて削除します。 アプリケーション起動時にIronOCRライセンスキーを追加してください。
// Single line replaces the entire file-based license setup
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
// Production pattern: pull from environment variable or secrets manager
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE");
ライセンスキーは、標準 for .NETシークレット管理パターン — appsettings.json、Azure Key Vault、AWS Secrets Manager、またはKubernetesシークレットに保存できます。 アプリケーションバイナリと共にファイルをデプロイする必要はありません。
コード移行の例
エンジン始動および停止ライフサイクル削除
LEADTOOLSでは、エンジンのライフサイクルを明示的に管理する必要があるため、厳格なサービスクラスパターンが必須となります。 コンストラクターはエンジンを開始し、Dispose()を前にしたコードパスはランタイムエラーを発生させます。
LEADTOOLSのOCRアプローチ:
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;
// Service class required purely to manage engine lifecycle
public class LeadtoolsOcrService : IDisposable
{
private IOcrEngine _engine;
private RasterCodecs _codecs;
private readonly string _runtimePath;
public LeadtoolsOcrService(string licPath, string keyPath, string runtimePath)
{
_runtimePath = runtimePath;
// License setup — two files, both must be present
RasterSupport.SetLicense(licPath, File.ReadAllText(keyPath));
// Codec layer — required before engine creation
_codecs = new RasterCodecs();
// Engine factory — engine type determines capability and cost
_engine = OcrEngineManager.CreateEngine(OcrEngineType.LEAD);
// Blocking startup — loads runtime into memory (500–2000ms)
_engine.Startup(_codecs, null, null, _runtimePath);
}
public bool IsReady => _engine?.IsStarted ?? false;
public string Process(string imagePath)
{
if (!IsReady)
throw new InvalidOperationException("Engine not started");
using var image = _codecs.Load(imagePath);
using var doc = _engine.DocumentManager.CreateDocument();
var page = doc.Pages.AddPage(image, null);
page.Recognize(null);
return page.GetText(-1);
}
public void Dispose()
{
// Order is mandatory: Shutdown before Dispose
if (_engine?.IsStarted == true)
_engine.Shutdown();
_engine?.Dispose();
_codecs?.Dispose();
}
}
IronOCRのアプローチ:
using IronOcr;
// No lifecycle management needed — the service class becomes trivial
public class OcrService
{
private readonly IronTesseract _ocr = new IronTesseract();
// Always ready — no IsStarted check needed
public string Process(string imagePath) => _ocr.Read(imagePath).Text;
// No Dispose() needed for the engine
// No Startup(), no Shutdown(), no codec layer
}
IronTesseractインスタンスは最初の使用時に初期化されます。 コンストラクタ引数なし、ランタイムパスなし、Startup()呼び出しなし。 上記のサービスクラスはIDisposableを全く実装する必要がありません — エンジンはステートレスであり、各usingパターンを通じて自らのクリーンアップを処理します。 IronTesseractのセットアップガイドでは、言語の選択や本番環境におけるパフォーマンスチューニングなど、設定オプションについて解説しています。
複数フレームTIFFバッチ処理
LEADTOOLSは、TIFFファイルを多フレームで処理する際、コーデックにフレーム合計数をクエリし、その後各lastPageパラメータを使用して反復します。 各フレーム画像は手動で破棄しないと、メモリに蓄積されてしまいます。
LEADTOOLSのOCRアプローチ:
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;
public class LeadtoolsTiffBatchService
{
private readonly IOcrEngine _engine;
private readonly RasterCodecs _codecs;
public List<string> ProcessMultiFrameTiff(string tiffPath)
{
var pageTexts = new List<string>();
// Must query page count before iterating
var info = _codecs.GetInformation(tiffPath, true);
int frameCount = info.TotalPages;
using var document = _engine.DocumentManager.CreateDocument();
for (int frameNum = 1; frameNum <= frameCount; frameNum++)
{
// Load one frame at a time — must specify firstPage/lastPage
using var frameImage = _codecs.Load(
tiffPath,
0, // bitsPerPixel
CodecsLoadByteOrder.BgrOrGray,
frameNum, // firstPage
frameNum); // lastPage
var page = document.Pages.AddPage(frameImage, null);
page.Recognize(null);
pageTexts.Add(page.GetText(-1));
// GC pressure accumulates if disposal is missed on any frame
}
return pageTexts;
}
public Dictionary<string, List<string>> ProcessTiffDirectory(string directoryPath)
{
var results = new Dictionary<string, List<string>>();
foreach (var tiffFile in Directory.GetFiles(directoryPath, "*.tiff"))
{
results[tiffFile] = ProcessMultiFrameTiff(tiffFile);
// Manual GC between files to prevent memory growth
GC.Collect();
GC.WaitForPendingFinalizers();
}
return results;
}
}
IronOCRのアプローチ:
using IronOcr;
public class TiffBatchService
{
private readonly IronTesseract _ocr = new IronTesseract();
public List<string> ProcessMultiFrameTiff(string tiffPath)
{
using var input = new OcrInput();
input.LoadImageFrames(tiffPath); // All frames loaded automatically
var result = _ocr.Read(input);
// Per-frame text available through result.Pages
return result.Pages.Select(p => p.Text).ToList();
}
public Dictionary<string, List<string>> ProcessTiffDirectory(string directoryPath)
{
var results = new Dictionary<string, List<string>>();
foreach (var tiffFile in Directory.GetFiles(directoryPath, "*.tiff"))
{
results[tiffFile] = ProcessMultiFrameTiff(tiffFile);
}
return results;
}
// Parallel processing across files — thread-safe out of the box
public Dictionary<string, List<string>> ProcessTiffDirectoryParallel(string directoryPath)
{
var concurrentResults = new System.Collections.Concurrent.ConcurrentDictionary<string, List<string>>();
var tiffFiles = Directory.GetFiles(directoryPath, "*.tiff");
Parallel.ForEach(tiffFiles, tiffFile =>
{
using var input = new OcrInput();
input.LoadImageFrames(tiffFile);
var result = new IronTesseract().Read(input);
concurrentResults[tiffFile] = result.Pages.Select(p => p.Text).ToList();
});
return new Dictionary<string, List<string>>(concurrentResults);
}
}
LoadImageFrames()は1つの呼び出しでTIFFからすべてのフレームを読み取ります。 フレーム数の照会も、ループ処理も、フレームごとの明示的な破棄処理もありません。 並列バージョンはスレッドごとに1つのIronTesseractインスタンスを作成します。このパターンが正しいです — 完全なスレッドモデルについてはマルチスレッド例を参照してください。 TIFF固有の入力オプションについては、 TIFFおよびGIF入力ガイドでフレーム選択とマルチフォーマット処理について説明しています。
ドキュメントライターパイプラインの簡素化
LEADTOOLSの検索可能なPDF作成には、エンジン上でdocument.Save()を呼び出す必要があります。 これらの各ステップは、それぞれ独立したオブジェクトであり、独立したAPI呼び出しです。
LEADTOOLSのOCRアプローチ:
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;
using Leadtools.Forms.DocumentWriters;
public class LeadtoolsDocumentWriterService
{
private readonly IOcrEngine _engine;
private readonly RasterCodecs _codecs;
public void CreateSearchablePdfFromImages(string[] imagePaths, string outputPath)
{
using var document = _engine.DocumentManager.CreateDocument();
foreach (var imagePath in imagePaths)
{
using var image = _codecs.Load(imagePath);
var page = document.Pages.AddPage(image, null);
page.Recognize(null);
}
// DocumentWriter configuration — four properties to set before save
var pdfOptions = new PdfDocumentOptions
{
DocumentType = PdfDocumentType.Pdf,
ImageOverText = true, // Image layer visible, text layer searchable
Linearized = false,
Title = "Searchable Output"
};
// Apply options to the engine's writer instance
_engine.DocumentWriterInstance.SetOptions(DocumentFormat.Pdf, pdfOptions);
// Save with format enum — the format must match the options set above
document.Save(outputPath, DocumentFormat.Pdf, null);
}
public void CreateSearchablePdfFromPdf(string inputPdfPath, string outputPath)
{
var pdfInfo = _codecs.GetInformation(inputPdfPath, true);
using var document = _engine.DocumentManager.CreateDocument();
for (int i = 1; i <= pdfInfo.TotalPages; i++)
{
using var pageImage = _codecs.Load(inputPdfPath, 0,
CodecsLoadByteOrder.BgrOrGray, i, i);
var page = document.Pages.AddPage(pageImage, null);
page.Recognize(null);
}
var pdfOptions = new PdfDocumentOptions
{
DocumentType = PdfDocumentType.Pdf,
ImageOverText = true,
Title = Path.GetFileNameWithoutExtension(inputPdfPath)
};
_engine.DocumentWriterInstance.SetOptions(DocumentFormat.Pdf, pdfOptions);
document.Save(outputPath, DocumentFormat.Pdf, null);
}
}
IronOCRのアプローチ:
using IronOcr;
public class SearchablePdfService
{
private readonly IronTesseract _ocr = new IronTesseract();
public void CreateSearchablePdfFromImages(string[] imagePaths, string outputPath)
{
using var input = new OcrInput();
foreach (var imagePath in imagePaths)
input.LoadImage(imagePath);
var result = _ocr.Read(input);
result.SaveAsSearchablePdf(outputPath); // DocumentWriter pipeline: gone
}
public void CreateSearchablePdfFromPdf(string inputPdfPath, string outputPath)
{
using var input = new OcrInput();
input.LoadPdf(inputPdfPath);
var result = _ocr.Read(input);
result.SaveAsSearchablePdf(outputPath);
}
// Get bytes directly — useful for streaming responses in ASP.NET
public byte[] CreateSearchablePdfBytes(string inputPdfPath)
{
using var input = new OcrInput();
input.LoadPdf(inputPdfPath);
return _ocr.Read(input).SaveAsSearchablePdfBytes();
}
}
PdfDocumentOptions + SetOptions() + document.Save()チェーン全体を置き換えます。 画像とテキストの重ね合わせレイヤーの動作は自動的に行われます。 検索可能なPDF出力に関する詳細なドキュメントについては、検索可能なPDFの使い方ガイドで出力オプションについて説明し、検索可能なPDFの例ではASP.NETレスポンスストリームとの統合方法を示しています。
マルチゾーンフィールド抽出移行
LEADTOOLSのゾーンベースのOCRはOcrZoneCharacterFiltersプロパティとともに使用します。 複数のゾーンが1つのページに追加され、page.Recognize()呼び出しで認識され、その後ゾーンインデックスで抽出されます。 ゾーンインデックスは挿入順序と一致するため、抽出ループはその順序を維持する必要があります。
LEADTOOLSのOCRアプローチ:
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;
public class LeadtoolsFormFieldExtractor
{
private readonly IOcrEngine _engine;
private readonly RasterCodecs _codecs;
// Invoice field extraction using named zones
public InvoiceFields ExtractInvoiceFields(string invoicePath)
{
using var image = _codecs.Load(invoicePath);
using var document = _engine.DocumentManager.CreateDocument();
var page = document.Pages.AddPage(image, null);
// Must clear auto-detected zones before adding custom ones
page.Zones.Clear();
// Zone definitions — index order matters for extraction
var zoneDefinitions = new[]
{
new { Name = "InvoiceNumber", X = 450, Y = 80, W = 200, H = 30 },
new { Name = "InvoiceDate", X = 450, Y = 115, W = 200, H = 30 },
new { Name = "VendorName", X = 50, Y = 150, W = 300, H = 40 },
new { Name = "TotalAmount", X = 450, Y = 600, W = 200, H = 30 }
};
foreach (var def in zoneDefinitions)
{
var zone = new OcrZone
{
Bounds = new LeadRect(def.X, def.Y, def.W, def.H),
ZoneType = OcrZoneType.Text,
CharacterFilters = OcrZoneCharacterFilters.None,
RecognitionModule = OcrZoneRecognitionModule.Auto
};
page.Zones.Add(zone);
}
page.Recognize(null);
// Extract by index — must match insertion order exactly
return new InvoiceFields
{
InvoiceNumber = page.Zones[0].Text?.Trim(),
InvoiceDate = page.Zones[1].Text?.Trim(),
VendorName = page.Zones[2].Text?.Trim(),
TotalAmount = page.Zones[3].Text?.Trim()
};
}
}
public class InvoiceFields
{
public string InvoiceNumber { get; set; }
public string InvoiceDate { get; set; }
public string VendorName { get; set; }
public string TotalAmount { get; set; }
}
IronOCRのアプローチ:
using IronOcr;
public class FormFieldExtractor
{
private readonly IronTesseract _ocr = new IronTesseract();
// Each field gets its own CropRectangle-scoped Read() call
// No zone index management, no zone ordering dependency
public InvoiceFields ExtractInvoiceFields(string invoicePath)
{
return new InvoiceFields
{
InvoiceNumber = ReadRegion(invoicePath, 450, 80, 200, 30),
InvoiceDate = ReadRegion(invoicePath, 450, 115, 200, 30),
VendorName = ReadRegion(invoicePath, 50, 150, 300, 40),
TotalAmount = ReadRegion(invoicePath, 450, 600, 200, 30)
};
}
private string ReadRegion(string imagePath, int x, int y, int width, int height)
{
using var input = new OcrInput();
input.LoadImage(imagePath, new CropRectangle(x, y, width, height));
return _ocr.Read(input).Text.Trim();
}
// Batch: extract the same field from many invoices in parallel
public Dictionary<string, string> ExtractInvoiceNumbersBatch(string[] invoicePaths)
{
var results = new System.Collections.Concurrent.ConcurrentDictionary<string, string>();
Parallel.ForEach(invoicePaths, invoicePath =>
{
using var input = new OcrInput();
input.LoadImage(invoicePath, new CropRectangle(450, 80, 200, 30));
results[invoicePath] = new IronTesseract().Read(input).Text.Trim();
});
return new Dictionary<string, string>(results);
}
}
page.Zones.Clear()呼び出しや、認識ステータスチェックも不要です。 地域ベースのOCRガイドでは、単一地域および複数地域の抽出パターンについて解説しています。 請求書のフィールド抽出に関する完全なチュートリアルについては、請求書OCRチュートリアルを参照してください。
単語座標を用いた構造化データ抽出
LEADTOOLSの構造化出力は、ページレベルおよびゾーンレベルで動作します。 バウンディングボックス座標とともに単語レベルのデータを取得するには、開発者は認識されたゾーン内からOcrWordオブジェクトにアクセスします。 このAPIでは、認識後にゾーンコレクションを処理し、ゾーンごとに単語リストを反復処理する必要があります。
LEADTOOLSのOCRアプローチ:
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;
public class LeadtoolsStructuredExtractor
{
private readonly IOcrEngine _engine;
private readonly RasterCodecs _codecs;
public List<WordLocation> ExtractWordsWithLocations(string imagePath)
{
var wordLocations = new List<WordLocation>();
using var image = _codecs.Load(imagePath);
using var document = _engine.DocumentManager.CreateDocument();
var page = document.Pages.AddPage(image, null);
page.Recognize(null);
// Access words through the zone collection
foreach (OcrZone zone in page.Zones)
{
foreach (OcrWord word in zone.Words)
{
wordLocations.Add(new WordLocation
{
Text = word.Value,
X = word.Bounds.X,
Y = word.Bounds.Y,
Width = word.Bounds.Width,
Height = word.Bounds.Height,
Confidence = word.Confidence
});
}
}
return wordLocations;
}
}
public class WordLocation
{
public string Text { get; set; }
public int X { get; set; }
public int Y { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public int Confidence { get; set; }
}
IronOCRのアプローチ:
using IronOcr;
public class StructuredExtractor
{
private readonly IronTesseract _ocr = new IronTesseract();
public List<WordLocation> ExtractWordsWithLocations(string imagePath)
{
var result = _ocr.Read(imagePath);
// Five-level hierarchy: Pages > Paragraphs > Lines > Words > Characters
return result.Pages
.SelectMany(page => page.Paragraphs)
.SelectMany(para => para.Lines)
.SelectMany(line => line.Words)
.Select(word => new WordLocation
{
Text = word.Text,
X = word.X,
Y = word.Y,
Width = word.Width,
Height = word.Height,
Confidence = (int)word.Confidence
})
.ToList();
}
// Paragraph-level extraction with position data
public void PrintDocumentStructure(string imagePath)
{
var result = _ocr.Read(imagePath);
Console.WriteLine($"Document confidence: {result.Confidence}%");
foreach (var page in result.Pages)
{
Console.WriteLine($"Page {page.PageNumber}:");
foreach (var paragraph in page.Paragraphs)
{
Console.WriteLine($" Paragraph at ({paragraph.X}, {paragraph.Y}):");
Console.WriteLine($" {paragraph.Text}");
}
}
}
}
IronOCRの結果階層はCharactersまで降ります。 各レベルはConfidenceを公開します。 LEADTOOLSのゾーンベースのアクセスパターンは廃止され、単語データにアクセスするためにゾーンを反復する必要がなくなりました。 読み取り結果の操作ガイドでは、座標アクセスパターンを含む完全な出力モデルについて説明しています。 OcrResult APIリファレンスには、結果階層上のすべてのプロパティが記載されています。
リードツールズOCR API からIronOCRへのマッピングリファレンス
| リードツールズOCR | IronOCR |
|---|---|
RasterSupport.SetLicense(licPath, keyContent) | IronOcr.License.LicenseKey = "key" |
new RasterCodecs() | 不要 |
OcrEngineManager.CreateEngine(OcrEngineType.LEAD) | new IronTesseract() |
engine.Startup(codecs, null, null, runtimePath) | 不要 |
engine.IsStarted | 必須ではありません(いつでも準備完了) |
engine.Shutdown() | 不要 |
engine.Dispose() | 不要 |
_codecs.Load(imagePath) | input.LoadImage(imagePath) |
_codecs.Load(path, 0, BgrOrGray, page, page) | input.LoadPdf(path) or input.LoadImageFrames(path) |
_codecs.GetInformation(path, true).TotalPages | 不要 - 自動 |
engine.DocumentManager.CreateDocument() | 不要 |
document.Pages.AddPage(image, null) | input.LoadImage(imagePath) |
page.Recognize(null) | ocr.Read(input) (recognition is part of Read()) |
page.GetText(-1) | result.Text |
page.RecognizeStatus | result.Confidence (percentage) |
OcrZone { Bounds = new LeadRect(x, y, w, h) } | new CropRectangle(x, y, w, h) |
page.Zones.Clear() | 不要 |
page.Zones.Add(zone) | input.LoadImage(path, cropRect) |
zone.Words / word.Bounds | result.Pages[n].Words / word.X, word.Y |
DeskewCommand().Run(image) | input.Deskew() |
DespeckleCommand().Run(image) | input.DeNoise() |
AutoBinarizeCommand().Run(image) | input.Binarize() |
ContrastBrightnessCommand().Run(image) | input.Contrast() |
new PdfDocumentOptions { ImageOverText = true } | 自動的にSaveAsSearchablePdf()で処理されます |
engine.DocumentWriterInstance.SetOptions(format, opts) | 不要 |
document.Save(path, DocumentFormat.Pdf, null) | result.SaveAsSearchablePdf(path) |
_codecs.Options.Pdf.Load.Password = password | input.LoadPdf(path, Password: password) |
RasterSupport.IsLocked(RasterSupportType.Document) | IronOcr.License.IsLicensed |
一般的な移行の問題と解決策
問題1:ライセンスファイルパス解決の失敗
LEADTOOLS: .LIC.KEYファイルパスを作業ディレクトリに相対的に解決します。 一般的な失敗モードは、開発で動作するパスが本番環境で"License file not found"を投げるもので、作業ディレクトリが変更されたためです。
解決策: 両方のライセンスファイルとSetLicense()呼び出しを完全に削除します。 環境変数から読み込む単一の文字列代入に置き換えてください。
// Remove this:
// RasterSupport.SetLicense(licPath, File.ReadAllText(keyPath));
// Replace with this:
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE")
?? throw new InvalidOperationException("IRONOCR_LICENSE environment variable not set");
文字列キーは、どの環境でも同じように動作します。 データベース接続文字列に使用しているのと同じシークレットマネージャに保存してください。
問題2:エンジン未始動例外
LEADTOOLS: IsStartedチェックでこれを防ぐ必要があります。
解決策: IronTesseractはスタートアップ呼び出しを必要とせず、開始/停止状態を持ちません。 IsStartedガードとライフサイクルサービスクラス全体を削除できます:
// Remove the guard:
// if (!_engine.IsStarted)
// throw new InvalidOperationException("Engine not started");
// IronTesseract is always ready — just call Read()
var result = _ocr.Read(imagePath);
問題3:バッチ処理におけるラスターイメージのメモリ蓄積
LEADTOOLS: PDFページまたは画像ファイルを繰り返し処理するバッチ処理は、ループ内でRasterImageを破棄する呼び出しがない場合 — 例えばusingブロックが終了する前に投げられた例外、または呼び出しが欠けた手動廃棄パターンなどが原因 — 解放されていない画像がメモリに蓄積します。 LEADTOOLSの生産コードには、通常バッチ間でGC.Collect() / GC.WaitForPendingFinalizers()呼び出しが含まれています。
解決策: すべてのGC.Collect()呼び出しを削除します。 usingブロックで各バッチ操作にスコープされています:
// Remove this pattern:
// GC.Collect();
// GC.WaitForPendingFinalizers();
// Replace with standard using scope:
foreach (var filePath in filePaths)
{
using var input = new OcrInput();
input.LoadImage(filePath);
var text = _ocr.Read(input).Text;
// input disposed here — no accumulation
}
メモリ最適化に関する詳細なガイダンスについては、 メモリ割り当て削減に関するブログ記事を参照してください。
問題4:DocumentWriterInstanceのオプションが呼び出し間で保持される
LEADTOOLS: engine.DocumentWriterInstance.SetOptions()は、共有エンジンライターインスタンスの状態を変更します。 あるコードパスがdocument.Save()を呼び出す前にオプションをリセットしない場合、前の呼び出しの出力フォーマットは持続します。 これは、共有エンジンインスタンスにおける状態を持つ副作用です。
解決策: IronOCRには共有ライター状態がありません。 各SaveAsSearchablePdf()呼び出しは独立しています:
// Remove the options setup:
// var pdfOptions = new PdfDocumentOptions { ... };
// _engine.DocumentWriterInstance.SetOptions(DocumentFormat.Pdf, pdfOptions);
// document.Save(outputPath, DocumentFormat.Pdf, null);
// Replace with:
result.SaveAsSearchablePdf(outputPath);
各呼び出しでは、画像オーバーレイと検索可能なテキストレイヤーを備えた標準的なPDF出力が生成されます。 呼び出し間でリセットするための共有オプション状態はありません。
問題5:バンドルが間違っている - 実行時にモジュールが見つからない
LEADTOOLS: ドキュメントイメージングSDKを購入したチームは、RasterExceptionを投げることがあるとわかるかもしれません。 これは、購入したバンドルにPDFモジュールが含まれていない場合に発生し、本番環境での実行時にのみエラーが発生します。
解決策: IronOCRは、すべてのライセンスティアで全ての機能を備えています。 暗号化されたドキュメントのための個別のPDFモジュール、アドオン、または高精度エンジンの二次供給商はありません。IronOcrパッケージを1つインストールしただけで、追加購入なしで完全な機能セットを利用できます:
問題6:並べ替え後のゾーンインデックスの不一致
LEADTOOLS: ゾーン抽出は、挿入順序に結びつく位置インデックス(page.Zones.Add()。 変更されたフォームレイアウトに合わせてゾーン定義の順序を変更すると、後続のすべてのインデックスがずれるため、抽出処理が意図せず中断されます。
解決策: IronOCRは、フィールドごとにCropRectangleを持つ名前付き変数を使用します。 フィールド定義の順序を変更しても、各フィールドは独立してスコープが設定されているため、抽出には影響しません。
// Each field is independent — reorder freely without breaking extraction
var invoiceNumber = ReadRegion(imagePath, 450, 80, 200, 30);
var invoiceDate = ReadRegion(imagePath, 450, 115, 200, 30);
var vendorName = ReadRegion(imagePath, 50, 150, 300, 40);
var totalAmount = ReadRegion(imagePath, 450, 600, 200, 30);
LEADTOOLS OCR移行チェックリスト
移行前
変更を加える前に、コードベースを監査してLEADTOOLSの使用箇所をすべて特定してください。
# Find all LEADTOOLS namespace imports
grep -rn "using Leadtools" --include="*.cs" .
# Find engine lifecycle calls
grep -rn "OcrEngineManager\|\.Startup(\|\.Shutdown()" --include="*.cs" .
# Find license file references
grep -rn "SetLicense\|LEADTOOLS\.LIC\|\.LIC\.KEY" --include="*.cs" .
# Find RasterCodecs usage
grep -rn "RasterCodecs\|_codecs\.Load\|GetInformation" --include="*.cs" .
# Find DocumentWriter usage
grep -rn "DocumentWriterInstance\|PdfDocumentOptions\|DocumentFormat\." --include="*.cs" .
# Find zone-based OCR
grep -rn "OcrZone\|page\.Zones\|LeadRect\|ZoneType" --include="*.cs" .
# Find GC workarounds to remove
grep -rn "GC\.Collect\|WaitForPendingFinalizers" --include="*.cs" .
移行後の検証時に同等のIronOCR機能がテストされるように、購入したLEADTOOLSバンドル(OCRモジュール、PDFモジュール、エンジンタイプ(LEAD、Tesseract、OmniPage))をメモしておいてください。
コードの移行
- すべてのLEADTOOLS NuGetパッケージを削除:
Leadtools,Leadtools.Ocr,Leadtools.Codecs,Leadtools.Forms.DocumentWriters,Leadtools.Pdf IronOcrNuGetパッケージをインストール- すべてのLEADTOOLS
using IronOcr;に置き換えます - プロジェクトおよびデプロイメント成果物から
.LIC.KEYファイルを削除 - アプリケーション起動時に
IronOcr.License.LicenseKey = "key"に置き換えます IOcrEngineサービスクラスをすべて削除OcrEngineManager.CreateEngine()+new IronTesseract()で置き換えますinput.LoadImage(imagePath)using var input = new OcrInput()以内で置き換えます- マルチフレームTIFFページループを
input.LoadImageFrames(tiffPath)に置き換えます - PDFページの反復ループを
input.LoadPdf(pdfPath)に置き換えます document.Pages.AddPage()+page.Recognize(null)+ocr.Read(input).Textに置き換えますOcrZone+input.LoadImage(path, new CropRectangle(x, y, w, h))に置き換えますPdfDocumentOptions+DocumentWriterInstance.SetOptions()+result.SaveAsSearchablePdf(path)に置き換えます- 前処理コマンドクラス (
DeskewCommand,DespeckleCommand,AutoBinarizeCommand) をinput.Deskew(),input.DeNoise(),OcrInputインスタンス上で置き換えます - LEADTOOLSメモリ管理を補償するために追加されたすべての
GC.Collect()/GC.WaitForPendingFinalizers()呼び出しを削除
移行後
- 代表的な画像およびPDFサンプルにおいて、認識されたテキスト出力がLEADTOOLSの出力と一致することを確認する。
result.Confidenceを使用して信頼スコアが予想範囲内であることを確認- マルチフレームTIFF処理のテストでは、LEADTOOLSのフレーム反復ループと同じページ数が生成されます。
- 検索可能なPDF出力が、PDFリーダー(Adobe Acrobatまたは同等のソフトウェア)でテキスト検索可能であることを検証する。
- 生産請求書またはフォームから既知の良好なフィールド値に対して、ゾーンベースのフィールド抽出をテストする
- パスワード保護されたPDF復号が別個の
Leadtools.Pdfモジュールなしで機能することを確認 - 負荷下でバッチ処理を実行し、メモリ成長がないことを確認(最初にすべての
GC.Collect()を削除します) - ライセンスファイルなしでDockerとCI/CDのデプロイをテストする — 環境変数から文字列ライセンスキーが正しく解決されることを確認する
- スレッドセーフであることを確認するために
Parallel.ForEachで並列処理をテスト - 構造化データ抽出 (
result.Pages,page.Paragraphs,page.Words)が正しい座標を返すことを確認
IronOCRへの移行の主なメリット
デプロイメントがステートレスになる。 LEADTOOLS .LIC と .LIC.KEY ファイルは、すべてのデプロイメント環境が持っていなければならない成果物です。 ライセンスデータを組み込んだコンテナは、イメージ履歴にライセンスデータを公開します。 それらを搭載するコンテナは、容積の調整が必要となる。 IronOCRへの移行後、ライセンスは環境変数内の文字列として扱われます。 デプロイ成果物は標準的なNuGet参照です。 ファイルもパスもマウント戦略もありません。 DockerのデプロイガイドとAzureのデプロイガイドには、コンテナ化された環境の完全なセットアップ方法が記載されています。
4つのパッケージが1つになる。 この移行はLeadtools.Ocr, Leadtools.Codecs, Leadtools.Forms.DocumentWriters, およびオプションでIronOcr参照に減少させます。 前処理、ネイティブPDF入力、検索可能なPDF出力、バーコード読み取り、構造化データ抽出、125以上の言語サポートなど、すべての機能がこの1つのパッケージに含まれています。 機能セットは、購入したバンドルによって左右されません。
バッチプロセッシングが手動メモリ管理を排除する。 LEADTOOLSのバッチコードは、マルチドキュメント実行中のメモリ蓄積を防ぐために、RasterImage廃棄を伴います。 IronOCRのusingブロックでスコープされ、自動的にクリーンアップを行います。 スレッドセーフの並列処理はIronTesseractインスタンスを使用し、同期コードなしでマルチコアスループットを提供します。 生産スループットの調整については、速度最適化ガイドを参照してください。
予測可能な総コスト。LEADTOOLSの推定コストは、開発者5名チームの場合、初年度で15,000ドル~40,000ドルで、アップデートを受けるにはライセンス費用の約20~25%の年間保守費用が必要です。 IronOCR Professionalは2,999ドルで、10人の開発者と10か所の導入場所をカバーする、一度限りの永久ライセンスです。 1年間のアップデートが含まれています。 アップデート期間終了後も継続して利用する場合、追加料金は発生しません。 IronOCRのライセンスページには、営業担当者との相談なしに、すべての料金プランが直接掲載されています。
ゾーン設定なしの構造化データ。 移行後、単語レベル、行レベル、および段落レベルのデータがOcrResult上で直接利用可能になり、ゾーン定義は不要です。 5レベル階層 (Pages, Paragraphs, Lines, Words, Characters) はそれぞれ位置座標と要素ごとの信頼スコアを公開します。 構造化データ抽出を実現するためにLEADTOOLSゾーン構成を必要としていたアプリケーションは、よりシンプルなAPIからより豊富なデータを得られるようになります。 OCR結果機能ページには、出力モデル全体が要約されています。
