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
// IronOCR: One namespace, one package, one line
using IronOcr;
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var text = 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
Installieren Sie IronOCR über NuGet :
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;
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");
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();
}
}
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
}
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;
}
}
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);
}
}
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);
}
}
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();
}
}
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; }
}
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);
}
}
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; }
}
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}");
}
}
}
}
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");
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);
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
}
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);
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:
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);
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" .
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.
