フッターコンテンツにスキップ
ビデオ

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
// 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
Imports Leadtools
Imports Leadtools.Ocr
Imports Leadtools.Codecs
Imports Leadtools.Forms.DocumentWriters

' LEADTOOLS: Four namespaces, four packages, six steps before recognition
RasterSupport.SetLicense(licPath, File.ReadAllText(keyPath)) ' Step 1: two files on disk
Dim codecs As New RasterCodecs()                             ' Step 2: codec layer
Dim engine As IOcrEngine = OcrEngineManager.CreateEngine(OcrEngineType.LEAD) ' Step 3: engine factory
engine.Startup(codecs, Nothing, Nothing, runtimePath)        ' Step 4: blocking startup
' ... still need to create document, add page, call Recognize(), extract text
$vbLabelText   $csharpLabel
// IronOCR: One namespace, one package, one line
using IronOcr;

IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var text = new IronTesseract().Read("document.jpg").Text;
// IronOCR: One namespace, one package, one line
using IronOcr;

IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var text = new IronTesseract().Read("document.jpg").Text;
Imports IronOcr

IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Dim text As String = New IronTesseract().Read("document.jpg").Text
$vbLabelText   $csharpLabel

IronOCRとLEADTOOLS OCR:機能比較

以下の表は、2つのライブラリ間の機能を直接対応付けたものです。

フィーチャー リードツールズOCR IronOCR
必要なNuGetパッケージ 最低4ページ(PDFの場合はそれ以上) 1 (IronOcr)
ライセンスメカニズム .LIC + .LIC.KEY ファイルペア 文字列キー
ライセンス展開 すべての生産機械上のファイル 環境変数または設定
価格設定モデル 開発者1人あたり年間3,000ドル~15,000ドル以上(推定) $999–$2,999 一回限りの永続的
エンジン初期化 ランタイムパスを使った手動の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
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
SHELL

NuGetからIronOCRをインストールしてください。

dotnet add package IronOcr

ステップ 2: 名前空間の更新

すべてのLEADTOOLSusingディレクティブを単一のIronOCRインポートに置き換えます:

// Before (LEADTOOLS)
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;
using Leadtools.Forms.DocumentWriters;

// After (IronOCR)
using IronOcr;
// Before (LEADTOOLS)
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;
using Leadtools.Forms.DocumentWriters;

// After (IronOCR)
using IronOcr;
Imports IronOcr
$vbLabelText   $csharpLabel

ステップ 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");
// 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");
Imports System

' 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")
$vbLabelText   $csharpLabel

ライセンスキーは、標準 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();
    }
}
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();
    }
}
Imports Leadtools
Imports Leadtools.Ocr
Imports Leadtools.Codecs
Imports System
Imports System.IO

' Service class required purely to manage engine lifecycle
Public Class LeadtoolsOcrService
    Implements IDisposable

    Private _engine As IOcrEngine
    Private _codecs As RasterCodecs
    Private ReadOnly _runtimePath As String

    Public Sub New(licPath As String, keyPath As String, runtimePath As String)
        _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, Nothing, Nothing, _runtimePath)
    End Sub

    Public ReadOnly Property IsReady As Boolean
        Get
            Return If(_engine?.IsStarted, False)
        End Get
    End Property

    Public Function Process(imagePath As String) As String
        If Not IsReady Then
            Throw New InvalidOperationException("Engine not started")
        End If

        Using image = _codecs.Load(imagePath)
            Using doc = _engine.DocumentManager.CreateDocument()
                Dim page = doc.Pages.AddPage(image, Nothing)
                page.Recognize(Nothing)
                Return page.GetText(-1)
            End Using
        End Using
    End Function

    Public Sub Dispose() Implements IDisposable.Dispose
        ' Order is mandatory: Shutdown before Dispose
        If _engine?.IsStarted = True Then
            _engine.Shutdown()
        End If

        _engine?.Dispose()
        _codecs?.Dispose()
    End Sub
End Class
$vbLabelText   $csharpLabel

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
}
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
}
Imports IronOcr

' No lifecycle management needed — the service class becomes trivial
Public Class OcrService
    Private ReadOnly _ocr As New IronTesseract()

    ' Always ready — no IsStarted check needed
    Public Function Process(imagePath As String) As String
        Return _ocr.Read(imagePath).Text
    End Function

    ' No Dispose() needed for the engine
    ' No Startup(), no Shutdown(), no codec layer
End Class
$vbLabelText   $csharpLabel

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;
    }
}
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;
    }
}
Imports Leadtools
Imports Leadtools.Ocr
Imports Leadtools.Codecs
Imports System.IO

Public Class LeadtoolsTiffBatchService
    Private ReadOnly _engine As IOcrEngine
    Private ReadOnly _codecs As RasterCodecs

    Public Function ProcessMultiFrameTiff(tiffPath As String) As List(Of String)
        Dim pageTexts As New List(Of String)()

        ' Must query page count before iterating
        Dim info = _codecs.GetInformation(tiffPath, True)
        Dim frameCount As Integer = info.TotalPages

        Using document = _engine.DocumentManager.CreateDocument()
            For frameNum As Integer = 1 To frameCount
                ' Load one frame at a time — must specify firstPage/lastPage
                Using frameImage = _codecs.Load(tiffPath, 0, CodecsLoadByteOrder.BgrOrGray, frameNum, frameNum)
                    Dim page = document.Pages.AddPage(frameImage, Nothing)
                    page.Recognize(Nothing)
                    pageTexts.Add(page.GetText(-1))

                    ' GC pressure accumulates if disposal is missed on any frame
                End Using
            Next
        End Using

        Return pageTexts
    End Function

    Public Function ProcessTiffDirectory(directoryPath As String) As Dictionary(Of String, List(Of String))
        Dim results As New Dictionary(Of String, List(Of String))()

        For Each tiffFile In Directory.GetFiles(directoryPath, "*.tiff")
            results(tiffFile) = ProcessMultiFrameTiff(tiffFile)

            ' Manual GC between files to prevent memory growth
            GC.Collect()
            GC.WaitForPendingFinalizers()
        Next

        Return results
    End Function
End Class
$vbLabelText   $csharpLabel

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);
    }
}
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);
    }
}
Imports IronOcr
Imports System.IO
Imports System.Collections.Concurrent
Imports System.Threading.Tasks

Public Class TiffBatchService
    Private ReadOnly _ocr As New IronTesseract()

    Public Function ProcessMultiFrameTiff(tiffPath As String) As List(Of String)
        Using input As New OcrInput()
            input.LoadImageFrames(tiffPath) ' All frames loaded automatically

            Dim result = _ocr.Read(input)

            ' Per-frame text available through result.Pages
            Return result.Pages.Select(Function(p) p.Text).ToList()
        End Using
    End Function

    Public Function ProcessTiffDirectory(directoryPath As String) As Dictionary(Of String, List(Of String))
        Dim results As New Dictionary(Of String, List(Of String))()

        For Each tiffFile In Directory.GetFiles(directoryPath, "*.tiff")
            results(tiffFile) = ProcessMultiFrameTiff(tiffFile)
        Next

        Return results
    End Function

    ' Parallel processing across files — thread-safe out of the box
    Public Function ProcessTiffDirectoryParallel(directoryPath As String) As Dictionary(Of String, List(Of String))
        Dim concurrentResults As New ConcurrentDictionary(Of String, List(Of String))()
        Dim tiffFiles = Directory.GetFiles(directoryPath, "*.tiff")

        Parallel.ForEach(tiffFiles, Sub(tiffFile)
                                        Using input As New OcrInput()
                                            input.LoadImageFrames(tiffFile)
                                            Dim result = New IronTesseract().Read(input)
                                            concurrentResults(tiffFile) = result.Pages.Select(Function(p) p.Text).ToList()
                                        End Using
                                    End Sub)

        Return New Dictionary(Of String, List(Of String))(concurrentResults)
    End Function
End Class
$vbLabelText   $csharpLabel

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);
    }
}
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);
    }
}
Imports Leadtools
Imports Leadtools.Ocr
Imports Leadtools.Codecs
Imports Leadtools.Forms.DocumentWriters

Public Class LeadtoolsDocumentWriterService
    Private ReadOnly _engine As IOcrEngine
    Private ReadOnly _codecs As RasterCodecs

    Public Sub CreateSearchablePdfFromImages(imagePaths As String(), outputPath As String)
        Using document = _engine.DocumentManager.CreateDocument()

            For Each imagePath In imagePaths
                Using image = _codecs.Load(imagePath)
                    Dim page = document.Pages.AddPage(image, Nothing)
                    page.Recognize(Nothing)
                End Using
            Next

            ' DocumentWriter configuration — four properties to set before save
            Dim pdfOptions As New PdfDocumentOptions With {
                .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, Nothing)
        End Using
    End Sub

    Public Sub CreateSearchablePdfFromPdf(inputPdfPath As String, outputPath As String)
        Dim pdfInfo = _codecs.GetInformation(inputPdfPath, True)
        Using document = _engine.DocumentManager.CreateDocument()

            For i As Integer = 1 To pdfInfo.TotalPages
                Using pageImage = _codecs.Load(inputPdfPath, 0, CodecsLoadByteOrder.BgrOrGray, i, i)
                    Dim page = document.Pages.AddPage(pageImage, Nothing)
                    page.Recognize(Nothing)
                End Using
            Next

            Dim pdfOptions As New PdfDocumentOptions With {
                .DocumentType = PdfDocumentType.Pdf,
                .ImageOverText = True,
                .Title = Path.GetFileNameWithoutExtension(inputPdfPath)
            }

            _engine.DocumentWriterInstance.SetOptions(DocumentFormat.Pdf, pdfOptions)
            document.Save(outputPath, DocumentFormat.Pdf, Nothing)
        End Using
    End Sub
End Class
$vbLabelText   $csharpLabel

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();
    }
}
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();
    }
}
Imports IronOcr

Public Class SearchablePdfService
    Private ReadOnly _ocr As New IronTesseract()

    Public Sub CreateSearchablePdfFromImages(imagePaths As String(), outputPath As String)
        Using input As New OcrInput()
            For Each imagePath In imagePaths
                input.LoadImage(imagePath)
            Next

            Dim result = _ocr.Read(input)
            result.SaveAsSearchablePdf(outputPath) ' DocumentWriter pipeline: gone
        End Using
    End Sub

    Public Sub CreateSearchablePdfFromPdf(inputPdfPath As String, outputPath As String)
        Using input As New OcrInput()
            input.LoadPdf(inputPdfPath)

            Dim result = _ocr.Read(input)
            result.SaveAsSearchablePdf(outputPath)
        End Using
    End Sub

    ' Get bytes directly — useful for streaming responses in ASP.NET
    Public Function CreateSearchablePdfBytes(inputPdfPath As String) As Byte()
        Using input As New OcrInput()
            input.LoadPdf(inputPdfPath)
            Return _ocr.Read(input).SaveAsSearchablePdfBytes()
        End Using
    End Function
End Class
$vbLabelText   $csharpLabel

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; }
}
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; }
}
Imports Leadtools
Imports Leadtools.Ocr
Imports Leadtools.Codecs

Public Class LeadtoolsFormFieldExtractor
    Private ReadOnly _engine As IOcrEngine
    Private ReadOnly _codecs As RasterCodecs

    ' Invoice field extraction using named zones
    Public Function ExtractInvoiceFields(invoicePath As String) As InvoiceFields
        Using image = _codecs.Load(invoicePath)
            Using document = _engine.DocumentManager.CreateDocument()
                Dim page = document.Pages.AddPage(image, Nothing)

                ' Must clear auto-detected zones before adding custom ones
                page.Zones.Clear()

                ' Zone definitions — index order matters for extraction
                Dim zoneDefinitions = New() {
                    New With {.Name = "InvoiceNumber", .X = 450, .Y = 80, .W = 200, .H = 30},
                    New With {.Name = "InvoiceDate", .X = 450, .Y = 115, .W = 200, .H = 30},
                    New With {.Name = "VendorName", .X = 50, .Y = 150, .W = 300, .H = 40},
                    New With {.Name = "TotalAmount", .X = 450, .Y = 600, .W = 200, .H = 30}
                }

                For Each def In zoneDefinitions
                    Dim zone = New OcrZone With {
                        .Bounds = New LeadRect(def.X, def.Y, def.W, def.H),
                        .ZoneType = OcrZoneType.Text,
                        .CharacterFilters = OcrZoneCharacterFilters.None,
                        .RecognitionModule = OcrZoneRecognitionModule.Auto
                    }
                    page.Zones.Add(zone)
                Next

                page.Recognize(Nothing)

                ' Extract by index — must match insertion order exactly
                Return New InvoiceFields With {
                    .InvoiceNumber = page.Zones(0).Text?.Trim(),
                    .InvoiceDate = page.Zones(1).Text?.Trim(),
                    .VendorName = page.Zones(2).Text?.Trim(),
                    .TotalAmount = page.Zones(3).Text?.Trim()
                }
            End Using
        End Using
    End Function
End Class

Public Class InvoiceFields
    Public Property InvoiceNumber As String
    Public Property InvoiceDate As String
    Public Property VendorName As String
    Public Property TotalAmount As String
End Class
$vbLabelText   $csharpLabel

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);
    }
}
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);
    }
}
Imports IronOcr
Imports System.Collections.Concurrent
Imports System.Threading.Tasks

Public Class FormFieldExtractor
    Private ReadOnly _ocr As New IronTesseract()

    ' Each field gets its own CropRectangle-scoped Read() call
    ' No zone index management, no zone ordering dependency
    Public Function ExtractInvoiceFields(invoicePath As String) As InvoiceFields
        Return New InvoiceFields With {
            .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)
        }
    End Function

    Private Function ReadRegion(imagePath As String, x As Integer, y As Integer, width As Integer, height As Integer) As String
        Using input As New OcrInput()
            input.LoadImage(imagePath, New CropRectangle(x, y, width, height))
            Return _ocr.Read(input).Text.Trim()
        End Using
    End Function

    ' Batch: extract the same field from many invoices in parallel
    Public Function ExtractInvoiceNumbersBatch(invoicePaths As String()) As Dictionary(Of String, String)
        Dim results As New ConcurrentDictionary(Of String, String)()

        Parallel.ForEach(invoicePaths, Sub(invoicePath)
                                           Using input As New OcrInput()
                                               input.LoadImage(invoicePath, New CropRectangle(450, 80, 200, 30))
                                               results(invoicePath) = New IronTesseract().Read(input).Text.Trim()
                                           End Using
                                       End Sub)

        Return New Dictionary(Of String, String)(results)
    End Function
End Class
$vbLabelText   $csharpLabel

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; }
}
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; }
}
Imports Leadtools
Imports Leadtools.Ocr
Imports Leadtools.Codecs

Public Class LeadtoolsStructuredExtractor
    Private ReadOnly _engine As IOcrEngine
    Private ReadOnly _codecs As RasterCodecs

    Public Function ExtractWordsWithLocations(imagePath As String) As List(Of WordLocation)
        Dim wordLocations As New List(Of WordLocation)()

        Using image = _codecs.Load(imagePath)
            Using document = _engine.DocumentManager.CreateDocument()
                Dim page = document.Pages.AddPage(image, Nothing)
                page.Recognize(Nothing)

                ' Access words through the zone collection
                For Each zone As OcrZone In page.Zones
                    For Each word As OcrWord In zone.Words
                        wordLocations.Add(New WordLocation With {
                            .Text = word.Value,
                            .X = word.Bounds.X,
                            .Y = word.Bounds.Y,
                            .Width = word.Bounds.Width,
                            .Height = word.Bounds.Height,
                            .Confidence = word.Confidence
                        })
                    Next
                Next
            End Using
        End Using

        Return wordLocations
    End Function
End Class

Public Class WordLocation
    Public Property Text As String
    Public Property X As Integer
    Public Property Y As Integer
    Public Property Width As Integer
    Public Property Height As Integer
    Public Property Confidence As Integer
End Class
$vbLabelText   $csharpLabel

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}");
            }
        }
    }
}
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}");
            }
        }
    }
}
Imports IronOcr

Public Class StructuredExtractor
    Private ReadOnly _ocr As New IronTesseract()

    Public Function ExtractWordsWithLocations(imagePath As String) As List(Of WordLocation)
        Dim result = _ocr.Read(imagePath)

        ' Five-level hierarchy: Pages > Paragraphs > Lines > Words > Characters
        Return result.Pages _
            .SelectMany(Function(page) page.Paragraphs) _
            .SelectMany(Function(para) para.Lines) _
            .SelectMany(Function(line) line.Words) _
            .Select(Function(word) New WordLocation With {
                .Text = word.Text,
                .X = word.X,
                .Y = word.Y,
                .Width = word.Width,
                .Height = word.Height,
                .Confidence = CInt(word.Confidence)
            }) _
            .ToList()
    End Function

    ' Paragraph-level extraction with position data
    Public Sub PrintDocumentStructure(imagePath As String)
        Dim result = _ocr.Read(imagePath)
        Console.WriteLine($"Document confidence: {result.Confidence}%")

        For Each page In result.Pages
            Console.WriteLine($"Page {page.PageNumber}:")
            For Each paragraph In page.Paragraphs
                Console.WriteLine($"  Paragraph at ({paragraph.X}, {paragraph.Y}):")
                Console.WriteLine($"  {paragraph.Text}")
            Next
        Next
    End Sub
End Class
$vbLabelText   $csharpLabel

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");
// 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");
' Remove this:
' RasterSupport.SetLicense(licPath, File.ReadAllText(keyPath))

' Replace with this:
IronOcr.License.LicenseKey = If(Environment.GetEnvironmentVariable("IRONOCR_LICENSE"), Throw New InvalidOperationException("IRONOCR_LICENSE environment variable not set"))
$vbLabelText   $csharpLabel

文字列キーは、どの環境でも同じように動作します。 データベース接続文字列に使用しているのと同じシークレットマネージャに保存してください。

問題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);
// Remove the guard:
// if (!_engine.IsStarted)
//     throw new InvalidOperationException("Engine not started");

// IronTesseract is always ready — just call Read()
var result = _ocr.Read(imagePath);
$vbLabelText   $csharpLabel

問題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
}
// 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
}
' Remove this pattern:
' GC.Collect()
' GC.WaitForPendingFinalizers()

' Replace with standard using scope:
For Each filePath In filePaths
    Using input As New OcrInput()
        input.LoadImage(filePath)
        Dim text = _ocr.Read(input).Text
        ' input disposed here — no accumulation
    End Using
Next
$vbLabelText   $csharpLabel

メモリ最適化に関する詳細なガイダンスについては、 メモリ割り当て削減に関するブログ記事を参照してください。

問題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);
// 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);
' Remove the options setup:
' Dim pdfOptions As New PdfDocumentOptions With { ... }
' _engine.DocumentWriterInstance.SetOptions(DocumentFormat.Pdf, pdfOptions)
' document.Save(outputPath, DocumentFormat.Pdf, Nothing)

' Replace with:
result.SaveAsSearchablePdf(outputPath)
$vbLabelText   $csharpLabel

各呼び出しでは、画像オーバーレイと検索可能なテキストレイヤーを備えた標準的なPDF出力が生成されます。 呼び出し間でリセットするための共有オプション状態はありません。

問題5:バンドルが間違っている - 実行時にモジュールが見つからない

LEADTOOLS: ドキュメントイメージングSDKを購入したチームは、RasterExceptionを投げることがあるとわかるかもしれません。 これは、購入したバンドルにPDFモジュールが含まれていない場合に発生し、本番環境での実行時にのみエラーが発生します。

解決策: IronOCRは、すべてのライセンスティアで全ての機能を備えています。 暗号化されたドキュメントのための個別のPDFモジュール、アドオン、または高精度エンジンの二次供給商はありません。IronOcrパッケージを1つインストールしただけで、追加購入なしで完全な機能セットを利用できます:

dotnet add package IronOcr

問題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);
// 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);
' Each field is independent — reorder freely without breaking extraction
Dim invoiceNumber = ReadRegion(imagePath, 450, 80, 200, 30)
Dim invoiceDate = ReadRegion(imagePath, 450, 115, 200, 30)
Dim vendorName = ReadRegion(imagePath, 50, 150, 300, 40)
Dim totalAmount = ReadRegion(imagePath, 450, 600, 200, 30)
$vbLabelText   $csharpLabel

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" .
# 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" .
SHELL

移行後の検証時に同等のIronOCR機能がテストされるように、購入したLEADTOOLSバンドル(OCRモジュール、PDFモジュール、エンジンタイプ(LEAD、Tesseract、OmniPage))をメモしておいてください。

コードの移行

  1. すべてのLEADTOOLS NuGetパッケージを削除: Leadtools, Leadtools.Ocr, Leadtools.Codecs, Leadtools.Forms.DocumentWriters, Leadtools.Pdf
  2. IronOcr NuGetパッケージをインストール
  3. すべてのLEADTOOLSusing IronOcr;に置き換えます
  4. プロジェクトおよびデプロイメント成果物から.LIC.KEYファイルを削除
  5. アプリケーション起動時にIronOcr.License.LicenseKey = "key"に置き換えます
  6. IOcrEngineサービスクラスをすべて削除
  7. OcrEngineManager.CreateEngine() + new IronTesseract()で置き換えます
  8. input.LoadImage(imagePath) using var input = new OcrInput()以内で置き換えます
  9. マルチフレームTIFFページループをinput.LoadImageFrames(tiffPath)に置き換えます
  10. PDFページの反復ループをinput.LoadPdf(pdfPath)に置き換えます
  11. document.Pages.AddPage() + page.Recognize(null) + ocr.Read(input).Textに置き換えます
  12. OcrZone + input.LoadImage(path, new CropRectangle(x, y, w, h))に置き換えます
  13. PdfDocumentOptions + DocumentWriterInstance.SetOptions() + result.SaveAsSearchablePdf(path)に置き換えます
  14. 前処理コマンドクラス (DeskewCommand, DespeckleCommand, AutoBinarizeCommand) をinput.Deskew(), input.DeNoise(), OcrInputインスタンス上で置き換えます
  15. 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結果機能ページには、出力モデル全体が要約されています。

ご注意Adobe Acrobat、Kofax OmniPage、LEADTOOLS、およびTesseractはそれぞれの所有者の登録商標です。 このサイトはAdobe Inc.、Apryse、Google、KofaxまたはLEAD Technologiesと提携、承認、または支援されていません。 すべての製品名、ロゴ、およびブランドは各所有者の所有物です。 比較は情報提供のみを目的としており、執筆時点で公開されている情報を反映しています。

よくある質問

なぜLEADTOOLS OCRからIronOCRに移行する必要があるのですか?

一般的な推進要因には、COM相互接続の複雑さの解消、ファイルベースのライセンス管理の置き換え、ページ単位の課金の回避、Docker/コンテナデプロイの有効化、標準的な.NETツールと統合するNuGetネイティブワークフローの採用などがあります。

LEADTOOLS OCRからIronOCRに移行する際の主なコード変更は何ですか?

LEADTOOLS OCRの初期化シーケンスをIronTesseractのインスタンス化に置き換え、COMのライフサイクル管理(明示的なCreate/Load/Closeパターン)を削除し、結果のプロパティ名を更新します。その結果、定型的な行が大幅に減りました。

IronOCRをインストールして移行を開始するにはどうすればいいですか?

パッケージマネージャーコンソールで'Install-Package IronOcr'を実行するか、CLIで'dotnet add package IronOcr'を実行してください。言語パックは別のパッケージです:例えばフランス語の場合は'dotnet add package IronOcr.Languages.French'となります。

IronOCRは、標準的なビジネス文書のLEADTOOLS OCRのOCR精度に匹敵しますか?

IronOCRは、請求書、契約書、領収書、入力フォームなどの標準的なビジネスコンテンツで高い精度を達成しています。画像前処理フィルター(傾き補正、ノイズ除去、コントラスト強調)により、劣化した入力の認識をさらに向上させます。

IronOcr はLEADTOOLS OCRが別途インストールする言語データをどのように扱うのですか?

IronOCRの言語データはNuGetパッケージとして配布されます。'dotnet add package IronOcr.Languages.German'でドイツ語のサポートがインストールされます。手動でのファイル配置やディレクトリパスは必要ありません。

LEADTOOLS OCRからIronOCRに移行する場合、導入インフラの変更は必要ですか?

IronOCRはLEADTOOLS OCRよりもインフラストラクチャの変更が少なくて済みます。SDKのバイナリパス、ライセンスファイルの配置、ライセンスサーバーの設定はありません。NuGetパッケージには完全なOCRエンジンが含まれており、ライセンスキーはアプリケーションコードに設定された文字列です。

移行後のIronOCRライセンスの設定方法は?

アプリケーションの起動コードでIronOcr.License.LicenseKey = "YOUR-KEY "を割り当てます。DockerまたはKubernetesでは、キーを環境変数として保存し、スタートアップで読み込む。License.IsValidLicenseを使用して、トラフィックを受け入れる前にバリデーションを行う。

IronOCRはLEADTOOLS OCRと同じようにPDFを処理できますか?

IronOCRはネイティブPDFもスキャンしたPDFも読み取ります。IronTesseractをインスタンス化し、ocr.Read(input)を呼び出し、入力はPDFパスまたはOcrPdfInputであり、OcrResultページを反復します。別のPDFレンダリングパイプラインは必要ありません。

IronOCRはどのように大量処理のスレッディングを処理するのですか?

IronTesseractはスレッド毎にインスタンス化しても安全です。Parallel.ForEachまたはタスクプールでスレッドごとにインスタンスを1つスピンアップし、OCRを同時に実行し、完了したら各インスタンスを破棄します。グローバルステートやロックは必要ありません。

IronOCRはテキスト抽出後、どのような出力形式をサポートしていますか?

IronOCRはテキスト、単語座標、信頼度スコア、ページ構造を含む構造化された結果を返します。エクスポートオプションには、プレーンテキスト、検索可能なPDF、下流処理用の構造化結果オブジェクトが含まれます。

IronOCRの価格は、ワークロードのスケーリングにおいてLEADTOOLS OCRよりも予測可能ですか?

IronOCRは、定額制の永久ライセンスを採用しており、ページ単位やボリューム単位の料金はかかりません。10,000ページでも1,000万ページでも、ライセンスコストは一定です。ボリュームライセンスとチームライセンスのオプションはIronOCRの価格ページをご覧ください。

LEADTOOLS OCRからIronOCRに移行した後、既存のテストはどうなりますか?

抽出されたテキストコンテンツをアサートするテストは、移行後もパスし続ける必要があります。APIコールパターンやCOMオブジェクトのライフサイクルを検証するテストは、IronOCRのシンプルな初期化と結果モデルを反映するように更新する必要があります。

Kannaopat Udonpant
ソフトウェアエンジニア
ソフトウェアエンジニアになる前に、Kannapatは北海道大学で環境資源の博士号を修了しました。博士号を追求する間に、彼はバイオプロダクションエンジニアリング学科の一部である車両ロボティクスラボラトリーのメンバーになりました。2022年には、C#のスキルを活用してIron Softwareのエンジニアリングチームに参加し、IronPDFに注力しています。Kannapatは、IronPDFの多くのコードを執筆している開発者から直接学んでいるため、この仕事を大切にしています。同僚から学びながら、Iron Softwareでの働く社会的側面も楽しんでいます。コードやドキュメントを書いていない時は、KannapatはPS5でゲームをしたり、『The Last of Us』を再視聴したりしていることが多いです。

アイアンサポートチーム

私たちは週5日、24時間オンラインで対応しています。
チャット
メール
電話してね