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
// 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
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 | Sí | Sí |
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
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
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")
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
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
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
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
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
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
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
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
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
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
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"))
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);
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
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)
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)
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" .
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
- Eliminar todos los paquetes NuGet de LEADTOOLS:
Leadtools,Leadtools.Ocr,Leadtools.Codecs,Leadtools.Forms.DocumentWriters,Leadtools.Pdf - Instalar paquete NuGet
IronOcr - Reemplazar todas las directivas
usingde LEADTOOLS conusing IronOcr; - Eliminar los archivos
.LICy.LIC.KEYdel proyecto y los artefactos de despliegue - Reemplazar
RasterSupport.SetLicense(licPath, keyContent)conIronOcr.License.LicenseKey = "key"al inicio de la aplicación - Eliminar todas las clases de servicio
IDisposableque existen solo para gestionar el ciclo de vida deIOcrEngineyRasterCodecs - Reemplazar
OcrEngineManager.CreateEngine()+engine.Startup()connew IronTesseract() - Reemplazar
_codecs.Load(imagePath)coninput.LoadImage(imagePath)dentro de un bloqueusing var input = new OcrInput() - Reemplazar los bucles de páginas TIFF de múltiples marcos con
input.LoadImageFrames(tiffPath) - Reemplazar el bucle de iteración de páginas PDF con
input.LoadPdf(pdfPath) - Reemplazar
document.Pages.AddPage()+page.Recognize(null)+page.GetText(-1)conocr.Read(input).Text - Reemplazar patrones
OcrZone+page.Zones.Add()coninput.LoadImage(path, new CropRectangle(x, y, w, h)) - Reemplazar
PdfDocumentOptions+DocumentWriterInstance.SetOptions()+document.Save()conresult.SaveAsSearchablePdf(path) - Reemplazar clases de comando de preprocesamiento (
DeskewCommand,DespeckleCommand,AutoBinarizeCommand) coninput.Deskew(),input.DeNoise(),input.Binarize()en la instanciaOcrInput - 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.Pdfseparado - 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.ForEachpara 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.
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.

