Saltar al pie de página
VIDEOS

Cómo realizar OCR en NET Maui

Esta guía acompaña a los desarrolladores .NET a través de una migración completa de OCR de Leadtools a IronOCR . Abarca todos los pasos, desde la sustitución de paquetes NuGet hasta la migración completa del código, con ejemplos de antes y después para los patrones más afectados por la ceremonia de inicialización de LEADTOOLS, la estructura de múltiples espacios de nombres y el modelo de implementación de licencias basado en archivos.

¿Por qué migrar desde LEADTOOLS OCR?

LEADTOOLS OCR viene integrado en una plataforma de procesamiento de imágenes Enterprise con orígenes que se remontan a 1990, y la API refleja ese linaje. La configuración mínima de funcionamiento requiere cuatro paquetes NuGet , cuatro espacios de nombres, dos archivos de licencia físicos implementados en una ruta conocida, una capa de códec inicializada antes del motor, la selección del tipo de motor entre tres opciones y una llamada de inicio bloqueante que carga los binarios de tiempo de ejecución en la memoria. Todo eso sucede antes de que se reconozca un solo carácter. Los equipos que migran a IronOCR eliminan toda esa capa: un paquete, un espacio de nombres, una línea para configurar una clave de licencia.

El despliegue de licencia basado en archivos falla en contenedores. LEADTOOLS requiere dos archivos físicos — LEADTOOLS.LIC y LEADTOOLS.LIC.KEY — que sean legibles en una ruta específica en cada máquina que ejecute la aplicación. En los contenedores Docker, esos archivos deben estar integrados en la imagen (exponiéndolos en el historial de capas) o montados en tiempo de ejecución (lo que requiere la coordinación de volúmenes en cada entorno de orquestación). Azure Functions y AWS Lambda no disponen de ningún mecanismo para la implementación de archivos de licencia sin recurrir a soluciones alternativas. Las canalizaciones de CI/CD necesitan que los archivos estén presentes en la ruta exacta utilizada por la llamada de inicio, o la aplicación generará un error antes de procesar un solo documento.IronOCR reemplaza ambos archivos con una cadena que se ajusta a una variable de entorno, un secreto de Kubernetes o una referencia a Azure Key Vault.

Cuatro paquetes para una tarea. Un proyecto OCR de LEADTOOLS en funcionamiento requiere Leadtools, Leadtools.Ocr, Leadtools.Codecs, y Leadtools.Forms.DocumentWriters como mínimo. El soporte para PDF protegido con contraseña requiere un módulo Leadtools.Pdf adicional que puede no estar incluido en el paquete comprado. Cada paquete debe estar presente, ser compatible y estar versionado conjuntamente.IronOCR se distribuye como un único paquete NuGet . Se incluyen todas las funcionalidades: entrada nativa de PDF, salida de PDF con capacidad de búsqueda, preprocesamiento y lectura de códigos de barras.

El ciclo de vida del motor crea una superficie de mantenimiento. LEADTOOLS envuelve el motor OCR en una clase de servicio IDisposable no por razones idiomáticas de .NET, sino porque la llamada Shutdown() debe preceder a Dispose() o la aplicación produce errores. Las implementaciones de producción de procesadores por lotes de LEADTOOLS comúnmente incluyen llamadas GC.Collect() / GC.WaitForPendingFinalizers() entre fragmentos de documentos para compensar instancias RasterImage que se acumulan.IronOCR utiliza bloques estándar using. OcrInput es el único objeto que necesita ser desechado.

Surge confusión en la configuración de los paquetes en producción. LEADTOOLS no publica sus precios públicamente. El SDK de procesamiento de imágenes de documentos (el nivel que incluye OCR) tiene un coste estimado de entre 3.000 y 8.000 dólares por desarrollador al año. Los equipos que compraron el módulo OCR sin el módulo PDF descubren la brecha cuando RasterSupport.IsLocked() devuelve verdadero en documentos protegidos por contraseña en producción. El nivel de precisión del motor OmniPage requiere un acuerdo de licencia de Kofax independiente, además de la compra de LEADTOOLS.IronOCR licencia todas las funciones en cada nivel. No existe ninguna relación con un proveedor secundario ni un módulo independiente para documentos cifrados.

El costo de inicialización afecta los arranques en frío. engine.Startup() es una llamada bloqueante que carga archivos en tiempo de ejecución en memoria. En entornos sin servidor donde la latencia de arranque en frío es importante (Azure Functions, AWS Lambda), una inicialización que bloquea el proceso entre 500 y 2000 ms antes de que se realice cualquier trabajo de reconocimiento es un problema estructural.IronOCR utiliza inicialización diferida. La instancia IronTesseract se inicializa en su primer uso, y las llamadas posteriores dentro del mismo proceso no tienen costo de inicialización alguno.

El problema fundamental

LEADTOOLS requiere cuatro espacios de nombres, cuatro paquetes NuGet y una ceremonia de inicio obligatoria antes de que se reconozca un carácter:

// 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 vs LEADTOOLS OCR: Comparación de características

La siguiente tabla relaciona directamente las funcionalidades de ambas bibliotecas.

Característica OCR de Leadtools IronOCR
Se requieren paquetes NuGet 4 mínimo (más para PDF) 1 (IronOcr)
Mecanismo de licencia .LIC + .LIC.KEY par de archivos Clave de cadena
Implementación de licencias Archivos en cada máquina de producción Variable de entorno o configuración
Modelo de precios Entre 3.000 y 15.000 dólares o más por promotor al año (estimado) $999–$2,999 de una vez perpetuo
Inicialización del motor Manual Startup() con ruta de tiempo de ejecución Automático, perezoso
Apagado del motor Manual Shutdown() antes de Dispose() No es necesario
Capa de códec RasterCodecs requerido para toda carga de imágenes No es necesario
Entrada de PDF Bucle de rasterización página por página Nativo LoadPdf()
PDF protegido con contraseña Requiere módulo Leadtools.Pdf separado Parámetro incorporado Password
Salida en PDF con capacidad de búsqueda DocumentWriter + PdfDocumentOptions + document.Save() result.SaveAsSearchablePdf()
Preprocesamiento Clases de comando separadas (DeskewCommand, DespeckleCommand, etc.) Métodos de filtro integrados en OcrInput
Archivo TIFF de varias páginas Iteración de marco manual con CodecsLoadByteOrder input.LoadImageFrames()
Salida estructurada Nivel de página y zona Página, párrafo, línea, palabra, carácter con coordenadas
Puntuación de confianza Enum OcrPageRecognizeStatus result.Confidence como porcentaje
Lectura de BarCodes Módulo de código de barras LEADTOOLS independiente Integrado (ReadBarCodes = true)
Seguridad de los hilos Requiere una gestión cuidadosa Completo (uno IronTesseract por hilo)
Idiomas compatibles 60–120 (dependiendo del motor) Más de 125 paquetes de lenguaje NuGet
Implementación de lenguaje tessdata files or engine-bundled files Paquete NuGet por idioma
Multiplataforma Compatible con configuración de tiempo de ejecución por plataforma. Windows, Linux, macOS, Docker, Azure, AWS
Implementación de Docker Los archivos .KEY deben montarse o integrarse Estándar dotnet publish
Soporte comercial

Inicio rápido: Migración de OCR de Leadtools a IronOCR

Paso 1: Sustituir el paquete NuGet

Elimine todos los paquetes de 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

Instala IronOCR desde NuGet :

dotnet add package IronOcr

Paso 2: Actualizar los espacios de nombres

Reemplazar todas las directivas using de LEADTOOLS con una sola importación de 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

Paso 3: Inicializar licencia

Eliminar todas las llamadas RasterSupport.SetLicense() y las referencias de archivos .LIC / .LIC.KEY. Agregue la clave de licencia de IronOCR al iniciar la aplicación:

// 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 clave de licencia se puede almacenar en cualquier patrón estándar de gestión de secretos de .NET — appsettings.json, Azure Key Vault, AWS Secrets Manager, o un secreto de Kubernetes. No es necesario desplegar ningún archivo junto con el binario de la aplicación.

Ejemplos de migración de código

Eliminación del ciclo de vida de arranque y parada del motor

LEADTOOLS exige un patrón estricto de clases de servicio porque el ciclo de vida del motor debe gestionarse de forma explícita. El constructor inicia el motor, Dispose() lo apaga en el orden correcto, y cualquier ruta de código que se salte Shutdown() antes de Dispose() produce un error en tiempo de ejecución.

Enfoque 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

Enfoque 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

La instancia IronTesseract se inicializa en su primer uso. Sin argumentos de constructor, sin ruta de tiempo de ejecución, sin llamada Startup(). La clase de servicio anterior no necesita implementar IDisposable en absoluto — el motor no tiene estado y los objetos OcrInput utilizados dentro de cada llamada Read() gestionan su propia limpieza a través del patrón estándar using. La guía de configuración de IronTesseract abarca las opciones de configuración, incluida la selección del idioma y la optimización del rendimiento para escenarios de producción.

Procesamiento por lotes de TIFF multifotograma

LEADTOOLS procesa archivos TIFF de múltiples marcos consultando el códec para obtener el recuento total de marcos, luego iterando con parámetros explícitos lastPage en cada llamada _codecs.Load(). Cada imagen de fotograma debe eliminarse manualmente o se acumulará memoria.

Enfoque 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

Enfoque 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() lee todos los marcos del TIFF en una sola llamada. Sin consulta de recuento de fotogramas, sin bucle, sin eliminación explícita por fotograma. La versión paralela crea una instancia IronTesseract por hilo, que es el patrón correcto — vea el ejemplo de multithreading para el modelo de threading completo. Para obtener información sobre las opciones de entrada específicas para TIFF, la guía de entrada de TIFF y GIF abarca la selección de fotogramas y el manejo de múltiples formatos.

Simplificación del flujo de trabajo de redacción de documentos

La creación de PDF buscable de LEADTOOLS requiere configurar una instancia DocumentWriter en el motor, construir un objeto PdfDocumentOptions con tipo de salida y configuraciones de superposición, aplicar las opciones a través de SetOptions(), y luego llamar a document.Save() con el enum de formato. Cada uno de esos pasos es un objeto independiente y una llamada a la API independiente.

Enfoque 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

Enfoque 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() reemplaza toda la cadena PdfDocumentOptions + SetOptions() + document.Save(). El comportamiento de la capa de imagen sobre texto es automático. Para obtener documentación completa sobre la salida de PDF con capacidad de búsqueda, la guía práctica sobre PDF con capacidad de búsqueda cubre las opciones de salida y el ejemplo de PDF con capacidad de búsqueda muestra la integración con flujos de respuesta de ASP.NET .

Migración de extracción de campo multizona

El OCR basado en zonas de LEADTOOLS utiliza objetos OcrZone con límites LeadRect, propiedades OcrZoneType, y características OcrZoneCharacterFilters. Se añaden múltiples zonas a una sola página y se reconocen en una llamada page.Recognize(), luego se extraen por índice de zona. El índice de zona coincide con el orden de inserción, lo que significa que el bucle de extracción debe mantener ese orden.

Enfoque 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

Enfoque 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 pasado directamente a LoadImage() reemplaza toda la configuración OcrZone. No hay índice de zona a rastrear, no se requiere llamada page.Zones.Clear(), y no se necesita verificación de estado de reconocimiento. La guía de OCR basada en regiones abarca patrones de extracción de una sola región y de múltiples regiones. Para obtener un tutorial completo sobre la extracción de campos de facturas, consulte el tutorial de OCR de facturas .

Extracción de datos estructurados con coordenadas de palabras

La salida estructurada de LEADTOOLS funciona a nivel de página y de zona. Para obtener datos a nivel de palabra con coordenadas de cuadro de delimitación, el desarrollador accede a objetos OcrWord desde dentro de una zona reconocida. La API requiere procesar la colección de zonas después del reconocimiento e iterar la lista de palabras por zona.

Enfoque 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

Enfoque 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 jerarquía de resultados de IronOCR desciende desde Pages a través de Paragraphs, Lines, Words, y Characters. Cada nivel expone X, Y, Width, Height, Text, y Confidence. El patrón de acceso basado en zonas de LEADTOOLS desaparece: no se requiere ninguna iteración de zona para acceder a los datos de las palabras. La guía práctica para la lectura de resultados abarca el modelo de salida completo con patrones de acceso por coordenadas. La documentación de referencia de la API de OcrResult documenta cada propiedad de la jerarquía de resultados.

Referencia de mapeo de la API OCR de LEADTOOLS a IronOCR

OCR de Leadtools IronOCR
RasterSupport.SetLicense(licPath, keyContent) IronOcr.License.LicenseKey = "key"
new RasterCodecs() No es necesario
OcrEngineManager.CreateEngine(OcrEngineType.LEAD) new IronTesseract()
engine.Startup(codecs, null, null, runtimePath) No es necesario
engine.IsStarted No es necesario (siempre listo)
engine.Shutdown() No es necesario
engine.Dispose() No es necesario
_codecs.Load(imagePath) input.LoadImage(imagePath)
_codecs.Load(path, 0, BgrOrGray, page, page) input.LoadPdf(path) o input.LoadImageFrames(path)
_codecs.GetInformation(path, true).TotalPages No es obligatorio — automático
engine.DocumentManager.CreateDocument() No es necesario
document.Pages.AddPage(image, null) input.LoadImage(imagePath)
page.Recognize(null) ocr.Read(input) (el reconocimiento es parte de Read())
page.GetText(-1) result.Text
page.RecognizeStatus result.Confidence (porcentaje)
OcrZone { Bounds = new LeadRect(x, y, w, h) } new CropRectangle(x, y, w, h)
page.Zones.Clear() No es necesario
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 } Manejado automáticamente por SaveAsSearchablePdf()
engine.DocumentWriterInstance.SetOptions(format, opts) No es necesario
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

Problemas comunes de migración y soluciones

Problema 1: Fallos en la resolución de la ruta del archivo de licencia

LEADTOOLS: RasterSupport.SetLicense() resuelve los caminos de archivo .LIC y .LIC.KEY en relación con el directorio de trabajo, que difiere entre bin/Debug, bin/Release, contenedores Docker, y conjuntos de aplicaciones IIS. Un modo común de fallo es un camino que funciona en desarrollo pero lanza "License file not found" en producción porque el directorio de trabajo cambió.

Solución: Eliminar completamente ambos archivos de licencia y la llamada SetLicense(). Reemplazar con una única asignación de cadena que lea desde una variable de entorno:

// 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 clave de cadena se comporta de forma idéntica en todos los entornos. Guárdalo en el mismo gestor de secretos que ya se utiliza para las cadenas de conexión a la base de datos.

Problema 2: Excepción de motor no arrancado

LEADTOOLS: Llamar a cualquier método de reconocimiento antes de que engine.Startup() se complete, o después de que engine.Shutdown() se haya llamado (por ejemplo, en una condición de carrera de descarte-antes-de-uso durante el apagado), lanza un InvalidOperationException con "Motor no iniciado." Las clases de servicio de larga duración deben protegerse contra esto con verificaciones IsStarted.

Solución: IronTesseract no requiere llamada de inicio y no tiene estado iniciado/detenido. El guardián IsStarted y toda la clase de servicio de ciclo de vida pueden ser eliminados:

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

Problema 3: Acumulación de memoria en imágenes ráster durante el procesamiento por lotes

LEADTOOLS: El procesamiento por lotes que itera sobre páginas PDF o archivos de imagen crea instancias RasterImage en un bucle. Si falla cualquier ruta de código al disponer de un RasterImage — debido a una excepción lanzada antes de que el bloque using se cierre, o por un patrón de disposición manual con una llamada faltante — las imágenes no liberadas se acumulan en memoria. El código de producción de LEADTOOLS comúnmente incluye llamadas GC.Collect() / GC.WaitForPendingFinalizers() entre lotes como un mecanismo de compensación.

Solución: Eliminar todas las llamadas GC.Collect(). OcrInput es el único IDisposable en el pipeline de IronOCR, y está limitado a cada operación por lotes con un bloque estándar 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

Para obtener más información sobre la optimización de la memoria, consulte la entrada del blog sobre la reducción de la asignación de memoria .

Problema 4: Las opciones de DocumentWriterInstance se mantienen entre llamadas.

LEADTOOLS: engine.DocumentWriterInstance.SetOptions() modifica el estado en la instancia compartida del escritor del motor. Si una ruta de código establece PdfDocumentOptions con DocumentType = PdfDocumentType.PdfA y una llamada posterior no restablece esas opciones antes de llamar a document.Save(), el formato de salida de la llamada anterior persiste. Este es un efecto secundario que afecta al estado de la instancia del motor compartido.

Solución:IronOCR no tiene un estado de escritor compartido. Cada llamada SaveAsSearchablePdf() es independiente:

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

Cada llamada genera un archivo PDF estándar con una superposición de imagen y una capa de texto con capacidad de búsqueda. No existe un estado de opciones compartidas que se pueda restablecer entre llamadas.

Problema 5: Paquete incorrecto: falta un módulo en tiempo de ejecución.

LEADTOOLS: Los equipos que compraron el SDK de Document Imaging pueden encontrar que los tipos Leadtools.Pdf no están disponibles o que las páginas PDF encriptadas lanzan RasterException con RasterExceptionCode.FeatureNotSupported. Esto ocurre cuando el paquete adquirido no incluye el módulo PDF, y el error solo se manifiesta en tiempo de ejecución en producción.

Solución:IronOCR incluye todas las funciones en todos los niveles de licencia. No hay módulo PDF separado, ni complemento para documentos encriptados, ni proveedor secundario para un motor de mayor precisión. Después de instalar el paquete IronOcr único, el conjunto completo de características está disponible sin ninguna compra adicional:

dotnet add package IronOcr

Problema 6: Discrepancia en el índice de zona tras la reordenación

LEADTOOLS: La extracción de zonas utiliza indexación posicional — page.Zones[0].Text, page.Zones[1].Text — que vincula la lógica de extracción al orden de inserción en page.Zones.Add(). Reordenar las definiciones de zona para que coincidan con un diseño de formulario modificado interrumpe silenciosamente la extracción al desplazar todos los índices subsiguientes.

Solución:IronOCR utiliza variables nombradas con CropRectangle por campo. Reordenar las definiciones de los campos no afecta a la extracción porque cada campo tiene un alcance independiente:

// 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 de verificación de migración de OCR de LEADTOOLS

Pre-Migración

Antes de realizar cualquier cambio, audita el código fuente para identificar todos los usos 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

Tenga en cuenta qué paquete de LEADTOOLS se adquirió (módulo OCR, módulo PDF y tipo de motor [LEAD, Tesseract u OmniPage]) para garantizar que se prueben las funciones equivalentes de IronOCR durante la validación posterior a la migración.

Migración de código

  1. Eliminar todos los paquetes NuGet de LEADTOOLS: Leadtools, Leadtools.Ocr, Leadtools.Codecs, Leadtools.Forms.DocumentWriters, Leadtools.Pdf
  2. Instalar paquete NuGet IronOcr
  3. Reemplazar todas las directivas using de LEADTOOLS con using IronOcr;
  4. Eliminar los archivos .LIC y .LIC.KEY del proyecto y los artefactos de despliegue
  5. Reemplazar RasterSupport.SetLicense(licPath, keyContent) con IronOcr.License.LicenseKey = "key" al inicio de la aplicación
  6. Eliminar todas las clases de servicio IDisposable que existen solo para gestionar el ciclo de vida de IOcrEngine y RasterCodecs
  7. Reemplazar OcrEngineManager.CreateEngine() + engine.Startup() con new IronTesseract()
  8. Reemplazar _codecs.Load(imagePath) con input.LoadImage(imagePath) dentro de un bloque using var input = new OcrInput()
  9. Reemplazar los bucles de páginas TIFF de múltiples marcos con input.LoadImageFrames(tiffPath)
  10. Reemplazar el bucle de iteración de páginas PDF con input.LoadPdf(pdfPath)
  11. Reemplazar document.Pages.AddPage() + page.Recognize(null) + page.GetText(-1) con ocr.Read(input).Text
  12. Reemplazar patrones OcrZone + page.Zones.Add() con input.LoadImage(path, new CropRectangle(x, y, w, h))
  13. Reemplazar PdfDocumentOptions + DocumentWriterInstance.SetOptions() + document.Save() con result.SaveAsSearchablePdf(path)
  14. Reemplazar clases de comando de preprocesamiento (DeskewCommand, DespeckleCommand, AutoBinarizeCommand) con input.Deskew(), input.DeNoise(), input.Binarize() en la instancia OcrInput
  15. Eliminar todas las llamadas GC.Collect() / GC.WaitForPendingFinalizers() añadidas para compensar la gestión de memoria de LEADTOOLS

Posmigración

  • Verificar que el texto reconocido coincida con el texto de salida de LEADTOOLS en una muestra representativa de imágenes y archivos PDF.
  • Confirmar que las puntuaciones de confianza están dentro de los rangos esperados utilizando result.Confidence
  • La prueba de procesamiento de TIFF multifotograma produce el mismo número de páginas que el bucle de iteración de fotogramas de LEADTOOLS.
  • Validar que el archivo PDF con capacidad de búsqueda permita realizar búsquedas de texto en un lector de PDF (Adobe Acrobat o equivalente).
  • Prueba la extracción de campos basada en zonas comparándola con valores de campos conocidos y válidos de facturas o formularios de producción.
  • Verificar que la desencriptación de PDF protegido por contraseña funciona sin el módulo Leadtools.Pdf separado
  • Ejecutar procesamiento por lotes bajo carga y confirmar que no hay crecimiento de memoria (eliminar todas las GC.Collect() primero)
  • Prueba Docker y despliegue CI/CD sin archivos de licencia: confirma que la clave de licencia de cadena se resuelve correctamente desde la variable de entorno.
  • Probar el procesamiento paralelo con Parallel.ForEach para verificar la seguridad de los hilos
  • Confirmar que la extracción de datos estructurados (result.Pages, page.Paragraphs, page.Words) devuelve las coordenadas correctas

Principales ventajas de migrar a IronOCR

El despliegue se torna sin estado. Los archivos .LIC y .LIC.KEY de LEADTOOLS son artefactos que cada entorno de despliegue debe llevar. Los contenedores que las incluyen de forma integrada exponen los datos de licencia en el historial de imágenes. Los contenedores que los montan necesitan coordinación de volumen. Tras la migración a IronOCR, la licencia es una cadena de texto en una variable de entorno. El artefacto de despliegue es una referencia estándar de NuGet . Sin archivos, sin rutas, sin estrategia de montaje. La guía de implementación de Docker y la guía de implementación de Azure muestran la configuración completa para entornos en contenedores.

Cuatro paquetes se convierten en uno. La migración reduce Leadtools, Leadtools.Ocr, Leadtools.Codecs, Leadtools.Forms.DocumentWriters, y opcionalmente Leadtools.Pdf a una sola referencia IronOcr. Todas las funcionalidades —preprocesamiento, entrada de PDF nativa, salida de PDF con capacidad de búsqueda, lectura de códigos de barras, extracción de datos estructurados, compatibilidad con más de 125 idiomas— están incluidas en ese único paquete. Las funciones disponibles no dependen del paquete que se haya adquirido.

El procesamiento por lotes elimina la gestión manual de memoria. El código de procesamiento por lotes de LEADTOOLS lleva llamadas defensivas GC.Collect() y disposición explícita RasterImage para prevenir la acumulación de memoria durante ejecuciones de varios documentos. El OcrInput de IronOCR limitado con un bloque using gestiona automáticamente la limpieza. El procesamiento paralelo seguro para hilos con Parallel.ForEach — una instancia IronTesseract por hilo — entrega rendimiento de múltiples núcleos sin código de sincronización. Consulte la guía de optimización de velocidad para ajustar el rendimiento de producción.

Coste total predecible. El coste estimado de LEADTOOLS para un equipo de cinco desarrolladores oscila entre 15.000 y 40.000 dólares en el primer año, con un mantenimiento anual que representa aproximadamente entre el 20 % y el 25 % del coste de la licencia para recibir actualizaciones.IronOCR Professional, con un precio de 2999 dólares, cubre a diez desarrolladores y diez ubicaciones de implementación como una compra única y perpetua. Se incluye un año de actualizaciones. El uso continuado tras el periodo de actualización no requiere ningún pago adicional. La página de licencias de IronOCR publica directamente todos los precios por niveles, sin necesidad de una consulta de ventas.

Datos estructurados sin configuración de zona. Después de la migración, los datos a nivel de palabra, línea, y párrafo con coordenadas de cuadro de delimitación están disponibles directamente en OcrResult — no se requieren definiciones de zona. La jerarquía de cinco niveles (Pages, Paragraphs, Lines, Words, Characters) expone cada uno coordenadas de posición y puntuaciones de confianza por elemento. Las aplicaciones que necesitaban la configuración de zonas de LEADTOOLS para lograr una extracción estructurada obtienen datos más completos a partir de una API más sencilla. La página de resultados de OCR resume el modelo de salida completo.

Por favor notaAdobe Acrobat, Kofax OmniPage, LEADTOOLS y Tesseract son marcas registradas de sus respectivos propietarios. Este sitio no está afiliado con, avalado por, o patrocinado por Adobe Inc., Apryse, Google, Kofax, o LEAD Technologies. Todos los nombres de producto, logotipos y marcas son propiedad de sus respectivos dueños. Las comparaciones son solo para fines informativos y reflejan información públicamente disponible en el momento de la redacción.

Preguntas Frecuentes

¿Por qué debería migrar de LEADTOOLS OCR a IronOCR?

Los impulsores comunes incluyen la eliminación de la complejidad de la interoperabilidad COM, la sustitución de la gestión de licencias basada en archivos, la evitación de la facturación por página, la habilitación de la implementación de Docker/contenedores y la adopción de un flujo de trabajo nativo de NuGet que se integre con las herramientas .NET estándar.

¿Cuáles son los principales cambios de código al migrar de LEADTOOLS OCR a IronOCR?

Sustituya las secuencias de inicialización de LEADTOOLS OCR por la instanciación de IronTesseract, elimine la gestión del ciclo de vida COM (patrones explícitos Create/Load/Close) y actualice los nombres de las propiedades de los resultados. El resultado es un número significativamente menor de líneas repetitivas.

¿Cómo instalo IronOCR para comenzar la migración?

Ejecute 'Install-Package IronOcr' en la consola del gestor de paquetes o 'dotnet add package IronOcr' en la CLI. Los paquetes de idiomas son paquetes independientes: 'dotnet add package IronOcr.Languages.French' para el francés, por ejemplo.

¿IronOCR ofrece la misma precisión de reconocimiento óptico de caracteres que LEADTOOLS OCR para documentos comerciales estándar?

IronOCR consigue una gran precisión para contenido empresarial estándar, como facturas, contratos, recibos y formularios mecanografiados. Los filtros de preprocesamiento de imágenes (eliminación de distorsiones, eliminación de ruido, mejora del contraste) mejoran aún más el reconocimiento en entradas degradadas.

¿Cómo gestiona IronOCR los datos de idioma que LEADTOOLS OCR instala por separado?

Los datos de idiomas en IronOCR se distribuyen como paquetes NuGet. dotnet add package IronOcr.Languages.German' instala la compatibilidad con el alemán. No es necesario colocar manualmente los archivos ni las rutas de los directorios.

¿La migración de LEADTOOLS OCR a IronOCR requiere cambios en la infraestructura de implementación?

IronOCR requiere menos cambios de infraestructura que LEADTOOLS OCR. No hay rutas binarias del SDK, ubicaciones de archivos de licencia ni configuraciones del servidor de licencias. El paquete NuGet contiene el motor OCR completo, y la clave de licencia es una cadena establecida en el código de la aplicación.

¿Cómo configuro las licencias de IronOCR después de la migración?

Asigne IronOcr.License.LicenseKey = "YOUR-KEY" en el código de inicio de la aplicación. En Docker o Kubernetes, almacene la clave como una variable de entorno y léala en el inicio. Utilice License.IsValidLicense para validar antes de aceptar tráfico.

¿Puede IronOCR procesar archivos PDF del mismo modo que LEADTOOLS OCR?

Sí, IronOCR lee PDF nativos y escaneados. Instancie IronTesseract, llame a ocr.Read(input) donde input es una ruta PDF u OcrPdfInput, e itere las páginas OcrResult. No es necesario un proceso de renderizado de PDF independiente.

¿Cómo gestiona IronOCR los hilos en el procesamiento de grandes volúmenes?

IronTesseract puede instanciarse de forma segura por subproceso. Gire una instancia por hilo en un Parallel.ForEach o Task pool, ejecute OCR concurrentemente, y disponga de cada instancia cuando termine. No se requiere estado global o bloqueo.

¿Qué formatos de salida admite IronOCR tras la extracción de texto?

IronOCR devuelve resultados estructurados que incluyen texto, coordenadas de palabras, puntuaciones de confianza y estructura de páginas. Las opciones de exportación incluyen texto sin formato, PDF con opción de búsqueda y objetos de resultados estructurados para su procesamiento posterior.

¿Es el precio de IronOCR más predecible que el de LEADTOOLS OCR para escalar cargas de trabajo?

IronOCR utiliza licencias perpetuas de tarifa plana sin cargos por página o volumen. Tanto si procesa 10.000 como 10 millones de páginas, el coste de la licencia permanece constante. Las opciones de licencias por volumen y por equipo se encuentran en la página de precios de IronOCR.

¿Qué sucede con mis pruebas existentes después de migrar de LEADTOOLS OCR a IronOCR?

Las pruebas que validan el contenido de texto extraído deben seguir superándose tras la migración. Las pruebas que validan patrones de llamada a API o el ciclo de vida de objetos COM deberán actualizarse para reflejar el modelo de inicialización y resultados más sencillo de IronOCR.

Kannaopat Udonpant
Ingeniero de Software
Antes de convertirse en Ingeniero de Software, Kannapat completó un doctorado en Recursos Ambientales de la Universidad de Hokkaido en Japón. Mientras perseguía su grado, Kannapat también se convirtió en miembro del Laboratorio de Robótica de Vehículos, que es parte del Departamento de Ingeniería ...
Leer más

Equipo de soporte de Iron

Estamos disponibles online las 24 horas, 5 días a la semana.
Chat
Email
Llámame