Przejdź do treści stopki
FILMY

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
// LEADTOOLS: Four namespaces, four packages, six steps before recognition
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;
using Leadtools.Forms.DocumentWriters;

RasterSupport.SetLicense(licPath, File.ReadAllText(keyPath)); // Step 1: two files on disk
var codecs = new RasterCodecs();                              // Step 2: codec layer
var engine = OcrEngineManager.CreateEngine(OcrEngineType.LEAD); // Step 3: engine factory
engine.Startup(codecs, null, null, runtimePath);              // Step 4: blocking startup
// ... still need to create document, add page, call Recognize(), extract text
Imports Leadtools
Imports Leadtools.Ocr
Imports Leadtools.Codecs
Imports Leadtools.Forms.DocumentWriters

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

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

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

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

IronOCR 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
dotnet remove package Leadtools
dotnet remove package Leadtools.Ocr
dotnet remove package Leadtools.Codecs
dotnet remove package Leadtools.Forms.DocumentWriters
dotnet remove package Leadtools.Pdf
SHELL

Zainstaluj IronOCR z NuGet:

dotnet add package IronOcr

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;
// Before (LEADTOOLS)
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;
using Leadtools.Forms.DocumentWriters;

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

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");
// Single line replaces the entire file-based license setup
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

// Production pattern: pull from environment variable or secrets manager
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE");
Imports System

' Single line replaces the entire file-based license setup
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"

' Production pattern: pull from environment variable or secrets manager
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE")
$vbLabelText   $csharpLabel

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();
    }
}
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;

// Service class required purely to manage engine lifecycle
public class LeadtoolsOcrService : IDisposable
{
    private IOcrEngine _engine;
    private RasterCodecs _codecs;
    private readonly string _runtimePath;

    public LeadtoolsOcrService(string licPath, string keyPath, string runtimePath)
    {
        _runtimePath = runtimePath;

        // License setup — two files, both must be present
        RasterSupport.SetLicense(licPath, File.ReadAllText(keyPath));

        // Codec layer — required before engine creation
        _codecs = new RasterCodecs();

        // Engine factory — engine type determines capability and cost
        _engine = OcrEngineManager.CreateEngine(OcrEngineType.LEAD);

        // Blocking startup — loads runtime into memory (500–2000ms)
        _engine.Startup(_codecs, null, null, _runtimePath);
    }

    public bool IsReady => _engine?.IsStarted ?? false;

    public string Process(string imagePath)
    {
        if (!IsReady)
            throw new InvalidOperationException("Engine not started");

        using var image = _codecs.Load(imagePath);
        using var doc = _engine.DocumentManager.CreateDocument();
        var page = doc.Pages.AddPage(image, null);
        page.Recognize(null);
        return page.GetText(-1);
    }

    public void Dispose()
    {
        // Order is mandatory: Shutdown before Dispose
        if (_engine?.IsStarted == true)
            _engine.Shutdown();

        _engine?.Dispose();
        _codecs?.Dispose();
    }
}
Imports Leadtools
Imports Leadtools.Ocr
Imports Leadtools.Codecs
Imports System
Imports System.IO

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

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

    Public Sub New(licPath As String, keyPath As String, runtimePath As String)
        _runtimePath = runtimePath

        ' License setup — two files, both must be present
        RasterSupport.SetLicense(licPath, File.ReadAllText(keyPath))

        ' Codec layer — required before engine creation
        _codecs = New RasterCodecs()

        ' Engine factory — engine type determines capability and cost
        _engine = OcrEngineManager.CreateEngine(OcrEngineType.LEAD)

        ' Blocking startup — loads runtime into memory (500–2000ms)
        _engine.Startup(_codecs, Nothing, Nothing, _runtimePath)
    End Sub

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

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

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

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

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

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
}
using IronOcr;

// No lifecycle management needed — the service class becomes trivial
public class OcrService
{
    private readonly IronTesseract _ocr = new IronTesseract();

    // Always ready — no IsStarted check needed
    public string Process(string imagePath) => _ocr.Read(imagePath).Text;

    // No Dispose() needed for the engine
    // No Startup(), no Shutdown(), no codec layer
}
Imports IronOcr

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

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

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

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

public class LeadtoolsTiffBatchService
{
    private readonly IOcrEngine _engine;
    private readonly RasterCodecs _codecs;

    public List<string> ProcessMultiFrameTiff(string tiffPath)
    {
        var pageTexts = new List<string>();

        // Must query page count before iterating
        var info = _codecs.GetInformation(tiffPath, true);
        int frameCount = info.TotalPages;

        using var document = _engine.DocumentManager.CreateDocument();

        for (int frameNum = 1; frameNum <= frameCount; frameNum++)
        {
            // Load one frame at a time — must specify firstPage/lastPage
            using var frameImage = _codecs.Load(
                tiffPath,
                0,                            // bitsPerPixel
                CodecsLoadByteOrder.BgrOrGray,
                frameNum,                     // firstPage
                frameNum);                    // lastPage

            var page = document.Pages.AddPage(frameImage, null);
            page.Recognize(null);
            pageTexts.Add(page.GetText(-1));

            // GC pressure accumulates if disposal is missed on any frame
        }

        return pageTexts;
    }

    public Dictionary<string, List<string>> ProcessTiffDirectory(string directoryPath)
    {
        var results = new Dictionary<string, List<string>>();

        foreach (var tiffFile in Directory.GetFiles(directoryPath, "*.tiff"))
        {
            results[tiffFile] = ProcessMultiFrameTiff(tiffFile);

            // Manual GC between files to prevent memory growth
            GC.Collect();
            GC.WaitForPendingFinalizers();
        }

        return results;
    }
}
Imports Leadtools
Imports Leadtools.Ocr
Imports Leadtools.Codecs
Imports System.IO

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

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

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

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

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

        Return pageTexts
    End Function

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

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

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

        Return results
    End Function
End Class
$vbLabelText   $csharpLabel

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

public class TiffBatchService
{
    private readonly IronTesseract _ocr = new IronTesseract();

    public List<string> ProcessMultiFrameTiff(string tiffPath)
    {
        using var input = new OcrInput();
        input.LoadImageFrames(tiffPath);  // All frames loaded automatically

        var result = _ocr.Read(input);

        // Per-frame text available through result.Pages
        return result.Pages.Select(p => p.Text).ToList();
    }

    public Dictionary<string, List<string>> ProcessTiffDirectory(string directoryPath)
    {
        var results = new Dictionary<string, List<string>>();

        foreach (var tiffFile in Directory.GetFiles(directoryPath, "*.tiff"))
        {
            results[tiffFile] = ProcessMultiFrameTiff(tiffFile);
        }

        return results;
    }

    // Parallel processing across files — thread-safe out of the box
    public Dictionary<string, List<string>> ProcessTiffDirectoryParallel(string directoryPath)
    {
        var concurrentResults = new System.Collections.Concurrent.ConcurrentDictionary<string, List<string>>();
        var tiffFiles = Directory.GetFiles(directoryPath, "*.tiff");

        Parallel.ForEach(tiffFiles, tiffFile =>
        {
            using var input = new OcrInput();
            input.LoadImageFrames(tiffFile);
            var result = new IronTesseract().Read(input);
            concurrentResults[tiffFile] = result.Pages.Select(p => p.Text).ToList();
        });

        return new Dictionary<string, List<string>>(concurrentResults);
    }
}
Imports IronOcr
Imports System.IO
Imports System.Collections.Concurrent
Imports System.Threading.Tasks

Public Class TiffBatchService
    Private ReadOnly _ocr As New IronTesseract()

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

            Dim result = _ocr.Read(input)

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

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

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

        Return results
    End Function

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

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

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

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

public class LeadtoolsDocumentWriterService
{
    private readonly IOcrEngine _engine;
    private readonly RasterCodecs _codecs;

    public void CreateSearchablePdfFromImages(string[] imagePaths, string outputPath)
    {
        using var document = _engine.DocumentManager.CreateDocument();

        foreach (var imagePath in imagePaths)
        {
            using var image = _codecs.Load(imagePath);
            var page = document.Pages.AddPage(image, null);
            page.Recognize(null);
        }

        // DocumentWriter configuration — four properties to set before save
        var pdfOptions = new PdfDocumentOptions
        {
            DocumentType = PdfDocumentType.Pdf,
            ImageOverText = true,     // Image layer visible, text layer searchable
            Linearized = false,
            Title = "Searchable Output"
        };

        // Apply options to the engine's writer instance
        _engine.DocumentWriterInstance.SetOptions(DocumentFormat.Pdf, pdfOptions);

        // Save with format enum — the format must match the options set above
        document.Save(outputPath, DocumentFormat.Pdf, null);
    }

    public void CreateSearchablePdfFromPdf(string inputPdfPath, string outputPath)
    {
        var pdfInfo = _codecs.GetInformation(inputPdfPath, true);
        using var document = _engine.DocumentManager.CreateDocument();

        for (int i = 1; i <= pdfInfo.TotalPages; i++)
        {
            using var pageImage = _codecs.Load(inputPdfPath, 0,
                CodecsLoadByteOrder.BgrOrGray, i, i);

            var page = document.Pages.AddPage(pageImage, null);
            page.Recognize(null);
        }

        var pdfOptions = new PdfDocumentOptions
        {
            DocumentType = PdfDocumentType.Pdf,
            ImageOverText = true,
            Title = Path.GetFileNameWithoutExtension(inputPdfPath)
        };

        _engine.DocumentWriterInstance.SetOptions(DocumentFormat.Pdf, pdfOptions);
        document.Save(outputPath, DocumentFormat.Pdf, null);
    }
}
Imports Leadtools
Imports Leadtools.Ocr
Imports Leadtools.Codecs
Imports Leadtools.Forms.DocumentWriters

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

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

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

            ' DocumentWriter configuration — four properties to set before save
            Dim pdfOptions As New PdfDocumentOptions With {
                .DocumentType = PdfDocumentType.Pdf,
                .ImageOverText = True,     ' Image layer visible, text layer searchable
                .Linearized = False,
                .Title = "Searchable Output"
            }

            ' Apply options to the engine's writer instance
            _engine.DocumentWriterInstance.SetOptions(DocumentFormat.Pdf, pdfOptions)

            ' Save with format enum — the format must match the options set above
            document.Save(outputPath, DocumentFormat.Pdf, Nothing)
        End Using
    End Sub

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

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

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

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

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

public class SearchablePdfService
{
    private readonly IronTesseract _ocr = new IronTesseract();

    public void CreateSearchablePdfFromImages(string[] imagePaths, string outputPath)
    {
        using var input = new OcrInput();
        foreach (var imagePath in imagePaths)
            input.LoadImage(imagePath);

        var result = _ocr.Read(input);
        result.SaveAsSearchablePdf(outputPath);  // DocumentWriter pipeline: gone
    }

    public void CreateSearchablePdfFromPdf(string inputPdfPath, string outputPath)
    {
        using var input = new OcrInput();
        input.LoadPdf(inputPdfPath);

        var result = _ocr.Read(input);
        result.SaveAsSearchablePdf(outputPath);
    }

    // Get bytes directly — useful for streaming responses in ASP.NET
    public byte[] CreateSearchablePdfBytes(string inputPdfPath)
    {
        using var input = new OcrInput();
        input.LoadPdf(inputPdfPath);
        return _ocr.Read(input).SaveAsSearchablePdfBytes();
    }
}
Imports IronOcr

Public Class SearchablePdfService
    Private ReadOnly _ocr As New IronTesseract()

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

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

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

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

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

SaveAsSearchablePdf() 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; }
}
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;

public class LeadtoolsFormFieldExtractor
{
    private readonly IOcrEngine _engine;
    private readonly RasterCodecs _codecs;

    // Invoice field extraction using named zones
    public InvoiceFields ExtractInvoiceFields(string invoicePath)
    {
        using var image = _codecs.Load(invoicePath);
        using var document = _engine.DocumentManager.CreateDocument();
        var page = document.Pages.AddPage(image, null);

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

        // Zone definitions — index order matters for extraction
        var zoneDefinitions = new[]
        {
            new { Name = "InvoiceNumber", X = 450, Y = 80,  W = 200, H = 30 },
            new { Name = "InvoiceDate",   X = 450, Y = 115, W = 200, H = 30 },
            new { Name = "VendorName",    X = 50,  Y = 150, W = 300, H = 40 },
            new { Name = "TotalAmount",   X = 450, Y = 600, W = 200, H = 30 }
        };

        foreach (var def in zoneDefinitions)
        {
            var zone = new OcrZone
            {
                Bounds = new LeadRect(def.X, def.Y, def.W, def.H),
                ZoneType = OcrZoneType.Text,
                CharacterFilters = OcrZoneCharacterFilters.None,
                RecognitionModule = OcrZoneRecognitionModule.Auto
            };
            page.Zones.Add(zone);
        }

        page.Recognize(null);

        // Extract by index — must match insertion order exactly
        return new InvoiceFields
        {
            InvoiceNumber = page.Zones[0].Text?.Trim(),
            InvoiceDate   = page.Zones[1].Text?.Trim(),
            VendorName    = page.Zones[2].Text?.Trim(),
            TotalAmount   = page.Zones[3].Text?.Trim()
        };
    }
}

public class InvoiceFields
{
    public string InvoiceNumber { get; set; }
    public string InvoiceDate   { get; set; }
    public string VendorName    { get; set; }
    public string TotalAmount   { get; set; }
}
Imports Leadtools
Imports Leadtools.Ocr
Imports Leadtools.Codecs

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

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

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

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

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

                page.Recognize(Nothing)

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

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

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

public class FormFieldExtractor
{
    private readonly IronTesseract _ocr = new IronTesseract();

    // Each field gets its own CropRectangle-scoped Read() call
    // No zone index management, no zone ordering dependency
    public InvoiceFields ExtractInvoiceFields(string invoicePath)
    {
        return new InvoiceFields
        {
            InvoiceNumber = ReadRegion(invoicePath, 450, 80,  200, 30),
            InvoiceDate   = ReadRegion(invoicePath, 450, 115, 200, 30),
            VendorName    = ReadRegion(invoicePath, 50,  150, 300, 40),
            TotalAmount   = ReadRegion(invoicePath, 450, 600, 200, 30)
        };
    }

    private string ReadRegion(string imagePath, int x, int y, int width, int height)
    {
        using var input = new OcrInput();
        input.LoadImage(imagePath, new CropRectangle(x, y, width, height));
        return _ocr.Read(input).Text.Trim();
    }

    // Batch: extract the same field from many invoices in parallel
    public Dictionary<string, string> ExtractInvoiceNumbersBatch(string[] invoicePaths)
    {
        var results = new System.Collections.Concurrent.ConcurrentDictionary<string, string>();

        Parallel.ForEach(invoicePaths, invoicePath =>
        {
            using var input = new OcrInput();
            input.LoadImage(invoicePath, new CropRectangle(450, 80, 200, 30));
            results[invoicePath] = new IronTesseract().Read(input).Text.Trim();
        });

        return new Dictionary<string, string>(results);
    }
}
Imports IronOcr
Imports System.Collections.Concurrent
Imports System.Threading.Tasks

Public Class FormFieldExtractor
    Private ReadOnly _ocr As New IronTesseract()

    ' Each field gets its own CropRectangle-scoped Read() call
    ' No zone index management, no zone ordering dependency
    Public Function ExtractInvoiceFields(invoicePath As String) As InvoiceFields
        Return New InvoiceFields With {
            .InvoiceNumber = ReadRegion(invoicePath, 450, 80, 200, 30),
            .InvoiceDate = ReadRegion(invoicePath, 450, 115, 200, 30),
            .VendorName = ReadRegion(invoicePath, 50, 150, 300, 40),
            .TotalAmount = ReadRegion(invoicePath, 450, 600, 200, 30)
        }
    End Function

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

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

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

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

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

public class LeadtoolsStructuredExtractor
{
    private readonly IOcrEngine _engine;
    private readonly RasterCodecs _codecs;

    public List<WordLocation> ExtractWordsWithLocations(string imagePath)
    {
        var wordLocations = new List<WordLocation>();

        using var image = _codecs.Load(imagePath);
        using var document = _engine.DocumentManager.CreateDocument();
        var page = document.Pages.AddPage(image, null);
        page.Recognize(null);

        // Access words through the zone collection
        foreach (OcrZone zone in page.Zones)
        {
            foreach (OcrWord word in zone.Words)
            {
                wordLocations.Add(new WordLocation
                {
                    Text       = word.Value,
                    X          = word.Bounds.X,
                    Y          = word.Bounds.Y,
                    Width      = word.Bounds.Width,
                    Height     = word.Bounds.Height,
                    Confidence = word.Confidence
                });
            }
        }

        return wordLocations;
    }
}

public class WordLocation
{
    public string Text       { get; set; }
    public int    X          { get; set; }
    public int    Y          { get; set; }
    public int    Width      { get; set; }
    public int    Height     { get; set; }
    public int    Confidence { get; set; }
}
Imports Leadtools
Imports Leadtools.Ocr
Imports Leadtools.Codecs

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

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

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

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

        Return wordLocations
    End Function
End Class

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

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

public class StructuredExtractor
{
    private readonly IronTesseract _ocr = new IronTesseract();

    public List<WordLocation> ExtractWordsWithLocations(string imagePath)
    {
        var result = _ocr.Read(imagePath);

        // Five-level hierarchy: Pages > Paragraphs > Lines > Words > Characters
        return result.Pages
            .SelectMany(page => page.Paragraphs)
            .SelectMany(para => para.Lines)
            .SelectMany(line => line.Words)
            .Select(word => new WordLocation
            {
                Text       = word.Text,
                X          = word.X,
                Y          = word.Y,
                Width      = word.Width,
                Height     = word.Height,
                Confidence = (int)word.Confidence
            })
            .ToList();
    }

    // Paragraph-level extraction with position data
    public void PrintDocumentStructure(string imagePath)
    {
        var result = _ocr.Read(imagePath);
        Console.WriteLine($"Document confidence: {result.Confidence}%");

        foreach (var page in result.Pages)
        {
            Console.WriteLine($"Page {page.PageNumber}:");
            foreach (var paragraph in page.Paragraphs)
            {
                Console.WriteLine($"  Paragraph at ({paragraph.X}, {paragraph.Y}):");
                Console.WriteLine($"  {paragraph.Text}");
            }
        }
    }
}
Imports IronOcr

Public Class StructuredExtractor
    Private ReadOnly _ocr As New IronTesseract()

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

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

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

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

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

// Replace with this:
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE")
    ?? throw new InvalidOperationException("IRONOCR_LICENSE environment variable not set");
' Remove this:
' RasterSupport.SetLicense(licPath, File.ReadAllText(keyPath))

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

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

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

Problem 3: 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
}
// Remove this pattern:
// GC.Collect();
// GC.WaitForPendingFinalizers();

// Replace with standard using scope:
foreach (var filePath in filePaths)
{
    using var input = new OcrInput();
    input.LoadImage(filePath);
    var text = _ocr.Read(input).Text;
    // input disposed here — no accumulation
}
' Remove this pattern:
' GC.Collect()
' GC.WaitForPendingFinalizers()

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

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);
// Remove the options setup:
// var pdfOptions = new PdfDocumentOptions { ... };
// _engine.DocumentWriterInstance.SetOptions(DocumentFormat.Pdf, pdfOptions);
// document.Save(outputPath, DocumentFormat.Pdf, null);

// Replace with:
result.SaveAsSearchablePdf(outputPath);
' Remove the options setup:
' Dim pdfOptions As New PdfDocumentOptions With { ... }
' _engine.DocumentWriterInstance.SetOptions(DocumentFormat.Pdf, pdfOptions)
' document.Save(outputPath, DocumentFormat.Pdf, Nothing)

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

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:

dotnet add package IronOcr

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

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" .
# Find all LEADTOOLS namespace imports
grep -rn "using Leadtools" --include="*.cs" .

# Find engine lifecycle calls
grep -rn "OcrEngineManager\|\.Startup(\|\.Shutdown()" --include="*.cs" .

# Find license file references
grep -rn "SetLicense\|LEADTOOLS\.LIC\|\.LIC\.KEY" --include="*.cs" .

# Find RasterCodecs usage
grep -rn "RasterCodecs\|_codecs\.Load\|GetInformation" --include="*.cs" .

# Find DocumentWriter usage
grep -rn "DocumentWriterInstance\|PdfDocumentOptions\|DocumentFormat\." --include="*.cs" .

# Find zone-based OCR
grep -rn "OcrZone\|page\.Zones\|LeadRect\|ZoneType" --include="*.cs" .

# Find GC workarounds to remove
grep -rn "GC\.Collect\|WaitForPendingFinalizers" --include="*.cs" .
SHELL

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

  1. Usuń wszystkie pakiety NuGet LEADTOOLS: Leadtools, Leadtools.Ocr, Leadtools.Codecs, Leadtools.Forms.DocumentWriters, Leadtools.Pdf
  2. Zainstaluj pakiet NuGet IronOcr
  3. Zastąp wszystkie dyrektywy LEADTOOLS using z using IronOcr;
  4. Usuń z projektu i artefaktów wdrożeniowych pliki .LIC i .LIC.KEY
  5. Zastąp RasterSupport.SetLicense(licPath, keyContent) z IronOcr.License.LicenseKey = "key" podczas uruchamiania aplikacji
  6. Usuń wszystkie klasy serwisowe IDisposable, które istnieją wyłącznie do zarządzania cyklem życia IOcrEngine i RasterCodecs
  7. Zastąp OcrEngineManager.CreateEngine() + engine.Startup() z new IronTesseract()
  8. Zastąp _codecs.Load(imagePath) z input.LoadImage(imagePath) wewnątrz bloku using var input = new OcrInput()
  9. Zastąp pętle stron TIFF z wieloma ramkami używając input.LoadImageFrames(tiffPath)
  10. Zastąp pętlę iteracji po stronach PDF używając input.LoadPdf(pdfPath)
  11. Zastąp document.Pages.AddPage() + page.Recognize(null) + page.GetText(-1) z ocr.Read(input).Text
  12. Zastąp wzorce OcrZone + page.Zones.Add() z input.LoadImage(path, new CropRectangle(x, y, w, h))
  13. Zastąp PdfDocumentOptions + DocumentWriterInstance.SetOptions() + document.Save() z result.SaveAsSearchablePdf(path)
  14. Zastąp klasy komend przetwarzania wstępnego (DeskewCommand, DespeckleCommand, AutoBinarizeCommand) z input.Deskew(), input.DeNoise(), input.Binarize() na instancji OcrInput
  15. 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.

Zwróć uwagęAdobe Acrobat, Kofax OmniPage, LEADTOOLS, i Tesseract są zarejestrowanymi znakami towarowymi ich właścicieli. Ta strona nie jest powiązana, wspierana ani sponsorowana przez Adobe Inc., Apryse, Google, Kofax ani LEAD Technologies. Wszystkie nazwy produktów, logo i marki są własnością ich odpowiednich właścicieli. Porównania mają charakter wyłącznie informacyjny i odzwierciedlają informacje dostępne publicznie w momencie pisania.

Często Zadawane Pytania

Dlaczego warto przejść z LEADTOOLS OCR na IronOCR?

Typowe czynniki motywujące to eliminacja złożoności interoperacyjności COM, zastąpienie zarządzania licencjami opartego na plikach, uniknięcie rozliczeń za stronę, umożliwienie wdrażania w Dockerze/kontenerach oraz przyjęcie natywnego dla NuGet przepływu pracy, który integruje się ze standardowymi narzędziami .NET.

Jakie są główne zmiany w kodzie podczas migracji z LEADTOOLS OCR do IronOCR?

Zastąp sekwencje inicjalizacji LEADTOOLS OCR instancjonowaniem IronTesseract, usuń zarządzanie cyklem życia COM (jawne wzorce Create/Load/Close) i zaktualizuj nazwy właściwości wyników. W rezultacie znacznie zmniejsza się liczba powtarzających się linii kodu.

Jak zainstalować IronOCR, aby rozpocząć migrację?

Uruchom polecenie „Install-Package IronOcr” w konsoli menedżera pakietów lub „dotnet add package IronOcr” w interfejsie CLI. Pakiety językowe są oddzielnymi pakietami: na przykład „dotnet add package IronOcr.Languages.French” dla języka francuskiego.

Czy IronOCR dorównuje dokładnością OCR LEADTOOLS OCR w przypadku standardowych dokumentów biznesowych?

IronOCR zapewnia wysoką dokładność w przypadku standardowych treści biznesowych, w tym faktur, umów, paragonów i formularzy wypełnionych na komputerze. Filtry przetwarzania wstępnego obrazu (prostowanie, usuwanie szumów, wzmacnianie kontrastu) dodatkowo poprawiają rozpoznawanie w przypadku pogorszonej jakości danych wejściowych.

W jaki sposób IronOCR obsługuje dane językowe, które LEADTOOLS OCR instaluje oddzielnie?

Dane językowe w IronOCR są dystrybuowane jako pakiety NuGet. Polecenie „dotnet add package IronOcr.Languages.German” instaluje obsługę języka niemiećkiego. Nie wymaga to ręcznego umieszczania plików ani podawania ścieżek katalogów.

Czy migracja z LEADTOOLS OCR do IronOCR wymaga zmian w infrastrukturze wdrożeniowej?

IronOCR wymaga mniej zmian w infrastrukturze niż LEADTOOLS OCR. Nie ma ścieżek binarnych SDK, lokalizacji plików licencyjnych ani konfiguracji serwerów licencyjnych. Pakiet NuGet zawiera kompletny silnik OCR, a klucz licencyjny jest ciągiem znaków ustawionym w kodzie aplikacji.

Jak skonfigurować licencjonowanie IronOCR po migracji?

W kodzie uruchamiającym aplikację przypisz IronOcr.License.LicenseKey = „YOUR-KEY”. W Dockerze lub Kubernetesie zapisz klucz jako zmienną środowiskową i odczytaj go podczas uruchamiania. Użyj License.IsValidLicense do sprawdzenia ważności przed przyjęciem ruchu.

Czy IronOCR może przetwarzać pliki PDF w taki sam sposób jak LEADTOOLS OCR?

Tak. IronOCR odczytuje zarówno natywne, jak i zeskanowane pliki PDF. Należy utworzyć instancję IronTesseract, wywołać ocr.Read(input), gdzie input jest ścieżką do pliku PDF lub obiektem OcrPdfInput, a następnie iterować strony OcrResult. Nie jest wymagany oddzielny proces renderowania plików PDF.

W jaki sposób IronOCR radzi sobie z wątkami podczas przetwarzania dużych ilości danych?

IronTesseract można bezpiecznie instancjonować dla każdego wątku. Uruchom jedną instancję na wątek w Parallel.ForEach lub puli zadań, uruchom OCR równolegle i usuń każdą instancję po zakończeniu. Nie jest wymagany żaden stan globalny ani blokowanie.

Jakie formaty wyjściowe obsługuje IronOCR po wyodrębnieniu tekstu?

IronOCR zwraca ustrukturyzowane wyniki, w tym tekst, współrzędne słów, wyniki pewności i strukturę strony. Opcje eksportu obejmują zwykły tekst, PDF z możliwością wyszukiwania oraz obiekty wyników ustrukturyzowanych do dalszego przetwarzania.

Czy ceny IronOCR są bardziej przewidywalne niż ceny LEADTOOLS OCR w przypadku skalowania obciążeń?

IronOCR stosuje licencję wieczystą z opłatą ryczałtową, bez opłat za stronę lub wolumen. Niezależnie od tego, czy przetwarzasz 10 000, czy 10 milionów stron, koszt licencji pozostaje stały. Opcje licencji wolumenowych i zespołowych znajdują się na stronie z cennikiem IronOCR.

Co stanie się z moimi istniejącymi testami po migracji z LEADTOOLS OCR do IronOCR?

Testy sprawdzające wyodrębnioną treść tekstową powinny nadal przechodzić pomyślnie po migracji. Testy weryfikujące wzorce wywołań API lub cykl życia obiektów COM będą wymagały aktualizacji, aby odzwierciedlały prostszy model inicjalizacji i wyników IronOCR.

Kannaopat Udonpant
Inżynier oprogramowania
Zanim stał się inżynierem oprogramowania, Kannapat ukończył doktorat z zasobów środowiskowych na Uniwersytecie Hokkaido w Japonii. W czasie studiowania, Kannapat również został członkiem Laboratorium Robotyki Pojazdów, które jest częścią Wydziału Inżynierii Bioprodukcji. W 2022 roku wykorzystał swoje umiejętności w ...
Czytaj więcej

Zespół wsparcia Iron

Jesteśmy online 24 godziny, 5 dni w tygodniu.
Czat
E-mail
Zadzwoń do mnie