Jak przeprowadzić OCR w NET Maui
Niniejszy przewodnik przeprowadza programistów .NET przez proces pełnej migracji zLEADTOOLS OCRdo IronOCR. Obejmuje on każdy etap, od wymiany pakietu NuGet po pełną migrację kodu, wraz z przykładami "przed i po" dla wzorców najbardziej dotkniętych procedurą inicjalizacji LEADTOOLS, strukturą wielu przestrzeni nazw oraz modelem wdrażania licencji opartym na plikach.
Dlaczego warto przejść z LEADTOOLS OCR
LEADTOOLS OCR jest dostarczany w ramach korporacyjnej platformy obrazówania, której korzenie sięgają 1990 roku, a API odzwierciedla tę tradycję. Minimalna konfiguracja robocza wymaga czterech pakietów NuGet, czterech przestrzeni nazw, dwóch fizycznych plików licencji wdrożonych w znanej ścieżce, warstwy kodeków zainicjowanej przed silnikiem, wyboru typu silnika spośród trzech opcji oraz blokującego wywołania startowego, które ładuje pliki binarne środowiska uruchomieniowego do pamięci. Wszystko to dzieje się, zanim rozpoznany zostanie choćby jeden znak. Zespoły przechodzące na IronOCR eliminują całą tę warstwę — jeden pakiet, jedna przestrzeń nazw, jedna linijka kodu do ustawienia klucza licencyjnego.
Oparty na plikach rozlokowanie licencji ulega przerwaniu w kontenerach. LEADTOOLS wymaga dwóch fizycznych plików — LEADTOOLS.LIC i LEADTOOLS.LIC.KEY — dostępnych do odczytu w określonej ścieżce na każdej maszynie uruchamiającej aplikację. W kontenerach Docker pliki te muszą być albo wbudowane w obraz (co powoduje ich ujawnienie w historii warstw), albo zamontowane w czasie wykonywania (co wymaga koordynacji woluminów w każdym środowisku orkiestracji). Azure Functions i AWS Lambda nie posiadają mechanizmu wdrażania plików licencyjnych bez stosowania rozwiązań alternatywnych. Pipeline'y CI/CD wymagają, aby pliki znajdowały się dokładnie w ścieżce używanej przez wywołanie startowe, w przeciwnym razie aplikacja zgłosi błąd przed przetworzeniem jakiegokolwiek dokumentu.IronOCR zastępuje oba pliki ciągiem znaków, który mieści się w zmiennej środowiskowej, sekrecie Kubernetes lub odwołaniu do Azure Key Vault.
Cztery pakiety dla jednego zadania. Działający projekt OCR LEADTOOLS wymaga co najmniej Leadtools, Leadtools.Ocr, Leadtools.Codecs i Leadtools.Forms.DocumentWriters. Obsługa PDF zabezpieczonych hasłem wymaga dodatkowego modułu Leadtools.Pdf, który może nie być zawarty w zakupionej paczce. Każdy pakiet musi być obecny, kompatybilny i posiadać wspólną wersję.IronOCR jest dostarczany jako jeden pakiet NuGet. Uwzględniono wszystkie funkcje — natywne wprowadzanie plików PDF, generowanie plików PDF z możliwością wyszukiwania, przetwarzanie wstępne, odczyt kodów BarCode.
Cykl życia silnika tworzy powierzchnię konserwacyjną. LEADTOOLS otacza silnik OCR w klasie serwisowej IDisposable, nie dla idiomatycznych powodów .NET, lecz ponieważ wywołanie Shutdown() musi poprzedzać Dispose(), inaczej aplikacja wywołuje błędy. Produkcyjna implementacja przetwarzaczy wsadowych LEADTOOLS zazwyczaj obejmuje wywołania GC.Collect() / GC.WaitForPendingFinalizers() pomiędzy częściami dokumentów, aby zrekompensować gromadzenie się instancji RasterImage.IronOCR używa standardowych bloków using. OcrInput to jedyny obiekt wymagający zlikwidowania.
W środowisku produkcyjnym pojawiają się problemy z pakietami. Firma LEADTOOLS nie publikuje cen. Pakiet Document Imaging SDK — poziom obejmujący OCR — kosztuje szacunkowo 3000–8000 USD rocznie na programistę. Zespoły, które zakupiły moduł OCR bez modułu PDF, odkrywają lukę, gdy RasterSupport.IsLocked() zwraca true w odniesieniu do dokumentów zabezpieczonych hasłem w produkcji. Poziom dokładności silnika OmniPage wymaga oddzielnej umowy licencyjnej Kofax oprócz zakupu LEADTOOLS.IronOCR udziela licencji na wszystkie funkcje na każdym poziomie. Nie ma żadnych powiązań z dostawcami zewnętrznymi, nie ma też oddzielnego modułu do obsługi dokumentów zaszyfrowanych.
Koszt inicjalizacji wpływa na zimne starty. engine.Startup() to blokujące wywołanie, które ładuje pliki wykonawcze do pamięci. W środowiskach bezserwerowych, w których liczy się opóźnienie przy zimnym starcie — Azure Functions, AWS Lambda — blokująca inicjalizacja trwająca 500–2000 ms przed rozpoczęciem jakichkolwiek działań rozpoznawczych stanowi problem strukturalny.IronOCR wykorzystuje leniwą inicjalizację. Instancja IronTesseract inicjalizuje się przy pierwszym użyciu, a kolejne wywołania w tym samym procesie nie ponoszą żadnego kosztu inicjalizacji.
Podstawowy problem
LEADTOOLS wymaga czterech przestrzeni nazw, czterech pakietów NuGet oraz obowiązkowej procedury uruchamiania przed rozpoznaniem znaku:
// 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 a LEADTOOLS OCR: porównanie funkcji
Poniższa tabela przedstawia bezpośrednie powiązania między funkcjami obu bibliotek.
| Funkcja | LEADTOOLS OCR | IronOCR |
|---|---|---|
| Wymagane pakiety NuGet | Minimum 4 strony (więcej w przypadku pliku PDF) | 1 (IronOcr) |
| Mechanizm licencji | .LIC + .LIC.KEY para plików | Klucz ciągu znaków |
| Wdrażanie licencji | Pliki na każdym komputerze produkcyjnym | Zmienna środowiskowa lub konfiguracja |
| Model cenowy | 3 000–15 000 USD+/programista/rok (szacunkowo) | $999–2 999 USD jednorazowy |
| Inicjalizacja silnika | Ręczne Startup() z ścieżką wykonawczą | Automatyczne, leniwe |
| Wyłączenie silnika | Ręczne Shutdown() przed Dispose() | Nie jest wymagane |
| Warstwa kodeków | RasterCodecs wymagane dla wszystkich ładowań obrazów | Nie jest wymagane |
| Plik wejściowy PDF | Pętla rasteryzacji strona po stronie | Wbudowany parametr Password |
| Plik PDF chroniony hasłem | Wymaga oddzielnego modułu Leadtools.Pdf | Wbudowany parametr Password |
| Wynik w formacie PDF z możliwością wyszukiwania | DocumentWriter + PdfDocumentOptions + document.Save() | result.SaveAsSearchablePdf() |
| Przetwarzanie wstępne | Oddzielne klasy komend (DeskewCommand, DespeckleCommand, itd.) | Wbudowane metody filtracji na OcrInput |
| Wielostronicowy plik TIFF | Ręczna iteracja klatek przy użyciu CodecsLoadByteOrder | input.LoadImageFrames() |
| Strukturalny wynik | Poziom strony i strefy | Strona, akapit, wiersz, słowo, znak z współrzędnymi |
| Ocena pewności | OcrPageRecognizeStatus enum | result.Confidence jako procent |
| Odczytywanie BarCode | Oddzielny moduł BarCode LEADTOOLS | Wbudowany (ReadBarCodes = true) |
| Bezpieczeństwo wątków | Wymaga starannego zarządzania | Pełny (jeden IronTesseract na wątek) |
| Obsługiwane języki | 60–120 (w zależności od silnika) | Ponad 125 pakietów językowych dostępnych za pośrednictwem NuGet |
| Wdrożenie językowe | tessdata files or engine-bundled files | Pakiet NuGet dla każdego języka |
| Wielopłatformowe | Obsługa konfiguracji środowiska uruchomieniowego dla poszczególnych platform | Windows, Linux, macOS, Docker, Azure, AWS |
| Wdrożenie Docker | Pliki .KEY muszą być zamontowane lub dodane do obrazu | Standard dotnet publish |
| Wsparcie komercyjne | Tak | Tak |
Szybki start: Migracja zLEADTOOLS OCRdo IronOCR
Krok 1: Zastąp pakiet NuGet
Usuń wszystkie pakiety LEADTOOLS:
dotnet remove package Leadtools
dotnet remove package Leadtools.Ocr
dotnet remove package Leadtools.Codecs
dotnet remove package Leadtools.Forms.DocumentWriters
dotnet remove package Leadtools.Pdf
Zainstaluj IronOCR z NuGet:
Krok 2: Aktualizacja przestrzeni nazw
Zastąp wszystkie dyrektywy LEADTOOLS using jednym importem IronOCR:
// Before (LEADTOOLS)
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;
using Leadtools.Forms.DocumentWriters;
// After (IronOCR)
using IronOcr;
Krok 3: Inicjalizacja licencji
Usuń wszystkie wywołania RasterSupport.SetLicense() oraz referencje do plików .LIC / .LIC.KEY. Dodaj klucz licencyjny IronOCR podczas uruchamiania aplikacji:
// 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");
Klucz licencyjny może być przechowywany w dowolnym standardowym wzorcu zarządzania sekretami .NET — appsettings.json, Azure Key Vault, AWS Secrets Manager lub sekret Kubernetes. Nie ma potrzeby wdrażania żadnych plików wraz z plikiem binarnym aplikacji.
Przykłady migracji kodu
Uruchamianie i wyłączanie silnika Usunięcie cyklu życia
LEADTOOLS wymaga ścisłego przestrzegania wzorca klasy usługowej, ponieważ cykl życia silnika musi być zarządzany w sposób jawny. Konstruktor uruchamia silnik, Dispose() wyłącza go w odpowiedniej kolejności, a każdy kod, który pomija Shutdown() przed Dispose(), wywołuje błąd wykonania.
Podejście LEADTOOLS OCR:
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;
// Service class required purely to manage engine lifecycle
public class LeadtoolsOcrService : IDisposable
{
private IOcrEngine _engine;
private RasterCodecs _codecs;
private readonly string _runtimePath;
public LeadtoolsOcrService(string licPath, string keyPath, string runtimePath)
{
_runtimePath = runtimePath;
// License setup — two files, both must be present
RasterSupport.SetLicense(licPath, File.ReadAllText(keyPath));
// Codec layer — required before engine creation
_codecs = new RasterCodecs();
// Engine factory — engine type determines capability and cost
_engine = OcrEngineManager.CreateEngine(OcrEngineType.LEAD);
// Blocking startup — loads runtime into memory (500–2000ms)
_engine.Startup(_codecs, null, null, _runtimePath);
}
public bool IsReady => _engine?.IsStarted ?? false;
public string Process(string imagePath)
{
if (!IsReady)
throw new InvalidOperationException("Engine not started");
using var image = _codecs.Load(imagePath);
using var doc = _engine.DocumentManager.CreateDocument();
var page = doc.Pages.AddPage(image, null);
page.Recognize(null);
return page.GetText(-1);
}
public void Dispose()
{
// Order is mandatory: Shutdown before Dispose
if (_engine?.IsStarted == true)
_engine.Shutdown();
_engine?.Dispose();
_codecs?.Dispose();
}
}
Podejście IronOCR:
using IronOcr;
// No lifecycle management needed — the service class becomes trivial
public class OcrService
{
private readonly IronTesseract _ocr = new IronTesseract();
// Always ready — no IsStarted check needed
public string Process(string imagePath) => _ocr.Read(imagePath).Text;
// No Dispose() needed for the engine
// No Startup(), no Shutdown(), no codec layer
}
Instancja IronTesseract inicjalizuje się przy pierwszym użyciu. Brak argumentów konstruktora, brak ścieżki wykonawczej, brak wywołania Startup(). Powyższa klasa serwisowa w ogóle nie musi implementować IDisposable — silnik jest bezstanowy, a obiekty OcrInput używane wewnątrz każdego wywołania Read() zajmują się własnym czyszczeniem przez standardowy wzorzec using. Podręcznik konfiguracji IronTesseract obejmuje opcje konfiguracyjne, w tym wybór języka i optymalizację wydajności dla scenariuszy produkcyjnych.
Wieloklatkowe przetwarzanie partii plików TIFF
LEADTOOLS przetwarza pliki TIFF z wieloma ramkami, pobierając wszystkie ramki z kodera, a następnie iterując z explicytnymi parametrami lastPage przy każdym wywołaniu _codecs.Load(). Każdy obraz ramki musi zostać usunięty ręcznie, w przeciwnym razie pamięć będzie się zapełniać.
Podejście LEADTOOLS OCR:
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;
public class LeadtoolsTiffBatchService
{
private readonly IOcrEngine _engine;
private readonly RasterCodecs _codecs;
public List<string> ProcessMultiFrameTiff(string tiffPath)
{
var pageTexts = new List<string>();
// Must query page count before iterating
var info = _codecs.GetInformation(tiffPath, true);
int frameCount = info.TotalPages;
using var document = _engine.DocumentManager.CreateDocument();
for (int frameNum = 1; frameNum <= frameCount; frameNum++)
{
// Load one frame at a time — must specify firstPage/lastPage
using var frameImage = _codecs.Load(
tiffPath,
0, // bitsPerPixel
CodecsLoadByteOrder.BgrOrGray,
frameNum, // firstPage
frameNum); // lastPage
var page = document.Pages.AddPage(frameImage, null);
page.Recognize(null);
pageTexts.Add(page.GetText(-1));
// GC pressure accumulates if disposal is missed on any frame
}
return pageTexts;
}
public Dictionary<string, List<string>> ProcessTiffDirectory(string directoryPath)
{
var results = new Dictionary<string, List<string>>();
foreach (var tiffFile in Directory.GetFiles(directoryPath, "*.tiff"))
{
results[tiffFile] = ProcessMultiFrameTiff(tiffFile);
// Manual GC between files to prevent memory growth
GC.Collect();
GC.WaitForPendingFinalizers();
}
return results;
}
}
Podejście IronOCR:
using IronOcr;
public class TiffBatchService
{
private readonly IronTesseract _ocr = new IronTesseract();
public List<string> ProcessMultiFrameTiff(string tiffPath)
{
using var input = new OcrInput();
input.LoadImageFrames(tiffPath); // All frames loaded automatically
var result = _ocr.Read(input);
// Per-frame text available through result.Pages
return result.Pages.Select(p => p.Text).ToList();
}
public Dictionary<string, List<string>> ProcessTiffDirectory(string directoryPath)
{
var results = new Dictionary<string, List<string>>();
foreach (var tiffFile in Directory.GetFiles(directoryPath, "*.tiff"))
{
results[tiffFile] = ProcessMultiFrameTiff(tiffFile);
}
return results;
}
// Parallel processing across files — thread-safe out of the box
public Dictionary<string, List<string>> ProcessTiffDirectoryParallel(string directoryPath)
{
var concurrentResults = new System.Collections.Concurrent.ConcurrentDictionary<string, List<string>>();
var tiffFiles = Directory.GetFiles(directoryPath, "*.tiff");
Parallel.ForEach(tiffFiles, tiffFile =>
{
using var input = new OcrInput();
input.LoadImageFrames(tiffFile);
var result = new IronTesseract().Read(input);
concurrentResults[tiffFile] = result.Pages.Select(p => p.Text).ToList();
});
return new Dictionary<string, List<string>>(concurrentResults);
}
}
LoadImageFrames() odczytuje wszystkie ramki z TIFFu w jednym wywołaniu. Bez zapytań o liczbę klatek, bez pętli, bez jawnego usuwania po każdej klatce. Wersja równoległa tworzy jedną instancję IronTesseract na wątek, co jest prawidłowym wzorcem — patrz przykład wielowątkowości dla pełnego modelu wątków. W przypadku opcji wejściowych specyficznych dla formatu TIFF, przewodnik dotyczący formatów TIFF i GIF obejmuje wybór klatek oraz obsługę wielu formatów.
Uproszczenie procesu tworzenia dokumentacji
Tworzenie PDFów z możliwością wyszukiwania w LEADTOOLS wymaga skonfigurowania instancji DocumentWriter na silniku, skonstruowania obiektu PdfDocumentOptions z typem wyjściowym i ustawieniami nakładki, zastosowania opcji przez SetOptions(), a następnie wywołania document.Save() z formatem enum. Każdy z tych kroków jest oddzielnym obiektem i oddzielnym wywołaniem API.
Podejście LEADTOOLS OCR:
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;
using Leadtools.Forms.DocumentWriters;
public class LeadtoolsDocumentWriterService
{
private readonly IOcrEngine _engine;
private readonly RasterCodecs _codecs;
public void CreateSearchablePdfFromImages(string[] imagePaths, string outputPath)
{
using var document = _engine.DocumentManager.CreateDocument();
foreach (var imagePath in imagePaths)
{
using var image = _codecs.Load(imagePath);
var page = document.Pages.AddPage(image, null);
page.Recognize(null);
}
// DocumentWriter configuration — four properties to set before save
var pdfOptions = new PdfDocumentOptions
{
DocumentType = PdfDocumentType.Pdf,
ImageOverText = true, // Image layer visible, text layer searchable
Linearized = false,
Title = "Searchable Output"
};
// Apply options to the engine's writer instance
_engine.DocumentWriterInstance.SetOptions(DocumentFormat.Pdf, pdfOptions);
// Save with format enum — the format must match the options set above
document.Save(outputPath, DocumentFormat.Pdf, null);
}
public void CreateSearchablePdfFromPdf(string inputPdfPath, string outputPath)
{
var pdfInfo = _codecs.GetInformation(inputPdfPath, true);
using var document = _engine.DocumentManager.CreateDocument();
for (int i = 1; i <= pdfInfo.TotalPages; i++)
{
using var pageImage = _codecs.Load(inputPdfPath, 0,
CodecsLoadByteOrder.BgrOrGray, i, i);
var page = document.Pages.AddPage(pageImage, null);
page.Recognize(null);
}
var pdfOptions = new PdfDocumentOptions
{
DocumentType = PdfDocumentType.Pdf,
ImageOverText = true,
Title = Path.GetFileNameWithoutExtension(inputPdfPath)
};
_engine.DocumentWriterInstance.SetOptions(DocumentFormat.Pdf, pdfOptions);
document.Save(outputPath, DocumentFormat.Pdf, null);
}
}
Podejście IronOCR:
using IronOcr;
public class SearchablePdfService
{
private readonly IronTesseract _ocr = new IronTesseract();
public void CreateSearchablePdfFromImages(string[] imagePaths, string outputPath)
{
using var input = new OcrInput();
foreach (var imagePath in imagePaths)
input.LoadImage(imagePath);
var result = _ocr.Read(input);
result.SaveAsSearchablePdf(outputPath); // DocumentWriter pipeline: gone
}
public void CreateSearchablePdfFromPdf(string inputPdfPath, string outputPath)
{
using var input = new OcrInput();
input.LoadPdf(inputPdfPath);
var result = _ocr.Read(input);
result.SaveAsSearchablePdf(outputPath);
}
// Get bytes directly — useful for streaming responses in ASP.NET
public byte[] CreateSearchablePdfBytes(string inputPdfPath)
{
using var input = new OcrInput();
input.LoadPdf(inputPdfPath);
return _ocr.Read(input).SaveAsSearchablePdfBytes();
}
}
SaveAsSearchablePdf() zastępuje cały łańcuch PdfDocumentOptions + SetOptions() + document.Save(). Warstwa obrazu nakładająca się na tekst działa automatycznie. Aby uzyskać pełną dokumentację dotyczącą plików PDF z funkcją wyszukiwania, zapoznaj się z przewodnikiem dotyczącym plików PDF z funkcją wyszukiwania, który omawia opcje wyjściowe, oraz przykładem pliku PDF z funkcją wyszukiwania, który pokazuje integrację ze strumieniami odpowiedzi ASP.NET.
Migracja ekstrakcji pól wielostrefowych
OCR oparte na strefach w LEADTOOLS używa obiektów OcrZone z granicami LeadRect, atrybutami OcrZoneType i właściwościami OcrZoneCharacterFilters. Wiele stref jest dodawanych do jednej strony i rozpoznawanych w jednym wywołaniu page.Recognize(), a następnie ekstraktowane według indeksu stref. Indeks stref odpowiada kolejności wstawiania, co oznacza, że pętla wyodrębniania musi zachować tę kolejność.
Podejście LEADTOOLS OCR:
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;
public class LeadtoolsFormFieldExtractor
{
private readonly IOcrEngine _engine;
private readonly RasterCodecs _codecs;
// Invoice field extraction using named zones
public InvoiceFields ExtractInvoiceFields(string invoicePath)
{
using var image = _codecs.Load(invoicePath);
using var document = _engine.DocumentManager.CreateDocument();
var page = document.Pages.AddPage(image, null);
// Must clear auto-detected zones before adding custom ones
page.Zones.Clear();
// Zone definitions — index order matters for extraction
var zoneDefinitions = new[]
{
new { Name = "InvoiceNumber", X = 450, Y = 80, W = 200, H = 30 },
new { Name = "InvoiceDate", X = 450, Y = 115, W = 200, H = 30 },
new { Name = "VendorName", X = 50, Y = 150, W = 300, H = 40 },
new { Name = "TotalAmount", X = 450, Y = 600, W = 200, H = 30 }
};
foreach (var def in zoneDefinitions)
{
var zone = new OcrZone
{
Bounds = new LeadRect(def.X, def.Y, def.W, def.H),
ZoneType = OcrZoneType.Text,
CharacterFilters = OcrZoneCharacterFilters.None,
RecognitionModule = OcrZoneRecognitionModule.Auto
};
page.Zones.Add(zone);
}
page.Recognize(null);
// Extract by index — must match insertion order exactly
return new InvoiceFields
{
InvoiceNumber = page.Zones[0].Text?.Trim(),
InvoiceDate = page.Zones[1].Text?.Trim(),
VendorName = page.Zones[2].Text?.Trim(),
TotalAmount = page.Zones[3].Text?.Trim()
};
}
}
public class InvoiceFields
{
public string InvoiceNumber { get; set; }
public string InvoiceDate { get; set; }
public string VendorName { get; set; }
public string TotalAmount { get; set; }
}
Podejście IronOCR:
using IronOcr;
public class FormFieldExtractor
{
private readonly IronTesseract _ocr = new IronTesseract();
// Each field gets its own CropRectangle-scoped Read() call
// No zone index management, no zone ordering dependency
public InvoiceFields ExtractInvoiceFields(string invoicePath)
{
return new InvoiceFields
{
InvoiceNumber = ReadRegion(invoicePath, 450, 80, 200, 30),
InvoiceDate = ReadRegion(invoicePath, 450, 115, 200, 30),
VendorName = ReadRegion(invoicePath, 50, 150, 300, 40),
TotalAmount = ReadRegion(invoicePath, 450, 600, 200, 30)
};
}
private string ReadRegion(string imagePath, int x, int y, int width, int height)
{
using var input = new OcrInput();
input.LoadImage(imagePath, new CropRectangle(x, y, width, height));
return _ocr.Read(input).Text.Trim();
}
// Batch: extract the same field from many invoices in parallel
public Dictionary<string, string> ExtractInvoiceNumbersBatch(string[] invoicePaths)
{
var results = new System.Collections.Concurrent.ConcurrentDictionary<string, string>();
Parallel.ForEach(invoicePaths, invoicePath =>
{
using var input = new OcrInput();
input.LoadImage(invoicePath, new CropRectangle(450, 80, 200, 30));
results[invoicePath] = new IronTesseract().Read(input).Text.Trim();
});
return new Dictionary<string, string>(results);
}
}
CropRectangle przekazywane bezpośrednio do LoadImage() zastępuje całą konfigurację OcrZone. Nie ma potrzeby śledzenia indeksu strefy, nie jest wymagane wywołanie page.Zones.Clear(), ani nie jest potrzebna kontrola statusu rozpoznania. Przewodnik po OCR oparty na regionach obejmuje wzorce ekstrakcji jedno- i wieloregionalnej. Aby zapoznać się z kompletnym samouczkiem dotyczącym wyodrębniania pól z faktur, zobacz samouczek dotyczący OCR faktur.
Pobieranie danych ustrukturyzowanych za pomocą współrzędnych słów WORD
Strukturalne dane wyjściowe LEADTOOLS działają na poziomie strony i strefy. Aby uzyskać dane na poziomie słów z współrzędnymi ramki ograniczającej, programista uzyskuje dostęp do obiektów OcrWord wewnątrz rozpoznawanej strefy. API wymaga przetworzenia kolekcji stref po rozpoznaniu i iteracji listy słów dla każdej strefy.
Podejście LEADTOOLS OCR:
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;
public class LeadtoolsStructuredExtractor
{
private readonly IOcrEngine _engine;
private readonly RasterCodecs _codecs;
public List<WordLocation> ExtractWordsWithLocations(string imagePath)
{
var wordLocations = new List<WordLocation>();
using var image = _codecs.Load(imagePath);
using var document = _engine.DocumentManager.CreateDocument();
var page = document.Pages.AddPage(image, null);
page.Recognize(null);
// Access words through the zone collection
foreach (OcrZone zone in page.Zones)
{
foreach (OcrWord word in zone.Words)
{
wordLocations.Add(new WordLocation
{
Text = word.Value,
X = word.Bounds.X,
Y = word.Bounds.Y,
Width = word.Bounds.Width,
Height = word.Bounds.Height,
Confidence = word.Confidence
});
}
}
return wordLocations;
}
}
public class WordLocation
{
public string Text { get; set; }
public int X { get; set; }
public int Y { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public int Confidence { get; set; }
}
Podejście IronOCR:
using IronOcr;
public class StructuredExtractor
{
private readonly IronTesseract _ocr = new IronTesseract();
public List<WordLocation> ExtractWordsWithLocations(string imagePath)
{
var result = _ocr.Read(imagePath);
// Five-level hierarchy: Pages > Paragraphs > Lines > Words > Characters
return result.Pages
.SelectMany(page => page.Paragraphs)
.SelectMany(para => para.Lines)
.SelectMany(line => line.Words)
.Select(word => new WordLocation
{
Text = word.Text,
X = word.X,
Y = word.Y,
Width = word.Width,
Height = word.Height,
Confidence = (int)word.Confidence
})
.ToList();
}
// Paragraph-level extraction with position data
public void PrintDocumentStructure(string imagePath)
{
var result = _ocr.Read(imagePath);
Console.WriteLine($"Document confidence: {result.Confidence}%");
foreach (var page in result.Pages)
{
Console.WriteLine($"Page {page.PageNumber}:");
foreach (var paragraph in page.Paragraphs)
{
Console.WriteLine($" Paragraph at ({paragraph.X}, {paragraph.Y}):");
Console.WriteLine($" {paragraph.Text}");
}
}
}
}
Hierarchia wyników IronOCR schodzi od Pages przez Paragraphs, Lines, Words i Characters. Każdy poziom udostępnia X, Y, Width, Height, Text i Confidence. Wzorzec dostępu oparty na strefach LEADTOOLS znika — nie jest wymagana iteracja stref, aby uzyskać dostęp do danych WORD. Przewodnik "Wyniki odczytu" obejmuje kompletny model wyjściowy wraz ze schematami dostępu do współrzędnych. Dokumentacja API OcrResult opisuje każdą właściwość w hierarchii wyników.
Dokumentacja APILEADTOOLS OCRdo IronOCR
| LEADTOOLS OCR | IronOCR |
|---|---|
RasterSupport.SetLicense(licPath, keyContent) | IronOcr.License.LicenseKey = "key" |
new RasterCodecs() | Nie jest wymagane |
OcrEngineManager.CreateEngine(OcrEngineType.LEAD) | new IronTesseract() |
engine.Startup(codecs, null, null, runtimePath) | Nie jest wymagane |
engine.IsStarted | Nie jest wymagane (zawsze gotowe) |
engine.Shutdown() | Nie jest wymagane |
engine.Dispose() | Nie jest wymagane |
_codecs.Load(imagePath) | input.LoadImage(imagePath) |
_codecs.Load(path, 0, BgrOrGray, page, page) | input.LoadPdf(path) lub input.LoadImageFrames(path) |
_codecs.GetInformation(path, true).TotalPages | Nie jest wymagane — automatyczne |
engine.DocumentManager.CreateDocument() | Nie jest wymagane |
document.Pages.AddPage(image, null) | input.LoadImage(imagePath) |
page.Recognize(null) | ocr.Read(input) (rozpoznanie jest częścią Read()) |
page.GetText(-1) | result.Text |
page.RecognizeStatus | result.Confidence (procent) |
OcrZone { Bounds = new LeadRect(x, y, w, h) } | new CropRectangle(x, y, w, h) |
page.Zones.Clear() | Nie jest wymagane |
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 } | Obsługiwane automatycznie przez SaveAsSearchablePdf() |
engine.DocumentWriterInstance.SetOptions(format, opts) | Nie jest wymagane |
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 |
Typowe problemy związane z migracją i ich rozwiązania
Problem 1: Błędy rozpoznawania ścieżki do pliku licencji
LEADTOOLS: RasterSupport.SetLicense() rozwiązuje ścieżki plików .LIC i .LIC.KEY względem katalogu roboczego, co różni się w bin/Debug, bin/Release, kontenerach Docker i pulach aplikacji IIS. Typowym trybem awarii jest ścieżka, która działa podczas rozwoju, ale wywołuje "License file not found" w produkcji, ponieważ katalog roboczy uległ zmianie.
Rozwiązanie: Usuń oba pliki licencyjne i całkowicie usuń wywołanie SetLicense(). Zastąp pojedynczym przypisaniem ciągu znaków, które odczytuje wartość ze zmiennej środowiskowej:
// 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");
Klucz ciągu znaków zachowuje się identycznie w każdym środowisku. Zapisz to w tym samym menedżerze sekretów, który jest już używany dla ciągów połączeń z bazą danych.
Problem 2: Wyjątek "Silnik nie został uruchomiony"
LEADTOOLS: Wywołanie dowolnej metody rozpoznania przed ukończeniem engine.Startup(), lub po wywołaniu engine.Shutdown() (na przykład w sytuacji wyścigu wymuszania usunięcia przed użyciem podczas zamykania), wywołuje InvalidOperationException z "Silnik nie został uruchomiony." Trwałe klasy serwisowe muszą się przed tym zabezpieczyć przy pomocy IsStarted.
Rozwiązanie: IronTesseract nie wymaga wywołania startowego i nie ma statusu rozpoczęcia/zatrzymania. Blokada IsStarted i cała klasa serwisowa cyklu życia mogą zostać usunięte:
// 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: Gromadzenie pamięci przez RasterImage podczas przetwarzania wsadowego
LEADTOOLS: Przetwarzanie wsadowe iterujące po stronach PDF lub plikach obrazów tworzy instancje RasterImage w pętli. Jeśli jakakolwiek ścieżka kodu nie zlikwiduje RasterImage — z powodu wyjątku wyrzuconego przed zakończeniem bloku using, lub przekazywania manualnego z usuniętym wywołaniem — niezlikwidowane obrazy gromadzą się w pamięci. Kod produkcyjny LEADTOOLS zazwyczaj obejmuje wywołania GC.Collect() / GC.WaitForPendingFinalizers() między wsadami jako mechanizm kompensujący.
Rozwiązanie: Usuń wszystkie wywołania GC.Collect(). OcrInput to jedyny IDisposable w potoku IronOCR, i jest przypisany do każdej operacji wsadowej ze standardowym blokiem using:
// Remove this pattern:
// GC.Collect();
// GC.WaitForPendingFinalizers();
// Replace with standard using scope:
foreach (var filePath in filePaths)
{
using var input = new OcrInput();
input.LoadImage(filePath);
var text = _ocr.Read(input).Text;
// input disposed here — no accumulation
}
Aby uzyskać dodatkowe wskazówki dotyczące optymalizacji pamięci, zapoznaj się z wpisem na blogu poświęconym redukcji alokacji pamięci.
Problem 4: Opcje DocumentWriterInstance są zachowywane między wywołaniami
LEADTOOLS: engine.DocumentWriterInstance.SetOptions() modyfikuje stan na współdzielonej instancji piszącej silnika. Jeżeli jedna ze ścieżek kodu ustawi PdfDocumentOptions z DocumentType = PdfDocumentType.PdfA, a kolejne wywołanie nie zresetuje tych opcji przed wywołaniem document.Save(), format wyjściowy z poprzedniego wywołania zostaje utrzymany. Jest to efekt uboczny z zachowaniem stanu w współdzielonej instancji silnika.
**Rozwiązanie:**IronOCR nie posiada współdzielonego stanu pisania. Każde wywołanie SaveAsSearchablePdf() jest niezależne:
// 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);
Każde wywołanie generuje standardowy plik PDF z nakładką graficzną i warstwą tekstową z możliwością wyszukiwania. Nie ma wspólnego stanu opcji, który można by zresetować między wywołaniami.
Problem 5: Nieprawidłowy pakiet — brakujący moduł w czasie wykonywania
LEADTOOLS: Zespoły, które zakupiły SDK obrazowania dokumentów, mogą stwierdzić, że typy Leadtools.Pdf są niedostępne, lub że zaszyfrowane strony PDF wyrzucają RasterException z RasterExceptionCode.FeatureNotSupported. Dzieje się tak, gdy zakupiony pakiet nie zawiera modułu PDF, a błąd ujawnia się dopiero w czasie wykonywania w środowisku produkcyjnym.
**Rozwiązanie:**IronOCR zawiera wszystkie funkcje na każdym poziomie licencji. Nie ma oddzielnego modułu PDF, dodatku dla zaszyfrowanych dokumentów ani drugiego dostawcy dla silnika z wyższą dokładnością. Po zainstalowaniu pojedynczego pakietu IronOcr, pełen zestaw funkcji jest dostępny bez żadnych dodatkowych zakupów:
Problem 6: Niezgodność indeksu stref po zmianie kolejności
LEADTOOLS: Ekstrakcja stref używa indeksowania pozycyjnego — page.Zones[0].Text, page.Zones[1].Text — co wiąże logikę ekstrakcji z kolejnością wstawiania w page.Zones.Add(). Zmiana kolejności definicji stref w celu dopasowania do zmienionego układu formularza powoduje ciche przerwanie ekstrakcji poprzez przesunięcie wszystkich kolejnych indeksów.
**Rozwiązanie:**IronOCR używa nazwanych zmiennych z CropRectangle na pole. Zmiana kolejności definicji pól nie ma wpływu na ekstrakcję, ponieważ zakres każdego pola jest niezależny:
// 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);
Lista kontrolna migracji LEADTOOLS OCR
Przed migracją
Przed wprowadzeniem jakichkolwiek zmian należy przeprowadzić audyt kodu źródłowego w celu zidentyfikowania wszystkich miejsc użycia LEADTOOLS:
# Find all LEADTOOLS namespace imports
grep -rn "using Leadtools" --include="*.cs" .
# Find engine lifecycle calls
grep -rn "OcrEngineManager\|\.Startup(\|\.Shutdown()" --include="*.cs" .
# Find license file references
grep -rn "SetLicense\|LEADTOOLS\.LIC\|\.LIC\.KEY" --include="*.cs" .
# Find RasterCodecs usage
grep -rn "RasterCodecs\|_codecs\.Load\|GetInformation" --include="*.cs" .
# Find DocumentWriter usage
grep -rn "DocumentWriterInstance\|PdfDocumentOptions\|DocumentFormat\." --include="*.cs" .
# Find zone-based OCR
grep -rn "OcrZone\|page\.Zones\|LeadRect\|ZoneType" --include="*.cs" .
# Find GC workarounds to remove
grep -rn "GC\.Collect\|WaitForPendingFinalizers" --include="*.cs" .
Należy odnotować, który pakiet LEADTOOLS został zakupiony — moduł OCR, moduł PDF oraz typ silnika (LEAD vs Tesseract vs OmniPage) — aby zapewnić przetestowanie równoważnych funkcji IronOCR podczas walidacji po migracji.
Migracja kodu
- Usuń wszystkie pakiety NuGet LEADTOOLS:
Leadtools,Leadtools.Ocr,Leadtools.Codecs,Leadtools.Forms.DocumentWriters,Leadtools.Pdf - Zainstaluj pakiet NuGet
IronOcr - Zastąp wszystkie dyrektywy LEADTOOLS
usingzusing IronOcr; - Usuń z projektu i artefaktów wdrożeniowych pliki
.LICi.LIC.KEY - Zastąp
RasterSupport.SetLicense(licPath, keyContent)zIronOcr.License.LicenseKey = "key"podczas uruchamiania aplikacji - Usuń wszystkie klasy serwisowe
IDisposable, które istnieją wyłącznie do zarządzania cyklem życiaIOcrEngineiRasterCodecs - Zastąp
OcrEngineManager.CreateEngine()+engine.Startup()znew IronTesseract() - Zastąp
_codecs.Load(imagePath)zinput.LoadImage(imagePath)wewnątrz blokuusing var input = new OcrInput() - Zastąp pętle stron TIFF z wieloma ramkami używając
input.LoadImageFrames(tiffPath) - Zastąp pętlę iteracji po stronach PDF używając
input.LoadPdf(pdfPath) - Zastąp
document.Pages.AddPage()+page.Recognize(null)+page.GetText(-1)zocr.Read(input).Text - Zastąp wzorce
OcrZone+page.Zones.Add()zinput.LoadImage(path, new CropRectangle(x, y, w, h)) - Zastąp
PdfDocumentOptions+DocumentWriterInstance.SetOptions()+document.Save()zresult.SaveAsSearchablePdf(path) - Zastąp klasy komend przetwarzania wstępnego (
DeskewCommand,DespeckleCommand,AutoBinarizeCommand) zinput.Deskew(),input.DeNoise(),input.Binarize()na instancjiOcrInput - Usuń wszystkie wywołania
GC.Collect()/GC.WaitForPendingFinalizers()dodane w celu rekompensaty zarządzania pamięcią LEADTOOLS
Po migracji
- Sprawdź, czy rozpoznany tekst wyjściowy odpowiada wynikom LEADTOOLS na reprezentatywnej próbce obrazów i plików PDF
- Potwierdź, że wskaźniki zaufania mieszczą się w oczekiwanych zakresach, używając
result.Confidence - Test przetwarzania plików TIFF z wieloma ramkami daje taką samą liczbę stron jak pętla iteracyjna ramek w LEADTOOLS
- Sprawdź, czy plik PDF z możliwością wyszukiwania umożliwia wyszukiwanie tekstu w czytniku PDF (Adobe Acrobat lub jego odpowiednik)
- Przetestuj ekstrakcję pól opartą na strefach w odniesieniu do sprawdzonych wartości pól z faktur lub formularzy produkcyjnych
- Zweryfikuj, że rozszyfrowywanie PDF zabezpieczonych hasłem działa bez oddzielnego modułu
Leadtools.Pdf - Uruchom przetwarzanie wsadowe pod obciążeniem i potwierdź brak wzrostu pamięci (najpierw usuń wszystkie
GC.Collect()) - Przetestuj wdrożenie Docker i CI/CD bez plików licencyjnych — sprawdź, czy klucz licencyjny typu string jest poprawnie rozpoznawany ze zmiennej środowiskowej
- Przetestuj przetwarzanie równoległe z użyciem
Parallel.ForEach, aby zweryfikować bezpieczeństwo wątków - Potwierdź, że ekstrakcja strukturalna danych (
result.Pages,page.Paragraphs,page.Words) zwraca poprawne współrzędne
Kluczowe korzyści z migracji do IronOCR
Rozlokowanie staje się bezstanowe. Pliki LEADTOOLS .LIC i .LIC.KEY są artefaktami, które każde środowisko wdrożeniowe musi posiadać. Kontenery, w których są one wbudowane, ujawniają dane licencyjne w historii obrazów. Kontejnery, które je montują, wymagają koordynacji woluminów. Po migracji do IronOCR licencja jest ciągiem znaków w zmiennej środowiskowej. Artefaktem wdrożeniowym jest standardowe odwołanie NuGet. Żadnych plików, żadnych ścieżek, żadnej strategii montowania. Przewodnik wdrażania Docker oraz przewodnik wdrażania Azure przedstawiają kompletną konfigurację środowisk kontenerowych.
Cztery pakiety stają się jednym. Migracja redukuje Leadtools, Leadtools.Ocr, Leadtools.Codecs, Leadtools.Forms.DocumentWriters, i opcjonalnie Leadtools.Pdf do pojedynczej referencji IronOcr. Wszystkie funkcje — przetwarzanie wstępne, natywne dane wejściowe w formacie PDF, dane wyjściowe w formacie PDF z możliwością wyszukiwania, odczyt kodów BarCode, ekstrakcja danych strukturalnych, obsługa ponad 125 języków — są zawarte w tym jednym pakiecie. Zestaw funkcji nie zależy od tego, który pakiet został zakupiony.
Przetwarzanie wsadowe eliminuje ręczne zarządzanie pamięcią. Kod wsadowy LEADTOOLS zawiera defensywne wywołania GC.Collect() i explicytne zlikwidowanie RasterImage, aby zapobiec nagromadzeniu pamięci podczas uruchamiania wielu dokumentów. Zakres OcrInputIronOCR z blokiem using zarządza sprzątaniem automatycznie. Bezpieczne dla wątków przetwarzanie równoległe z Parallel.ForEach — jedna instancja IronTesseract na wątek — dostarcza przepustowość wielordzeniową bez kodu synchronizującego. Zapoznaj się z przewodnikiem po optymalizacji prędkości, aby dostroić przepustowość produkcji.
Przewidywalny całkowity koszt. Szacowany koszt LEADTOOLS dla pięcioosobowego zespołu programistów wynosi 15 000–40 000 USD w pierwszym roku, a roczna opłata za utrzymanie, wymagana do otrzymywania aktualizacji, wynosi około 20–25% kosztu licencji.IronOCR Professional w cenie 2999 USD obejmuje dziesięciu programistów i dziesięć lokalizacji wdrożeniowych w ramach jednorazowego zakupu na czas nieograniczony. W cenie zawarty jest roczny dostęp do aktualizacji. Dalsze korzystanie po upływie okresu aktualizacji nie wymaga dodatkowej opłaty. Strona licencyjna IronOCR publikuje wszystkie ceny bezpośrednio, bez konsultacji sprzedażowej.
Strukturalne dane bez konfiguracji stref. Po migracji, dane na poziomie słów, linii i akapitów z współrzędnymi ramki ograniczającej są dostępne bezpośrednio na OcrResult — nie są wymagane definicje stref. Pięciopoziomowa hierarchia (Pages, Paragraphs, Lines, Words, Characters) ujawnia współrzędne pozycji i wskaźniki zaufania na element. Aplikacje, które wymagały konfiguracji stref LEADTOOLS w celu uzyskania ustrukturyzowanego wyciągu, otrzymują bogatsze dane z prostszego interfejsu API. Strona z wynikami OCR zawiera podsumowanie kompletnego modelu wyjściowego.
