Passer au contenu du pied de page
VIDéOS

Comment effectuer l'OCR dans NET Maui

Ce guide accompagne les développeurs .NET tout au long d'une migration complète de LEADTOOLS OCRvers IronOCR . Il couvre chaque étape, du remplacement des packages NuGet à la migration complète du code, avec des exemples avant et après pour les modèles les plus affectés par la cérémonie d'initialisation de LEADTOOLS, sa structure multi-espaces de noms et son modèle de déploiement de licence basé sur des fichiers.

Pourquoi migrer depuis LEADTOOLS OCR?

LEADTOOLS OCR est intégré à une plateforme d'imagerie Enterprise dont les origines remontent à 1990, et l'API reflète cette filiation. La configuration minimale requise pour le fonctionnement nécessite quatre packages NuGet , quatre espaces de noms, deux fichiers de licence physiques déployés sur un chemin connu, une couche de codec initialisée avant le moteur, une sélection du type de moteur parmi trois options et un appel de démarrage bloquant qui charge les binaires d'exécution en mémoire. Tout cela se déroule avant même qu'un seul caractère ne soit reconnu. Les équipes qui migrent vers IronOCR éliminent toute cette couche : un seul package, un seul espace de noms, une seule ligne pour définir une clé de licence.

Le déploiement de licence basé sur des fichiers échoue dans les conteneurs. LEADTOOLS nécessite deux fichiers physiques — LEADTOOLS.LIC et LEADTOOLS.LIC.KEY — lisibles à un chemin spécifique sur chaque machine exécutant l'application. Dans les conteneurs Docker, ces fichiers doivent soit être intégrés à l'image (et exposés dans l'historique des couches), soit montés lors de l'exécution (nécessitant une coordination des volumes dans chaque environnement d'orchestration). Azure Functions et AWS Lambda ne disposent d'aucun mécanisme permettant le déploiement de fichiers de licence sans recourir à des solutions de contournement. Les pipelines CI/CD nécessitent que les fichiers soient présents à l'emplacement exact utilisé par l'appel de démarrage, sinon l'application génère une erreur avant même de traiter un seul document. IronOCR remplace les deux fichiers par une chaîne de caractères qui peut être stockée dans une variable d'environnement, un secret Kubernetes ou une référence Azure Key Vault.

Quatre paquets pour une tâche. Un projet OCR LEADTOOLS fonctionnel nécessite au minimum Leadtools, Leadtools.Ocr, Leadtools.Codecs et Leadtools.Forms.DocumentWriters. Le support PDF protégé par mot de passe nécessite un module Leadtools.Pdf supplémentaire qui peut ne pas être inclus dans le paquet acheté. Chaque paquet doit être présent, compatible et versionné ensemble. IronOCR est distribué sous forme d'un seul package NuGet . Toutes les fonctionnalités — entrée PDF native, sortie PDF consultable, prétraitement, lecture de codes-barres — sont incluses.

Le cycle de vie du moteur crée une surface de maintenance. LEADTOOLS enrobe le moteur OCR dans une classe de service IDisposable non pas pour des raisons idiomatiques .NET, mais parce que l'appel Shutdown() doit précéder Dispose(), sinon l'application produit des erreurs. Les implémentations de production des processeurs de lot LEADTOOLS incluent fréquemment les appels GC.Collect() / GC.WaitForPendingFinalizers() entre les segments de documents pour compenser les instances RasterImage qui s'accumulent. IronOCR utilise des blocs using standard. OcrInput est le seul objet qui nécessite une disposition.

Des problèmes de compatibilité apparaissent en production. LEADTOOLS ne publie pas ses tarifs. Le kit de développement logiciel (SDK) d'imagerie de documents — la version qui inclut la reconnaissance optique de caractères (OCR) — coûte environ entre 3 000 et 8 000 dollars par développeur et par an. Les équipes qui ont acheté le module OCR sans le module PDF découvrent le manque lorsque RasterSupport.IsLocked() renvoie vrai contre des documents protégés par mot de passe en production. Le niveau de précision du moteur OmniPage nécessite un contrat de licence Kofax distinct en plus de l'achat de LEADTOOLS. IronOCR propose toutes les fonctionnalités sous licence à chaque niveau. Il n'existe aucune relation avec un fournisseur secondaire, ni de module distinct pour les documents chiffrés.

Le coût d'initialisation affecte les démarrages à froid. engine.Startup() est un appel bloquant qui charge les fichiers d'exécution en mémoire. Dans les environnements sans serveur où la latence de démarrage à froid est importante — Azure Functions, AWS Lambda — une initialisation bloquante de 500 à 2000 ms avant toute opération de reconnaissance constitue un problème structurel. IronOCR utilise une initialisation paresseuse. L'instance IronTesseract s'initialise lors de la première utilisation, et les appels suivants dans le même processus ne paient aucun coût d'initialisation.

Le problème fondamental

LEADTOOLS requiert quatre espaces de noms, quatre packages NuGet et une cérémonie de démarrage obligatoire avant qu'un caractère ne soit reconnu :

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

Comparaison des fonctionnalités IronOCR et de LEADTOOLS OCR

Le tableau suivant établit une correspondance directe entre les fonctionnalités des deux bibliothèques.

Fonction LEADTOOLS OCR IronOCR
Packages NuGet requis 4 minimum (davantage pour les PDF) 1 (IronOcr)
mécanisme de licence .LIC + .LIC.KEY paire de fichiers Clé de chaîne
Déploiement de licences Fichiers sur chaque machine de production Variable d'environnement ou configuration
Modèle de tarification 3 000 $ à 15 000 $ et plus par promoteur et par an (estimation) $999–2 999 $ unique perpétuel
Initialisation du moteur Manuel Startup() avec chemin d'exécution Automatique, paresseux
Arrêt du moteur Manuel Shutdown() avant Dispose() Non requis
Couche de codec RasterCodecs requis pour tout chargement d'image Non requis
Entrée PDF Boucle de rastérisation page par page Natif LoadPdf()
PDF protégé par mot de passe Nécessite un module Leadtools.Pdf séparé Paramètre intégré Password
Sortie PDF consultable DocumentWriter + PdfDocumentOptions + document.Save() result.SaveAsSearchablePdf()
Prétraitement Classes de commande séparées (DeskewCommand, DespeckleCommand, etc.) Méthodes de filtrage intégrées sur OcrInput
TIFF multipage Itération manuelle de trame avec CodecsLoadByteOrder input.LoadImageFrames()
Sortie structurée Niveau de page et de zone Page, paragraphe, ligne, mot, caractère avec coordonnées
Score de confiance Énumération OcrPageRecognizeStatus result.Confidence en pourcentage
Lecture de codes-barres Module de code-barres LEADTOOLS séparé Intégré (ReadBarCodes = true)
Sécurité des threads Nécessite une gestion rigoureuse Complet (un IronTesseract par thread)
Langues prises en charge 60–120 (selon le moteur) Plus de 125 packages de langage disponibles via NuGet
Déploiement du langage tessdata files or engine-bundled files Package NuGet par langue
Multiplateforme Prise en charge de la configuration d'exécution par plateforme Windows, Linux, macOS, Docker, Azure, AWS
Déploiement Docker Les fichiers .KEY doivent être montés ou intégrés Standard dotnet publish
soutien commercial Oui Oui

Démarrage rapide : Migration de LEADTOOLS OCRvers IronOCR

Étape 1 : Remplacer le package NuGet

Supprimez tous les packages 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

Installez IronOCR depuis NuGet :

dotnet add package IronOcr

Étape 2 : Mise à jour des espaces de noms

Remplacez toutes les directives using de LEADTOOLS par une seule importation 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

Étape 3 : initialisation de la licence

Supprimez tous les appels RasterSupport.SetLicense() et les références de fichiers .LIC / .LIC.KEY. Ajoutez la clé de licence IronOCR au démarrage de l'application :

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

La clé de licence peut être stockée dans n'importe quel modèle de gestion des secrets standard .NET — appsettings.json, Azure Key Vault, AWS Secrets Manager, ou un secret Kubernetes. Aucun fichier ne doit être déployé en même temps que le fichier binaire de l'application.

Exemples de migration de code

Suppression du cycle de vie du démarrage et de l'arrêt du moteur

LEADTOOLS impose un modèle strict de classes de service car le cycle de vie du moteur doit être géré explicitement. Le constructeur démarre le moteur, Dispose() le ferme dans le bon ordre, et tout chemin de code qui saute Shutdown() avant Dispose() produit une erreur d'exécution.

Approche OCR de LEADTOOLS :

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

Approche 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

L'instance IronTesseract s'initialise lors de la première utilisation. Aucun argument de constructeur, pas de chemin d'exécution, aucun appel Startup(). La classe de service ci-dessus n'a pas besoin de mettre en œuvre IDisposable du tout — le moteur est sans état et les objets OcrInput utilisés à l'intérieur de chaque appel Read() gèrent leur propre nettoyage via le modèle standard using. Le guide d'installation d'IronTesseract couvre les options de configuration, notamment la sélection du langage et l'optimisation des performances pour les scénarios de production.

Traitement par lots TIFF multi-images

LEADTOOLS traite les fichiers TIFF multi-images en interrogeant le codec pour le nombre total de trames, puis en itérant avec des paramètres explicites lastPage sur chaque appel _codecs.Load(). Chaque image d'une image fixe doit être éliminée manuellement, sinon la mémoire s'accumule.

Approche OCR de LEADTOOLS :

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

Approche 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() lit toutes les trames du TIFF en un seul appel. Aucune requête de comptage d'images, aucune boucle, aucune suppression explicite par image. La version parallèle crée une instance IronTesseract par thread, ce qui est le bon modèle — voir l'exemple de multithreading pour le modèle de threading complet. Pour les options d'entrée spécifiques au format TIFF, le guide d'entrée TIFF et GIF couvre la sélection des images et la gestion multiformat.

Simplification du processus de rédaction de documents

La création de PDF interrogeables LEADTOOLS nécessite la configuration d'une instance DocumentWriter sur le moteur, la construction d'un objet PdfDocumentOptions avec le type de sortie et les paramètres de superposition, l'application des options via SetOptions(), puis l'appel de document.Save() avec l'énumération de format. Chacune de ces étapes correspond à un objet distinct et à un appel API distinct.

Approche OCR de LEADTOOLS :

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

Approche 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() remplace toute la chaîne PdfDocumentOptions + SetOptions() + document.Save(). Le comportement du calque image sur texte est automatique. Pour une documentation complète sur la sortie PDF consultable, le guide pratique sur les PDF consultables couvre les options de sortie et l' exemple de PDF consultable montre l'intégration avec les flux de réponses ASP.NET .

Migration d'extraction de champs multizones

OCR basé sur des zones LEADTOOLS utilise des objets OcrZone avec des limites LeadRect, OcrZoneType et des propriétés OcrZoneCharacterFilters. Plusieurs zones sont ajoutées à une seule page et reconnues en un seul appel page.Recognize(), puis extraites par indice de zone. L'index de zone correspond à l'ordre d'insertion, ce qui signifie que la boucle d'extraction doit maintenir cet ordre.

Approche OCR de LEADTOOLS :

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

Approche 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 passé directement à LoadImage() remplace tout le paramétrage OcrZone. Il n'y a pas d'indice de zone à suivre, aucun appel page.Zones.Clear() requis, et aucune vérification de l'état de reconnaissance nécessaire. Le guide OCR basé sur les régions couvre les modèles d'extraction mono-régionaux et multi-régionaux. Pour un tutoriel complet sur l'extraction des champs de factures, consultez le tutoriel OCR sur les factures .

Extraction de données structurées à l'aide des coordonnées des mots

La sortie structurée de LEADTOOLS fonctionne au niveau de la page et de la zone. Pour obtenir des données au niveau des mots avec des coordonnées de boîtes englobantes, le développeur accède aux objets OcrWord depuis une zone reconnue. L'API nécessite de parcourir la collection de zones après la reconnaissance et d'itérer la liste de mots pour chaque zone.

Approche OCR de LEADTOOLS :

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

Approche 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

La hiérarchie de résultats d'IronOCR descend de Pages à travers Paragraphs, Lines, Words et Characters. Chaque niveau expose X, Y, Width, Height, Text et Confidence. Le modèle d'accès par zone de LEADTOOLS disparaît : aucune itération de zone n'est requise pour accéder aux données textuelles. Le guide pratique de lecture des résultats couvre l'intégralité du modèle de sortie avec les modèles d'accès aux coordonnées. La documentation de référence de l'API OcrResult répertorie chaque propriété de la hiérarchie des résultats.

Référence de mappage de l'API OCR LEADTOOLS vers IronOCR

LEADTOOLS OCR IronOCR
RasterSupport.SetLicense(licPath, keyContent) IronOcr.License.LicenseKey = "key"
new RasterCodecs() Non requis
OcrEngineManager.CreateEngine(OcrEngineType.LEAD) new IronTesseract()
engine.Startup(codecs, null, null, runtimePath) Non requis
engine.IsStarted Non requis (toujours prêt)
engine.Shutdown() Non requis
engine.Dispose() Non requis
_codecs.Load(imagePath) input.LoadImage(imagePath)
_codecs.Load(path, 0, BgrOrGray, page, page) input.LoadPdf(path) ou input.LoadImageFrames(path)
_codecs.GetInformation(path, true).TotalPages Non requis — automatique
engine.DocumentManager.CreateDocument() Non requis
document.Pages.AddPage(image, null) input.LoadImage(imagePath)
page.Recognize(null) ocr.Read(input) (la reconnaissance fait partie de Read())
page.GetText(-1) result.Text
page.RecognizeStatus result.Confidence (pourcentage)
OcrZone { Bounds = new LeadRect(x, y, w, h) } new CropRectangle(x, y, w, h)
page.Zones.Clear() Non requis
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 } Géré automatiquement par SaveAsSearchablePdf()
engine.DocumentWriterInstance.SetOptions(format, opts) Non requis
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

Problèmes de migration courants et solutions

Problème 1 : Échecs de la résolution du chemin d'accès au fichier de licence

LEADTOOLS : RasterSupport.SetLicense() résout les chemins de fichier .LIC et .LIC.KEY en relatif au répertoire de travail, qui diffère entre bin/Debug, bin/Release, les conteneurs Docker, et les pools d'applications IIS. Un mode de défaillance courant est un chemin qui fonctionne en développement mais génère "License file not found" en production car le répertoire de travail a changé.

Solution : Supprimez complètement les deux fichiers de licence et l'appel SetLicense(). Remplacez par une simple affectation de chaîne de caractères qui lit une valeur à partir d'une variable d'environnement :

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

La clé de chaîne se comporte de manière identique dans tous les environnements. Stockez-le dans le même gestionnaire de secrets que celui utilisé pour les chaînes de connexion à la base de données.

Problème 2 : Exception " Moteur non démarré "

LEADTOOLS : L'appel de toute méthode de reconnaissance avant que engine.Startup() ne soit terminée, ou après que engine.Shutdown() ait été appelée (par exemple, dans une condition de concurrence dispose-before-use lors de l'arrêt), jette un InvalidOperationException avec "Engine not started." Les classes de service à longue durée de vie doivent s'en protéger avec des vérifications IsStarted.

Solution : IronTesseract ne nécessite aucun appel de démarrage et n'a pas d'état démarré/arrêté. La garde IsStarted et toute la classe de service du cycle de vie peuvent être supprimées :

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

Problème 3 : Accumulation de mémoire pour les images raster lors du traitement par lots

LEADTOOLS : Le traitement par lots qui itère sur les pages PDF ou les fichiers image crée des instances RasterImage dans une boucle. Si l'un quelconque des chemins de code échoue à disposer d'un RasterImage — en raison d'une exception lancée avant la sortie du bloc using, ou d'un modèle de disposition manuel avec un appel manquant — les images non libérées s'accumulent en mémoire. Le code de production LEADTOOLS inclut fréquemment des appels GC.Collect() / GC.WaitForPendingFinalizers() entre les lots comme un mécanisme de compensation.

Solution : Supprimez tous les appels GC.Collect(). OcrInput est le seul IDisposable dans le pipeline IronOCR, et il est limité à chaque opération de lot avec un bloc using standard :

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

Pour obtenir des conseils supplémentaires sur l'optimisation de la mémoire, consultez l' article de blog sur la réduction de l'allocation de mémoire .

Problème 4 : Les options de DocumentWriterInstance persistent entre les appels

LEADTOOLS : engine.DocumentWriterInstance.SetOptions() modifie l'état sur l'instance d'écrivain moteur partagée. Si un chemin de code définit PdfDocumentOptions avec DocumentType = PdfDocumentType.PdfA et qu'un appel suivant ne réinitialise pas ces options avant d'appeler document.Save(), le format de sortie de l'appel précédent persiste. Il s'agit d'un effet secondaire avec état sur l'instance de moteur partagée.

Solution : IronOCR ne possède pas d'état d'écriture partagé. Chaque appel SaveAsSearchablePdf() est indépendant :

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

Chaque appel produit une sortie PDF standard avec une superposition d'image et une couche de texte consultable. Il n'existe pas d'état d'options partagé à réinitialiser entre les appels.

Problème 5 : Module manquant lors de l'exécution (bundle incorrect)

LEADTOOLS : Les équipes qui ont acheté le SDK d'imagerie documentaire peuvent constater que les types Leadtools.Pdf sont indisponibles, ou que des pages PDF cryptées génèrent RasterException avec RasterExceptionCode.FeatureNotSupported. Cela se produit lorsque le pack acheté n'inclut pas le module PDF, et l'erreur n'apparaît qu'en production, lors de l'exécution.

Solution : IronOCR inclut toutes les fonctionnalités à chaque niveau de licence. Il n'y a pas de module PDF séparé, pas de module complémentaire pour les documents cryptés, et pas de fournisseur secondaire pour un moteur de plus grande précision. Après avoir installé le paquet unique IronOcr, l'ensemble complet des fonctionnalités est disponible sans aucun achat supplémentaire :

dotnet add package IronOcr

Problème n° 6 : Incohérence de l'index de zone après réorganisation

LEADTOOLS : L'extraction de zone utilise l'indexation positionnelle — page.Zones[0].Text, page.Zones[1].Text — qui lie la logique d'extraction à l'ordre d'insertion dans page.Zones.Add(). La réorganisation des définitions de zone pour correspondre à une mise en page de formulaire modifiée interrompt silencieusement l'extraction en décalant tous les index suivants.

Solution : IronOCR utilise des variables nommées avec CropRectangle par champ. Le réordonnancement des définitions de champs n'a aucun effet sur l'extraction car chaque champ est défini indépendamment :

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

Liste de contrôle de migration OCR LEADTOOLS

Pré-migration

Avant d'apporter des modifications, auditez le code source afin d'identifier toutes les utilisations de 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

Notez quel ensemble LEADTOOLS a été acheté — module OCR, module PDF et type de moteur (LEAD vs Tesseract vs OmniPage) — afin de garantir que les fonctionnalités IronOCR équivalentes soient testées lors de la validation post-migration.

Migration de code

  1. Supprimez tous les paquets NuGet LEADTOOLS : Leadtools, Leadtools.Ocr, Leadtools.Codecs, Leadtools.Forms.DocumentWriters, Leadtools.Pdf
  2. Installez le paquet NuGet IronOcr
  3. Remplacez toutes les directives using de LEADTOOLS par using IronOcr;
  4. Supprimez les fichiers .LIC et .LIC.KEY du projet et des artefacts de déploiement
  5. Remplacez RasterSupport.SetLicense(licPath, keyContent) par IronOcr.License.LicenseKey = "key" au démarrage de l'application
  6. Supprimez toutes les classes de service IDisposable qui existent uniquement pour gérer le cycle de vie IOcrEngine et RasterCodecs
  7. Remplacez OcrEngineManager.CreateEngine() + engine.Startup() par new IronTesseract()
  8. Remplacez _codecs.Load(imagePath) par input.LoadImage(imagePath) à l'intérieur d'un bloc using var input = new OcrInput()
  9. Remplacez les boucles de pages TIFF multi-images par input.LoadImageFrames(tiffPath)
  10. Remplacez la boucle d'itération des pages PDF par input.LoadPdf(pdfPath)
  11. Remplacez document.Pages.AddPage() + page.Recognize(null) + page.GetText(-1) par ocr.Read(input).Text
  12. Remplacez les modèles OcrZone + page.Zones.Add() par input.LoadImage(path, new CropRectangle(x, y, w, h))
  13. Remplacez PdfDocumentOptions + DocumentWriterInstance.SetOptions() + document.Save() par result.SaveAsSearchablePdf(path)
  14. Remplacez les classes de commandes de prétraitement (DeskewCommand, DespeckleCommand, AutoBinarizeCommand) par input.Deskew(), input.DeNoise(), input.Binarize() sur l'instance OcrInput
  15. Supprimez tous les appels GC.Collect() / GC.WaitForPendingFinalizers() ajoutés pour compenser la gestion de la mémoire LEADTOOLS

Après la migration

  • Vérifier que le texte reconnu correspond bien au texte de sortie de LEADTOOLS sur un échantillon représentatif d'images et de fichiers PDF.
  • Confirmez que les scores de confiance sont dans les plages attendues à l'aide de result.Confidence
  • Le test de traitement TIFF multi-images produit le même nombre de pages que la boucle d'itération d'images de LEADTOOLS
  • Vérifier que le fichier PDF interrogeable est bien interrogeable par texte dans un lecteur PDF (Adobe Acrobat ou équivalent).
  • Tester l'extraction de champs par zone en la comparant à des valeurs de champs valides issues de factures ou de formulaires de production.
  • Vérifiez que le déchiffrement des PDF protégés par mot de passe fonctionne sans le module Leadtools.Pdf séparé
  • Exécutez le traitement par lots sous charge et confirmez l'absence de croissance mémoire (supprimez tous les GC.Collect() d'abord)
  • Tester le déploiement Docker et CI/CD sans fichiers de licence : vérifier que la clé de licence (chaîne de caractères) est correctement résolue à partir de la variable d'environnement.
  • Testez le traitement parallèle avec Parallel.ForEach pour vérifier la sécurité des threads
  • Confirmez que l'extraction de données structurées (result.Pages, page.Paragraphs, page.Words) renvoie les coordonnées correctes

Principaux avantages de la migration vers IronOCR

Le déploiement devient sans état. Les fichiers .LIC et .LIC.KEY de LEADTOOLS sont des artefacts que chaque environnement de déploiement doit transporter. Les conteneurs qui les intègrent exposent les données de licence dans l'historique des images. Les conteneurs qui les accueillent nécessitent une coordination des volumes. Après la migration vers IronOCR, la licence est une chaîne de caractères dans une variable d'environnement. L'artefact de déploiement est une référence NuGet standard. Aucun fichier, aucun chemin, aucune stratégie de montage. Le guide de déploiement Docker et le guide de déploiement Azure présentent la configuration complète des environnements conteneurisés.

Quatre paquets deviennent un. La migration réduit Leadtools, Leadtools.Ocr, Leadtools.Codecs, Leadtools.Forms.DocumentWriters, et optionnellement Leadtools.Pdf à une seule référence IronOcr. Toutes les fonctionnalités — prétraitement, entrée PDF native, sortie PDF consultable, lecture de codes-barres, extraction de données structurées, prise en charge de plus de 125 langues — sont incluses dans ce seul package. Les fonctionnalités ne dépendent pas du pack acheté.

Le traitement par lots élimine la gestion manuelle de la mémoire. Le code de traitement par lots de LEADTOOLS contient des appels GC.Collect() défensifs et une disposition RasterImage explicite pour prévenir l'accumulation de mémoire lors des exécutions multi-documents. Le OcrInput d'IronOCR, limité par un bloc using, gère automatiquement le nettoyage. Traitement parallèle sûr avec Parallel.ForEach — une instance IronTesseract par thread — offre un débit multi-cœur sans code de synchronisation. Consultez le guide d'optimisation de la vitesse pour le réglage du débit de production.

Coût total prévisible. Le coût estimé de LEADTOOLS pour une équipe de cinq développeurs se situe entre 15 000 et 40 000 dollars la première année, avec une maintenance annuelle d'environ 20 à 25 % du coût de la licence nécessaire pour recevoir les mises à jour. IronOCR Professional, au prix de 2 999 $, couvre dix développeurs et dix sites de déploiement dans le cadre d'un achat perpétuel unique. Une année de mises à jour est incluse. L'utilisation continue après la période de mise à jour ne nécessite aucun paiement supplémentaire. La page de licences IronOCR publie directement tous les prix par niveau, sans consultation commerciale.

Données structurées sans configuration de zone. Après la migration, les données au niveau des mots, des lignes et des paragraphes avec des coordonnées de boîtes englobantes sont disponibles directement sur OcrResult — aucun définition de zone requise. La hiérarchie à cinq niveaux (Pages, Paragraphs, Lines, Words, Characters) expose chacune des coordonnées de position et des scores de confiance par élément. Les applications qui nécessitaient une configuration de zone LEADTOOLS pour réaliser une extraction structurée obtiennent des données plus riches grâce à une API plus simple. La page présentant les résultats de la reconnaissance optique de caractères (OCR) résume le modèle de sortie complet.

Veuillez noterAdobe Acrobat, Kofax OmniPage, LEADTOOLS et Tesseract sont des marques déposées de leurs propriétaires respectifs. Ce site n'est affilié à, endossé par ou sponsorisé par Adobe Inc., Apryse, Google, Kofax ou LEAD Technologies. Tous les noms de produits, logos et marques sont la propriété de leurs propriétaires respectifs. Les comparaisons sont à titre informatif uniquement et reflètent les informations publiquement disponibles au moment de l'écriture.

Questions Fréquemment Posées

Pourquoi devrais-je migrer de LEADTOOLS OCR vers IronOCR ?

Parmi les motivations communes, citons l'élimination de la complexité de l'interopérabilité COM, le remplacement de la gestion des licences basée sur les fichiers, l'absence de facturation à la page, l'activation du déploiement Docker/conteneur et l'adoption d'un flux de travail NuGet-natif qui s'intègre à l'outillage .NET standard.

Quels sont les principaux changements de code lors de la migration de LEADTOOLS OCR vers IronOcr ?

Remplacer les séquences d'initialisation de LEADTOOLS OCR par l'instanciation d'IronTesseract, supprimer la gestion du cycle de vie de COM (modèles explicites Create/Load/Close) et mettre à jour les noms des propriétés des résultats. Le résultat est une réduction significative du nombre de lignes de code.

Comment installer IronOCR pour commencer la migration ?

Exécutez "Install-Package IronOcr" dans la console du Package Manager ou "dotnet add package IronOcr" dans le CLI. Les packs de langues sont des paquets distincts : 'dotnet add package IronOcr.Languages.French' pour le français, par exemple.

IronOCR offre-t-il la même précision d'OCR que LEADTOOLS OCR pour les documents commerciaux standard ?

IronOcr atteint un niveau de précision élevé pour les contenus commerciaux standard, notamment les factures, les contrats, les reçus et les formulaires dactylographiés. Les filtres de prétraitement d'image (désalignement, suppression du bruit, amélioration du contraste) améliorent encore la reconnaissance sur des données dégradées.

Comment IronOCR gère-t-il les données linguistiques que LEADTOOLS OCR installe séparément ?

Les données linguistiques de l'IronOcr sont distribuées sous forme de packages NuGet. 'dotnet add package IronOcr.Languages.German' installe la prise en charge de l'allemand. Il n'y a pas de placement manuel de fichiers ou de chemins d'accès aux répertoires.

La migration de LEADTOOLS OCR vers IronOCR nécessite-t-elle de modifier l'infrastructure de déploiement ?

IronOcr nécessite moins de changements d'infrastructure que LEADTOOLS OCR. Il n'y a pas de chemin binaire pour le SDK, de placement de fichier de licence ou de configuration de serveur de licence. Le package NuGet contient le moteur d'OCR complet, et la clé de licence est une chaîne de caractères définie dans le code de l'application.

Comment configurer les licences IronOCR après la migration ?

Attribuer IronOcr.License.LicenseKey = "YOUR-KEY" dans le code de démarrage de l'application. Dans Docker ou Kubernetes, stockez la clé dans une variable d'environnement et lisez-la au démarrage. Utilisez License.IsValidLicense pour valider avant d'accepter le trafic.

IronOCR peut-il traiter les PDF de la même manière que LEADTOOLS OCR ?

Oui, IronOCR lit aussi bien les PDF natifs que les PDF numérisés. Instanciez IronTesseract, appelez ocr.Read(input) où l'input est un chemin PDF ou OcrPdfInput, et itérez les pages OcrResult. Aucun pipeline de rendu PDF séparé n'est nécessaire.

Comment IronOcr gère-t-il le threading dans le cadre d'un traitement à haut volume ?

IronTesseract est sûr pour l'instanciation par thread. Créez une instance par thread dans un Parallel.ForEach ou un Task pool, exécutez l'OCR simultanément et disposez de chaque instance lorsque vous avez terminé. Aucun état global ou verrouillage n'est nécessaire.

Quels formats de sortie IronOCR prend-il en charge après l'extraction du texte ?

IronOCR renvoie des résultats structurés comprenant le texte, les coordonnées des mots, les scores de confiance et la structure des pages. Les options d'exportation comprennent le texte brut, le PDF interrogeable et les objets de résultats structurés pour le traitement en aval.

La tarification d'IronOCR est-elle plus prévisible que celle de LEADTOOLS OCR pour la mise à l'échelle des charges de travail ?

IronOCR utilise des licences perpétuelles forfaitaires sans frais par page ou par volume. Que vous traitiez 10 000 ou 10 millions de pages, le coût de la licence reste constant. Les options de licence en volume et en équipe sont disponibles sur la page de tarification d'IronOcr.

Qu'advient-il de mes tests existants après la migration de LEADTOOLS OCR vers IronOcr ?

Les tests qui vérifient le contenu du texte extrait doivent continuer à passer après la migration. Les tests qui valident les modèles d'appel d'API ou le cycle de vie des objets COM devront être mis à jour pour refléter le modèle d'initialisation et de résultat plus simple d'IronOcr.

Kannaopat Udonpant
Ingénieur logiciel
Avant de devenir ingénieur logiciel, Kannapat a obtenu un doctorat en ressources environnementales à l'université d'Hokkaido au Japon. Pendant qu'il poursuivait son diplôme, Kannapat est également devenu membre du laboratoire de robotique de véhicules, qui fait partie du département de bioproduction. En 2022, il a utilisé ses compé...
Lire la suite

Équipe de soutien Iron

Nous sommes en ligne 24 heures sur 24, 5 jours sur 7.
Chat
Email
Appelez-moi