Como realizar OCR no NET Maui
Este guia orienta os desenvolvedores .NET em uma migração completa do OCR do Leadtools para o IronOCR . Abrange todas as etapas, desde a substituição de pacotes NuGet até a migração completa do código, com exemplos de antes e depois para os padrões mais afetados pela inicialização do LEADTOOLS, pela estrutura de múltiplos namespaces e pelo modelo de implantação de licenças baseado em arquivos.
Por que migrar do OCR da LEADTOOLS?
O OCR do Leadtools é fornecido dentro de uma plataforma de imagem Enterprise com raízes que remontam a 1990, e a API reflete essa linhagem. A configuração mínima de funcionamento requer quatro pacotes NuGet , quatro namespaces, dois arquivos de licença física implantados em um caminho conhecido, uma camada de codec inicializada antes do mecanismo, a seleção de um tipo de mecanismo entre três opções e uma chamada de inicialização bloqueante que carrega os binários de tempo de execução na memória. Tudo isso ocorre antes que um único caractere seja reconhecido. As equipes que migram para o IronOCR eliminam toda essa camada — um pacote, um namespace, uma linha para definir uma chave de licença.
Implantação de Licença Baseada em Arquivo Falha em Contêineres. LEADTOOLS requer dois arquivos físicos — LEADTOOLS.LIC e LEADTOOLS.LIC.KEY — legíveis em um caminho específico em cada máquina que executa o aplicativo. Em contêineres Docker, esses arquivos devem ser incorporados à imagem (expondo-os no histórico de camadas) ou montados em tempo de execução (exigindo coordenação de volumes em cada ambiente de orquestração). O Azure Functions e o AWS Lambda não possuem um mecanismo para implantação de arquivos de licença sem soluções alternativas. Os pipelines de CI/CD precisam que os arquivos estejam presentes exatamente no caminho usado pela chamada de inicialização; caso contrário, o aplicativo gera um erro antes de processar um único documento. O IronOCR substitui ambos os arquivos por uma string que se encaixa em uma variável de ambiente, um segredo do Kubernetes ou uma referência do Azure Key Vault.
Quatro Pacotes para Uma Tarefa. Um projeto OCR do Leadtools funcional requer Leadtools, Leadtools.Ocr, Leadtools.Codecs, e Leadtools.Forms.DocumentWriters no mínimo. O suporte a PDFs protegidos por senha requer um módulo adicional Leadtools.Pdf que pode não estar incluído no pacote comprado. Cada pacote deve estar presente, ser compatível e ter a mesma versão. O IronOCR é distribuído como um único pacote NuGet . Todas as funcionalidades — entrada nativa de PDF, saída de PDF pesquisável, pré-processamento, leitura de código de barras — estão incluídas.
O Ciclo de Vida do Motor Cria uma Superfície de Manutenção. LEADTOOLS envolve o motor OCR em uma classe de serviço IDisposable não por razões idiomáticas do .NET, mas porque a chamada Shutdown() deve preceder Dispose() ou o aplicativo produz erros. Implementações de produção de processadores de lote do LEADTOOLS normalmente incluem chamadas GC.Collect() / GC.WaitForPendingFinalizers() entre pedaços de documentos para compensar instâncias RasterImage que se acumulam.IronOCR usa blocos padrão using. OcrInput é o único objeto que precisa de descarte.
Confusão sobre pacotes surge na produção. A LEADTOOLS não divulga preços publicamente. O SDK de Imagens de Documentos — o nível que inclui OCR — custa aproximadamente de US$ 3.000 a US$ 8.000 por desenvolvedor por ano. Equipes que compraram o módulo OCR sem o módulo PDF descobrem a lacuna quando RasterSupport.IsLocked() retorna verdadeiro contra documentos protegidos por senha em produção. O nível de precisão do mecanismo OmniPage requer um contrato de licença Kofax separado, além da compra do LEADTOOLS. A IronOCR licencia todos os recursos em cada nível. Não existe relacionamento com fornecedores secundários, nem módulo separado para documentos criptografados.
Custo de Inicialização Afeta Partidas Frias. engine.Startup() é uma chamada bloqueante que carrega arquivos de execução para a memória. Em ambientes sem servidor onde a latência de inicialização a frio é importante — Azure Functions, AWS Lambda — uma inicialização bloqueante de 500 a 2000 ms antes que qualquer trabalho de reconhecimento ocorra é um problema estrutural. O IronOCR utiliza inicialização preguiçosa. A instância IronTesseract inicializa no primeiro uso, e chamadas subsequentes dentro do mesmo processo não pagam nenhum custo de inicialização.
O problema fundamental
O LEADTOOLS requer quatro namespaces, quatro pacotes NuGet e uma cerimônia de inicialização obrigatória antes que um caractere seja reconhecido:
// LEADTOOLS: Four namespaces, four packages, six steps before recognition
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;
using Leadtools.Forms.DocumentWriters;
RasterSupport.SetLicense(licPath, File.ReadAllText(keyPath)); // Step 1: two files on disk
var codecs = new RasterCodecs(); // Step 2: codec layer
var engine = OcrEngineManager.CreateEngine(OcrEngineType.LEAD); // Step 3: engine factory
engine.Startup(codecs, null, null, runtimePath); // Step 4: blocking startup
// ... still need to create document, add page, call Recognize(), extract text
// IronOCR: One namespace, one package, one line
using IronOcr;
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var text = new IronTesseract().Read("document.jpg").Text;
##IronOCR vs. LEADTOOLS OCR: Comparação de Recursos
A tabela a seguir mapeia diretamente as funcionalidades entre as duas bibliotecas.
| Recurso | OCR do Leadtools | IronOCR |
|---|---|---|
| Pacotes NuGet necessários | Mínimo de 4 (mais para PDF) | 1 (IronOcr) |
| Mecanismo de licenciamento | .LIC + .LIC.KEY par de arquivos | Chave de string |
| Implantação de licença | Arquivos em todas as máquinas de produção | Variável de ambiente ou configuração |
| Modelo de preços | US$ 3.000 a US$ 15.000+ por desenvolvedor por ano (estimativa) | $999–$2.999 único perpétuo |
| Inicialização do motor | Manual Startup() com caminho de execução | Automático, preguiçoso |
| Desligamento do motor | Manual Shutdown() antes de Dispose() | Não é necessário |
| Camada de codec | RasterCodecs necessário para todo carregamento de imagem | Não é necessário |
| Entrada de PDF | Loop de rasterização página por página | Nativo LoadPdf() |
| PDF protegido por senha | Requer módulo Leadtools.Pdf separado | Parâmetro embutido Password |
| Saída em PDF pesquisável | DocumentWriter + PdfDocumentOptions + document.Save() | result.SaveAsSearchablePdf() |
| Pré-processamento | Classes de comando separadas (DeskewCommand, DespeckleCommand, etc.) | Métodos de filtro embutidos em OcrInput |
| TIFF de várias páginas | Iteração manual de quadro com CodecsLoadByteOrder | input.LoadImageFrames() |
| Saída estruturada | Nível de página e zona | Página, parágrafo, linha, palavra, caractere com coordenadas |
| Pontuação de confiança | OcrPageRecognizeStatus enum | result.Confidence como percentual |
| Leitura de código de barras | Módulo de código de barras LEADTOOLS separado | Embutido (ReadBarCodes = true) |
| Segurança da rosca | Requer gestão cuidadosa | Completo (um IronTesseract por thread) |
| Idiomas suportados | 60–120 (dependendo do motor) | Mais de 125 idiomas disponíveis via pacotes NuGet |
| Implantação de idiomas | tessdata files or engine-bundled files | Pacote NuGet por idioma |
| Multiplataforma | Compatível com configuração de tempo de execução por plataforma. | Windows, Linux, macOS, Docker, Azure, AWS |
| Implantação do Docker | Arquivos .KEY devem ser montados ou incorporados | Padrão dotnet publish |
| Apoio comercial | Sim | Sim |
Guia rápido: Migração do OCR do LEADTOOLS para o IronOCR
Passo 1: Substitua o pacote NuGet
Remova todos os pacotes 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
Instale o IronOCR a partir do NuGet :
Etapa 2: Atualizar Namespaces
Substitua todas as diretivas LEADTOOLS using por uma única importação IronOCR:
// Before (LEADTOOLS)
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;
using Leadtools.Forms.DocumentWriters;
// After (IronOCR)
using IronOcr;
Etapa 3: Inicializar a licença
Remova todas as chamadas RasterSupport.SetLicense() e as referências aos arquivos .LIC / .LIC.KEY. Adicione a chave de licença do IronOCR na inicialização do aplicativo:
// 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");
A chave de licença pode ser armazenada em qualquer padrão de gerenciamento de segredos .NET padrão — appsettings.json, Azure Key Vault, AWS Secrets Manager, ou um segredo Kubernetes. Não é necessário implantar nenhum arquivo junto com o binário do aplicativo.
Exemplos de migração de código
Remoção do ciclo de vida de inicialização e desligamento do motor
O LEADTOOLS exige um padrão rígido de classe de serviço porque o ciclo de vida do mecanismo deve ser gerenciado explicitamente. O construtor inicia o motor, Dispose() o desliga na ordem correta, e qualquer caminho de código que pule Shutdown() antes de Dispose() produz um erro de execução.
Abordagem OCR do 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();
}
}
Abordagem 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
}
A instância IronTesseract inicializa no primeiro uso. Sem argumentos de construtor, sem caminho de execução, sem chamada Startup(). A classe de serviço acima não precisa implementar IDisposable de forma alguma — o motor é sem estado e os objetos OcrInput usados dentro de cada chamada Read() lidam com sua própria limpeza através do padrão using padrão. O guia de configuração do IronTesseract aborda opções de configuração, incluindo seleção de idioma e otimização de desempenho para cenários de produção.
Processamento em lote de TIFF com múltiplos quadros
LEADTOOLS processa arquivos TIFF com múltiplos quadros consultando o codec para a contagem total de quadros, depois iterando com parâmetros explícitos lastPage em cada chamada _codecs.Load(). Cada imagem de quadro deve ser descartada manualmente, caso contrário, a memória se acumula.
Abordagem OCR do 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;
}
}
Abordagem IronOCR:
using IronOcr;
public class TiffBatchService
{
private readonly IronTesseract _ocr = new IronTesseract();
public List<string> ProcessMultiFrameTiff(string tiffPath)
{
using var input = new OcrInput();
input.LoadImageFrames(tiffPath); // All frames loaded automatically
var result = _ocr.Read(input);
// Per-frame text available through result.Pages
return result.Pages.Select(p => p.Text).ToList();
}
public Dictionary<string, List<string>> ProcessTiffDirectory(string directoryPath)
{
var results = new Dictionary<string, List<string>>();
foreach (var tiffFile in Directory.GetFiles(directoryPath, "*.tiff"))
{
results[tiffFile] = ProcessMultiFrameTiff(tiffFile);
}
return results;
}
// Parallel processing across files — thread-safe out of the box
public Dictionary<string, List<string>> ProcessTiffDirectoryParallel(string directoryPath)
{
var concurrentResults = new System.Collections.Concurrent.ConcurrentDictionary<string, List<string>>();
var tiffFiles = Directory.GetFiles(directoryPath, "*.tiff");
Parallel.ForEach(tiffFiles, tiffFile =>
{
using var input = new OcrInput();
input.LoadImageFrames(tiffFile);
var result = new IronTesseract().Read(input);
concurrentResults[tiffFile] = result.Pages.Select(p => p.Text).ToList();
});
return new Dictionary<string, List<string>>(concurrentResults);
}
}
LoadImageFrames() lê todos os quadros do TIFF em uma única chamada. Sem consulta de contagem de quadros, sem loop, sem descarte explícito por quadro. A versão paralela cria uma instância IronTesseract por thread, que é o padrão correto — veja o exemplo de multithreading para o modelo completo de threading. Para opções de entrada específicas para TIFF, o guia de entrada TIFF e GIF aborda a seleção de quadros e o processamento de múltiplos formatos.
Simplificação do fluxo de trabalho do redator de documentos
A criação de PDF pesquisável do LEADTOOLS requer a configuração de uma instância DocumentWriter no motor, construindo um objeto PdfDocumentOptions com tipo de saída e configurações de sobreposição, aplicando as opções via SetOptions(), e então chamando document.Save() com o enum de formato. Cada uma dessas etapas é um objeto separado e uma chamada de API separada.
Abordagem OCR do 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);
}
}
Abordagem IronOCR:
using IronOcr;
public class SearchablePdfService
{
private readonly IronTesseract _ocr = new IronTesseract();
public void CreateSearchablePdfFromImages(string[] imagePaths, string outputPath)
{
using var input = new OcrInput();
foreach (var imagePath in imagePaths)
input.LoadImage(imagePath);
var result = _ocr.Read(input);
result.SaveAsSearchablePdf(outputPath); // DocumentWriter pipeline: gone
}
public void CreateSearchablePdfFromPdf(string inputPdfPath, string outputPath)
{
using var input = new OcrInput();
input.LoadPdf(inputPdfPath);
var result = _ocr.Read(input);
result.SaveAsSearchablePdf(outputPath);
}
// Get bytes directly — useful for streaming responses in ASP.NET
public byte[] CreateSearchablePdfBytes(string inputPdfPath)
{
using var input = new OcrInput();
input.LoadPdf(inputPdfPath);
return _ocr.Read(input).SaveAsSearchablePdfBytes();
}
}
SaveAsSearchablePdf() substitui toda a cadeia PdfDocumentOptions + SetOptions() + document.Save(). O comportamento da camada de imagem sobre texto é automático. Para obter documentação completa sobre a saída de PDFs pesquisáveis, o guia prático de PDFs pesquisáveis aborda as opções de saída e o exemplo de PDF pesquisável mostra a integração com fluxos de resposta do ASP.NET .
Migração de extração de campo multizona
O OCR baseado em zona do LEADTOOLS usa objetos OcrZone com limites LeadRect, OcrZoneType, e propriedades OcrZoneCharacterFilters. Várias zonas são adicionadas a uma única página e reconhecidas em uma chamada page.Recognize(), depois extraídas pelo índice da zona. O índice da zona corresponde à ordem de inserção, o que significa que o loop de extração deve manter essa ordem.
Abordagem OCR do 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; }
}
Abordagem IronOCR:
using IronOcr;
public class FormFieldExtractor
{
private readonly IronTesseract _ocr = new IronTesseract();
// Each field gets its own CropRectangle-scoped Read() call
// No zone index management, no zone ordering dependency
public InvoiceFields ExtractInvoiceFields(string invoicePath)
{
return new InvoiceFields
{
InvoiceNumber = ReadRegion(invoicePath, 450, 80, 200, 30),
InvoiceDate = ReadRegion(invoicePath, 450, 115, 200, 30),
VendorName = ReadRegion(invoicePath, 50, 150, 300, 40),
TotalAmount = ReadRegion(invoicePath, 450, 600, 200, 30)
};
}
private string ReadRegion(string imagePath, int x, int y, int width, int height)
{
using var input = new OcrInput();
input.LoadImage(imagePath, new CropRectangle(x, y, width, height));
return _ocr.Read(input).Text.Trim();
}
// Batch: extract the same field from many invoices in parallel
public Dictionary<string, string> ExtractInvoiceNumbersBatch(string[] invoicePaths)
{
var results = new System.Collections.Concurrent.ConcurrentDictionary<string, string>();
Parallel.ForEach(invoicePaths, invoicePath =>
{
using var input = new OcrInput();
input.LoadImage(invoicePath, new CropRectangle(450, 80, 200, 30));
results[invoicePath] = new IronTesseract().Read(input).Text.Trim();
});
return new Dictionary<string, string>(results);
}
}
CropRectangle passado diretamente para LoadImage() substitui toda a configuração OcrZone. Não há índice de zona para rastrear, nenhuma chamada page.Zones.Clear() necessária, e nenhuma verificação de status de reconhecimento necessária. O guia de OCR baseado em regiões abrange padrões de extração de região única e de múltiplas regiões. Para um tutorial completo sobre extração de campos de faturas, consulte o tutorial de OCR de faturas .
Extração de dados estruturados com coordenadas de palavras
A saída estruturada do LEADTOOLS opera nos níveis de página e zona. Para obter dados ao nível de palavra com coordenadas da caixa delimitadora, o desenvolvedor acessa objetos OcrWord de dentro de uma zona reconhecida. A API exige que se trabalhe com a coleção de zonas após o reconhecimento e que se itere a lista de palavras por zona.
Abordagem OCR do 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; }
}
Abordagem 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}");
}
}
}
}
A hierarquia de resultados do IronOCR desce de Pages através de Paragraphs, Lines, Words, e Characters. Cada nível expõe X, Y, Width, Height, Text, e Confidence. O padrão de acesso baseado em zonas do LEADTOOLS desaparece — nenhuma iteração de zona é necessária para acessar os dados das palavras. O guia prático de leitura de resultados abrange o modelo de saída completo com padrões de acesso a coordenadas. A documentação de referência da API OcrResult descreve todas as propriedades da hierarquia de resultados.
Referência de mapeamento da API OCR da LEADTOOLS para o IronOCR
| OCR do Leadtools | IronOCR |
|---|---|
RasterSupport.SetLicense(licPath, keyContent) | IronOcr.License.LicenseKey = "key" |
new RasterCodecs() | Não é necessário |
OcrEngineManager.CreateEngine(OcrEngineType.LEAD) | new IronTesseract() |
engine.Startup(codecs, null, null, runtimePath) | Não é necessário |
engine.IsStarted | Não é necessário (sempre pronto) |
engine.Shutdown() | Não é necessário |
engine.Dispose() | Não é necessário |
_codecs.Load(imagePath) | input.LoadImage(imagePath) |
_codecs.Load(path, 0, BgrOrGray, page, page) | input.LoadPdf(path) or input.LoadImageFrames(path) |
_codecs.GetInformation(path, true).TotalPages | Não é necessário — automático |
engine.DocumentManager.CreateDocument() | Não é necessário |
document.Pages.AddPage(image, null) | input.LoadImage(imagePath) |
page.Recognize(null) | ocr.Read(input) (o reconhecimento faz parte de Read()) |
page.GetText(-1) | result.Text |
page.RecognizeStatus | result.Confidence (porcentagem) |
OcrZone { Bounds = new LeadRect(x, y, w, h) } | new CropRectangle(x, y, w, h) |
page.Zones.Clear() | Não é necessário |
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 } | Manipulado automaticamente por SaveAsSearchablePdf() |
engine.DocumentWriterInstance.SetOptions(format, opts) | Não é necessário |
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 e soluções comuns em migrações
Problema 1: Falhas na resolução do caminho do arquivo de licença
LEADTOOLS: RasterSupport.SetLicense() resolve os caminhos de arquivo .LIC e .LIC.KEY relativos ao diretório de trabalho, que difere entre bin/Debug, bin/Release, contêineres Docker, e pools de aplicativos IIS. Um modo comum de falha é um caminho que funciona em desenvolvimento, mas lança "License file not found" em produção porque o diretório de trabalho mudou.
Solução: Apague ambos os arquivos de licença e a chamada SetLicense() inteiramente. Substitua por uma atribuição de string única que lê de uma variável de ambiente:
// 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");
A chave de string se comporta da mesma forma em todos os ambientes. Armazene-o no mesmo gerenciador de segredos já utilizado para as strings de conexão do banco de dados.
Problema 2: Exceção de motor não iniciado
LEADTOOLS: Chamar qualquer método de reconhecimento antes que engine.Startup() seja concluído, ou depois que engine.Shutdown() tenha sido chamado (por exemplo, em uma condição de corrida de descarte antes de uso durante o desligamento), lança um InvalidOperationException com "Engine not started." As classes de serviço de longa duração devem se proteger contra isso com verificações IsStarted.
Solução: IronTesseract não requer chamada de inicialização e não tem estado iniciado/parado. O guardião IsStarted e toda a classe de serviço de ciclo de vida podem ser deletados:
// 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: Acumulação de memória de imagens raster no processamento em lote
LEADTOOLS: O processamento em lote que itera sobre páginas de PDF ou arquivos de imagem cria instâncias RasterImage em um loop. Se qualquer caminho de código falhar em descartar um RasterImage — devido a uma exceção lançada antes que o bloco using saia ou um padrão de descarte manual com uma chamada ausente — as imagens não liberadas se acumulam na memória. O código de produção do LEADTOOLS comumente inclui chamadas GC.Collect() / GC.WaitForPendingFinalizers() entre lotes como um mecanismo compensatório.
Solução: Remova todas as chamadas GC.Collect(). OcrInput é o único IDisposable no pipeline do IronOCR, e é restrito a cada operação em lote com um bloco padrão 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
}
Para obter orientações adicionais sobre otimização de memória, consulte a postagem do blog sobre redução de alocação de memória .
Problema 4: As opções da instância DocumentWriter persistem entre chamadas
LEADTOOLS: engine.DocumentWriterInstance.SetOptions() modifica o estado na instância de escritor compartilhada do motor. Se um caminho de código define PdfDocumentOptions com DocumentType = PdfDocumentType.PdfA e uma chamada subsequente não redefinir essas opções antes de chamar document.Save(), o formato de saída da chamada anterior persiste. Este é um efeito colateral com estado na instância do mecanismo compartilhado.
Solução: O IronOCR não possui estado de gravação compartilhado. Cada chamada SaveAsSearchablePdf() é independente:
// 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);
Cada chamada gera um arquivo PDF padrão com uma imagem sobreposta e uma camada de texto pesquisável. Não existe um estado de opções compartilhado para redefinir entre chamadas.
Problema 5: Pacote incorreto — Módulo ausente em tempo de execução
LEADTOOLS: Equipes que compraram o Document Imaging SDK podem descobrir que os tipos Leadtools.Pdf estão indisponíveis ou que páginas de PDF criptografadas lançam RasterException com RasterExceptionCode.FeatureNotSupported. Isso ocorre quando o pacote adquirido não inclui o módulo PDF, e o erro só aparece em tempo de execução em produção.
Solução: O IronOCR inclui todos os recursos em todos os níveis de licença. Não há módulo PDF separado, nenhum complemento para documentos criptografados, e nenhum fornecedor secundário para um motor de maior precisão. Após instalar o pacote único IronOcr, o conjunto completo de recursos está disponível sem qualquer compra adicional:
Problema 6: Incompatibilidade do índice de zona após reordenação
LEADTOOLS: A extração de zona usa indexação posicional — page.Zones[0].Text, page.Zones[1].Text — que vincula a lógica de extração à ordem de inserção em page.Zones.Add(). Reordenar as definições de zona para corresponder a um layout de formulário alterado interrompe silenciosamente a extração, deslocando todos os índices subsequentes.
**Solução:**IronOCR usa variáveis nomeadas com CropRectangle por campo. Reordenar as definições de campo não afeta a extração, pois cada campo possui escopo independente:
// Each field is independent — reorder freely without breaking extraction
var invoiceNumber = ReadRegion(imagePath, 450, 80, 200, 30);
var invoiceDate = ReadRegion(imagePath, 450, 115, 200, 30);
var vendorName = ReadRegion(imagePath, 50, 150, 300, 40);
var totalAmount = ReadRegion(imagePath, 450, 600, 200, 30);
Lista de verificação para migração de OCR do LEADTOOLS
Pré-migração
Antes de fazer qualquer alteração, audite o código-fonte para identificar todo o uso do 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" .
Anote qual pacote do LEADTOOLS foi adquirido — módulo OCR, módulo PDF e tipo de mecanismo (LEAD vs Tesseract vs OmniPage) — para garantir que os recursos equivalentes do IronOCR sejam testados durante a validação pós-migração.
Migração de código
- Remova todos os pacotes NuGet do LEADTOOLS:
Leadtools,Leadtools.Ocr,Leadtools.Codecs,Leadtools.Forms.DocumentWriters,Leadtools.Pdf - Instale o pacote NuGet
IronOcr - Substitua todas as diretivas LEADTOOLS
usingporusing IronOcr; - Remova os arquivos
.LICe.LIC.KEYdo projeto e dos artefatos de implantação - Substitua
RasterSupport.SetLicense(licPath, keyContent)porIronOcr.License.LicenseKey = "key"na inicialização do aplicativo - Exclua todas as classes de serviço
IDisposableque existem apenas para gerenciar o ciclo de vidaIOcrEngineeRasterCodecs - Substitua
OcrEngineManager.CreateEngine()+engine.Startup()pornew IronTesseract() - Substitua
_codecs.Load(imagePath)porinput.LoadImage(imagePath)dentro de um blocousing var input = new OcrInput() - Substitua os loops de página TIFF de múltiplos quadros por
input.LoadImageFrames(tiffPath) - Substitua o loop de iteração de página PDF por
input.LoadPdf(pdfPath) - Substitua
document.Pages.AddPage()+page.Recognize(null)+page.GetText(-1)porocr.Read(input).Text - Substitua padrões
OcrZone+page.Zones.Add()porinput.LoadImage(path, new CropRectangle(x, y, w, h)) - Substitua
PdfDocumentOptions+DocumentWriterInstance.SetOptions()+document.Save()porresult.SaveAsSearchablePdf(path) - Substitua classes de comando de pré-processamento (
DeskewCommand,DespeckleCommand,AutoBinarizeCommand) porinput.Deskew(),input.DeNoise(),input.Binarize()na instânciaOcrInput - Remova todas as chamadas
GC.Collect()/GC.WaitForPendingFinalizers()adicionadas para compensar o gerenciamento de memória do LEADTOOLS
Pós-migração
- Verificar se o texto reconhecido corresponde à saída do LEADTOOLS em uma amostra representativa de imagens e PDFs.
- Confirme se as pontuações de confiança estão dentro dos intervalos esperados usando
result.Confidence - O teste de processamento TIFF com múltiplos quadros produz o mesmo número de páginas que o loop de iteração de quadros do LEADTOOLS.
- Validar se o PDF pesquisável permite a busca de texto em um leitor de PDF (Adobe Acrobat ou equivalente)
- Testar a extração de campos baseada em zonas comparando-a com valores de campos confiáveis provenientes de faturas ou formulários de produção.
- Verifique se a descriptografia de PDF protegido por senha funciona sem o módulo
Leadtools.Pdfseparado - Execute o processamento em lote sob carga e confirme se não há crescimento de memória (remova todos os
GC.Collect()primeiro) - Teste a implantação do Docker e do CI/CD sem arquivos de licença — confirme se a chave de licença (string) é resolvida corretamente a partir da variável de ambiente.
- Teste o processamento paralelo com
Parallel.ForEachpara verificar a segurança do thread - Confirme se a extração de dados estruturados (
result.Pages,page.Paragraphs,page.Words) retorna coordenadas corretas
Principais benefícios da migração para o IronOCR
Implantação Torna-se Sem Estado. Os arquivos .LIC e .LIC.KEY do LEADTOOLS são artefatos que cada ambiente de implantação deve carregar. Os contêineres que os incorporam expõem os dados de licença no histórico da imagem. Os contêineres que os acomodam precisam de coordenação de volume. Após a migração para o IronOCR, a licença passa a ser uma string em uma variável de ambiente. O artefato de implantação é uma referência padrão do NuGet . Sem arquivos, sem caminhos, sem estratégia de montagem. O guia de implantação do Docker e o guia de implantação do Azure mostram a configuração completa para ambientes conteinerizados.
Quatro Pacotes Tornam-se Um. A migração reduz Leadtools, Leadtools.Ocr, Leadtools.Codecs, Leadtools.Forms.DocumentWriters, e opcionalmente Leadtools.Pdf a uma única referência IronOcr. Todas as funcionalidades — pré-processamento, entrada nativa de PDF, saída de PDF pesquisável, leitura de código de barras, extração de dados estruturados, suporte a mais de 125 idiomas — estão incluídas nesse pacote. O conjunto de funcionalidades não depende do pacote adquirido.
O Processamento em Lote Elimina o Gerenciamento Manual de Memória. O código de lote do LEADTOOLS carrega chamadas defensivas GC.Collect() e descarte explícito RasterImage para prevenir o acúmulo de memória durante execuções multi-documento. O OcrInput do IronOCR delimitado com um bloco using lida com a limpeza automaticamente. Processamento paralelo seguro para threads com Parallel.ForEach — uma instância IronTesseract por thread — fornece throughput multi-core sem código de sincronização. Consulte o guia de otimização de velocidade para ajustar o rendimento da produção.
Custo Total Previsível. O custo estimado do LEADTOOLS para uma equipe de cinco desenvolvedores varia de US$ 15.000 a US$ 40.000 no primeiro ano, com uma taxa anual de manutenção de aproximadamente 20 a 25% do custo da licença para receber atualizações. O IronOCR Professional, por US$ 2.999, cobre dez desenvolvedores e dez locais de implantação em uma compra única e perpétua. Inclui um ano de atualizações. O uso continuado após o período de atualização não requer pagamento adicional. A página de licenciamento do IronOCR publica os preços de todos os níveis diretamente, sem necessidade de consulta de vendas.
Dados Estruturados Sem Configuração de Zona. Após a migração, dados em nível de palavra, linha e parágrafo com coordenadas de caixa delimitadora estão disponíveis diretamente em OcrResult — nenhuma definição de zona é necessária. A hierarquia de cinco níveis (Pages, Paragraphs, Lines, Words, Characters) expõe cada uma coordenadas de posição e pontuações de confiança por elemento. Aplicações que necessitavam de configuração de zona do LEADTOOLS para obter extração estruturada conseguem obter dados mais ricos a partir de uma API mais simples. A página de resultados do OCR resume o modelo de saída completo.
