Como usar Async e Multithreading em C#
A migração doMindeepara o IronOCR transfere o processamento de documentos de servidores em nuvem externos para sua própria infraestrutura, eliminando a cobrança por página, a transmissão de dados por terceiros e a disponibilidade de rede como dependências de tempo de execução. As seções abaixo abordam a substituição de pacotes, atualizações de namespace e quatro cenários práticos de código antes e depois: extração da zona de faturas, processamento de PDFs de recibos com várias páginas, pré-processamento de documentos financeiros e arquivamento de PDFs pesquisáveis.
Por que migrar do Mindee?
Mindee resolve um problema real de forma elegante: envie um documento e receba campos JSON estruturados. Para equipes em que a transmissão de documentos financeiros para uma API externa é aceitável e o volume permanece moderado, isso proporciona resultados rápidos. O gatilho da migração geralmente surge quando uma das várias condições se altera.
Os dados financeiros cruzam uma fronteira de conformidade. O ParseAsync<InvoiceV4> doMindeefaz o upload do documento completo para servidores externos. Códigos IBAN, números de roteamento, EINs de fornecedores e itens de linha detalhados trafegam pela internet pública e são processados em hardware fora do seu controle. A certificação SOC 2 Tipo II confirma que aMindeesegue as práticas de segurança; Não mantém seus documentos dentro do perímetro de segurança. Quando uma revisão de conformidade, um novo contrato com um cliente ou uma regulamentação de residência de dados estabelece um limite no processamento em nuvem de terceiros, a arquitetura daMindeetorna-se desqualificante, independentemente da precisão de sua extração.
O custo por página aumenta gradativamente. O plano inicial oferece 1.000 páginas por mês por US$ 49/mês. O plano Pro oferece 5.000 páginas por mês por US$ 499/mês. PDFs com várias páginas contabilizam todas as páginas. Os processos de desenvolvimento contam para o limite de execução. Uma equipe que processa 4.000 faturas por mês paga US$ 499/mês — US$ 5.988/ano — sendo esse valor zerado anualmente e aumentando proporcionalmente ao crescimento do volume. Com 24.000 páginas por mês, você está negociando preços para Enterprise . A licença perpétua do IronOCR, a US$ 1.499 (nível Professional ), cobre um volume ilimitado de documentos e recupera o investimento em comparação com oMindeePro em menos de quatro meses.
A sondagem assíncrona se propaga por toda a sua pilha. Cada operação doMindeeé assíncrona porque cada operação é uma requisição de rede. Essa cadeia assíncrona obrigatória se propaga em cascata através de controladores, camadas de serviço e classes de trabalho. Quando a rede está indisponível ou o serviço daMindeesofre uma interrupção, o resultado em cascata é uma falha sem alternativa local.IronOCR processa de forma síncrona — trabalho de CPU local — e envolve em Task.Run onde interfaces assíncronas são necessárias para consistência arquitetônica.
O catálogo de API pré-construído tem um limite rígido.Mindeecobre InvoiceV4, ReceiptV5, PassportV1, e um pequeno catálogo de outros tipos de documentos. Qualquer tipo de documento fora dessa lista exige treinamento de modelo personalizado do plano Enterprise: coleta de amostras, rotulagem, tempo de treinamento e tempo de resposta antes que a API esteja disponível. Cada novo tipo de documento que sua empresa necessita representa um contrato com um fornecedor diferente.IronOCR processa qualquer tipo de documento usando a mesma chamada IronTesseract().Read(); Adicionar um novo tipo de documento significa escrever padrões de extração, não negociar um contrato de treinamento.
O gerenciamento de chaves de API aumenta a capacidade operacional. As credenciais do usuário devem ser armazenadas com segurança no servidor, rotacionadas periodicamente e protegidas contra exposição. As regras de saída da rede devem permitir HTTPS de saída para os endpoints do Mindee. A lógica de repetição deve lidar com HttpRequestException e MindeeException das inevitáveis falhas de rede. Remover oMindeeelimina toda essa superfície operacional: sem credenciais, sem regras de saída, sem mecanismos de repetição em torno de operações de E/S de rede.
Ambientes isolados da internet e com restrições estão excluídos. Contratados do governo, subcontratados da área de defesa, redes de saúde com controles rígidos de saída para a internet e sistemas industriais implantados em instalações sem conectividade confiável à internet não podem usar oMindeeconforme projetado. O IronOCR funciona de forma idêntica em contêineres Docker sem acesso externo, em Azure Functions com restrições de saída e em ambientes totalmente isolados da internet. Não há dependência da nuvem em tempo de execução. Consulte a página de licenciamento do IronOCR para obter detalhes sobre os níveis de implantação.
O problema fundamental
Todos os documentos financeiros processados pelaMindeeatravessam uma fronteira de rede que você não controla:
// Mindee: invoice with bank details leaves your infrastructure on this line
var response = await _client.ParseAsync<InvoiceV4>(new LocalInputSource("invoice.pdf"));
// IBAN, routing number, EIN, line items — all transmitted toMindeecloud
// Mindee: invoice with bank details leaves your infrastructure on this line
var response = await _client.ParseAsync<InvoiceV4>(new LocalInputSource("invoice.pdf"));
// IBAN, routing number, EIN, line items — all transmitted toMindeecloud
Imports System.Threading.Tasks
' Mindee: invoice with bank details leaves your infrastructure on this line
Dim response = Await _client.ParseAsync(Of InvoiceV4)(New LocalInputSource("invoice.pdf"))
' IBAN, routing number, EIN, line items — all transmitted to Mindee cloud
// IronOCR: same invoice, same result, zero data transmission
var result = new IronTesseract().Read("invoice.pdf");
// All processing local — bank details never leave your machine
// IronOCR: same invoice, same result, zero data transmission
var result = new IronTesseract().Read("invoice.pdf");
// All processing local — bank details never leave your machine
' IronOCR: same invoice, same result, zero data transmission
Dim result = New IronTesseract().Read("invoice.pdf")
' All processing local — bank details never leave your machine
IronOCR vs Mindee: Comparação de Recursos
A tabela a seguir apresenta as diferenças arquitetônicas e de capacidade que influenciam as decisões de migração.
| Recurso | Mindee | IronOCR |
|---|---|---|
| Local de processamento | Servidores em nuvemMindee | Máquina local / sua infraestrutura |
| É necessário ter internet durante a execução do programa. | Sempre | Nunca |
| Transmissão de dados | Documento completo carregado por chamada | None |
| Custo por documento | Sim (por página, de acordo com o plano) | Não (licença perpétua) |
| Preço inicial | US$ 49/mês (1.000 páginas) | $999 uma vez (Lite) |
| Suporte offline/isolado da internet | Não suportado | Suporte total |
| Implantação local | Não disponível | Única opção |
| Análise de faturas | API pré-construída estruturada (InvoiceV4) |
OCR + extração baseada em região ou expressão regular |
| Análise de recibos | API pré-construída estruturada (ReceiptV5) |
OCR + padrões de extração |
| Tipos de documentos arbitrários | Não é compatível sem treinamento personalizado. | Suporte completo, qualquer documento. |
| Treinamento de modelo personalizado | Plano Enterprise + prazo de entrega | Não é necessário |
| padrão de chamada de API | Assíncrono obrigatório (E/S de rede) | Síncrono (assíncrono opcional) |
| Limitação de taxa | Dependente do plano | None |
| Entrada de PDF | Sim | Sim (nativo, sem conversão) |
| Processamento de PDFs com várias páginas | Sim | Sim |
| Saída em PDF pesquisável | Não | Sim (result.SaveAsSearchablePdf()) |
| Pré-processamento de imagens | Nenhum exposto | Correção de distorção, redução de ruído, contraste, binarização, nitidez, escala |
| Extração baseada em região | Coordenadas do campo em resposta | CropRectangle na entrada |
| Suporte para mais de 125 idiomas | Limitado | Sim (agrupado, sem gerenciamento de tessdata) |
| Leitura de código de barras | Não | Sim (simultaneamente com OCR) |
| Coordenadas de palavras estruturadas | Não | Sim (Páginas, Parágrafos, Linhas, Palavras) |
| Segurança da rosca | N/A (entrada/saída de rede por chamada) | Embutido |
| Multiplataforma | Nuvem (independente de plataforma) | Windows, Linux, macOS, Docker, Azure, AWS |
| Compatibilidade com .NET | .NET Standard 2.0+ | .NET Framework 4.6.2+, .NET 5/6/7/8/9 |
| Apoio comercial | Sim | Sim |
Guia rápido: Migração doMindeepara o IronOCR
Passo 1: Substitua o pacote NuGet
Remova o pacote Mindee:
dotnet remove package Mindee
dotnet remove package Mindee
Instale o IronOCR a partir do NuGet :
dotnet add package IronOcr
Etapa 2: Atualizar Namespaces
Substitua o bloco de namespaceMindeepelo namespace IronOCR:
// Before (Mindee)
using Mindee;
using Mindee.Input;
using Mindee.Product.Invoice;
using Mindee.Product.Receipt;
using Mindee.Product.FinancialDocument;
// After (IronOCR)
using IronOcr;
// Before (Mindee)
using Mindee;
using Mindee.Input;
using Mindee.Product.Invoice;
using Mindee.Product.Receipt;
using Mindee.Product.FinancialDocument;
// After (IronOCR)
using IronOcr;
Imports Mindee
Imports Mindee.Input
Imports Mindee.Product.Invoice
Imports Mindee.Product.Receipt
Imports Mindee.Product.FinancialDocument
Imports IronOcr
Etapa 3: Inicializar a licença
Adicione a chave de licença na inicialização do aplicativo (antes da primeira chamada de OCR):
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Exemplos de migração de código
Extração de campos de fatura usando zonas de documento
OMindeeretorna campos pré-analisados em locais conhecidos porque seu modelo do lado do servidor sabe onde os números de faturas, datas e totais normalmente aparecem. O equivalente do IronOCR usa CropRectangle para ler zonas específicas do documento, correspondendo à consciência espacial da extração doMindeesem transmitir o documento.
Abordagem Mindee:
using Mindee;
using Mindee.Input;
using Mindee.Product.Invoice;
public class MindeeZoneExtractor
{
private readonly MindeeClient _client;
public MindeeZoneExtractor(string apiKey)
{
_client = new MindeeClient(apiKey);
}
public async Task<(string invoiceNumber, string supplierName, decimal? total)>
ExtractKeyFieldsAsync(string filePath)
{
// Document transmitted toMindee— spatial field detection happens server-side
var inputSource = new LocalInputSource(filePath);
var response = await _client.ParseAsync<InvoiceV4>(inputSource);
var prediction = response.Document.Inference.Prediction;
// Fields arrive pre-parsed; no zone configuration required on your end
return (
prediction.InvoiceNumber?.Value,
prediction.SupplierName?.Value,
prediction.TotalAmount?.Value
);
}
}
using Mindee;
using Mindee.Input;
using Mindee.Product.Invoice;
public class MindeeZoneExtractor
{
private readonly MindeeClient _client;
public MindeeZoneExtractor(string apiKey)
{
_client = new MindeeClient(apiKey);
}
public async Task<(string invoiceNumber, string supplierName, decimal? total)>
ExtractKeyFieldsAsync(string filePath)
{
// Document transmitted toMindee— spatial field detection happens server-side
var inputSource = new LocalInputSource(filePath);
var response = await _client.ParseAsync<InvoiceV4>(inputSource);
var prediction = response.Document.Inference.Prediction;
// Fields arrive pre-parsed; no zone configuration required on your end
return (
prediction.InvoiceNumber?.Value,
prediction.SupplierName?.Value,
prediction.TotalAmount?.Value
);
}
}
Imports Mindee
Imports Mindee.Input
Imports Mindee.Product.Invoice
Imports System.Threading.Tasks
Public Class MindeeZoneExtractor
Private ReadOnly _client As MindeeClient
Public Sub New(apiKey As String)
_client = New MindeeClient(apiKey)
End Sub
Public Async Function ExtractKeyFieldsAsync(filePath As String) As Task(Of (invoiceNumber As String, supplierName As String, total As Decimal?))
' Document transmitted to Mindee— spatial field detection happens server-side
Dim inputSource = New LocalInputSource(filePath)
Dim response = Await _client.ParseAsync(Of InvoiceV4)(inputSource)
Dim prediction = response.Document.Inference.Prediction
' Fields arrive pre-parsed; no zone configuration required on your end
Return (
prediction.InvoiceNumber?.Value,
prediction.SupplierName?.Value,
prediction.TotalAmount?.Value
)
End Function
End Class
Abordagem IronOCR:
using IronOcr;
using System.Text.RegularExpressions;
public class IronOcrZoneExtractor
{
private readonly IronTesseract _ocr = new IronTesseract();
public (string invoiceNumber, string supplierName, string total)
ExtractKeyFields(string filePath)
{
// Zone 1: supplier name — top 12% of document, full width
var headerRegion = new CropRectangle(0, 0, 850, 120);
using var headerInput = new OcrInput();
headerInput.LoadImage(filePath, headerRegion);
var supplierResult = _ocr.Read(headerInput);
// Zone 2: invoice number and date — upper-right quadrant
var metaRegion = new CropRectangle(450, 80, 400, 100);
using var metaInput = new OcrInput();
metaInput.LoadImage(filePath, metaRegion);
var metaResult = _ocr.Read(metaInput);
// Zone 3: totals block — bottom-right 20%
var totalsRegion = new CropRectangle(500, 800, 350, 200);
using var totalsInput = new OcrInput();
totalsInput.LoadImage(filePath, totalsRegion);
var totalsResult = _ocr.Read(totalsInput);
var invoiceNumber = Regex.Match(
metaResult.Text,
@"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
RegexOptions.IgnoreCase).Groups[1].Value;
var total = Regex.Match(
totalsResult.Text,
@"Total\s*(?:Due)?\s*:?\s*\$?([\d,]+\.?\d*)",
RegexOptions.IgnoreCase).Groups[1].Value;
// Supplier name is the first substantial text line in the header zone
var supplierName = supplierResult.Text
.Split('\n')
.FirstOrDefault(l => l.Trim().Length > 4)
?.Trim();
return (invoiceNumber, supplierName, total);
}
}
using IronOcr;
using System.Text.RegularExpressions;
public class IronOcrZoneExtractor
{
private readonly IronTesseract _ocr = new IronTesseract();
public (string invoiceNumber, string supplierName, string total)
ExtractKeyFields(string filePath)
{
// Zone 1: supplier name — top 12% of document, full width
var headerRegion = new CropRectangle(0, 0, 850, 120);
using var headerInput = new OcrInput();
headerInput.LoadImage(filePath, headerRegion);
var supplierResult = _ocr.Read(headerInput);
// Zone 2: invoice number and date — upper-right quadrant
var metaRegion = new CropRectangle(450, 80, 400, 100);
using var metaInput = new OcrInput();
metaInput.LoadImage(filePath, metaRegion);
var metaResult = _ocr.Read(metaInput);
// Zone 3: totals block — bottom-right 20%
var totalsRegion = new CropRectangle(500, 800, 350, 200);
using var totalsInput = new OcrInput();
totalsInput.LoadImage(filePath, totalsRegion);
var totalsResult = _ocr.Read(totalsInput);
var invoiceNumber = Regex.Match(
metaResult.Text,
@"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
RegexOptions.IgnoreCase).Groups[1].Value;
var total = Regex.Match(
totalsResult.Text,
@"Total\s*(?:Due)?\s*:?\s*\$?([\d,]+\.?\d*)",
RegexOptions.IgnoreCase).Groups[1].Value;
// Supplier name is the first substantial text line in the header zone
var supplierName = supplierResult.Text
.Split('\n')
.FirstOrDefault(l => l.Trim().Length > 4)
?.Trim();
return (invoiceNumber, supplierName, total);
}
}
Imports IronOcr
Imports System.Text.RegularExpressions
Public Class IronOcrZoneExtractor
Private ReadOnly _ocr As New IronTesseract()
Public Function ExtractKeyFields(filePath As String) As (invoiceNumber As String, supplierName As String, total As String)
' Zone 1: supplier name — top 12% of document, full width
Dim headerRegion As New CropRectangle(0, 0, 850, 120)
Using headerInput As New OcrInput()
headerInput.LoadImage(filePath, headerRegion)
Dim supplierResult = _ocr.Read(headerInput)
' Zone 2: invoice number and date — upper-right quadrant
Dim metaRegion As New CropRectangle(450, 80, 400, 100)
Using metaInput As New OcrInput()
metaInput.LoadImage(filePath, metaRegion)
Dim metaResult = _ocr.Read(metaInput)
' Zone 3: totals block — bottom-right 20%
Dim totalsRegion As New CropRectangle(500, 800, 350, 200)
Using totalsInput As New OcrInput()
totalsInput.LoadImage(filePath, totalsRegion)
Dim totalsResult = _ocr.Read(totalsInput)
Dim invoiceNumber = Regex.Match(metaResult.Text, "Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)", RegexOptions.IgnoreCase).Groups(1).Value
Dim total = Regex.Match(totalsResult.Text, "Total\s*(?:Due)?\s*:?\s*\$?([\d,]+\.?\d*)", RegexOptions.IgnoreCase).Groups(1).Value
' Supplier name is the first substantial text line in the header zone
Dim supplierName = supplierResult.Text.Split(vbLf).FirstOrDefault(Function(l) l.Trim().Length > 4)?.Trim()
Return (invoiceNumber, supplierName, total)
End Using
End Using
End Using
End Function
End Class
O OCR baseado em zonas corresponde à intenção da detecção de campos espaciais do Mindee: ler áreas específicas do documento em vez de aplicar expressões regulares à página inteira. CropRectangle(x, y, width, height) aceita coordenadas de pixels, então o ajuste requer medição contra uma fatura de amostra representativa. O guia de OCR baseado em região cobre a medição de coordenadas e estratégias de sobreposição de zonas. Para faturas com layouts variáveis, combinar extração de zonas com regex de página inteira em result.Text produz os resultados mais confiáveis.
Processamento de relatórios de despesas com várias páginas em PDF
OMindeeaceita um arquivo PDF e processa cada página como uma unidade de documento discreta, retornando um resultado estruturado por página.IronOCR lê PDFs de várias páginas nativamente através do OcrInput.LoadPdf(), processando todas as páginas em uma única chamada e retornando result.Pages com conteúdo por página e coordenadas de palavras.
Abordagem Mindee:
using Mindee;
using Mindee.Input;
using Mindee.Product.Receipt;
public class MindeeExpenseReportProcessor
{
private readonly MindeeClient _client;
public MindeeExpenseReportProcessor(string apiKey)
{
_client = new MindeeClient(apiKey);
}
public async Task<List<ExpenseSummary>> ProcessExpenseReportAsync(string pdfPath)
{
var summaries = new List<ExpenseSummary>();
//Mindeeprocesses per-document; each page of a PDF is a separate upload
// For a 10-page expense report: 10 API calls, 10 pages billed
var inputSource = new LocalInputSource(pdfPath);
var response = await _client.ParseAsync<ReceiptV5>(inputSource);
var prediction = response.Document.Inference.Prediction;
summaries.Add(new ExpenseSummary
{
MerchantName = prediction.SupplierName?.Value,
Date = prediction.Date?.Value?.ToString(),
TotalAmount = prediction.TotalAmount?.Value ?? 0m,
Category = prediction.Category?.Value
});
return summaries;
}
}
using Mindee;
using Mindee.Input;
using Mindee.Product.Receipt;
public class MindeeExpenseReportProcessor
{
private readonly MindeeClient _client;
public MindeeExpenseReportProcessor(string apiKey)
{
_client = new MindeeClient(apiKey);
}
public async Task<List<ExpenseSummary>> ProcessExpenseReportAsync(string pdfPath)
{
var summaries = new List<ExpenseSummary>();
//Mindeeprocesses per-document; each page of a PDF is a separate upload
// For a 10-page expense report: 10 API calls, 10 pages billed
var inputSource = new LocalInputSource(pdfPath);
var response = await _client.ParseAsync<ReceiptV5>(inputSource);
var prediction = response.Document.Inference.Prediction;
summaries.Add(new ExpenseSummary
{
MerchantName = prediction.SupplierName?.Value,
Date = prediction.Date?.Value?.ToString(),
TotalAmount = prediction.TotalAmount?.Value ?? 0m,
Category = prediction.Category?.Value
});
return summaries;
}
}
Imports Mindee
Imports Mindee.Input
Imports Mindee.Product.Receipt
Imports System.Threading.Tasks
Public Class MindeeExpenseReportProcessor
Private ReadOnly _client As MindeeClient
Public Sub New(apiKey As String)
_client = New MindeeClient(apiKey)
End Sub
Public Async Function ProcessExpenseReportAsync(pdfPath As String) As Task(Of List(Of ExpenseSummary))
Dim summaries As New List(Of ExpenseSummary)()
' Mindee processes per-document; each page of a PDF is a separate upload
' For a 10-page expense report: 10 API calls, 10 pages billed
Dim inputSource As New LocalInputSource(pdfPath)
Dim response = Await _client.ParseAsync(Of ReceiptV5)(inputSource)
Dim prediction = response.Document.Inference.Prediction
summaries.Add(New ExpenseSummary With {
.MerchantName = prediction.SupplierName?.Value,
.Date = prediction.Date?.Value?.ToString(),
.TotalAmount = If(prediction.TotalAmount?.Value, 0D),
.Category = prediction.Category?.Value
})
Return summaries
End Function
End Class
Abordagem IronOCR:
using IronOcr;
using System.Text.RegularExpressions;
public class IronOcrExpenseReportProcessor
{
private readonly IronTesseract _ocr = new IronTesseract();
public List<ExpenseSummary> ProcessExpenseReport(string pdfPath)
{
// Load entire multi-page PDF in one call — no per-page upload
using var input = new OcrInput();
input.LoadPdf(pdfPath); // all pages loaded locally
var result = _ocr.Read(input);
var summaries = new List<ExpenseSummary>();
// Each page maps to one receipt in the expense report
foreach (var page in result.Pages)
{
var pageText = page.Text;
// Skip pages without receipt content
if (!pageText.Contains("Total", StringComparison.OrdinalIgnoreCase))
continue;
var merchantName = page.Lines
.FirstOrDefault(l => l.Text.Trim().Length > 4)
?.Text.Trim();
var totalMatch = Regex.Match(
pageText,
@"(?:Total|Amount\s+Due|Grand\s+Total)\s*:?\s*\$?([\d,]+\.\d{2})",
RegexOptions.IgnoreCase);
var dateMatch = Regex.Match(
pageText,
@"\b(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})\b");
var categoryMatch = Regex.Match(
pageText,
@"(?:Category|Dept|Department)\s*:?\s*(.+)",
RegexOptions.IgnoreCase);
summaries.Add(new ExpenseSummary
{
PageNumber = page.PageNumber,
MerchantName = merchantName,
Date = dateMatch.Success ? dateMatch.Value : null,
TotalAmount = totalMatch.Success
? decimal.Parse(totalMatch.Groups[1].Value.Replace(",", ""))
: 0m,
Category = categoryMatch.Success
? categoryMatch.Groups[1].Value.Trim()
: null,
Confidence = page.Words.Any()
? page.Words.Average(w => (double)w.Confidence)
: 0
});
}
return summaries;
}
}
public class ExpenseSummary
{
public int PageNumber { get; set; }
public string MerchantName { get; set; }
public string Date { get; set; }
public decimal TotalAmount { get; set; }
public string Category { get; set; }
public double Confidence { get; set; }
}
using IronOcr;
using System.Text.RegularExpressions;
public class IronOcrExpenseReportProcessor
{
private readonly IronTesseract _ocr = new IronTesseract();
public List<ExpenseSummary> ProcessExpenseReport(string pdfPath)
{
// Load entire multi-page PDF in one call — no per-page upload
using var input = new OcrInput();
input.LoadPdf(pdfPath); // all pages loaded locally
var result = _ocr.Read(input);
var summaries = new List<ExpenseSummary>();
// Each page maps to one receipt in the expense report
foreach (var page in result.Pages)
{
var pageText = page.Text;
// Skip pages without receipt content
if (!pageText.Contains("Total", StringComparison.OrdinalIgnoreCase))
continue;
var merchantName = page.Lines
.FirstOrDefault(l => l.Text.Trim().Length > 4)
?.Text.Trim();
var totalMatch = Regex.Match(
pageText,
@"(?:Total|Amount\s+Due|Grand\s+Total)\s*:?\s*\$?([\d,]+\.\d{2})",
RegexOptions.IgnoreCase);
var dateMatch = Regex.Match(
pageText,
@"\b(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})\b");
var categoryMatch = Regex.Match(
pageText,
@"(?:Category|Dept|Department)\s*:?\s*(.+)",
RegexOptions.IgnoreCase);
summaries.Add(new ExpenseSummary
{
PageNumber = page.PageNumber,
MerchantName = merchantName,
Date = dateMatch.Success ? dateMatch.Value : null,
TotalAmount = totalMatch.Success
? decimal.Parse(totalMatch.Groups[1].Value.Replace(",", ""))
: 0m,
Category = categoryMatch.Success
? categoryMatch.Groups[1].Value.Trim()
: null,
Confidence = page.Words.Any()
? page.Words.Average(w => (double)w.Confidence)
: 0
});
}
return summaries;
}
}
public class ExpenseSummary
{
public int PageNumber { get; set; }
public string MerchantName { get; set; }
public string Date { get; set; }
public decimal TotalAmount { get; set; }
public string Category { get; set; }
public double Confidence { get; set; }
}
Imports IronOcr
Imports System.Text.RegularExpressions
Public Class IronOcrExpenseReportProcessor
Private ReadOnly _ocr As New IronTesseract()
Public Function ProcessExpenseReport(pdfPath As String) As List(Of ExpenseSummary)
' Load entire multi-page PDF in one call — no per-page upload
Using input As New OcrInput()
input.LoadPdf(pdfPath) ' all pages loaded locally
Dim result = _ocr.Read(input)
Dim summaries As New List(Of ExpenseSummary)()
' Each page maps to one receipt in the expense report
For Each page In result.Pages
Dim pageText = page.Text
' Skip pages without receipt content
If Not pageText.Contains("Total", StringComparison.OrdinalIgnoreCase) Then
Continue For
End If
Dim merchantName = page.Lines _
.FirstOrDefault(Function(l) l.Text.Trim().Length > 4) _
?.Text.Trim()
Dim totalMatch = Regex.Match(
pageText,
"(?:Total|Amount\s+Due|Grand\s+Total)\s*:?\s*\$?([\d,]+\.\d{2})",
RegexOptions.IgnoreCase)
Dim dateMatch = Regex.Match(
pageText,
"\b(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})\b")
Dim categoryMatch = Regex.Match(
pageText,
"(?:Category|Dept|Department)\s*:?\s*(.+)",
RegexOptions.IgnoreCase)
summaries.Add(New ExpenseSummary With {
.PageNumber = page.PageNumber,
.MerchantName = merchantName,
.Date = If(dateMatch.Success, dateMatch.Value, Nothing),
.TotalAmount = If(totalMatch.Success, Decimal.Parse(totalMatch.Groups(1).Value.Replace(",", "")), 0D),
.Category = If(categoryMatch.Success, categoryMatch.Groups(1).Value.Trim(), Nothing),
.Confidence = If(page.Words.Any(), page.Words.Average(Function(w) CDbl(w.Confidence)), 0)
})
Next
Return summaries
End Using
End Function
End Class
Public Class ExpenseSummary
Public Property PageNumber As Integer
Public Property MerchantName As String
Public Property Date As String
Public Property TotalAmount As Decimal
Public Property Category As String
Public Property Confidence As Double
End Class
O processamento de um relatório de despesas em PDF de 10 páginas com oMindeegera 10 chamadas de API e 10 páginas faturadas. O IronOCR processa o mesmo arquivo em uma única chamada local, sem custo por página e sem viagens de ida e volta na rede por página. A coleção result.Pages fornece texto por página, caixas de delimitação em nível de palavra e posições de linha para extração consciente de layout. Consulte o guia de entrada de PDF para carregamento de PDFs protegidos por senha e seleção de intervalo de páginas, e o guia de resultados de leitura estruturada para trabalhar com coordenadas de palavras.
Fluxo de pré-processamento de faturas digitalizadas
O processamento em nuvem doMindeeaplica sua própria normalização de imagem antes de executar a extração de campos. A qualidade dessa normalização é opaca — você não tem controle sobre ela nem visibilidade sobre qual pré-processamento foi executado. Quando uma fatura digitalizada apresenta distorção, sombra ou baixo contraste, oMindeeretorna pontuações de confiança mais baixas, sem nenhum mecanismo para que você possa melhorar a digitalização antes do envio. O IronOCR expõe explicitamente o pipeline de pré-processamento, permitindo que você aplique exatamente as correções necessárias a um determinado documento antes da execução do reconhecimento.
Abordagem Mindee:
using Mindee;
using Mindee.Input;
using Mindee.Product.Invoice;
public class MindeeScanProcessor
{
private readonly MindeeClient _client;
public MindeeScanProcessor(string apiKey)
{
_client = new MindeeClient(apiKey);
}
public async Task<ScanResult> ProcessScannedInvoiceAsync(string scanPath)
{
//Mindeeapplies internal normalization — no developer control over preprocessing
var inputSource = new LocalInputSource(scanPath);
var response = await _client.ParseAsync<InvoiceV4>(inputSource);
var prediction = response.Document.Inference.Prediction;
return new ScanResult
{
InvoiceNumber = prediction.InvoiceNumber?.Value,
Total = prediction.TotalAmount?.Value,
// Per-field confidence: only proxy for scan quality
InvoiceNumberConfidence = prediction.InvoiceNumber?.Confidence,
TotalConfidence = prediction.TotalAmount?.Confidence
};
}
}
using Mindee;
using Mindee.Input;
using Mindee.Product.Invoice;
public class MindeeScanProcessor
{
private readonly MindeeClient _client;
public MindeeScanProcessor(string apiKey)
{
_client = new MindeeClient(apiKey);
}
public async Task<ScanResult> ProcessScannedInvoiceAsync(string scanPath)
{
//Mindeeapplies internal normalization — no developer control over preprocessing
var inputSource = new LocalInputSource(scanPath);
var response = await _client.ParseAsync<InvoiceV4>(inputSource);
var prediction = response.Document.Inference.Prediction;
return new ScanResult
{
InvoiceNumber = prediction.InvoiceNumber?.Value,
Total = prediction.TotalAmount?.Value,
// Per-field confidence: only proxy for scan quality
InvoiceNumberConfidence = prediction.InvoiceNumber?.Confidence,
TotalConfidence = prediction.TotalAmount?.Confidence
};
}
}
Imports Mindee
Imports Mindee.Input
Imports Mindee.Product.Invoice
Imports System.Threading.Tasks
Public Class MindeeScanProcessor
Private ReadOnly _client As MindeeClient
Public Sub New(apiKey As String)
_client = New MindeeClient(apiKey)
End Sub
Public Async Function ProcessScannedInvoiceAsync(scanPath As String) As Task(Of ScanResult)
' Mindee applies internal normalization — no developer control over preprocessing
Dim inputSource = New LocalInputSource(scanPath)
Dim response = Await _client.ParseAsync(Of InvoiceV4)(inputSource)
Dim prediction = response.Document.Inference.Prediction
Return New ScanResult With {
.InvoiceNumber = prediction.InvoiceNumber?.Value,
.Total = prediction.TotalAmount?.Value,
' Per-field confidence: only proxy for scan quality
.InvoiceNumberConfidence = prediction.InvoiceNumber?.Confidence,
.TotalConfidence = prediction.TotalAmount?.Confidence
}
End Function
End Class
Abordagem IronOCR:
using IronOcr;
using System.Text.RegularExpressions;
public class IronOcrScanProcessor
{
private readonly IronTesseract _ocr = new IronTesseract();
public ScanResult ProcessScannedInvoice(string scanPath)
{
// First pass: read without preprocessing
var quickResult = _ocr.Read(scanPath);
OcrResult result;
if (quickResult.Confidence < 75)
{
// Low confidence — apply full preprocessing pipeline
using var input = new OcrInput();
input.LoadImage(scanPath);
input.Deskew(); // correct page rotation up to ±45 degrees
input.DeNoise(); // remove scanner artifacts and speckle
input.Contrast(); // normalize contrast for faded or overexposed scans
input.Sharpen(); // sharpen blurred text edges
result = _ocr.Read(input);
}
else
{
result = quickResult;
}
var invoiceNumber = Regex.Match(
result.Text,
@"Invoice\s*(?:Number|#|No\.?)?\s*:?\s*([A-Z0-9\-]+)",
RegexOptions.IgnoreCase).Groups[1].Value;
var total = Regex.Match(
result.Text,
@"(?:Total|Amount\s+Due)\s*:?\s*\$?([\d,]+\.\d{2})",
RegexOptions.IgnoreCase).Groups[1].Value;
return new ScanResult
{
InvoiceNumber = invoiceNumber,
Total = total,
DocumentConfidence = result.Confidence,
PreprocessingApplied = quickResult.Confidence < 75
};
}
}
public class ScanResult
{
public string InvoiceNumber { get; set; }
public string Total { get; set; }
public double DocumentConfidence { get; set; }
public bool PreprocessingApplied { get; set; }
}
using IronOcr;
using System.Text.RegularExpressions;
public class IronOcrScanProcessor
{
private readonly IronTesseract _ocr = new IronTesseract();
public ScanResult ProcessScannedInvoice(string scanPath)
{
// First pass: read without preprocessing
var quickResult = _ocr.Read(scanPath);
OcrResult result;
if (quickResult.Confidence < 75)
{
// Low confidence — apply full preprocessing pipeline
using var input = new OcrInput();
input.LoadImage(scanPath);
input.Deskew(); // correct page rotation up to ±45 degrees
input.DeNoise(); // remove scanner artifacts and speckle
input.Contrast(); // normalize contrast for faded or overexposed scans
input.Sharpen(); // sharpen blurred text edges
result = _ocr.Read(input);
}
else
{
result = quickResult;
}
var invoiceNumber = Regex.Match(
result.Text,
@"Invoice\s*(?:Number|#|No\.?)?\s*:?\s*([A-Z0-9\-]+)",
RegexOptions.IgnoreCase).Groups[1].Value;
var total = Regex.Match(
result.Text,
@"(?:Total|Amount\s+Due)\s*:?\s*\$?([\d,]+\.\d{2})",
RegexOptions.IgnoreCase).Groups[1].Value;
return new ScanResult
{
InvoiceNumber = invoiceNumber,
Total = total,
DocumentConfidence = result.Confidence,
PreprocessingApplied = quickResult.Confidence < 75
};
}
}
public class ScanResult
{
public string InvoiceNumber { get; set; }
public string Total { get; set; }
public double DocumentConfidence { get; set; }
public bool PreprocessingApplied { get; set; }
}
Imports IronOcr
Imports System.Text.RegularExpressions
Public Class IronOcrScanProcessor
Private ReadOnly _ocr As New IronTesseract()
Public Function ProcessScannedInvoice(scanPath As String) As ScanResult
' First pass: read without preprocessing
Dim quickResult = _ocr.Read(scanPath)
Dim result As OcrResult
If quickResult.Confidence < 75 Then
' Low confidence — apply full preprocessing pipeline
Using input As New OcrInput()
input.LoadImage(scanPath)
input.Deskew() ' correct page rotation up to ±45 degrees
input.DeNoise() ' remove scanner artifacts and speckle
input.Contrast() ' normalize contrast for faded or overexposed scans
input.Sharpen() ' sharpen blurred text edges
result = _ocr.Read(input)
End Using
Else
result = quickResult
End If
Dim invoiceNumber = Regex.Match(
result.Text,
"Invoice\s*(?:Number|#|No\.?)?\s*:?\s*([A-Z0-9\-]+)",
RegexOptions.IgnoreCase).Groups(1).Value
Dim total = Regex.Match(
result.Text,
"(?:Total|Amount\s+Due)\s*:?\s*\$?([\d,]+\.\d{2})",
RegexOptions.IgnoreCase).Groups(1).Value
Return New ScanResult With {
.InvoiceNumber = invoiceNumber,
.Total = total,
.DocumentConfidence = result.Confidence,
.PreprocessingApplied = quickResult.Confidence < 75
}
End Function
End Class
Public Class ScanResult
Public Property InvoiceNumber As String
Public Property Total As String
Public Property DocumentConfidence As Double
Public Property PreprocessingApplied As Boolean
End Class
O padrão de duas passagens — leitura rápida primeiro, pré-processamento em baixa confiança — evita a sobrecarga do pré-processamento completo em varreduras limpas. Para cenários de qualidade de digitalização mais comuns em contas a pagar (digitalizações de mesa levemente inclinadas, artefatos de fax, faturas fotocopiadas), Deskew() e DeNoise() juntos recuperam a maioria da precisão de reconhecimento. O guia de correção da qualidade da imagem documenta todos os filtros disponíveis, com orientações sobre quais combinações funcionam melhor para diferentes defeitos de digitalização. O guia de correção de orientação de imagem abrange a rotação automática de documentos digitalizados de cabeça para baixo.
Arquivamento de PDFs pesquisáveis para documentos financeiros
OMindeenão gera nenhum arquivo PDF. Sua arquitetura é somente de entrada: envie um documento e receba campos JSON. As equipes que arquivam faturas digitalizadas em um formato pesquisável devem manter esse fluxo de trabalho separadamente da extração do Mindee. O IronOCR gera PDFs pesquisáveis diretamente a partir da mesma etapa de reconhecimento usada para extração de campos — sem biblioteca adicional, sem segunda etapa de processamento.
Abordagem Mindee:
using Mindee;
using Mindee.Input;
using Mindee.Product.Invoice;
public class MindeeArchiveProcessor
{
private readonly MindeeClient _client;
public MindeeArchiveProcessor(string apiKey)
{
_client = new MindeeClient(apiKey);
}
public async Task ArchiveInvoiceAsync(string scanPath, string archivePath)
{
// Extract fields viaMindee(document transmitted to cloud)
var inputSource = new LocalInputSource(scanPath);
var response = await _client.ParseAsync<InvoiceV4>(inputSource);
var prediction = response.Document.Inference.Prediction;
//Mindeereturns no PDF output — searchable PDF requires a separate library
// (e.g., iTextSharp, PdfSharp, or a separate OCR library)
// The scan at scanPath is the only PDF available for archiving;
// it remains image-only with no embedded text layer
Console.WriteLine($"Fields extracted. Searchable PDF: not available from Mindee.");
Console.WriteLine($"Invoice: {prediction.InvoiceNumber?.Value}");
}
}
using Mindee;
using Mindee.Input;
using Mindee.Product.Invoice;
public class MindeeArchiveProcessor
{
private readonly MindeeClient _client;
public MindeeArchiveProcessor(string apiKey)
{
_client = new MindeeClient(apiKey);
}
public async Task ArchiveInvoiceAsync(string scanPath, string archivePath)
{
// Extract fields viaMindee(document transmitted to cloud)
var inputSource = new LocalInputSource(scanPath);
var response = await _client.ParseAsync<InvoiceV4>(inputSource);
var prediction = response.Document.Inference.Prediction;
//Mindeereturns no PDF output — searchable PDF requires a separate library
// (e.g., iTextSharp, PdfSharp, or a separate OCR library)
// The scan at scanPath is the only PDF available for archiving;
// it remains image-only with no embedded text layer
Console.WriteLine($"Fields extracted. Searchable PDF: not available from Mindee.");
Console.WriteLine($"Invoice: {prediction.InvoiceNumber?.Value}");
}
}
Imports Mindee
Imports Mindee.Input
Imports Mindee.Product.Invoice
Imports System.Threading.Tasks
Public Class MindeeArchiveProcessor
Private ReadOnly _client As MindeeClient
Public Sub New(apiKey As String)
_client = New MindeeClient(apiKey)
End Sub
Public Async Function ArchiveInvoiceAsync(scanPath As String, archivePath As String) As Task
' Extract fields via Mindee (document transmitted to cloud)
Dim inputSource = New LocalInputSource(scanPath)
Dim response = Await _client.ParseAsync(Of InvoiceV4)(inputSource)
Dim prediction = response.Document.Inference.Prediction
' Mindee returns no PDF output — searchable PDF requires a separate library
' (e.g., iTextSharp, PdfSharp, or a separate OCR library)
' The scan at scanPath is the only PDF available for archiving;
' it remains image-only with no embedded text layer
Console.WriteLine($"Fields extracted. Searchable PDF: not available from Mindee.")
Console.WriteLine($"Invoice: {prediction.InvoiceNumber?.Value}")
End Function
End Class
Abordagem IronOCR:
using IronOcr;
using System.Text.RegularExpressions;
public class IronOcrArchiveProcessor
{
private readonly IronTesseract _ocr = new IronTesseract();
public ArchiveResult ArchiveInvoice(string scanPath, string archiveOutputPath)
{
// Apply preprocessing before recognition and archiving
using var input = new OcrInput();
input.LoadPdf(scanPath); // works with both PDF scans and image files
input.Deskew();
input.DeNoise();
var result = _ocr.Read(input);
// Extract fields from the same recognition pass
var invoiceNumber = Regex.Match(
result.Text,
@"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
RegexOptions.IgnoreCase).Groups[1].Value;
var vendor = result.Pages.FirstOrDefault()?.Lines
.FirstOrDefault(l => l.Text.Trim().Length > 4)
?.Text.Trim();
var totalMatch = Regex.Match(
result.Text,
@"Total\s*(?:Due)?\s*:?\s*\$?([\d,]+\.\d{2})",
RegexOptions.IgnoreCase);
// Write searchable PDF to archive in one call — no separate library needed
result.SaveAsSearchablePdf(archiveOutputPath);
return new ArchiveResult
{
InvoiceNumber = invoiceNumber,
VendorName = vendor,
Total = totalMatch.Success ? totalMatch.Groups[1].Value : null,
ArchivePath = archiveOutputPath,
DocumentConfidence = result.Confidence,
PageCount = result.Pages.Count()
};
}
}
public class ArchiveResult
{
public string InvoiceNumber { get; set; }
public string VendorName { get; set; }
public string Total { get; set; }
public string ArchivePath { get; set; }
public double DocumentConfidence { get; set; }
public int PageCount { get; set; }
}
using IronOcr;
using System.Text.RegularExpressions;
public class IronOcrArchiveProcessor
{
private readonly IronTesseract _ocr = new IronTesseract();
public ArchiveResult ArchiveInvoice(string scanPath, string archiveOutputPath)
{
// Apply preprocessing before recognition and archiving
using var input = new OcrInput();
input.LoadPdf(scanPath); // works with both PDF scans and image files
input.Deskew();
input.DeNoise();
var result = _ocr.Read(input);
// Extract fields from the same recognition pass
var invoiceNumber = Regex.Match(
result.Text,
@"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
RegexOptions.IgnoreCase).Groups[1].Value;
var vendor = result.Pages.FirstOrDefault()?.Lines
.FirstOrDefault(l => l.Text.Trim().Length > 4)
?.Text.Trim();
var totalMatch = Regex.Match(
result.Text,
@"Total\s*(?:Due)?\s*:?\s*\$?([\d,]+\.\d{2})",
RegexOptions.IgnoreCase);
// Write searchable PDF to archive in one call — no separate library needed
result.SaveAsSearchablePdf(archiveOutputPath);
return new ArchiveResult
{
InvoiceNumber = invoiceNumber,
VendorName = vendor,
Total = totalMatch.Success ? totalMatch.Groups[1].Value : null,
ArchivePath = archiveOutputPath,
DocumentConfidence = result.Confidence,
PageCount = result.Pages.Count()
};
}
}
public class ArchiveResult
{
public string InvoiceNumber { get; set; }
public string VendorName { get; set; }
public string Total { get; set; }
public string ArchivePath { get; set; }
public double DocumentConfidence { get; set; }
public int PageCount { get; set; }
}
Imports IronOcr
Imports System.Text.RegularExpressions
Imports System.Linq
Public Class IronOcrArchiveProcessor
Private ReadOnly _ocr As New IronTesseract()
Public Function ArchiveInvoice(scanPath As String, archiveOutputPath As String) As ArchiveResult
' Apply preprocessing before recognition and archiving
Using input As New OcrInput()
input.LoadPdf(scanPath) ' works with both PDF scans and image files
input.Deskew()
input.DeNoise()
Dim result = _ocr.Read(input)
' Extract fields from the same recognition pass
Dim invoiceNumber = Regex.Match(result.Text, "Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)", RegexOptions.IgnoreCase).Groups(1).Value
Dim vendor = result.Pages.FirstOrDefault()?.Lines.FirstOrDefault(Function(l) l.Text.Trim().Length > 4)?.Text.Trim()
Dim totalMatch = Regex.Match(result.Text, "Total\s*(?:Due)?\s*:?\s*\$?([\d,]+\.\d{2})", RegexOptions.IgnoreCase)
' Write searchable PDF to archive in one call — no separate library needed
result.SaveAsSearchablePdf(archiveOutputPath)
Return New ArchiveResult With {
.InvoiceNumber = invoiceNumber,
.VendorName = vendor,
.Total = If(totalMatch.Success, totalMatch.Groups(1).Value, Nothing),
.ArchivePath = archiveOutputPath,
.DocumentConfidence = result.Confidence,
.PageCount = result.Pages.Count()
}
End Using
End Function
End Class
Public Class ArchiveResult
Public Property InvoiceNumber As String
Public Property VendorName As String
Public Property Total As String
Public Property ArchivePath As String
Public Property DocumentConfidence As Double
Public Property PageCount As Integer
End Class
result.SaveAsSearchablePdf() insere a camada de texto reconhecido diretamente no PDF de saída, produzindo um arquivo onde cada palavra é selecionável e pesquisável em qualquer visualizador de PDF ou sistema de gerenciamento de conteúdo empresarial. A extração de campo e o arquivamento são executados na mesma passagem — uma chamada _ocr.Read(input) fornece tanto result.Text para correspondência de padrões quanto result.SaveAsSearchablePdf() para o arquivo. O guia de PDF pesquisável cobre opções de saída, incluindo exportação HOCR e a página de caso de uso do PDF OCR cobre padrões de implantação de produção para pipelines de arquivamento de documentos.
Removendo o endpoint personalizado FinancialDocumentV1
A API FinancialDocumentV1 doMindeelida tanto com faturas quanto com recibos detectando automaticamente o tipo de documento. As equipes que o utilizam eliminam a lógica de ramificação, ao custo de uma maior dependência da nuvem — todos os documentos, independentemente do tipo, são carregados. A substituição pelo IronOCR utiliza dados posicionais em nível de palavra para detectar a estrutura do documento e direcionar a lógica de extração sem qualquer chamada à nuvem.
Abordagem Mindee:
using Mindee;
using Mindee.Input;
using Mindee.Product.FinancialDocument;
public class MindeeFinancialRouter
{
private readonly MindeeClient _client;
public MindeeFinancialRouter(string apiKey)
{
_client = new MindeeClient(apiKey);
}
public async Task<FinancialSummary> ProcessFinancialDocumentAsync(string filePath)
{
// All financial documents uploaded for auto-detection and parsing
var inputSource = new LocalInputSource(filePath);
var response = await _client.ParseAsync<FinancialDocumentV1>(inputSource);
var prediction = response.Document.Inference.Prediction;
return new FinancialSummary
{
DocumentType = prediction.DocumentType?.Value, // "INVOICE" or "RECEIPT"
InvoiceNumber = prediction.InvoiceNumber?.Value,
Date = prediction.Date?.Value?.ToString(),
TotalAmount = prediction.TotalAmount?.Value,
SupplierName = prediction.SupplierName?.Value,
CustomerName = prediction.CustomerName?.Value
};
}
}
using Mindee;
using Mindee.Input;
using Mindee.Product.FinancialDocument;
public class MindeeFinancialRouter
{
private readonly MindeeClient _client;
public MindeeFinancialRouter(string apiKey)
{
_client = new MindeeClient(apiKey);
}
public async Task<FinancialSummary> ProcessFinancialDocumentAsync(string filePath)
{
// All financial documents uploaded for auto-detection and parsing
var inputSource = new LocalInputSource(filePath);
var response = await _client.ParseAsync<FinancialDocumentV1>(inputSource);
var prediction = response.Document.Inference.Prediction;
return new FinancialSummary
{
DocumentType = prediction.DocumentType?.Value, // "INVOICE" or "RECEIPT"
InvoiceNumber = prediction.InvoiceNumber?.Value,
Date = prediction.Date?.Value?.ToString(),
TotalAmount = prediction.TotalAmount?.Value,
SupplierName = prediction.SupplierName?.Value,
CustomerName = prediction.CustomerName?.Value
};
}
}
Imports Mindee
Imports Mindee.Input
Imports Mindee.Product.FinancialDocument
Imports System.Threading.Tasks
Public Class MindeeFinancialRouter
Private ReadOnly _client As MindeeClient
Public Sub New(apiKey As String)
_client = New MindeeClient(apiKey)
End Sub
Public Async Function ProcessFinancialDocumentAsync(filePath As String) As Task(Of FinancialSummary)
' All financial documents uploaded for auto-detection and parsing
Dim inputSource = New LocalInputSource(filePath)
Dim response = Await _client.ParseAsync(Of FinancialDocumentV1)(inputSource)
Dim prediction = response.Document.Inference.Prediction
Return New FinancialSummary With {
.DocumentType = prediction.DocumentType?.Value, ' "INVOICE" or "RECEIPT"
.InvoiceNumber = prediction.InvoiceNumber?.Value,
.Date = prediction.Date?.Value?.ToString(),
.TotalAmount = prediction.TotalAmount?.Value,
.SupplierName = prediction.SupplierName?.Value,
.CustomerName = prediction.CustomerName?.Value
}
End Function
End Class
Abordagem IronOCR:
using IronOcr;
using System.Text.RegularExpressions;
public class IronOcrFinancialRouter
{
private readonly IronTesseract _ocr = new IronTesseract();
public FinancialSummary ProcessFinancialDocument(string filePath)
{
var result = _ocr.Read(filePath);
var text = result.Text;
// Detect document type using structural keywords from word data
var isInvoice = DetectInvoice(result);
if (isInvoice)
{
return new FinancialSummary
{
DocumentType = "INVOICE",
InvoiceNumber = Regex.Match(text,
@"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
RegexOptions.IgnoreCase).Groups[1].Value,
Date = Regex.Match(text,
@"(?:Invoice\s+)?Date\s*:?\s*(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})",
RegexOptions.IgnoreCase).Groups[1].Value,
TotalAmount = ParseAmount(text,
@"Total\s*(?:Due|Amount)?\s*:?\s*\$?([\d,]+\.\d{2})"),
SupplierName = ExtractTopLine(result),
CustomerName = Regex.Match(text,
@"Bill\s+To\s*:?\s*\n?(.+)",
RegexOptions.IgnoreCase).Groups[1].Value.Trim()
};
}
else
{
// Receipt structure: merchant at top, no "Bill To" section
return new FinancialSummary
{
DocumentType = "RECEIPT",
Date = Regex.Match(text,
@"\b(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})\b").Value,
TotalAmount = ParseAmount(text,
@"(?:Total|Amount\s+Due|Grand\s+Total)\s*:?\s*\$?([\d,]+\.\d{2})"),
SupplierName = ExtractTopLine(result)
};
}
}
private bool DetectInvoice(OcrResult result)
{
// Invoice indicators: "Invoice", "Bill To", "Due Date", line items with quantities
var text = result.Text;
var invoiceSignals = new[] { "Invoice", "Bill To", "Due Date", "Purchase Order" };
return invoiceSignals.Any(s =>
text.IndexOf(s, StringComparison.OrdinalIgnoreCase) >= 0);
}
private string ExtractTopLine(OcrResult result)
{
return result.Pages
.FirstOrDefault()
?.Lines
.FirstOrDefault(l => l.Text.Trim().Length > 4)
?.Text.Trim();
}
private decimal? ParseAmount(string text, string pattern)
{
var match = Regex.Match(text, pattern, RegexOptions.IgnoreCase);
if (match.Success &&
decimal.TryParse(match.Groups[1].Value.Replace(",", ""), out var val))
return val;
return null;
}
}
public class FinancialSummary
{
public string DocumentType { get; set; }
public string InvoiceNumber { get; set; }
public string Date { get; set; }
public decimal? TotalAmount { get; set; }
public string SupplierName { get; set; }
public string CustomerName { get; set; }
}
using IronOcr;
using System.Text.RegularExpressions;
public class IronOcrFinancialRouter
{
private readonly IronTesseract _ocr = new IronTesseract();
public FinancialSummary ProcessFinancialDocument(string filePath)
{
var result = _ocr.Read(filePath);
var text = result.Text;
// Detect document type using structural keywords from word data
var isInvoice = DetectInvoice(result);
if (isInvoice)
{
return new FinancialSummary
{
DocumentType = "INVOICE",
InvoiceNumber = Regex.Match(text,
@"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
RegexOptions.IgnoreCase).Groups[1].Value,
Date = Regex.Match(text,
@"(?:Invoice\s+)?Date\s*:?\s*(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})",
RegexOptions.IgnoreCase).Groups[1].Value,
TotalAmount = ParseAmount(text,
@"Total\s*(?:Due|Amount)?\s*:?\s*\$?([\d,]+\.\d{2})"),
SupplierName = ExtractTopLine(result),
CustomerName = Regex.Match(text,
@"Bill\s+To\s*:?\s*\n?(.+)",
RegexOptions.IgnoreCase).Groups[1].Value.Trim()
};
}
else
{
// Receipt structure: merchant at top, no "Bill To" section
return new FinancialSummary
{
DocumentType = "RECEIPT",
Date = Regex.Match(text,
@"\b(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})\b").Value,
TotalAmount = ParseAmount(text,
@"(?:Total|Amount\s+Due|Grand\s+Total)\s*:?\s*\$?([\d,]+\.\d{2})"),
SupplierName = ExtractTopLine(result)
};
}
}
private bool DetectInvoice(OcrResult result)
{
// Invoice indicators: "Invoice", "Bill To", "Due Date", line items with quantities
var text = result.Text;
var invoiceSignals = new[] { "Invoice", "Bill To", "Due Date", "Purchase Order" };
return invoiceSignals.Any(s =>
text.IndexOf(s, StringComparison.OrdinalIgnoreCase) >= 0);
}
private string ExtractTopLine(OcrResult result)
{
return result.Pages
.FirstOrDefault()
?.Lines
.FirstOrDefault(l => l.Text.Trim().Length > 4)
?.Text.Trim();
}
private decimal? ParseAmount(string text, string pattern)
{
var match = Regex.Match(text, pattern, RegexOptions.IgnoreCase);
if (match.Success &&
decimal.TryParse(match.Groups[1].Value.Replace(",", ""), out var val))
return val;
return null;
}
}
public class FinancialSummary
{
public string DocumentType { get; set; }
public string InvoiceNumber { get; set; }
public string Date { get; set; }
public decimal? TotalAmount { get; set; }
public string SupplierName { get; set; }
public string CustomerName { get; set; }
}
Imports IronOcr
Imports System.Text.RegularExpressions
Public Class IronOcrFinancialRouter
Private ReadOnly _ocr As New IronTesseract()
Public Function ProcessFinancialDocument(filePath As String) As FinancialSummary
Dim result = _ocr.Read(filePath)
Dim text = result.Text
' Detect document type using structural keywords from word data
Dim isInvoice = DetectInvoice(result)
If isInvoice Then
Return New FinancialSummary With {
.DocumentType = "INVOICE",
.InvoiceNumber = Regex.Match(text,
"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
RegexOptions.IgnoreCase).Groups(1).Value,
.Date = Regex.Match(text,
"(?:Invoice\s+)?Date\s*:?\s*(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})",
RegexOptions.IgnoreCase).Groups(1).Value,
.TotalAmount = ParseAmount(text,
"Total\s*(?:Due|Amount)?\s*:?\s*\$?([\d,]+\.\d{2})"),
.SupplierName = ExtractTopLine(result),
.CustomerName = Regex.Match(text,
"Bill\s+To\s*:?\s*\n?(.+)",
RegexOptions.IgnoreCase).Groups(1).Value.Trim()
}
Else
' Receipt structure: merchant at top, no "Bill To" section
Return New FinancialSummary With {
.DocumentType = "RECEIPT",
.Date = Regex.Match(text,
"\b(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})\b").Value,
.TotalAmount = ParseAmount(text,
"(?:Total|Amount\s+Due|Grand\s+Total)\s*:?\s*\$?([\d,]+\.\d{2})"),
.SupplierName = ExtractTopLine(result)
}
End If
End Function
Private Function DetectInvoice(result As OcrResult) As Boolean
' Invoice indicators: "Invoice", "Bill To", "Due Date", line items with quantities
Dim text = result.Text
Dim invoiceSignals = {"Invoice", "Bill To", "Due Date", "Purchase Order"}
Return invoiceSignals.Any(Function(s) text.IndexOf(s, StringComparison.OrdinalIgnoreCase) >= 0)
End Function
Private Function ExtractTopLine(result As OcrResult) As String
Return result.Pages _
.FirstOrDefault() _
?.Lines _
.FirstOrDefault(Function(l) l.Text.Trim().Length > 4) _
?.Text.Trim()
End Function
Private Function ParseAmount(text As String, pattern As String) As Decimal?
Dim match = Regex.Match(text, pattern, RegexOptions.IgnoreCase)
If match.Success AndAlso
Decimal.TryParse(match.Groups(1).Value.Replace(",", ""), val) Then
Return val
End If
Return Nothing
End Function
End Class
Public Class FinancialSummary
Public Property DocumentType As String
Public Property InvoiceNumber As String
Public Property Date As String
Public Property TotalAmount As Decimal?
Public Property SupplierName As String
Public Property CustomerName As String
End Class
A lógica de detecção de tipo de documento substitui o campo DocumentType doMindeeno servidor por uma simples verificação de palavras-chave contra result.Text. Na grande maioria dos documentos financeiros, a presença de "Fatura" ou "Cobrar de" identifica inequivocamente as faturas; a ausência identifica recibos. Dados de posição em nível de palavra em result.Pages permitem detecção baseada em layout mais sofisticada se seu conjunto de documentos incluir casos extremos. O guia de leitura de resultados explica o modelo de objeto completo, incluindo Words, Lines, e Paragraphs com suas coordenadas delimitadoras.
Referência de mapeamento da API doMindeepara o IronOCR
| Mindee | Equivalente de IronOCR |
|---|---|
new MindeeClient(apiKey) |
new IronTesseract() — não é necessária chave da API |
new LocalInputSource(filePath) |
new OcrInput() + input.LoadImage(filePath) ou ocr.Read(filePath) |
_client.ParseAsync<InvoiceV4>(input) |
_ocr.Read(filePath) + extração regex em result.Text |
_client.ParseAsync<ReceiptV5>(input) |
_ocr.Read(filePath) + padrões de extração de recibos |
_client.ParseAsync<PassportV1>(input) |
_ocr.Read(filePath) + extração de zona MRZ via CropRectangle |
_client.ParseAsync<FinancialDocumentV1>(input) |
_ocr.Read(filePath) + detecção de tipo de documento em result.Text |
response.Document.Inference.Prediction |
result.Text, result.Pages, result.Lines, result.Words |
prediction.InvoiceNumber?.Value |
Regex.Match(result.Text, @"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)") |
prediction.SupplierName?.Value |
Linha superior da primeira página via result.Pages[0].Lines.First() |
prediction.TotalAmount?.Value |
Regex.Match(result.Text, @"Total\s*:?\s*\$?([\d,]+\.\d{2})") |
prediction.LineItems |
result.Lines filtrados por padrões de regex de item de linha |
prediction.SupplierPaymentDetails[].Iban |
Regex.Match(result.Text, @"IBAN\s*:?\s*([\w\s]+)") |
prediction.InvoiceDate?.Value |
Regex.Match(result.Text, @"Date\s*:?\s*(\d{1,2}[/-]\d{1,2}[/-]\d{4})") |
field.Confidence |
result.Confidence (nível de documento) ou word.Confidence (por palavra) |
catch (MindeeException ex) |
Não há equivalente — nenhum erro de rede no momento do reconhecimento. |
await Task.Delay(100) (limite de taxa) |
Não é obrigatório — sem limites de taxa |
input.LoadPdf(path) |
ocrInput.LoadPdf(path) — mesma operação, totalmente local |
| Chave de API na configuração | Chave de licença via IronOcr.License.LicenseKey = "..." — não há necessidade de rotação |
Problemas e soluções comuns em migrações
Problema 1: As coordenadas do retângulo de recorte não correspondem ao layout do documento
Mindee: As coordenadas dos campos espaciais são gerenciadas no servidor. O modelo doMindeese adapta a diferentes layouts de faturas sem qualquer configuração de coordenadas por parte do desenvolvedor.
Solução: Meça as coordenadas da zona em relação a uma amostra representativa de documentos do conjunto de documentos do seu fornecedor. Abra uma fatura de amostra em qualquer editor de imagens, leia as dimensões dos pixels e defina CropRectangle(x, y, width, height) de acordo. Para faturas com layouts variáveis, leia primeiro o documento completo e utilize os dados de posição da palavra para localizar as zonas dinamicamente:
var result = _ocr.Read("invoice.jpg");
var allWords = result.Pages.First().Words;
// Find the word "Total" and read the region 50px to its right
var totalLabel = allWords.FirstOrDefault(w =>
w.Text.Equals("Total", StringComparison.OrdinalIgnoreCase));
if (totalLabel != null)
{
var valueRegion = new CropRectangle(
totalLabel.X + totalLabel.Width + 5,
totalLabel.Y - 5,
200,
totalLabel.Height + 10);
using var valueInput = new OcrInput();
valueInput.LoadImage("invoice.jpg", valueRegion);
var valueResult = _ocr.Read(valueInput);
Console.WriteLine($"Total: {valueResult.Text.Trim()}");
}
var result = _ocr.Read("invoice.jpg");
var allWords = result.Pages.First().Words;
// Find the word "Total" and read the region 50px to its right
var totalLabel = allWords.FirstOrDefault(w =>
w.Text.Equals("Total", StringComparison.OrdinalIgnoreCase));
if (totalLabel != null)
{
var valueRegion = new CropRectangle(
totalLabel.X + totalLabel.Width + 5,
totalLabel.Y - 5,
200,
totalLabel.Height + 10);
using var valueInput = new OcrInput();
valueInput.LoadImage("invoice.jpg", valueRegion);
var valueResult = _ocr.Read(valueInput);
Console.WriteLine($"Total: {valueResult.Text.Trim()}");
}
Imports System
Imports System.Linq
Dim result = _ocr.Read("invoice.jpg")
Dim allWords = result.Pages.First().Words
' Find the word "Total" and read the region 50px to its right
Dim totalLabel = allWords.FirstOrDefault(Function(w) w.Text.Equals("Total", StringComparison.OrdinalIgnoreCase))
If totalLabel IsNot Nothing Then
Dim valueRegion = New CropRectangle(totalLabel.X + totalLabel.Width + 5, totalLabel.Y - 5, 200, totalLabel.Height + 10)
Using valueInput As New OcrInput()
valueInput.LoadImage("invoice.jpg", valueRegion)
Dim valueResult = _ocr.Read(valueInput)
Console.WriteLine($"Total: {valueResult.Text.Trim()}")
End Using
End If
O exemplo de OCR baseado em regiões demonstra esse padrão de zona dinâmica em um exemplo completo e funcional.
Problema 2: Sites de chamadas assíncronas param de funcionar após a remoção do Mindee
Mindee: Toda a superfície da API doMindeeé assíncrona porque se trata de entrada/saída de rede. Controladores e métodos de serviço construídos em await _client.ParseAsync<t>() são assíncronos ao longo da cadeia de chamada.
Solução: O método Read() do IronOCR é síncrono. Os sites de chamadas assíncronas existentes funcionam imediatamente ao envolver a chamada síncrona em Task.Run:
// Existing async signature preserved for callers
public async Task<string> ExtractInvoiceNumberAsync(string filePath)
{
return await Task.Run(() =>
{
var result = new IronTesseract().Read(filePath);
return Regex.Match(result.Text,
@"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
RegexOptions.IgnoreCase).Groups[1].Value;
});
}
// Existing async signature preserved for callers
public async Task<string> ExtractInvoiceNumberAsync(string filePath)
{
return await Task.Run(() =>
{
var result = new IronTesseract().Read(filePath);
return Regex.Match(result.Text,
@"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
RegexOptions.IgnoreCase).Groups[1].Value;
});
}
Imports System.Text.RegularExpressions
Imports System.Threading.Tasks
' Existing async signature preserved for callers
Public Async Function ExtractInvoiceNumberAsync(filePath As String) As Task(Of String)
Return Await Task.Run(Function()
Dim result = New IronTesseract().Read(filePath)
Return Regex.Match(result.Text,
"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
RegexOptions.IgnoreCase).Groups(1).Value
End Function)
End Function
Para cenários ASP.NET de alta capacidade, revise o guia de OCR assíncrono antes de decidir entre invólucros Task.Run e o caminho assíncrono nativo.
Problema 3: A precisão da extração diminui em digitalizações de baixa qualidade.
Mindee: O pré-processamento doMindeeé interno e automático. Problemas na qualidade da digitalização reduzem os índices de confiança em campo, sem que o desenvolvedor possa intervir antes do reenvio.
Solução: Aplique o pipeline de pré-processamento do IronOCR antes do reconhecimento. A combinação de Deskew() e DeNoise() recupera a maior parte da precisão nos defeitos típicos de digitalização plana vistos em fluxos de trabalho de contas a pagar:
using var input = new OcrInput();
input.LoadImage("faded-invoice.jpg");
input.Deskew();
input.DeNoise();
input.Contrast(); // for faded or low-contrast thermal paper receipts
input.Binarize(); // convert to clean black-and-white before recognition
var result = new IronTesseract().Read(input);
using var input = new OcrInput();
input.LoadImage("faded-invoice.jpg");
input.Deskew();
input.DeNoise();
input.Contrast(); // for faded or low-contrast thermal paper receipts
input.Binarize(); // convert to clean black-and-white before recognition
var result = new IronTesseract().Read(input);
Imports IronOcr
Using input As New OcrInput()
input.LoadImage("faded-invoice.jpg")
input.Deskew()
input.DeNoise()
input.Contrast() ' for faded or low-contrast thermal paper receipts
input.Binarize() ' convert to clean black-and-white before recognition
Dim result = New IronTesseract().Read(input)
End Using
Para digitalizações severamente degradadas, input.DeepCleanBackgroundNoise() aplica uma passagem mais intensa de remoção de fundo. O guia de correção da qualidade da imagem documenta os limites e o assistente de filtros ajuda a identificar quais filtros melhoram a precisão em uma determinada amostra de documento.
Problema 4: Chave de API e configuração de saída de rede removidas
Mindee: A chave da API armazenada nas configurações do aplicativo, as regras de saída da rede permitindo HTTPS de saída para api.mindee.net, e a lógica de repetição envolvendo HttpRequestException são todas infraestruturas necessárias.
Solução: Os três são removidos juntos. A entrada da chave da API doMindeefoi excluída da configuração. A regra de saída de rede foi revogada. O bloco try/catch (HttpRequestException) é removido. A única credencial restante é a chave de licença do IronOCR, definida uma única vez na inicialização:
// appsettings.json: remove "Mindee:ApiKey"
// Firewall: revoke egress rule for api.mindee.net
// Startup.cs or Program.cs:
IronOcr.License.LicenseKey = Configuration["IronOcr:LicenseKey"];
// Não rotation schedule, no secure storage beyond standard config secret management
// appsettings.json: remove "Mindee:ApiKey"
// Firewall: revoke egress rule for api.mindee.net
// Startup.cs or Program.cs:
IronOcr.License.LicenseKey = Configuration["IronOcr:LicenseKey"];
// Não rotation schedule, no secure storage beyond standard config secret management
' appsettings.json: remove "Mindee:ApiKey"
' Firewall: revoke egress rule for api.mindee.net
' Startup.vb or Program.vb:
IronOcr.License.LicenseKey = Configuration("IronOcr:LicenseKey")
' Não rotation schedule, no secure storage beyond standard config secret management
Problema 5: Cobrança por contagem de páginas em PDFs com várias páginas
Mindee: Um extrato de fornecedor de 12 páginas carregado noMindeeconsome 12 páginas da cota mensal. Não plano Pro, com as tarifas adicionais, esse documento individual gera um custo extra de US$ 1,20 além da franquia mensal, caso você esteja próximo do limite.
Solução: O IronOCR processa PDFs com várias páginas sem custo por página. Carregue o documento inteiro e processe as páginas em sequência:
using var input = new OcrInput();
input.LoadPdf("vendor-statement.pdf"); // 12 pages — no billing unit increment
var result = new IronTesseract().Read(input);
foreach (var page in result.Pages)
Console.WriteLine($"Page {page.PageNumber}: {page.Text.Length} characters recognized");
using var input = new OcrInput();
input.LoadPdf("vendor-statement.pdf"); // 12 pages — no billing unit increment
var result = new IronTesseract().Read(input);
foreach (var page in result.Pages)
Console.WriteLine($"Page {page.PageNumber}: {page.Text.Length} characters recognized");
Imports IronOcr
Using input As New OcrInput()
input.LoadPdf("vendor-statement.pdf") ' 12 pages — no billing unit increment
Dim result = New IronTesseract().Read(input)
For Each page In result.Pages
Console.WriteLine($"Page {page.PageNumber}: {page.Text.Length} characters recognized")
Next
End Using
Problema 6: A dependência do FinancialDocumentV1 não pode ser substituída por um único padrão.
Mindee: FinancialDocumentV1 detecta automaticamente o tipo de documento e extrai campos sem que o código de chamada precise condicionar com base no tipo de documento.
Solução: Construir um detector leve usando a presença de palavras-chave. Os documentos financeiros dividem-se claramente em faturas (que contêm "Fatura", "Cobrar de" ou "Data de Vencimento") e recibos (que contêm "Recibo", "Agradecimento" ou não possuem as indicações de fatura). Dois métodos de extração, Plus de um detector de três linhas, substituem a chamada da APIMindeesem comprometer a precisão em documentos bem formados:
var result = _ocr.Read(filePath);
var summary = result.Text.IndexOf("Invoice", StringComparison.OrdinalIgnoreCase) >= 0
? ExtractInvoiceFields(result)
: ExtractReceiptFields(result);
var result = _ocr.Read(filePath);
var summary = result.Text.IndexOf("Invoice", StringComparison.OrdinalIgnoreCase) >= 0
? ExtractInvoiceFields(result)
: ExtractReceiptFields(result);
Dim result = _ocr.Read(filePath)
Dim summary = If(result.Text.IndexOf("Invoice", StringComparison.OrdinalIgnoreCase) >= 0, ExtractInvoiceFields(result), ExtractReceiptFields(result))
Lista de verificação para migração de Mindee
Pré-migração
Audite toda a utilização doMindeeem toda a base de código:
grep -r "using Mindee" --include="*.cs" .
grep -r "MindeeClient\|ParseAsync\|InvoiceV4\|ReceiptV5\|PassportV1\|FinancialDocumentV1" --include="*.cs" .
grep -r "LocalInputSource\|MindeeException" --include="*.cs" .
grep -r "Mindee:ApiKey\|mindee_api_key\|MINDEE_API_KEY" --include="*.json" --include="*.env" --include="*.yml" .
grep -r "using Mindee" --include="*.cs" .
grep -r "MindeeClient\|ParseAsync\|InvoiceV4\|ReceiptV5\|PassportV1\|FinancialDocumentV1" --include="*.cs" .
grep -r "LocalInputSource\|MindeeException" --include="*.cs" .
grep -r "Mindee:ApiKey\|mindee_api_key\|MINDEE_API_KEY" --include="*.json" --include="*.env" --include="*.yml" .
Resultados do inventário:
- Conte sites de chamadas únicos usando
ParseAsync<t> - Anote quais tipos de documentos são usados (
InvoiceV4,ReceiptV5,PassportV1,FinancialDocumentV1) - Identificar todas as cadeias de métodos assíncronos que se propagam a partir de chamadas do Mindee
- Localize as referências às chaves de API nos arquivos de configuração e nos cofres de segredos.
- Identificar a lógica de repetição que envolve as chamadas de rede do Mindee
- Identificar regras de saída de rede que permitam tráfego de saída para os endpoints do Mindee
Migração de código
- Remova o pacote NuGet
Mindeedo arquivo de projeto - Execute
dotnet add package IronOcrpara instalar o IronOCR - Adicione
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"ao início do aplicativo - Remova todas as diretivas
using Mindee,using Mindee.Inputeusing Mindee.Product.* - Substitua a injeção de construtor
MindeeClientpor instânciaIronTesseractou registro DI - Substitua cada chamada
ParseAsync<InvoiceV4>por_ocr.Read(filePath)mais métodos de extração de faturas - Substitua cada chamada
ParseAsync<ReceiptV5>por_ocr.Read(filePath)mais métodos de extração de recibos - Substitua cada chamada
ParseAsync<PassportV1>por OCR baseado em zona usandoCropRectanglena área MRZ - Substitua cada chamada
ParseAsync<FinancialDocumentV1>pelo detector de tipo de documento + padrão de roteamento - Remova todas as linhas de conformidade de limite de taxa
await Task.Delay(...) - Remova todos os blocos
catch (MindeeException)ecatch (HttpRequestException)dos caminhos de OCR - Adicione chamadas de pré-processamento
OcrInput(Deskew,DeNoise,Contrast) para caminhos de documentos digitalizados - Adicione
result.SaveAsSearchablePdf(archivePath)a quaisquer caminhos de arquivamento de documentos - Remova a chave da API doMindeede todos os arquivos de configuração e cofres de segredos.
- Atualize os testes de integração para serem executados de forma síncrona, sem simulação de rede.
Pós-migração
- Verificar a precisão da extração em um conjunto de amostra de 20 a 30 documentos, abrangendo cada tipo de documento.
- Confirme valores
result.Confidenceacima de 80 em digitalizações limpas representativas; Aplicar pré-processamento se abaixo - Teste o pipeline de pré-processamento (
Deskew,DeNoise) em amostras de digitalização inclinadas e degradadas - Verifique se PDFs de várias páginas produzem
result.Pages.Counte conteúdo por página corretos - Confirme que a saída
result.SaveAsSearchablePdf()é pesquisável no Adobe Acrobat e visualizadores de PDF do sistema - Teste toda extração baseada em zona (
CropRectangle) contra variações de layout de documentos no seu conjunto de fornecedores - Execute processamento em lote sem chamadas
Task.Delaye verifique se não há problemas de encadeamento - Confirme se o aplicativo inicia e funciona corretamente sem acesso à internet.
- Verifique se a inicialização da chave de licença é executada antes da primeira chamada
_ocr.Read()em cada ponto de entrada - Verifique se não restam referências à chave da API doMindeenas configurações, variáveis de ambiente ou segredos.
Principais benefícios da migração para o IronOCR
Soberania total sobre os dados dos documentos financeiros. Após a migração, os códigos IBAN dos fornecedores, os números de roteamento, os EINs dos clientes e os itens detalhados das transações permanecem intactos em sua infraestrutura. O âmbito da conformidade abrange apenas a sua organização. Os contratos de processamento com fornecedores terceirizados de IA são removidos do seu histórico de auditoria. Equipes que operam sob os requisitos de GLBA, CMMC, FedRAMP ou de residência de dados podem processar documentos financeiros sem a revisão de conformidade exigida pela transmissão externa para a nuvem.
Custo previsível independentemente do volume. A licença Lite do IronOCR em $999 cobre um desenvolvedor com processamento ilimitado de documentos. Aumentar a produção de 500 faturas por mês para 50.000 faturas por mês não acarreta custos adicionais. Os picos de processamento de fim de ano, as execuções de correção em lote e os ciclos de teste de desenvolvimento não incrementam nenhum contador de faturamento. O custo total de propriedade em três anos para uma equipe que processa 5.000 páginas por mês cai de aproximadamente US$ 17.964 (Mindee Pro) para US$ 1.499 a US$ 2.999 (licença perpétua do IronOCR). Consulte a página completa do produto IronOCR para obter detalhes sobre os níveis de qualidade.
Lógica de extração que você possui permanentemente. Os padrões de extração do IronOCR são código C# simples em seu repositório. Eles possuem controle de versão, são revisáveis, testáveis e implantáveis independentemente do cronograma de lançamentos ou das decisões de versionamento de API de qualquer fornecedor externo. Quando oMindeelança InvoiceV5 e desativa InvoiceV4, isso é um prazo de migração imposto pelo fornecedor. Quando você mantém padrões de extração, a melhoria é um git commit.
Controle de pré-processamento para qualidade de digitalização no mundo real. As operações de contas a pagar recebem faturas de dezenas ou centenas de fornecedores, cada uma digitalizada com equipamentos diferentes e em configurações distintas. O pipeline de pré-processamento do IronOCR— Deskew(), DeNoise(), Contrast(), Sharpen(), Binarize() — aborda os defeitos específicos em cada categoria de digitalização. Uma fotografia térmica de um recibo apresenta defeitos diferentes de uma fatura digitalizada com várias páginas; você aplica os filtros apropriados para cada caso. O pré-processamento doMindeeé invisível e não configurável. Consulte a página de recursos de pré-processamento para obter o catálogo completo de filtros.
Armazenamento de PDF pesquisável em uma etapa. Cada fatura processada pelo IronOCR pode ser arquivada como um PDF pesquisável com result.SaveAsSearchablePdf(). A camada de texto reconhecida é incorporada ao arquivo de saída, tornando cada palavra pesquisável por texto completo em sistemas de gerenciamento de documentos, bibliotecas do SharePoint e repositórios de conteúdo Enterprise . OMindeenão gera nenhum arquivo PDF; Anteriormente, o arquivamento pesquisável exigia uma biblioteca separada, uma etapa de processamento separada e manutenção adicional. Após a migração, extração e arquivamento se unem em uma única chamada _ocr.Read(input).
Implantação em qualquer lugar sem alterações de arquitetura. O IronOCR pode ser implantado no Windows, Linux, macOS, Docker, Azure App Service, AWS Lambda e Google Cloud Run usando o mesmo pacote NuGet e a mesma inicialização. Ambientes isolados por ar, sistemas de chão de fábrica e instalações governamentais de segurança funcionam sem modificações. O guia de implantação do Docker , o guia do Azure e o guia da AWS abrangem a configuração específica de cada ambiente para cada plataforma.
Perguntas frequentes
Por que devo migrar da API OCR da Mindee para a IronOCR?
Entre os principais motivos, incluem-se a eliminação da complexidade da interoperabilidade COM, a substituição do gerenciamento de licenças baseado em arquivos, a eliminação da cobrança por página, a viabilização da implantação em Docker/contêineres e a adoção de um fluxo de trabalho nativo do NuGet que se integra às ferramentas padrão do .NET.
Quais são as principais alterações de código ao migrar da API OCR do Mindee para o IronOCR?
Substitua as sequências de inicialização do Mindee pela instanciação do IronTesseract, remova o gerenciamento do ciclo de vida COM (padrões explícitos de Criação/Carregamento/Fechamento) e atualize os nomes das propriedades de resultado. O resultado é uma redução significativa no número de linhas de código repetitivo.
Como faço para instalar o IronOCR para iniciar a migração?
Execute 'Install-Package IronOcr' no Console do Gerenciador de Pacotes ou 'dotnet add package IronOcr' na CLI. Os pacotes de idiomas são pacotes separados: 'dotnet add package IronOcr.Languages.French' para francês, por exemplo.
O IronOCR atinge a mesma precisão de OCR que a API OCR da Mindee para documentos comerciais padrão?
O IronOCR alcança alta precisão para conteúdo comercial padrão, incluindo faturas, contratos, recibos e formulários digitados. Filtros de pré-processamento de imagem (correção de distorção, remoção de ruído, aprimoramento de contraste) melhoram ainda mais o reconhecimento em entradas de baixa qualidade.
Como o IronOCR lida com os dados de idioma que a API OCR do Mindee instala separadamente?
Os dados de idioma no IronOCR são distribuídos como pacotes NuGet. O comando 'dotnet add package IronOcr.Languages.German' instala o suporte ao alemão. Não é necessário inserir arquivos manualmente nem configurar caminhos de diretório.
A migração da API OCR da Mindee para a IronOCR requer alterações na infraestrutura de implantação?
O IronOCR requer menos alterações de infraestrutura do que a API OCR da Mindee. Não há caminhos binários do SDK, necessidade de instalar arquivos de licença ou configurar servidores de licença. O pacote NuGet contém o mecanismo OCR completo e a chave de licença é uma string definida no código do aplicativo.
Como configuro o licenciamento do IronOCR após a migração?
Atribua `IronOcr.License.LicenseKey = "YOUR-KEY"` no código de inicialização do aplicativo. No Docker ou Kubernetes, armazene a chave como uma variável de ambiente e leia-a na inicialização. Use `License.IsValidLicense` para validar a licença antes de aceitar o tráfego.
O IronOCR consegue processar PDFs da mesma forma que o Mindee?
Sim. O IronOCR lê PDFs nativos e digitalizados. Instancie o IronTesseract, chame ocr.Read(input) onde input é um caminho para um PDF ou OcrPdfInput, e itere pelas páginas do OcrResult. Não é necessário um pipeline de renderização de PDF separado.
Como o IronOCR lida com multithreading em processamento de alto volume?
O IronTesseract pode ser instanciado com segurança por thread. Crie uma instância por thread em um Parallel.ForEach ou pool de Tasks, execute o OCR simultaneamente e descarte cada instância ao terminar. Não é necessário nenhum estado global ou bloqueio.
Quais formatos de saída o IronOCR suporta após a extração de texto?
O IronOCR retorna resultados estruturados, incluindo texto, coordenadas de palavras, níveis de confiança e estrutura da página. As opções de exportação incluem texto simples, PDF pesquisável e objetos de resultados estruturados para processamento posterior.
O preço do IronOCR é mais previsível do que o da API OCR do Mindee para dimensionamento de cargas de trabalho?
O IronOCR utiliza licenciamento perpétuo com preço fixo, sem cobranças por página ou volume. Independentemente de você processar 10.000 ou 10 milhões de páginas, o custo da licença permanece constante. As opções de licenciamento por volume e para equipes estão disponíveis na página de preços do IronOCR.
O que acontece com meus testes existentes após a migração da API OCR do Mindee para o IronOCR?
Os testes que verificam o conteúdo de texto extraído devem continuar a ser aprovados após a migração. Os testes que validam padrões de chamadas de API ou o ciclo de vida de objetos COM precisarão ser atualizados para refletir o modelo de inicialização e resultados mais simples do IronOCR.

