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
// 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
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
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
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")
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
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
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
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
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
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
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
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
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
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
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"))
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);
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
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)
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)
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" .
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
- Entfernen Sie alle LEADTOOLS-NuGet-Pakete:
Leadtools,Leadtools.Ocr,Leadtools.Codecs,Leadtools.Forms.DocumentWriters,Leadtools.Pdf - Installieren Sie das
IronOcrNuGet-Paket - Ersetzen Sie alle LEADTOOLS-
using-Direktiven durchusing IronOcr; - Entfernen Sie die
.LIC- und.LIC.KEY-Dateien aus dem Projekt und den Bereitstellungsartefakten - Ersetzen Sie
RasterSupport.SetLicense(licPath, keyContent)durchIronOcr.License.LicenseKey = "key"beim Anwendungsstart - Löschen Sie alle
IDisposable-Dienstklassen, die nur zur Verwaltung desIOcrEngineundRasterCodecsLebenszyklus existieren - Ersetzen Sie
OcrEngineManager.CreateEngine()+engine.Startup()durchnew IronTesseract() - Ersetzen Sie
_codecs.Load(imagePath)durchinput.LoadImage(imagePath)innerhalb einesusing var input = new OcrInput()-Blocks - Ersetzen Sie mehrseitige TIFF-Seitenschleifen durch
input.LoadImageFrames(tiffPath) - Ersetzen Sie die PDF-Seiteniterationsschleife durch
input.LoadPdf(pdfPath) - Ersetzen Sie
document.Pages.AddPage()+page.Recognize(null)+page.GetText(-1)durchocr.Read(input).Text - Ersetzen Sie
OcrZone+page.Zones.Add()-Muster durchinput.LoadImage(path, new CropRectangle(x, y, w, h)) - Ersetzen Sie
PdfDocumentOptions+DocumentWriterInstance.SetOptions()+document.Save()durchresult.SaveAsSearchablePdf(path) - Ersetzen Sie Befehlsklassen für die Vorverarbeitung (
DeskewCommand,DespeckleCommand,AutoBinarizeCommand) durchinput.Deskew(),input.DeNoise(),input.Binarize()auf derOcrInput-Instanz - 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.Confidenceinnerhalb 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.
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.

