Zum Fußzeileninhalt springen
VIDEOS

Wie man OCR in NET Maui durchführt

Dieser Leitfaden führt .NET -Entwickler durch eine vollständige Migration von LEADTOOLS OCRzu IronOCR . Es deckt jeden Schritt ab, vom Ersetzen von NuGet -Paketen bis zur vollständigen Code-Migration, mit Vorher-Nachher-Beispielen für die Muster, die am stärksten von der Initialisierungszeremonie, der Multi-Namespace-Struktur und dem dateibasierten Lizenzbereitstellungsmodell von LEADTOOLS betroffen sind.

Warum von LEADTOOLS OCRmigrieren?

LEADTOOLS OCR ist in eine Enterprise -Imaging-Plattform integriert, deren Wurzeln bis ins Jahr 1990 zurückreichen, und die API spiegelt diese Herkunft wider. Die minimale Arbeitskonfiguration erfordert vier NuGet Pakete, vier Namespaces, zwei physische Lizenzdateien, die in einem bekannten Pfad bereitgestellt werden, eine Codec-Schicht, die vor der Engine initialisiert wird, die Auswahl eines Engine-Typs aus drei Optionen und einen blockierenden Startaufruf, der Laufzeitbinärdateien in den Speicher lädt. All das geschieht, bevor auch nur ein einziges Zeichen erkannt wird. Teams, die auf IronOCR umsteigen, eliminieren diese gesamte Ebene – ein Paket, ein Namespace, eine Zeile zum Festlegen eines Lizenzschlüssels.

Dateibasiertes Lizenzbereitstellungsproblem in Containern. LEADTOOLS erfordert zwei physische Dateien — LEADTOOLS.LIC und LEADTOOLS.LIC.KEY —, die an einem bestimmten Pfad auf jeder Maschine, auf der die Anwendung läuft, lesbar sein müssen. In Docker-Containern müssen diese Dateien entweder in das Image eingebunden werden (wodurch sie in der Layer-Historie sichtbar werden) oder zur Laufzeit eingebunden werden (was in jeder Orchestrierungsumgebung eine Volume-Koordination erfordert). Azure Functions und AWS Lambda bieten keinen Mechanismus für die Bereitstellung von Lizenzdateien ohne Workarounds. CI/CD-Pipelines benötigen die Dateien unter dem exakten Pfad, der beim Startaufruf verwendet wird, andernfalls löst die Anwendung eine Fehlermeldung aus, bevor sie auch nur ein einziges Dokument verarbeiten kann.IronOCR ersetzt beide Dateien durch eine Zeichenkette, die in eine Umgebungsvariable, ein Kubernetes-Secret oder eine Azure Key Vault-Referenz passt.

Vier Pakete für eine Aufgabe. Ein funktionierendes LEADTOOLS-OCR-Projekt erfordert mindestens Leadtools, Leadtools.Ocr, Leadtools.Codecs und Leadtools.Forms.DocumentWriters. Passwortgeschützte PDF-Unterstützung erfordert ein zusätzliches Leadtools.Pdf-Modul, das möglicherweise nicht im gekauften Bundle enthalten ist. Alle Pakete müssen vorhanden, kompatibel und versioniert sein.IronOCR wird als ein einzelnes NuGet Paket ausgeliefert. Alle Funktionen – native PDF-Eingabe, durchsuchbare PDF-Ausgabe, Vorverarbeitung, Barcode-Lesung – sind enthalten.

Der Lebenszyklus des Motors schafft eine Wartungsoberfläche. LEADTOOLS umschließt den OCR-Motor in einer IDisposable-Dienstklasse, nicht aus idiomatischen .NET-Gründen, sondern weil der Shutdown()-Aufruf Dispose() vorangehen muss, sonst treten Anwendungsfehler auf. Produktionsimplementierungen von LEADTOOLS-Batchprozessoren beinhalten häufig GC.Collect() / GC.WaitForPendingFinalizers()-Aufrufe zwischen Dokumentenstücken, um RasterImage-Instanzen auszugleichen, die sich ansammeln.IronOCR verwendet Standard-using-Blöcke. OcrInput ist das einzige Objekt, das entsorgt werden muss.

Verwirrung um die Paketpreise tritt in der Produktion auf. LEADTOOLS veröffentlicht keine Preise. Das Document Imaging SDK – die Stufe, die OCR beinhaltet – kostet schätzungsweise 3.000 bis 8.000 US-Dollar pro Entwickler und Jahr. Teams, die das OCR-Modul ohne das PDF-Modul gekauft haben, entdecken die Lücke, wenn RasterSupport.IsLocked() bei passwortgeschützten Dokumenten in der Produktion wahr zurückgibt. Für die Genauigkeitsstufe der OmniPage-Engine ist zusätzlich zum Kauf von LEADTOOLS eine separate Kofax-Lizenzvereinbarung erforderlich.IronOCR lizenziert alle Funktionen in jeder Stufe. Es gibt keine sekundäre Lieferantenbeziehung, kein separates Modul für verschlüsselte Dokumente.

Initialisierungskosten beeinflussen Kaltstarts. engine.Startup() ist ein blockierender Aufruf, der Laufzeitdateien in den Speicher lädt. In serverlosen Umgebungen, in denen die Kaltstartlatenz eine Rolle spielt – Azure Functions, AWS Lambda – stellt eine blockierende Initialisierung von 500–2000 ms, bevor überhaupt eine Erkennungsarbeit stattfindet, ein strukturelles Problem dar.IronOCR verwendet verzögerte Initialisierung. Die IronTesseract-Instanz initialisiert sich beim ersten Gebrauch, und nachfolgende Aufrufe im selben Prozess verursachen keine Initialisierungskosten.

Das grundsätzliche Problem

LEADTOOLS benötigt vier Namensräume, vier NuGet Pakete und eine obligatorische Startzeremonie, bevor ein Zeichen erkannt wird:

// 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 vs. LEADTOOLS OCR: Funktionsvergleich

Die folgende Tabelle stellt die Funktionen der beiden Bibliotheken direkt gegenüber.

Feature LEADTOOLS OCR IronOCR
Erforderliche NuGet Pakete Mindestens 4 (mehr für PDF) 1 (IronOcr)
Lizenzmechanismus .LIC + .LIC.KEY Dateipaar String-Schlüssel
Lizenzbereitstellung Dateien auf jeder Produktionsmaschine Umgebungsvariable oder Konfiguration
Preismodell 3.000–15.000+ US-Dollar/Entwickler/Jahr (geschätzt) $999–2.999 $ einmalige unbefristete
Motorinitialisierung Manuelles Startup() mit Laufzeitpfad Automatisch, faul
Motor abgestellt Manuelles Shutdown() vor Dispose() Nicht erforderlich
Codec-Schicht RasterCodecs erforderlich für das Laden aller Bilder Nicht erforderlich
PDF-Eingabe Seitenweise Rasterisierungsschleife Natives LoadPdf()
Passwortgeschütztes PDF Erfordert separates Leadtools.Pdf-Modul Eingebauter Password-Parameter
Durchsuchbare PDF-Ausgabe DocumentWriter + PdfDocumentOptions + document.Save() result.SaveAsSearchablePdf()
Vorverarbeitung Separate Befehlsklassen (DeskewCommand, DespeckleCommand, usw.) Eingebaute Filtermethoden auf OcrInput
Mehrseitiges TIFF Manuelle Rahmeniteration mit CodecsLoadByteOrder input.LoadImageFrames()
Strukturierte Ausgabe Seiten- und Zonenebene Seite, Absatz, Zeile, Wort, Zeichen mit Koordinaten
Konfidenzbewertung OcrPageRecognizeStatus Enum result.Confidence als Prozentsatz
Barcode-Lesung Separates LEADTOOLS Barcode-Modul Eingebaut (ReadBarCodes = true)
Thread-Sicherheit Erfordert sorgfältiges Management Voll (ein IronTesseract pro Thread)
Unterstützte Sprachen 60–120 (motorabhängig) Mehr als 125 Sprachpakete via NuGet
Sprachbereitstellung tessdata files or engine-bundled files NuGet Paket pro Sprache
Plattformübergreifend Unterstützt durch plattformspezifische Laufzeitkonfiguration Windows, Linux, macOS, Docker, Azure, AWS
Docker-Bereitstellung .KEY-Dateien müssen gemountet oder gebacken werden Standard-dotnet publish
Kommerzielle Unterstützung Ja Ja

Schnellstart: Migration von LEADTOOLS OCRzu IronOCR

Schritt 1: Ersetzen des NuGet-Pakets

Entfernen Sie alle LEADTOOLS-Pakete:

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

Installieren Sie IronOCR über NuGet :

dotnet add package IronOcr

Schritt 2: Namespaces aktualisieren

Ersetzen Sie alle LEADTOOLS-using-Direktiven mit einem einzigen IronOCR-Import:

// 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

Schritt 3: Lizenz initialisieren

Entfernen Sie alle RasterSupport.SetLicense()-Aufrufe und die .LIC / .LIC.KEY-Dateireferenzen. Fügen Sie den IronOCR-Lizenzschlüssel beim Start der Anwendung hinzu:

// 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

Der Lizenzschlüssel kann in jedem Standard- .NET-Geheimnisverwaltungsmuster gespeichert werden — appsettings.json, Azure Key Vault, AWS Secrets Manager oder ein Kubernetes-Geheimnis. Es müssen keine Dateien zusammen mit der Anwendungsdatei bereitgestellt werden.

Beispiele für die Code-Migration

Entfernung des Motorstart- und -abschaltzyklus

LEADTOOLS schreibt ein striktes Service-Class-Muster vor, da der Lebenszyklus der Engine explizit verwaltet werden muss. Der Konstruktor startet den Motor, Dispose() schaltet ihn in der richtigen Reihenfolge aus, und jeder Codepfad, der Shutdown() vor Dispose() überspringt, erzeugt einen Laufzeitfehler.

LEADTOOLS OCR-Ansatz:

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 Ansatz:

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

Die IronTesseract-Instanz initialisiert sich beim ersten Gebrauch. Keine Konstruktorargumente, kein Laufzeitpfad, kein Startup()-Aufruf. Die oben genannte Dienstklasse muss IDisposable überhaupt nicht implementieren - der Motor ist zustandslos, und die OcrInput-Objekte, die in jedem Read()-Aufruf verwendet werden, verwalten ihre eigene Bereinigung über das Standard-using-Muster. Der IronTesseract-Einrichtungsleitfaden behandelt Konfigurationsoptionen, einschließlich Sprachauswahl und Leistungsoptimierung für Produktionsszenarien.

Stapelverarbeitung von TIFF-Bildern mit mehreren Einzelbildern

LEADTOOLS verarbeitet mehrseitige TIFF-Dateien, indem der Codec nach der gesamten Rahmenanzahl abgefragt wird und dann mit expliziten lastPage-Parametern bei jedem _codecs.Load()-Aufruf iteriert wird. Jedes Einzelbild muss manuell gelöscht werden, sonst füllt sich der Speicherplatz.

LEADTOOLS OCR-Ansatz:

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 Ansatz:

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() liest alle Rahmen aus dem TIFF in einem Aufruf. Keine Abfrage der Frame-Anzahl, keine Schleife, keine explizite Freigabe pro Frame. Die parallele Version erstellt eine IronTesseract-Instanz pro Thread, was das korrekte Muster ist — siehe das Multithreading-Beispiel für das vollständige Threading-Modell. Für TIFF-spezifische Eingabeoptionen bietet der TIFF- und GIF-Eingabeleitfaden Informationen zur Frame-Auswahl und zur Verarbeitung mehrerer Formate.

Vereinfachung der Dokumentenschreiber-Pipeline

Die Erstellung suchbarer PDF-Dateien mit LEADTOOLS erfordert die Konfiguration einer DocumentWriter-Instanz auf dem Motor, das Erstellen eines PdfDocumentOptions-Objekts mit Ausgabetyp und Überlagerungseinstellungen, das Anwenden der Optionen über SetOptions() und dann das Aufrufen von document.Save() mit dem Format-Enum. Jeder dieser Schritte stellt ein separates Objekt und einen separaten API-Aufruf dar.

LEADTOOLS OCR-Ansatz:

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 Ansatz:

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

SaveAsSearchablePdf() ersetzt die gesamte PdfDocumentOptions + SetOptions() + document.Save() Kette. Das Verhalten der Bild-über-Text-Ebene erfolgt automatisch. Eine vollständige Dokumentation zur Ausgabe von durchsuchbaren PDFs finden Sie im Leitfaden zur Erstellung durchsuchbarer PDFs. Dieser beschreibt die Ausgabeoptionen, und das Beispiel zur Erstellung durchsuchbarer PDFs zeigt die Integration mit ASP.NET Antwortströmen.

Migration zur Extraktion von Mehrzonenfeldern

Das zonenbasierte OCR von LEADTOOLS verwendet OcrZone-Objekte mit LeadRect-Grenzen, OcrZoneType und OcrZoneCharacterFilters-Eigenschaften. Mehrere Zonen werden auf einer einzigen Seite hinzugefügt und in einem page.Recognize()-Aufruf erkannt, dann durch Zonenindex extrahiert. Der Zonenindex entspricht der Einfügereihenfolge, was bedeutet, dass die Extraktionsschleife diese Reihenfolge beibehalten muss.

LEADTOOLS OCR-Ansatz:

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 Ansatz:

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

CropRectangle direkt an LoadImage() übergeben ersetzt das gesamte OcrZone-Setup. Es gibt keinen zu überwachenden Zonenindex, keinen page.Zones.Clear()-Aufruf erforderlich und keine Erkennungsstatusüberprüfung notwendig. Der regionsbasierte OCR-Leitfaden umfasst Extraktionsmuster für einzelne und mehrere Regionen. Eine vollständige Anleitung zur Extraktion von Rechnungsfeldern finden Sie im Tutorial zur Rechnungs-OCR .

Strukturierte Datenextraktion mit Wortkoordinaten

LEADTOOLS arbeitet mit strukturierter Ausgabe auf Seiten- und Zonenebene. Um Wort-Ebenen-Daten mit Begrenzungsrahmenkoordinaten zu erhalten, greift der Entwickler auf OcrWord-Objekte innerhalb einer erkannten Zone zu. Die API erfordert, dass nach der Erkennung die Zonensammlung durchgearbeitet und die Wortliste für jede Zone iteriert wird.

LEADTOOLS OCR-Ansatz:

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 Ansatz:

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

Die Ergebnishierarchie von IronOCR steigt von Pages über Paragraphs, Lines, Words und Characters ab. Jede Ebene gibt X, Y, Width, Height, Text und Confidence frei. Das zonenbasierte Zugriffsmuster von LEADTOOLS entfällt – es ist keine Zoneniteration mehr erforderlich, um auf die Wortdaten zuzugreifen. Die Anleitung zum Lesen von Ergebnissen beschreibt das vollständige Ausgabemodell mit Koordinatenzugriffsmustern. Die OcrResult-API-Referenz dokumentiert jede Eigenschaft der Ergebnishierarchie.

LEADTOOLS OCRAPI zu IronOCR Mapping-Referenz

LEADTOOLS OCR IronOCR
RasterSupport.SetLicense(licPath, keyContent) IronOcr.License.LicenseKey = "key"
new RasterCodecs() Nicht erforderlich
OcrEngineManager.CreateEngine(OcrEngineType.LEAD) new IronTesseract()
engine.Startup(codecs, null, null, runtimePath) Nicht erforderlich
engine.IsStarted Nicht erforderlich (immer bereit)
engine.Shutdown() Nicht erforderlich
engine.Dispose() Nicht erforderlich
_codecs.Load(imagePath) input.LoadImage(imagePath)
_codecs.Load(path, 0, BgrOrGray, page, page) input.LoadPdf(path) oder input.LoadImageFrames(path)
_codecs.GetInformation(path, true).TotalPages Nicht erforderlich – automatisch
engine.DocumentManager.CreateDocument() Nicht erforderlich
document.Pages.AddPage(image, null) input.LoadImage(imagePath)
page.Recognize(null) ocr.Read(input) (Erkennung ist Teil von Read())
page.GetText(-1) result.Text
page.RecognizeStatus result.Confidence (Prozentsatz)
OcrZone { Bounds = new LeadRect(x, y, w, h) } new CropRectangle(x, y, w, h)
page.Zones.Clear() Nicht erforderlich
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 } Automatisch von SaveAsSearchablePdf() behandelt
engine.DocumentWriterInstance.SetOptions(format, opts) Nicht erforderlich
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

Gängige Migrationsprobleme und Lösungen

Problem 1: Fehler bei der Auflösung des Lizenzdateipfads

LEADTOOLS: RasterSupport.SetLicense() löst die .LIC- und .LIC.KEY Dateipfade relativ zum Arbeitsverzeichnis auf, welches zwischen bin/Debug, bin/Release, Docker-Containern und IIS-Anwendungspools variiert. Ein häufiges Ausfallmuster ist ein Pfad, der in der Entwicklung funktioniert, aber "License file not found" in der Produktion wirft, weil sich das Arbeitsverzeichnis geändert hat.

Lösung: Löschen Sie beide Lizenzdateien und den SetLicense()-Aufruf vollständig. Ersetzen Sie dies durch eine einzelne Zeichenkettenzuweisung, die aus einer Umgebungsvariablen liest:

// 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

Der String-Schlüssel verhält sich in jeder Umgebung identisch. Speichern Sie es im selben Geheimnismanager, der bereits für Datenbankverbindungszeichenfolgen verwendet wird.

Problem 2: Ausnahme "Motor nicht gestartet"

LEADTOOLS: Der Aufruf einer Erkennungsmethode vor Abschluss von engine.Startup() oder nachdem engine.Shutdown() aufgerufen wurde (z.B. in einem Entsorgen-vor-Verwenden-Rennzustand während des Herunterfahrens) wirft einen InvalidOperationException mit "Motor nicht gestartet." auf. Langlebige Dienstklassen müssen sich mit IsStarted-Prüfungen davor schützen.

Lösung: IronTesseract erfordert keinen Startaufruf und hat keinen gestartet/gestoppt-Zustand. Der IsStarted-Schutz und die gesamte Lebenszyklus-Dienstklasse kann gelöscht werden:

// 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

Problem 3: Speicherakkumulation bei Rasterbildern in der Stapelverarbeitung

LEADTOOLS: Batch-Verarbeitung, die über PDF-Seiten oder Bilddateien iteriert, erstellt RasterImage-Instanzen in einer Schleife. Wenn ein Codepfad versäumt, eine RasterImage zu entsorgen — aufgrund einer ausgelösten Ausnahme vor dem Beenden des using-Blocks oder eines manuellen Entsorgungsmusters mit einem fehlenden Aufruf — sammeln sich die nicht freigegebenen Bilder im Speicher an. LEADTOOLS-Produktionscode enthält häufig GC.Collect() / GC.WaitForPendingFinalizers()-Aufrufe zwischen Batchen als Ausgleichsmechanismus.

Lösung: Entfernen Sie alle GC.Collect()-Aufrufe. OcrInput ist die einzige IDisposable in der IronOCR-Pipeline und ist für jede Batch-Operation mit einem Standard-using-Block abgegrenzt:

// 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

Weitere Hinweise zur Speicheroptimierung finden Sie im Blogbeitrag zur Reduzierung der Speicherbelegung .

Problem 4: DocumentWriterInstance-Optionen bleiben über Aufrufe hinweg erhalten

LEADTOOLS: engine.DocumentWriterInstance.SetOptions() ändert den Zustand der freigegebenen Motor-Schreiber-Instanz. Wenn ein Codepfad PdfDocumentOptions mit DocumentType = PdfDocumentType.PdfA setzt und ein nachfolgender Aufruf diese Optionen nicht vor dem Aufrufen von document.Save() zurücksetzt, bleibt das Ausgabeformat des vorherigen Aufrufs bestehen. Dies ist ein zustandsbehafteter Nebeneffekt der gemeinsam genutzten Engine-Instanz.

Lösung:IronOCR verfügt über keinen gemeinsamen Writer-Status. Jeder SaveAsSearchablePdf()-Aufruf ist unabhängig:

// 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

Jeder Aufruf erzeugt eine Standard-PDF-Ausgabe mit einer Bildüberlagerung und einer durchsuchbaren Textebene. Es gibt keinen gemeinsamen Optionsstatus, der zwischen Anrufen zurückgesetzt werden könnte.

Problem 5: Falsches Paket – Fehlendes Modul zur Laufzeit

LEADTOOLS: Teams, die das Document Imaging SDK gekauft haben, könnten feststellen, dass Leadtools.Pdf-Typen nicht verfügbar sind oder dass verschlüsselte PDF-Seiten RasterException mit RasterExceptionCode.FeatureNotSupported auslösen. Dies geschieht, wenn das gekaufte Paket das PDF-Modul nicht enthält, und der Fehler tritt erst zur Laufzeit in der Produktionsumgebung auf.

Lösung:IronOCR beinhaltet alle Funktionen in jeder Lizenzstufe. Es gibt kein separates PDF-Modul, kein Add-on für verschlüsselte Dokumente und keinen sekundären Anbieter für einen genaueren Motor. Nach der Installation des einzelnen IronOcr-Pakets steht der vollständige Funktionsumfang ohne zusätzlichen Kauf zur Verfügung:

dotnet add package IronOcr

Problem 6: Zonenindex-Fehler nach Neuanordnung

LEADTOOLS: Zonenextraktion verwendet Positionsindizierung — page.Zones[0].Text, page.Zones[1].Text — die die Extraktionslogik an die Einfügereihenfolge in page.Zones.Add() bindet. Das Neuanordnen von Zonendefinitionen, um sie an ein geändertes Formularlayout anzupassen, führt stillschweigend zu einem Fehler bei der Datenextraktion, da alle nachfolgenden Indizes verschoben werden.

Lösung:IronOCR verwendet benannte Variablen mit CropRectangle pro Feld. Die Neuanordnung von Felddefinitionen hat keinen Einfluss auf die Extraktion, da jedes Feld unabhängig definiert ist:

// 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-Migrations-Checkliste

Vor der Migration

Prüfen Sie den Quellcode, um alle LEADTOOLS-Nutzungen zu identifizieren, bevor Sie Änderungen vornehmen:

# 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

Notieren Sie, welches LEADTOOLS-Paket erworben wurde – OCR-Modul, PDF-Modul und Engine-Typ (LEAD vs. Tesseract vs. OmniPage) –, um sicherzustellen, dass bei der Validierung nach der Migration gleichwertige IronOCR Funktionen getestet werden.

Code-Migration

  1. Entfernen Sie alle LEADTOOLS-NuGet-Pakete: Leadtools, Leadtools.Ocr, Leadtools.Codecs, Leadtools.Forms.DocumentWriters, Leadtools.Pdf
  2. Installieren Sie das IronOcr NuGet-Paket
  3. Ersetzen Sie alle LEADTOOLS-using-Direktiven durch using IronOcr;
  4. Entfernen Sie die .LIC- und .LIC.KEY-Dateien aus dem Projekt und den Bereitstellungsartefakten
  5. Ersetzen Sie RasterSupport.SetLicense(licPath, keyContent) durch IronOcr.License.LicenseKey = "key" beim Anwendungsstart
  6. Löschen Sie alle IDisposable-Dienstklassen, die nur zur Verwaltung des IOcrEngine und RasterCodecs Lebenszyklus existieren
  7. Ersetzen Sie OcrEngineManager.CreateEngine() + engine.Startup() durch new IronTesseract()
  8. Ersetzen Sie _codecs.Load(imagePath) durch input.LoadImage(imagePath) innerhalb eines using var input = new OcrInput()-Blocks
  9. Ersetzen Sie mehrseitige TIFF-Seitenschleifen durch input.LoadImageFrames(tiffPath)
  10. Ersetzen Sie die PDF-Seiteniterationsschleife durch input.LoadPdf(pdfPath)
  11. Ersetzen Sie document.Pages.AddPage() + page.Recognize(null) + page.GetText(-1) durch ocr.Read(input).Text
  12. Ersetzen Sie OcrZone + page.Zones.Add()-Muster durch input.LoadImage(path, new CropRectangle(x, y, w, h))
  13. Ersetzen Sie PdfDocumentOptions + DocumentWriterInstance.SetOptions() + document.Save() durch result.SaveAsSearchablePdf(path)
  14. Ersetzen Sie Befehlsklassen für die Vorverarbeitung (DeskewCommand, DespeckleCommand, AutoBinarizeCommand) durch input.Deskew(), input.DeNoise(), input.Binarize() auf der OcrInput-Instanz
  15. Entfernen Sie alle GC.Collect() / GC.WaitForPendingFinalizers()-Aufrufe, die hinzugefügt wurden, um das Speichermanagement von LEADTOOLS auszugleichen

Nach der Migration

  • Überprüfen Sie anhand einer repräsentativen Auswahl von Bildern und PDFs, ob die erkannte Textausgabe mit der LEADTOOLS-Ausgabe übereinstimmt.
  • Bestätigen Sie, dass die Konfidenzwerte mit result.Confidence innerhalb der erwarteten Bereiche liegen
  • Der Test der Mehrbild-TIFF-Verarbeitung erzeugt die gleiche Seitenzahl wie die LEADTOOLS-Frame-Iterationsschleife.
  • Sicherstellen, dass die durchsuchbare PDF-Ausgabe in einem PDF-Reader (Adobe Acrobat oder vergleichbar) textdurchsuchbar ist
  • Testen Sie die zonenbasierte Feldextraktion anhand bekannter, korrekter Feldwerte aus Produktionsrechnungen oder Formularen.
  • Überprüfen Sie, dass die Entschlüsselung passwortgeschützter PDFs ohne das separate Leadtools.Pdf-Modul funktioniert
  • Führen Sie Batch-Verarbeitung unter Last aus und bestätigen Sie kein Speicherwachstum (alle GC.Collect() zuerst entfernen)
  • Docker- und CI/CD-Bereitstellung ohne Lizenzdateien testen – bestätigen, dass der Lizenzschlüssel als Zeichenkette korrekt aus der Umgebungsvariablen aufgelöst wird
  • Testen Sie parallele Verarbeitung mit Parallel.ForEach, um die Thread-Sicherheit zu überprüfen
  • Bestätigen Sie, dass strukturiertes Datenextraktion (result.Pages, page.Paragraphs, page.Words) die richtigen Koordinaten zurückgibt

Wichtigste Vorteile der Migration zu IronOCR

Bereitstellung wird zustandslos. Die LEADTOOLS .LIC- und .LIC.KEY-Dateien sind Artefakte, die jede Bereitstellungsumgebung mitführen muss. Container, die diese einbetten, legen Lizenzdaten im Image-Verlauf offen. Die Behälter, in denen sie montiert werden, erfordern eine Volumenkoordination. Nach der Migration zu IronOCR ist die Lizenz ein String in einer Umgebungsvariablen. Das Bereitstellungsartefakt ist eine Standard- NuGet Referenz. Keine Dateien, keine Pfade, keine Einbindungsstrategie. Die Docker-Bereitstellungsanleitung und die Azure-Bereitstellungsanleitung zeigen die vollständige Einrichtung für containerisierte Umgebungen.

Vier Pakete werden zu einem. Die Migration reduziert Leadtools, Leadtools.Ocr, Leadtools.Codecs, Leadtools.Forms.DocumentWriters und optional Leadtools.Pdf auf einen einzigen IronOcr-Verweis. Alle Funktionen – Vorverarbeitung, native PDF-Eingabe, durchsuchbare PDF-Ausgabe, Barcode-Lesung, Extraktion strukturierter Daten, Unterstützung von über 125 Sprachen – sind in diesem einen Paket enthalten. Der Funktionsumfang hängt nicht davon ab, welches Paket erworben wurde.

Batch-Verarbeitung eliminiert manuelles Speichermanagement. LEADTOOLS-Batch-Code führt defensive GC.Collect()-Aufrufe und explizite RasterImage-Entsorgung durch, um Speicheransammlungen bei Mehrfachdokumentläufen zu verhindern. IronOCR's OcrInput, abgegrenzt mit einem using-Block, verwaltet die Bereinigung automatisch. Threadsichere parallele Verarbeitung mit Parallel.ForEach — eine IronTesseract-Instanz pro Thread — liefert Multi-Core-Durchsatz ohne Synchronisationscode. Im Leitfaden zur Geschwindigkeitsoptimierung finden Sie Informationen zur Optimierung des Produktionsdurchsatzes.

Vorhersehbare Gesamtkosten. Die geschätzten Kosten für LEADTOOLS für ein fünfköpfiges Entwicklerteam belaufen sich im ersten Jahr auf 15.000 bis 40.000 US-Dollar, wobei die jährliche Wartung etwa 20 bis 25 % der Lizenzkosten beträgt, um Updates zu erhalten.IronOCR Professional kostet 2.999 US-Dollar und umfasst zehn Entwickler und zehn Bereitstellungsstandorte als einmalige, dauerhafte Lizenz. Updates für ein Jahr sind inklusive. Die weitere Nutzung nach Ablauf des Aktualisierungszeitraums erfordert keine zusätzlichen Zahlungen. Auf der IronOCR -Lizenzseite werden alle Stufenpreise direkt veröffentlicht, ohne vorherige Verkaufsberatung.

Strukturierte Daten ohne Zonen-Setup. Nach der Migration sind Wort-, Zeilen- und Absatzebenen-Daten mit Begrenzungsrahmenkoordinaten direkt auf OcrResult verfügbar — keine Zonendefinitionen erforderlich. Die fünfstufige Hierarchie (Pages, Paragraphs, Lines, Words, Characters) gibt jeweils Positionskoordinaten und elementweise Konfidenzwerte frei. Anwendungen, die eine LEADTOOLS-Zonenkonfiguration für die strukturierte Datenextraktion benötigten, erhalten umfangreichere Daten über eine einfachere API. Die Ergebnisseite der OCR-Texturierung fasst das vollständige Ausgabemodell zusammen.

Hinweis:Adobe Acrobat, Kofax OmniPage, LEADTOOLS, und Tesseract sind eingetragene Warenzeichen ihrer jeweiligen Inhaber. Diese Website steht in keiner Verbindung mit, wird weder unterstützt noch gesponsert von Adobe Inc., Apryse, Google, Kofax oder LEAD Technologies. Alle Produktnamen, Logos und Marken sind Eigentum ihrer jeweiligen Eigentümer. Vergleiche dienen nur zu Informationszwecken und spiegeln öffentlich zugängliche Informationen zum Zeitpunkt des Schreibens wider.

Häufig gestellte Fragen

Warum sollte ich von LEADTOOLS OCR auf IronOCR umsteigen?

Zu den häufigsten Gründen gehören die Beseitigung der Komplexität der COM-Interoperabilität, die Ablösung der dateibasierten Lizenzverwaltung, die Vermeidung der seitenweisen Abrechnung, die Ermöglichung der Docker-/Container-Bereitstellung und die Einführung eines NuGet-nativen Workflows, der sich in die Standard-.NET-Werkzeuge integriert.

Was sind die wichtigsten Codeänderungen bei der Migration von LEADTOOLS OCR zu IronOCR?

Ersetzen Sie die LEADTOOLS OCR-Initialisierungssequenzen durch IronTesseract-Instanziierung, entfernen Sie das COM-Lebenszyklusmanagement (explizite Create/Load/Close-Muster) und aktualisieren Sie die Namen der Ergebniseigenschaften. Das Ergebnis sind deutlich weniger Boilerplate-Zeilen.

Wie installiere ich IronOCR, um die Migration zu beginnen?

Führen Sie 'Install-Package IronOcr' in der Paketmanager-Konsole oder 'dotnet add package IronOcr' in der CLI aus. Sprachpakete sind separate Pakete: 'dotnet add package IronOcr.Languages.French' für Französisch, zum Beispiel.

Kann IronOCR die OCR-Genauigkeit von LEADTOOLS OCR für Standardgeschäftsdokumente erreichen?

IronOCR erzielt eine hohe Genauigkeit bei Standardgeschäftsinhalten wie Rechnungen, Verträgen, Quittungen und getippten Formularen. Bildvorverarbeitungsfilter (Schräglagenkorrektur, Rauschunterdrückung, Kontrastverbesserung) verbessern die Erkennungsgenauigkeit bei verschlechterten Eingaben weiter.

Wie geht IronOCR mit den Sprachdaten um, die von LEADTOOLS OCR separat installiert werden?

Die Sprachdaten in IronOCR werden als NuGet-Pakete verteilt. mit 'dotnet add package IronOcr.Languages.German' wird die deutsche Unterstützung installiert. Es sind keine manuellen Dateiplatzierungen oder Verzeichnispfade erforderlich.

Erfordert die Migration von LEADTOOLS OCR zu IronOCR Änderungen an der Bereitstellungsinfrastruktur?

IronOCR erfordert weniger Änderungen an der Infrastruktur als LEADTOOLS OCR. Es gibt keine SDK-Binärpfade, keine Platzierung von Lizenzdateien oder Lizenzserverkonfigurationen. Das NuGet-Paket enthält die komplette OCR-Engine, und der Lizenzschlüssel ist eine im Anwendungscode festgelegte Zeichenfolge.

Wie konfiguriere ich die IronOCR-Lizenzierung nach der Migration?

Weisen Sie IronOcr.License.LicenseKey = "YOUR-KEY" im Startup-Code der Anwendung zu. In Docker oder Kubernetes speichern Sie den Schlüssel als Umgebungsvariable und lesen ihn beim Starten aus. Verwenden Sie License.IsValidLicense zur Validierung, bevor Sie den Datenverkehr akzeptieren.

Kann IronOCR PDFs auf die gleiche Weise verarbeiten wie LEADTOOLS OCR?

Ja, IronOCR liest sowohl native als auch gescannte PDFs. Instanziieren Sie IronTesseract, rufen Sie ocr.Read(input) auf, wobei input ein PDF-Pfad oder OcrPdfInput ist, und iterieren Sie die OcrResult-Seiten. Es ist keine separate PDF-Rendering-Pipeline erforderlich.

Wie handhabt IronOCR das Threading bei der Verarbeitung großer Datenmengen?

IronTesseract kann sicher pro Thread instanziiert werden. Sie können eine Instanz pro Thread in einem Parallel.ForEach- oder Task-Pool aufsetzen, OCR gleichzeitig ausführen und jede Instanz nach Abschluss entsorgen. Es ist kein globaler Zustand oder Sperren erforderlich.

Welche Ausgabeformate unterstützt IronOCR nach der Textextraktion?

IronOCR liefert strukturierte Ergebnisse mit Text, Wortkoordinaten, Konfidenzwerten und Seitenstruktur. Zu den Exportoptionen gehören reiner Text, durchsuchbare PDF-Dateien und strukturierte Ergebnisobjekte für die nachgeschaltete Verarbeitung.

Ist die Preisgestaltung von IronOCR bei der Skalierung von Arbeitslasten berechenbarer als die von LEADTOOLS OCR?

IronOCR verwendet eine pauschale, unbefristete Lizenzierung ohne Seiten- oder Volumengebühren. Egal, ob Sie 10.000 oder 10 Millionen Seiten verarbeiten, die Lizenzkosten bleiben konstant. Optionen für Volumen- und Teamlizenzen finden Sie auf der IronOCR-Preisseite.

Was passiert mit meinen bestehenden Tests nach der Migration von LEADTOOLS OCR zu IronOCR?

Tests, die extrahierte Textinhalte überprüfen, sollten auch nach der Migration bestehen. Tests, die API-Aufrufmuster oder den Lebenszyklus von COM-Objekten validieren, müssen aktualisiert werden, um das einfachere Initialisierungs- und Ergebnismodell von IronOCR widerzuspiegeln.

Kannaopat Udonpant
Software Ingenieur
Bevor er Software-Ingenieur wurde, absolvierte Kannapat ein PhD in Umweltressourcen an der Hokkaido University in Japan. Während seines Studiums wurde Kannapat auch Mitglied des Vehicle Robotics Laboratory, das Teil der Fakultät für Bioproduktionstechnik ist. Im Jahr 2022 nutzte er seine C#-Kenntnisse, um dem Engineering-Team von Iron Software ...
Weiterlesen

Iron-Support-Team

Wir sind 24 Stunden am Tag, 5 Tage die Woche online.
Chat
E-Mail
Rufen Sie mich an