Como implementar OCR em C# usando bibliotecas de código aberto
Este guia destina-se a desenvolvedores .NET que integraram a API REST do Klippa e estão migrando para o IronOCR para processamento de documentos local. Este documento aborda os passos práticos para remover a infraestrutura do cliente HTTP, eliminar a desserialização JSON e substituir os uploads de documentos dependentes da nuvem por chamadas OCR locais que nunca acessam a rede.
Por que migrar do Klippa OCR?
Klippa é um serviço de inteligência de documentos totalmente em nuvem, sem SDK for .NET . Cada integração é um cliente REST desenvolvido manualmente. Essa realidade arquitetônica tem consequências subsequentes que se acumulam ao longo da vida útil de um sistema de produção.
Sem pacote NuGet , você tem o controle da camada de integração. Não há nada para instalar. O custo de entrada é escrever um wrapper HttpClient, configurar cabeçalhos de autenticação X-Auth-Key, construir corpos de solicitação MultipartFormDataContent, desserializar o esquema de resposta JSON do Klippa e conectar a lógica de repetição para falhas transitórias. Isso significa de 2 a 4 dias de trabalho de instalação hidráulica antes que o primeiro documento seja processado de forma confiável na produção. Quando a Klippa atualiza o esquema da sua API, seu código de desserialização para de funcionar e requer manutenção manual.
Cada envio de documento depende da rede. A Klippa processa documentos exclusivamente em servidores localizados na UE. Interrupções na produção no servidor Klippa, latência elevada ou qualquer interrupção no acesso à internet de saída do seu servidor de aplicativos paralisam completamente o processamento de documentos. Não existe alternativa, modo local ou nova tentativa que resolva a indisponibilidade de um serviço em nuvem.
Documentos confidenciais saem da sua infraestrutura. Documentos financeiros — recibos com detalhes de pagamento, faturas com números de IVA e valores, documentos de identidade com dados de passaporte — são transmitidos para um servidor de terceiros a cada chamada de API. As disposições do RGPD sobre transferência de dados abordam alguns desses aspectos no processamento realizado na UE, mas o escopo da auditoria ainda se estende à infraestrutura da Klippa, às políticas de retenção de dados e aos subcontratados. Para equipes com contratos nas áreas de saúde, jurídica, financeira ou governamental, a opção "hospedado na UE" não atende ao requisito de que os dados não saiam da organização.
O preço por documento é escalonável, sem limite máximo. A Klippa não divulga preços. Em qualquer volume significativo de documentos — 10.000 recibos por mês em um sistema de gestão de despesas, 500 faturas por dia em um fluxo de trabalho de automação de contas a pagar — o modelo de cobrança por documento acumula custos que uma licença perpétua jamais acarretaria. A trajetória dos custos está diretamente ligada ao crescimento dos negócios, o que é o oposto do que o investimento em infraestrutura deveria fazer.
O escopo de atuação do especialista se torna mais complexo quando as necessidades aumentam. Klippa é treinada em recibos, faturas e documentos de identidade. Um aplicativo que começa como um gerenciador de despesas raramente permanece apenas isso. Na primeira vez que um tipo de documento fora dessas três categorias aparece — um contrato de trabalho digitalizado, um formulário médico, um desenho técnico, uma ordem de compra com layout não padrão — o Klippa não retorna nada útil. O IronOCR processa qualquer documento que contenha texto, sem restrições de categoria.
Chamadas REST exclusivamente assíncronas adicionam latência em contextos síncronos. Cada chamada Klippa é uma operação HTTP assíncrona. O envio e recebimento de um único documento pela rede leva de 500ms a 2000ms. O IronOCR processa o mesmo documento localmente em 100 a 400 ms, sem a sobrecarga assíncrona, em cenários onde o processamento síncrono se adequa melhor à arquitetura.
O problema fundamental
Klippa não possui SDK. OCR significa construir e enviar uma solicitação HTTP e, em seguida, desserializar o JSON:
// Klippa: 15+ lines of HTTP plumbing before you read a single character
var content = new MultipartFormDataContent();
content.Add(new ByteArrayContent(File.ReadAllBytes(imagePath)), "document", "receipt.jpg");
_client.DefaultRequestHeaders.Add("X-Auth-Key", _apiKey); // auth header — rotates, breaks, leaks
var response = await _client.PostAsync(
"https://custom-ocr.klippa.com/api/v1/parseDocument", content);
response.EnsureSuccessStatusCode(); // throws on 4xx/5xx — no retry, document lost
var json = await response.Content.ReadAsStringAsync();
var parsed = JsonSerializer.Deserialize<KlippaResponse>(json); // your schema, your maintenance
var text = parsed?.Data?.ParsedDocument?.Text; // nullable chain — breaks when schema changes
// Klippa: 15+ lines of HTTP plumbing before you read a single character
var content = new MultipartFormDataContent();
content.Add(new ByteArrayContent(File.ReadAllBytes(imagePath)), "document", "receipt.jpg");
_client.DefaultRequestHeaders.Add("X-Auth-Key", _apiKey); // auth header — rotates, breaks, leaks
var response = await _client.PostAsync(
"https://custom-ocr.klippa.com/api/v1/parseDocument", content);
response.EnsureSuccessStatusCode(); // throws on 4xx/5xx — no retry, document lost
var json = await response.Content.ReadAsStringAsync();
var parsed = JsonSerializer.Deserialize<KlippaResponse>(json); // your schema, your maintenance
var text = parsed?.Data?.ParsedDocument?.Text; // nullable chain — breaks when schema changes
Imports System.Net.Http
Imports System.IO
Imports System.Text.Json
Imports System.Threading.Tasks
' Klippa: 15+ lines of HTTP plumbing before you read a single character
Dim content As New MultipartFormDataContent()
content.Add(New ByteArrayContent(File.ReadAllBytes(imagePath)), "document", "receipt.jpg")
_client.DefaultRequestHeaders.Add("X-Auth-Key", _apiKey) ' auth header — rotates, breaks, leaks
Dim response As HttpResponseMessage = Await _client.PostAsync("https://custom-ocr.klippa.com/api/v1/parseDocument", content)
response.EnsureSuccessStatusCode() ' throws on 4xx/5xx — no retry, document lost
Dim json As String = Await response.Content.ReadAsStringAsync()
Dim parsed As KlippaResponse = JsonSerializer.Deserialize(Of KlippaResponse)(json) ' your schema, your maintenance
Dim text As String = parsed?.Data?.ParsedDocument?.Text ' nullable chain — breaks when schema changes
O IronOCR substitui tudo isso:
// IronOCR: no HTTP, no auth headers, no JSON — just text
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var text = new IronTesseract().Read(imagePath).Text;
// IronOCR: no HTTP, no auth headers, no JSON — just text
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var text = new IronTesseract().Read(imagePath).Text;
' IronOCR: no HTTP, no auth headers, no JSON — just text
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Dim text = New IronTesseract().Read(imagePath).Text
IronOCR vs Klippa OCR: Comparação de Recursos
A tabela abaixo compara as duas bibliotecas nas dimensões mais importantes para uma decisão de migração para produção.
| Recurso | OCR Klippa | IronOCR |
|---|---|---|
| Modelo de implantação | Somente na nuvem (servidores da UE) | Não local, totalmente local |
| SDK .NET / Pacote NuGet | None | IronOcr pacote NuGet |
| É necessário ter acesso à internet. | Sim, em todas as chamadas. | Nunca |
| Os dados do documento saem da rede. | Sempre | Nunca |
| OCR de uso geral | Não (somente recibos, faturas e documentos de identificação) | Sim (qualquer tipo de documento) |
| Configuração de autenticação | X-Auth-Key cabeçalho HTTP |
IronOcr.License.LicenseKey string |
| É necessário um cliente HTTP. | Sim | Não |
| Desserialização de resposta | Análise manual de JSON | Objeto digitado OcrResult |
| Lógica de repetição/tempo limite | enrolado à mão | Não é necessário (chamada local) |
| Suporte offline/isolado da internet | Não | Sim |
| Entrada de PDF | Sim (nuvem) | Sim (nativo, local) |
| Entrada TIFF de várias páginas | Desconhecido | Sim |
| formatos de entrada de imagem | JPG, PNG (nuvem) | JPG, PNG, BMP, TIFF, GIF e muito mais |
| Entrada de fluxo e matriz de bytes | Sem SDK | Sim |
| Pré-processamento automático de imagens | Lado da nuvem (opaco) | Sim (Corrigir distorção, Reduzir ruído, Contraste, Binarizar, Nitidez) |
| Saída estruturada: coordenadas das palavras | Não | Sim |
| Índices de confiança por palavra | Não | Sim |
| Saída em PDF pesquisável | Não | Sim |
| Leitura de código de barras durante OCR | Não | Sim |
| Suporte multilíngue | Limitado a tipos de documentos treinados | Mais de 125 idiomas |
| Segurança da rosca | Não aplicável (chamadas HTTP) | Sim (um IronTesseract por thread) |
| Implantação multiplataforma | agnóstico em relação ao REST | Windows, Linux, macOS, Docker, Azure, AWS |
| Conformidade com HIPAA/ITAR/ambiente isolado (air-gapped) | Não | Sim |
| Modelo de preços | SaaS por documento (valores não divulgados) | Licença perpétua de $999 |
| Custo por página em escala | Sim, ilimitado. | None |
Guia rápido: Migração do OCR Klippa para o IronOCR
Passo 1: Substitua o pacote NuGet
Klippa não possui um pacote NuGet oficial. Remova as dependências do cliente HTTP que existem exclusivamente para dar suporte à integração com o Klippa:
# Remove Klippa-related packages (if installed for REST support)
dotnet remove package Newtonsoft.Json
dotnet remove package System.Net.Http.Json
# Remove Klippa-related packages (if installed for REST support)
dotnet remove package Newtonsoft.Json
dotnet remove package System.Net.Http.Json
Instale o IronOCR a partir do NuGet :
dotnet add package IronOcr
Etapa 2: Atualizar Namespaces
Remova os namespaces HTTP e JSON necessários para a integração com o Klippa. Adicione o namespace único do IronOCR:
// Before (Klippa integration)
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Text.Json.Serialization;
// After (IronOCR)
using IronOcr;
// Before (Klippa integration)
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Text.Json.Serialization;
// After (IronOCR)
using IronOcr;
Imports System.Net.Http
Imports System.Net.Http.Headers
Imports System.Text.Json
Imports System.Text.Json.Serialization
Imports IronOcr
Etapa 3: Inicializar a licença
Adicione a inicialização da licença uma vez na inicialização da aplicação — em Program.cs, Startup.cs, ou 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
Substituindo a classe de serviço do cliente HTTP
A integração com o Klippa requer uma classe de serviço completa que encapsula a infraestrutura HTTP. Não há como evitar isso porque não existe um SDK.
Abordagem de Klippa:
// Klippa: entire service class just to send one HTTP request
public class KlippaOcrService : IDisposable
{
private readonly HttpClient _httpClient;
private readonly string _baseUrl = "https://custom-ocr.klippa.com/api/v1";
public KlippaOcrService(string apiKey)
{
_httpClient = new HttpClient();
_httpClient.DefaultRequestHeaders.Add("X-Auth-Key", apiKey);
_httpClient.Timeout = TimeSpan.FromSeconds(30); // network timeout required
}
public async Task<string> ReadDocumentTextAsync(string filePath)
{
using var form = new MultipartFormDataContent();
var fileBytes = await File.ReadAllBytesAsync(filePath);
form.Add(new ByteArrayContent(fileBytes), "document", Path.GetFileName(filePath));
var response = await _httpClient.PostAsync($"{_baseUrl}/parseDocument", form);
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(json);
// navigate Klippa's nested JSON schema
return doc.RootElement
.GetProperty("data")
.GetProperty("parsed_document")
.GetProperty("text")
.GetString() ?? string.Empty;
}
public void Dispose() => _httpClient.Dispose();
}
// Klippa: entire service class just to send one HTTP request
public class KlippaOcrService : IDisposable
{
private readonly HttpClient _httpClient;
private readonly string _baseUrl = "https://custom-ocr.klippa.com/api/v1";
public KlippaOcrService(string apiKey)
{
_httpClient = new HttpClient();
_httpClient.DefaultRequestHeaders.Add("X-Auth-Key", apiKey);
_httpClient.Timeout = TimeSpan.FromSeconds(30); // network timeout required
}
public async Task<string> ReadDocumentTextAsync(string filePath)
{
using var form = new MultipartFormDataContent();
var fileBytes = await File.ReadAllBytesAsync(filePath);
form.Add(new ByteArrayContent(fileBytes), "document", Path.GetFileName(filePath));
var response = await _httpClient.PostAsync($"{_baseUrl}/parseDocument", form);
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(json);
// navigate Klippa's nested JSON schema
return doc.RootElement
.GetProperty("data")
.GetProperty("parsed_document")
.GetProperty("text")
.GetString() ?? string.Empty;
}
public void Dispose() => _httpClient.Dispose();
}
Imports System
Imports System.IO
Imports System.Net.Http
Imports System.Threading.Tasks
Imports System.Text.Json
' Klippa: entire service class just to send one HTTP request
Public Class KlippaOcrService
Implements IDisposable
Private ReadOnly _httpClient As HttpClient
Private ReadOnly _baseUrl As String = "https://custom-ocr.klippa.com/api/v1"
Public Sub New(apiKey As String)
_httpClient = New HttpClient()
_httpClient.DefaultRequestHeaders.Add("X-Auth-Key", apiKey)
_httpClient.Timeout = TimeSpan.FromSeconds(30) ' network timeout required
End Sub
Public Async Function ReadDocumentTextAsync(filePath As String) As Task(Of String)
Using form As New MultipartFormDataContent()
Dim fileBytes = Await File.ReadAllBytesAsync(filePath)
form.Add(New ByteArrayContent(fileBytes), "document", Path.GetFileName(filePath))
Dim response = Await _httpClient.PostAsync($"{_baseUrl}/parseDocument", form)
response.EnsureSuccessStatusCode()
Dim json = Await response.Content.ReadAsStringAsync()
Using doc = JsonDocument.Parse(json)
' navigate Klippa's nested JSON schema
Return doc.RootElement _
.GetProperty("data") _
.GetProperty("parsed_document") _
.GetProperty("text") _
.GetString() OrElse String.Empty
End Using
End Using
End Function
Public Sub Dispose() Implements IDisposable.Dispose
_httpClient.Dispose()
End Sub
End Class
Abordagem IronOCR:
// IronOCR: no HTTP, no JSON navigation, no Dispose plumbing
public class OcrService
{
private readonly IronTesseract _ocr = new IronTesseract();
public string ReadDocumentText(string filePath)
{
return _ocr.Read(filePath).Text;
}
}
// At startup:
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
// Usage — identical call site, different internals:
var service = new OcrService();
var text = service.ReadDocumentText("invoice.jpg"); // local, synchronous, zero network
// IronOCR: no HTTP, no JSON navigation, no Dispose plumbing
public class OcrService
{
private readonly IronTesseract _ocr = new IronTesseract();
public string ReadDocumentText(string filePath)
{
return _ocr.Read(filePath).Text;
}
}
// At startup:
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
// Usage — identical call site, different internals:
var service = new OcrService();
var text = service.ReadDocumentText("invoice.jpg"); // local, synchronous, zero network
Imports IronOcr
Public Class OcrService
Private ReadOnly _ocr As New IronTesseract()
Public Function ReadDocumentText(filePath As String) As String
Return _ocr.Read(filePath).Text
End Function
End Class
' At startup:
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
' Usage — identical call site, different internals:
Dim service As New OcrService()
Dim text As String = service.ReadDocumentText("invoice.jpg") ' local, synchronous, zero network
A classe de serviço Klippa existe exclusivamente porque a API requer infraestrutura HTTP. O equivalente do IronOCR se reduz a uma única chamada Read(). Os tempos limite, os cabeçalhos de autenticação e os padrões de descarte desaparecem porque não há rede. Consulte o guia de configuração do IronTesseract para opções de inicialização e o exemplo básico de OCR para código funcional.
Eliminando o envio de formulários multipartes
A Klippa recebe documentos como uploads de formulários multipartes. O código de upload é mecânico, mas frágil: leitura de arquivos, cabeçalhos de tipo de conteúdo, construção de limites e gerenciamento do tamanho do upload.
Abordagem de Klippa:
// Klippa: multipart upload — every document is an HTTP form POST
public async Task<KlippaResult> UploadAndParseAsync(
string filePath, string documentType = "financial")
{
using var form = new MultipartFormDataContent();
// read file into memory — entire document in RAM before upload
var fileBytes = await File.ReadAllBytesAsync(filePath);
var byteContent = new ByteArrayContent(fileBytes);
byteContent.Headers.ContentType =
new System.Net.Http.Headers.MediaTypeHeaderValue("image/jpeg");
form.Add(byteContent, "document", Path.GetFileName(filePath));
form.Add(new StringContent(documentType), "DocumentType");
// document leaves your server here
var response = await _httpClient.PostAsync(
"https://custom-ocr.klippa.com/api/v1/parseDocument", form);
if (!response.IsSuccessStatusCode)
{
var error = await response.Content.ReadAsStringAsync();
throw new InvalidOperationException($"Klippa API error: {response.StatusCode} — {error}");
}
var json = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<KlippaResult>(json,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
}
// Klippa: multipart upload — every document is an HTTP form POST
public async Task<KlippaResult> UploadAndParseAsync(
string filePath, string documentType = "financial")
{
using var form = new MultipartFormDataContent();
// read file into memory — entire document in RAM before upload
var fileBytes = await File.ReadAllBytesAsync(filePath);
var byteContent = new ByteArrayContent(fileBytes);
byteContent.Headers.ContentType =
new System.Net.Http.Headers.MediaTypeHeaderValue("image/jpeg");
form.Add(byteContent, "document", Path.GetFileName(filePath));
form.Add(new StringContent(documentType), "DocumentType");
// document leaves your server here
var response = await _httpClient.PostAsync(
"https://custom-ocr.klippa.com/api/v1/parseDocument", form);
if (!response.IsSuccessStatusCode)
{
var error = await response.Content.ReadAsStringAsync();
throw new InvalidOperationException($"Klippa API error: {response.StatusCode} — {error}");
}
var json = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<KlippaResult>(json,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
}
Imports System.IO
Imports System.Net.Http
Imports System.Text.Json
Imports System.Threading.Tasks
Public Class KlippaUploader
Private ReadOnly _httpClient As HttpClient
Public Sub New(httpClient As HttpClient)
_httpClient = httpClient
End Sub
Public Async Function UploadAndParseAsync(filePath As String, Optional documentType As String = "financial") As Task(Of KlippaResult)
Using form As New MultipartFormDataContent()
' read file into memory — entire document in RAM before upload
Dim fileBytes = Await File.ReadAllBytesAsync(filePath)
Dim byteContent = New ByteArrayContent(fileBytes)
byteContent.Headers.ContentType = New System.Net.Http.Headers.MediaTypeHeaderValue("image/jpeg")
form.Add(byteContent, "document", Path.GetFileName(filePath))
form.Add(New StringContent(documentType), "DocumentType")
' document leaves your server here
Dim response = Await _httpClient.PostAsync("https://custom-ocr.klippa.com/api/v1/parseDocument", form)
If Not response.IsSuccessStatusCode Then
Dim error = Await response.Content.ReadAsStringAsync()
Throw New InvalidOperationException($"Klippa API error: {response.StatusCode} — {error}")
End If
Dim json = Await response.Content.ReadAsStringAsync()
Return JsonSerializer.Deserialize(Of KlippaResult)(json, New JsonSerializerOptions With {.PropertyNameCaseInsensitive = True})
End Using
End Function
End Class
Abordagem IronOCR:
// IronOCR: load from file path, byte array, or stream — no upload, no form
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
// From file path
using var input = new OcrInput();
input.LoadImage("invoice.jpg");
var result = new IronTesseract().Read(input);
// From byte array (same bytes Klippa was uploading)
byte[] fileBytes = await File.ReadAllBytesAsync("invoice.jpg");
using var inputFromBytes = new OcrInput();
inputFromBytes.LoadImage(fileBytes);
var resultFromBytes = new IronTesseract().Read(inputFromBytes);
Console.WriteLine(result.Text);
// IronOCR: load from file path, byte array, or stream — no upload, no form
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
// From file path
using var input = new OcrInput();
input.LoadImage("invoice.jpg");
var result = new IronTesseract().Read(input);
// From byte array (same bytes Klippa was uploading)
byte[] fileBytes = await File.ReadAllBytesAsync("invoice.jpg");
using var inputFromBytes = new OcrInput();
inputFromBytes.LoadImage(fileBytes);
var resultFromBytes = new IronTesseract().Read(inputFromBytes);
Console.WriteLine(result.Text);
Imports IronOcr
Imports System.IO
' IronOCR: load from file path, byte array, or stream — no upload, no form
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
' From file path
Using input As New OcrInput()
input.LoadImage("invoice.jpg")
Dim result = New IronTesseract().Read(input)
' From byte array (same bytes Klippa was uploading)
Dim fileBytes As Byte() = Await File.ReadAllBytesAsync("invoice.jpg")
Using inputFromBytes As New OcrInput()
inputFromBytes.LoadImage(fileBytes)
Dim resultFromBytes = New IronTesseract().Read(inputFromBytes)
Console.WriteLine(result.Text)
End Using
End Using
A construção MultipartFormDataContent, os cabeçalhos de tipo de conteúdo e o próprio upload desapareceram. O IronOCR lê diretamente do caminho do arquivo, de uma matriz de bytes ou de um Stream — os mesmos dados que Klippa estava transmitindo para a nuvem permanecem local. O guia de entrada de imagem abrange todos os formatos de entrada suportados, e o guia de entrada de fluxo abrange o caminho do fluxo de memória para documentos que chegam como matrizes de bytes de processos upstream.
Substituindo a desserialização de respostas JSON
Klippa retorna uma estrutura JSON aninhada. Navegar nessa estrutura requer um modelo C# correspondente ou uma travessia inline JsonDocument — ambos quebram quando Klippa altera seu esquema de resposta.
Abordagem de Klippa:
// Klippa: deserialization model — breaks when API schema changes
public class KlippaResponse
{
[JsonPropertyName("data")]
public KlippaData Data { get; set; }
}
public class KlippaData
{
[JsonPropertyName("parsed_document")]
public KlippaParsedDocument ParsedDocument { get; set; }
}
public class KlippaParsedDocument
{
[JsonPropertyName("text")]
public string Text { get; set; }
[JsonPropertyName("amount")]
public decimal? Amount { get; set; }
[JsonPropertyName("merchant")]
public string Merchant { get; set; }
[JsonPropertyName("date")]
public string Date { get; set; }
}
// Usage: navigate the nullable chain every time
public async Task<string> GetExtractedTextAsync(string imagePath)
{
var klippaResult = await UploadAndParseAsync(imagePath);
// every property access is nullable — schema drift breaks this silently
return klippaResult?.Data?.ParsedDocument?.Text ?? string.Empty;
}
// Klippa: deserialization model — breaks when API schema changes
public class KlippaResponse
{
[JsonPropertyName("data")]
public KlippaData Data { get; set; }
}
public class KlippaData
{
[JsonPropertyName("parsed_document")]
public KlippaParsedDocument ParsedDocument { get; set; }
}
public class KlippaParsedDocument
{
[JsonPropertyName("text")]
public string Text { get; set; }
[JsonPropertyName("amount")]
public decimal? Amount { get; set; }
[JsonPropertyName("merchant")]
public string Merchant { get; set; }
[JsonPropertyName("date")]
public string Date { get; set; }
}
// Usage: navigate the nullable chain every time
public async Task<string> GetExtractedTextAsync(string imagePath)
{
var klippaResult = await UploadAndParseAsync(imagePath);
// every property access is nullable — schema drift breaks this silently
return klippaResult?.Data?.ParsedDocument?.Text ?? string.Empty;
}
Imports System.Text.Json.Serialization
' Klippa: deserialization model — breaks when API schema changes
Public Class KlippaResponse
<JsonPropertyName("data")>
Public Property Data As KlippaData
End Class
Public Class KlippaData
<JsonPropertyName("parsed_document")>
Public Property ParsedDocument As KlippaParsedDocument
End Class
Public Class KlippaParsedDocument
<JsonPropertyName("text")>
Public Property Text As String
<JsonPropertyName("amount")>
Public Property Amount As Decimal?
<JsonPropertyName("merchant")>
Public Property Merchant As String
<JsonPropertyName("date")>
Public Property Date As String
End Class
' Usage: navigate the nullable chain every time
Public Async Function GetExtractedTextAsync(imagePath As String) As Task(Of String)
Dim klippaResult = Await UploadAndParseAsync(imagePath)
' every property access is nullable — schema drift breaks this silently
Return If(klippaResult?.Data?.ParsedDocument?.Text, String.Empty)
End Function
Abordagem IronOCR:
// IronOCR: typed result object — no JSON schema, no nullable chains
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var ocr = new IronTesseract();
var result = ocr.Read("invoice.jpg");
// Direct property access — no deserialization, no nullable navigation
string fullText = result.Text;
double confidence = result.Confidence;
int pageCount = result.Pages.Count();
// Structured data: lines and words with coordinates
foreach (var page in result.Pages)
{
foreach (var line in page.Lines)
{
Console.WriteLine($"Line: '{line.Text}' at Y={line.Y}");
}
}
// IronOCR: typed result object — no JSON schema, no nullable chains
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var ocr = new IronTesseract();
var result = ocr.Read("invoice.jpg");
// Direct property access — no deserialization, no nullable navigation
string fullText = result.Text;
double confidence = result.Confidence;
int pageCount = result.Pages.Count();
// Structured data: lines and words with coordinates
foreach (var page in result.Pages)
{
foreach (var line in page.Lines)
{
Console.WriteLine($"Line: '{line.Text}' at Y={line.Y}");
}
}
Imports IronOcr
' IronOCR: typed result object — no JSON schema, no nullable chains
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Dim ocr As New IronTesseract()
Dim result = ocr.Read("invoice.jpg")
' Direct property access — no deserialization, no nullable navigation
Dim fullText As String = result.Text
Dim confidence As Double = result.Confidence
Dim pageCount As Integer = result.Pages.Count()
' Structured data: lines and words with coordinates
For Each page In result.Pages
For Each line In page.Lines
Console.WriteLine($"Line: '{line.Text}' at Y={line.Y}")
Next
Next
OcrResult é um objeto .NET tipado. Não há JSON para analisar, nenhuma classe de modelo para manter e nenhum risco de desvio de esquema que possa comprometer a desserialização em produção. O guia de leitura de resultados documenta o modelo completo de objetos OcrResult, incluindo coordenadas de palavras, pontuações de confiança e hierarquia de páginas estruturadas. Para padrões de extração de campos específicos de faturas construídos sobre OcrResult, o tutorial de OCR de faturas cobre a lógica de extração de ponta a ponta.
Removendo a infraestrutura de tratamento de erros e repetição.
A integração do Klippa via HTTP requer tratamento de erros para cada modo de falha que uma chamada de rede pode produzir: tempos limite, respostas 4xx, respostas 5xx, limites de taxa e JSON parcial. As equipes que executam integrações em produção adicionam políticas de repetição usando Polly ou lógica personalizada. Essa infraestrutura desaparece quando a chamada de rede desaparece.
Abordagem de Klippa:
// Klippa: retry policy required — cloud calls fail unpredictably
public async Task<string> ReadWithRetryAsync(string filePath, int maxRetries = 3)
{
var delay = TimeSpan.FromSeconds(1);
for (int attempt = 1; attempt <= maxRetries; attempt++)
{
try
{
using var form = new MultipartFormDataContent();
form.Add(
new ByteArrayContent(await File.ReadAllBytesAsync(filePath)),
"document",
Path.GetFileName(filePath));
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var response = await _httpClient.PostAsync(
"https://custom-ocr.klippa.com/api/v1/parseDocument", form, cts.Token);
if (response.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
{
// rate limited — back off and retry
await Task.Delay(delay * attempt);
continue;
}
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync(cts.Token);
var parsed = JsonSerializer.Deserialize<KlippaResponse>(json);
return parsed?.Data?.ParsedDocument?.Text ?? string.Empty;
}
catch (HttpRequestException) when (attempt < maxRetries)
{
await Task.Delay(delay * attempt); // exponential backoff
}
catch (TaskCanceledException) when (attempt < maxRetries)
{
await Task.Delay(delay * attempt); // timeout — retry
}
}
throw new InvalidOperationException($"Klippa API failed after {maxRetries} attempts");
}
// Klippa: retry policy required — cloud calls fail unpredictably
public async Task<string> ReadWithRetryAsync(string filePath, int maxRetries = 3)
{
var delay = TimeSpan.FromSeconds(1);
for (int attempt = 1; attempt <= maxRetries; attempt++)
{
try
{
using var form = new MultipartFormDataContent();
form.Add(
new ByteArrayContent(await File.ReadAllBytesAsync(filePath)),
"document",
Path.GetFileName(filePath));
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var response = await _httpClient.PostAsync(
"https://custom-ocr.klippa.com/api/v1/parseDocument", form, cts.Token);
if (response.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
{
// rate limited — back off and retry
await Task.Delay(delay * attempt);
continue;
}
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync(cts.Token);
var parsed = JsonSerializer.Deserialize<KlippaResponse>(json);
return parsed?.Data?.ParsedDocument?.Text ?? string.Empty;
}
catch (HttpRequestException) when (attempt < maxRetries)
{
await Task.Delay(delay * attempt); // exponential backoff
}
catch (TaskCanceledException) when (attempt < maxRetries)
{
await Task.Delay(delay * attempt); // timeout — retry
}
}
throw new InvalidOperationException($"Klippa API failed after {maxRetries} attempts");
}
Imports System
Imports System.IO
Imports System.Net.Http
Imports System.Text.Json
Imports System.Threading
Imports System.Threading.Tasks
Public Class KlippaService
Private ReadOnly _httpClient As HttpClient
Public Sub New(httpClient As HttpClient)
_httpClient = httpClient
End Sub
' Klippa: retry policy required — cloud calls fail unpredictably
Public Async Function ReadWithRetryAsync(filePath As String, Optional maxRetries As Integer = 3) As Task(Of String)
Dim delay As TimeSpan = TimeSpan.FromSeconds(1)
For attempt As Integer = 1 To maxRetries
Try
Using form As New MultipartFormDataContent()
form.Add(New ByteArrayContent(Await File.ReadAllBytesAsync(filePath)), "document", Path.GetFileName(filePath))
Using cts As New CancellationTokenSource(TimeSpan.FromSeconds(30))
Dim response As HttpResponseMessage = Await _httpClient.PostAsync("https://custom-ocr.klippa.com/api/v1/parseDocument", form, cts.Token)
If response.StatusCode = System.Net.HttpStatusCode.TooManyRequests Then
' rate limited — back off and retry
Await Task.Delay(delay * attempt)
Continue For
End If
response.EnsureSuccessStatusCode()
Dim json As String = Await response.Content.ReadAsStringAsync(cts.Token)
Dim parsed As KlippaResponse = JsonSerializer.Deserialize(Of KlippaResponse)(json)
Return If(parsed?.Data?.ParsedDocument?.Text, String.Empty)
End Using
End Using
Catch ex As HttpRequestException When attempt < maxRetries
Await Task.Delay(delay * attempt) ' exponential backoff
Catch ex As TaskCanceledException When attempt < maxRetries
Await Task.Delay(delay * attempt) ' timeout — retry
End Try
Next
Throw New InvalidOperationException($"Klippa API failed after {maxRetries} attempts")
End Function
End Class
Public Class KlippaResponse
Public Property Data As KlippaData
End Class
Public Class KlippaData
Public Property ParsedDocument As ParsedDocument
End Class
Public Class ParsedDocument
Public Property Text As String
End Class
Abordagem IronOCR:
// IronOCR: no network, no retry policy needed — local call either works or throws once
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
public string ReadDocument(string filePath)
{
// Não retry loop. Não CancellationTokenSource. Não HTTP status checks.
// Não rate limit handling. Não partial-JSON guards.
var result = new IronTesseract().Read(filePath);
return result.Text;
}
// IronOCR: no network, no retry policy needed — local call either works or throws once
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
public string ReadDocument(string filePath)
{
// Não retry loop. Não CancellationTokenSource. Não HTTP status checks.
// Não rate limit handling. Não partial-JSON guards.
var result = new IronTesseract().Read(filePath);
return result.Text;
}
Imports IronOcr
' IronOCR: no network, no retry policy needed — local call either works or throws once
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Public Function ReadDocument(filePath As String) As String
' Não retry loop. Não CancellationTokenSource. Não HTTP status checks.
' Não rate limit handling. Não partial-JSON guards.
Dim result = New IronTesseract().Read(filePath)
Return result.Text
End Function
Toda a infraestrutura de repetição — o loop, o cálculo do atraso, o CancellationTokenSource, o desvio de código de status HTTP, o bloco de captura TaskCanceledException — existe unicamente por causa da rede. Remova a chamada de rede e tudo desaparecerá. Uma chamada OCR local falha rapidamente com uma exceção tipada se o arquivo de entrada estiver ausente ou ilegível, e é bem-sucedida caso contrário. O guia de otimização de velocidade aborda o ajuste de desempenho do IronOCR se a taxa de transferência for uma preocupação após a migração.
Processamento de PDFs com várias páginas sem upload na nuvem
Klippa aceita uploads de PDF através do mesmo endpoint parseDocument. PDFs com várias páginas ainda saem da sua rede. O IronOCR lê PDFs nativamente, durante o processo, com acesso aos resultados página por página.
Abordagem de Klippa:
// Klippa: PDF upload — entire document transmitted, results depend on cloud availability
public async Task<List<string>> ExtractPdfPagesAsync(string pdfPath)
{
var pages = new List<string>();
// Klippa parses the entire PDF server-side and returns combined results
// You cannot control per-page processing or access raw page text
using var form = new MultipartFormDataContent();
form.Add(
new ByteArrayContent(await File.ReadAllBytesAsync(pdfPath)),
"document",
Path.GetFileName(pdfPath));
var response = await _httpClient.PostAsync(
"https://custom-ocr.klippa.com/api/v1/parseDocument", form);
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<KlippaResponse>(json);
// Klippa returns the combined parsed text — no per-page breakdown in basic API
pages.Add(result?.Data?.ParsedDocument?.Text ?? string.Empty);
return pages;
}
// Klippa: PDF upload — entire document transmitted, results depend on cloud availability
public async Task<List<string>> ExtractPdfPagesAsync(string pdfPath)
{
var pages = new List<string>();
// Klippa parses the entire PDF server-side and returns combined results
// You cannot control per-page processing or access raw page text
using var form = new MultipartFormDataContent();
form.Add(
new ByteArrayContent(await File.ReadAllBytesAsync(pdfPath)),
"document",
Path.GetFileName(pdfPath));
var response = await _httpClient.PostAsync(
"https://custom-ocr.klippa.com/api/v1/parseDocument", form);
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<KlippaResponse>(json);
// Klippa returns the combined parsed text — no per-page breakdown in basic API
pages.Add(result?.Data?.ParsedDocument?.Text ?? string.Empty);
return pages;
}
Imports System.IO
Imports System.Net.Http
Imports System.Text.Json
Imports System.Threading.Tasks
Public Class PdfExtractor
Private ReadOnly _httpClient As HttpClient
Public Sub New(httpClient As HttpClient)
_httpClient = httpClient
End Sub
' Klippa: PDF upload — entire document transmitted, results depend on cloud availability
Public Async Function ExtractPdfPagesAsync(pdfPath As String) As Task(Of List(Of String))
Dim pages As New List(Of String)()
' Klippa parses the entire PDF server-side and returns combined results
' You cannot control per-page processing or access raw page text
Using form As New MultipartFormDataContent()
form.Add(New ByteArrayContent(Await File.ReadAllBytesAsync(pdfPath)), "document", Path.GetFileName(pdfPath))
Dim response = Await _httpClient.PostAsync("https://custom-ocr.klippa.com/api/v1/parseDocument", form)
response.EnsureSuccessStatusCode()
Dim json = Await response.Content.ReadAsStringAsync()
Dim result = JsonSerializer.Deserialize(Of KlippaResponse)(json)
' Klippa returns the combined parsed text — no per-page breakdown in basic API
pages.Add(If(result?.Data?.ParsedDocument?.Text, String.Empty))
End Using
Return pages
End Function
End Class
Public Class KlippaResponse
Public Property Data As KlippaData
End Class
Public Class KlippaData
Public Property ParsedDocument As ParsedDocument
End Class
Public Class ParsedDocument
Public Property Text As String
End Class
Abordagem IronOCR:
// IronOCR: native PDF OCR with per-page structured access — no upload
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
using var input = new OcrInput();
input.LoadPdf("multi-page-invoice.pdf"); // reads locally — no HTTP
var ocr = new IronTesseract();
var result = ocr.Read(input);
// Per-page access — not available from Klippa's combined response
foreach (var page in result.Pages)
{
Console.WriteLine($"Page {page.PageNumber}: {page.Lines.Count()} lines");
Console.WriteLine(page.Text);
}
// Or produce a searchable PDF from the scanned original
result.SaveAsSearchablePdf("searchable-output.pdf");
// IronOCR: native PDF OCR with per-page structured access — no upload
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
using var input = new OcrInput();
input.LoadPdf("multi-page-invoice.pdf"); // reads locally — no HTTP
var ocr = new IronTesseract();
var result = ocr.Read(input);
// Per-page access — not available from Klippa's combined response
foreach (var page in result.Pages)
{
Console.WriteLine($"Page {page.PageNumber}: {page.Lines.Count()} lines");
Console.WriteLine(page.Text);
}
// Or produce a searchable PDF from the scanned original
result.SaveAsSearchablePdf("searchable-output.pdf");
Imports IronOcr
' IronOCR: native PDF OCR with per-page structured access — no upload
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Using input As New OcrInput()
input.LoadPdf("multi-page-invoice.pdf") ' reads locally — no HTTP
Dim ocr As New IronTesseract()
Dim result = ocr.Read(input)
' Per-page access — not available from Klippa's combined response
For Each page In result.Pages
Console.WriteLine($"Page {page.PageNumber}: {page.Lines.Count()} lines")
Console.WriteLine(page.Text)
Next
' Or produce a searchable PDF from the scanned original
result.SaveAsSearchablePdf("searchable-output.pdf")
End Using
O IronOCR lê arquivos PDF nativamente, sem qualquer etapa de conversão. Cada página é acessível individualmente, com sua hierarquia completa de linhas, palavras e caracteres. A chamada SaveAsSearchablePdf() produz um PDF com camada de texto a partir de um documento escaneado — uma capacidade que Klippa não oferece. O guia de entrada de PDF aborda as opções de carregamento, e o guia de PDF pesquisável aborda as opções de saída, incluindo PDF/A para conformidade com arquivamento.
Referência de mapeamento da API OCR do Klippa para o IronOCR
Klippa é uma API REST, não um SDK tipado. O mapeamento abaixo traduz a superfície de integração de Klippa para equivalentes no IronOCR.
| Conceito Klippa | Equivalente de IronOCR |
|---|---|
HttpClient com cabeçalho X-Auth-Key |
IronTesseract instância — sem configuração de autenticação |
MultipartFormDataContent |
OcrInput.LoadImage(path) ou OcrInput.LoadPdf(path) |
POST /api/v1/parseDocument |
IronTesseract.Read(input) |
await _client.PostAsync(...) |
ocr.Read(input) — síncrono, sem necessidade de await |
response.EnsureSuccessStatusCode() |
Não é necessário — nenhuma resposta HTTP |
JsonSerializer.Deserialize<KlippaResponse>(json) |
Objeto OcrResult tipado — sem desserialização |
KlippaResponse.Data.ParsedDocument.Text |
OcrResult.Text |
KlippaResponse.Data.ParsedDocument.Amount |
Regex personalizado em OcrResult.Text ou OcrResult.Lines |
KlippaResponse.Data.ParsedDocument.Merchant |
OcrResult.Pages[0].Lines[0].Text |
Loop de repetição com Task.Delay |
Não é necessário — nenhum modo de falha de rede |
CancellationTokenSource(TimeSpan.FromSeconds(30)) |
Não é necessário — execução local |
| Tratamento de limite de taxa (HTTP 429) | Não é necessário — não há limites de taxa. |
| Roteamento de documentos na nuvem para servidores da UE | Execução local em processo |
KlippaService.Dispose() / HttpClient.Dispose() |
Descarte OcrInput via declaração using |
| Campos de resposta JSON estruturados | OcrResult.Text + OcrResult.Pages + OcrResult.Words |
| Assinatura de API SaaS | String IronOcr.License.LicenseKey — perpétua |
Problemas e soluções comuns em migrações
Problema 1: Sites com chamadas somente assíncronas após a remoção do HTTP
Klippa: Toda a integração com o Klippa é assíncrona porque as chamadas HTTP exigem isso. Controladores, serviços e trabalhadores em segundo plano em toda a sua base de código chamam await ProcessDocumentAsync(...). Remover a chamada HTTP significa que o await não é mais necessário, mas as assinaturas do método async permanecem.
Solução: O IronOCR fornece APIs síncronas e assíncronas. Para locais de chamada que devem permanecer assíncronos (controladores ASP.NET Core, serviços de fundo com CancellationToken), use ReadAsync:
// Keep async method signatures — switch the implementation
public async Task<string> ProcessDocumentAsync(
string filePath, CancellationToken cancellationToken = default)
{
// Previously: await _httpClient.PostAsync(...)
// Now: local call, same awaitable pattern
var ocr = new IronTesseract();
var result = await ocr.ReadAsync(filePath);
return result.Text;
}
// Keep async method signatures — switch the implementation
public async Task<string> ProcessDocumentAsync(
string filePath, CancellationToken cancellationToken = default)
{
// Previously: await _httpClient.PostAsync(...)
// Now: local call, same awaitable pattern
var ocr = new IronTesseract();
var result = await ocr.ReadAsync(filePath);
return result.Text;
}
Imports System.Threading
Imports System.Threading.Tasks
Public Class DocumentProcessor
Public Async Function ProcessDocumentAsync(filePath As String, Optional cancellationToken As CancellationToken = Nothing) As Task(Of String)
Dim ocr As New IronTesseract()
Dim result = Await ocr.ReadAsync(filePath)
Return result.Text
End Function
End Class
O guia de OCR assíncrono cobre a integração ReadAsync e CancellationToken para padrões de serviço hospedados e ASP.NET Core.
Problema 2: Registro de Injeção de Dependência
Klippa: A classe KlippaService é registrada no DI como um serviço singleton ou com escopo e encapsula HttpClient. Remover isso significa atualizar o registro DI e todos os pontos de injeção.
Solução: Registre IronTesseract como um singleton (é thread-safe) e injete-o diretamente, ou crie uma fina camada que espelha sua interface de serviço existente:
// In Program.cs or Startup.cs
builder.Services.AddSingleton<IronTesseract>();
// Or wrap for interface compatibility
builder.Services.AddSingleton<IOcrService, IronOcrService>();
public class IronOcrService : IOcrService
{
private readonly IronTesseract _ocr;
public IronOcrService(IronTesseract ocr) => _ocr = ocr;
public string ReadDocument(string path) => _ocr.Read(path).Text;
}
// In Program.cs or Startup.cs
builder.Services.AddSingleton<IronTesseract>();
// Or wrap for interface compatibility
builder.Services.AddSingleton<IOcrService, IronOcrService>();
public class IronOcrService : IOcrService
{
private readonly IronTesseract _ocr;
public IronOcrService(IronTesseract ocr) => _ocr = ocr;
public string ReadDocument(string path) => _ocr.Read(path).Text;
}
Imports Microsoft.Extensions.DependencyInjection
' In Program.vb or Startup.vb
builder.Services.AddSingleton(Of IronTesseract)()
' Or wrap for interface compatibility
builder.Services.AddSingleton(Of IOcrService, IronOcrService)()
Public Class IronOcrService
Implements IOcrService
Private ReadOnly _ocr As IronTesseract
Public Sub New(ocr As IronTesseract)
_ocr = ocr
End Sub
Public Function ReadDocument(path As String) As String Implements IOcrService.ReadDocument
Return _ocr.Read(path).Text
End Function
End Class
Uma instância IronTesseract registrada como singleton lida com solicitações concorrentes. Cada chamada para Read() é thread-safe.
Problema 3: Extração de Campos Estruturados sem JSON Pré-Analisado
Klippa: Klippa retorna amount, merchant, date, e vat_amount como propriedades JSON tipadas. A migração para o IronOCR significa que esses campos não chegam mais pré-analisados.
Solução: O OcrResult do IronOCR fornece o texto bruto e coordenadas de nível palavra para construir uma extração equivalente. Para documentos com layouts previsíveis, o OCR baseado em regiões direciona-se diretamente a campos específicos:
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
// Target specific layout regions instead of relying on pre-parsed cloud fields
var totalRegion = new CropRectangle(350, 580, 250, 50); // bottom-right total area
var merchantRegion = new CropRectangle(50, 30, 400, 60); // top header area
using var merchantInput = new OcrInput();
merchantInput.LoadImage("receipt.jpg", merchantRegion);
var merchantName = new IronTesseract().Read(merchantInput).Text.Trim();
using var totalInput = new OcrInput();
totalInput.LoadImage("receipt.jpg", totalRegion);
var totalText = new IronTesseract().Read(totalInput).Text.Trim();
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
// Target specific layout regions instead of relying on pre-parsed cloud fields
var totalRegion = new CropRectangle(350, 580, 250, 50); // bottom-right total area
var merchantRegion = new CropRectangle(50, 30, 400, 60); // top header area
using var merchantInput = new OcrInput();
merchantInput.LoadImage("receipt.jpg", merchantRegion);
var merchantName = new IronTesseract().Read(merchantInput).Text.Trim();
using var totalInput = new OcrInput();
totalInput.LoadImage("receipt.jpg", totalRegion);
var totalText = new IronTesseract().Read(totalInput).Text.Trim();
Imports IronOcr
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
' Target specific layout regions instead of relying on pre-parsed cloud fields
Dim totalRegion As New CropRectangle(350, 580, 250, 50) ' bottom-right total area
Dim merchantRegion As New CropRectangle(50, 30, 400, 60) ' top header area
Using merchantInput As New OcrInput()
merchantInput.LoadImage("receipt.jpg", merchantRegion)
Dim merchantName As String = New IronTesseract().Read(merchantInput).Text.Trim()
End Using
Using totalInput As New OcrInput()
totalInput.LoadImage("receipt.jpg", totalRegion)
Dim totalText As String = New IronTesseract().Read(totalInput).Text.Trim()
End Using
O guia baseado em região para OCR cobre o uso de CropRectangle em detalhe. Para obter padrões de extração completos em layouts de recibos e faturas, o tutorial de digitalização de recibos fornece o código completo e funcional.
Edição 4: Documentos chegando como fluxos de serviços upstream
Klippa: O Klippa recebe documentos como uploads de formulários multipartes — bytes de arquivo encapsulados no conteúdo de um formulário HTTP. Se seu aplicativo recebe documentos como fluxos do S3, do Armazenamento de Blobs do Azure ou de APIs internas, você estava lendo o fluxo para bytes e, em seguida, carregando esses bytes para o Klippa.
Solução: O IronOCR aceita objetos Stream diretamente. A etapa de conversão de bytes desaparece:
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
// Stream from S3, Azure Blob, or any upstream source
public async Task<string> ProcessDocumentStreamAsync(Stream documentStream)
{
using var input = new OcrInput();
input.LoadImage(documentStream); // accepts Stream directly
var ocr = new IronTesseract();
var result = ocr.Read(input);
return result.Text;
}
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
// Stream from S3, Azure Blob, or any upstream source
public async Task<string> ProcessDocumentStreamAsync(Stream documentStream)
{
using var input = new OcrInput();
input.LoadImage(documentStream); // accepts Stream directly
var ocr = new IronTesseract();
var result = ocr.Read(input);
return result.Text;
}
Imports System.IO
Imports System.Threading.Tasks
Imports IronOcr
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Public Async Function ProcessDocumentStreamAsync(documentStream As Stream) As Task(Of String)
Using input As New OcrInput()
input.LoadImage(documentStream) ' accepts Stream directly
Dim ocr As New IronTesseract()
Dim result = ocr.Read(input)
Return result.Text
End Using
End Function
Sem ReadAllBytes, sem construção MultipartFormDataContent, sem POST HTTP. O fluxo vai diretamente para OcrInput. O guia de entrada de dados em córregos abrange os tipos de córregos e os padrões de descarte.
Problema 5: Testes de integração que dependem de simulação HTTP
Klippa: Testes de integração para o código Klippa simulam HttpClient ou usam interceptores HTTP (por exemplo, WireMock, MockHttp) para simular respostas de API. Esses testes simulam a camada HTTP, não a lógica do OCR.
Solução: Os testes do IronOCR utilizam documentos reais com resultados esperados conhecidos. Não é necessária nenhuma infraestrutura de simulação. Os testes são executados offline:
[Fact]
public void ReadDocument_ReturnsExpectedText()
{
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var ocr = new IronTesseract();
// Use a real test fixture — no HTTP mocking, runs fully offline
var result = ocr.Read("test-fixtures/sample-invoice.jpg");
Assert.Contains("Invoice", result.Text, StringComparison.OrdinalIgnoreCase);
Assert.True(result.Confidence > 70);
}
[Fact]
public void ReadDocument_ReturnsExpectedText()
{
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var ocr = new IronTesseract();
// Use a real test fixture — no HTTP mocking, runs fully offline
var result = ocr.Read("test-fixtures/sample-invoice.jpg");
Assert.Contains("Invoice", result.Text, StringComparison.OrdinalIgnoreCase);
Assert.True(result.Confidence > 70);
}
<Fact>
Public Sub ReadDocument_ReturnsExpectedText()
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Dim ocr = New IronTesseract()
' Use a real test fixture — no HTTP mocking, runs fully offline
Dim result = ocr.Read("test-fixtures/sample-invoice.jpg")
Assert.Contains("Invoice", result.Text, StringComparison.OrdinalIgnoreCase)
Assert.True(result.Confidence > 70)
End Sub
Testes que antes exigiam uma conexão Klippa ativa ou uma configuração complexa de simulação HTTP agora são executados em CI sem acesso à rede.
Problema 6: Documentos de baixa qualidade que o Klippa aprimorou no servidor
Klippa: O processamento em nuvem aplica aprimoramento de imagem antes do reconhecimento. Os desenvolvedores nunca configuram isso — acontece automaticamente nos servidores da Klippa. Ao migrar, documentos que o Klippa processou silenciosamente podem apresentar menor precisão sem um pré-processamento explícito no IronOCR.
Solução: Aplique explicitamente os filtros de pré-processamento do IronOCR. O conjunto de filtros reflete o que os serviços em nuvem aplicam no lado do servidor:
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
using var input = new OcrInput();
input.LoadImage("low-quality-scan.jpg");
input.Deskew(); // fix rotation from camera or scanner
input.DeNoise(); // remove compression noise
input.Contrast(); // boost faded ink
input.Binarize(); // clean background for clearer character edges
var result = new IronTesseract().Read(input);
Console.WriteLine($"Confidence: {result.Confidence}%");
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
using var input = new OcrInput();
input.LoadImage("low-quality-scan.jpg");
input.Deskew(); // fix rotation from camera or scanner
input.DeNoise(); // remove compression noise
input.Contrast(); // boost faded ink
input.Binarize(); // clean background for clearer character edges
var result = new IronTesseract().Read(input);
Console.WriteLine($"Confidence: {result.Confidence}%");
Imports IronOcr
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Using input As New OcrInput()
input.LoadImage("low-quality-scan.jpg")
input.Deskew() ' fix rotation from camera or scanner
input.DeNoise() ' remove compression noise
input.Contrast() ' boost faded ink
input.Binarize() ' clean background for clearer character edges
Dim result = New IronTesseract().Read(input)
Console.WriteLine($"Confidence: {result.Confidence}%")
End Using
O guia de correção da qualidade da imagem abrange todos os filtros de pré-processamento e a ordem em que devem ser aplicados para diferentes tipos de degradação do documento.
Lista de verificação de migração de OCR Klippa
Pré-migração
Antes de remover qualquer coisa, faça uma auditoria em sua base de código para localizar todo o código específico do Klippa:
# Find all files containing Klippa HTTP integration code
grep -r "X-Auth-Key" --include="*.cs" .
grep -r "klippa.com" --include="*.cs" .
grep -r "KlippaService\|KlippaResponse\|KlippaResult\|KlippaData" --include="*.cs" .
# Find all files with MultipartFormDataContent (likely Klippa upload code)
grep -r "MultipartFormDataContent" --include="*.cs" .
# Find all JSON deserialization models that map to Klippa response fields
grep -r "parsed_document\|vat_amount\|merchant\|X-Auth-Key" --include="*.cs" .
# Find all async methods that wrap Klippa calls
grep -r "ParseDocumentAsync\|ProcessReceiptAsync\|UploadAndParseAsync" --include="*.cs" .
# Find test files with HTTP mocks for Klippa
grep -r "MockHttp\|WireMock\|klippa" --include="*.cs" .
# Find all files containing Klippa HTTP integration code
grep -r "X-Auth-Key" --include="*.cs" .
grep -r "klippa.com" --include="*.cs" .
grep -r "KlippaService\|KlippaResponse\|KlippaResult\|KlippaData" --include="*.cs" .
# Find all files with MultipartFormDataContent (likely Klippa upload code)
grep -r "MultipartFormDataContent" --include="*.cs" .
# Find all JSON deserialization models that map to Klippa response fields
grep -r "parsed_document\|vat_amount\|merchant\|X-Auth-Key" --include="*.cs" .
# Find all async methods that wrap Klippa calls
grep -r "ParseDocumentAsync\|ProcessReceiptAsync\|UploadAndParseAsync" --include="*.cs" .
# Find test files with HTTP mocks for Klippa
grep -r "MockHttp\|WireMock\|klippa" --include="*.cs" .
Notas de inventário:
- Registre todas as classes que encapsulam
HttpClientpara chamadas Klippa - Liste todos os modelos de classe de desserialização JSON (
KlippaResponse,KlippaParsedDocument, etc.) - Documentar todos os mapeamentos de campos que consomem as propriedades JSON pré-analisadas do Klippa.
- Observe quaisquer políticas de repetição do Polly ou loops de repetição personalizados criados para o Klippa.
Migração de código
- Instale o pacote NuGet
IronOcr(dotnet add package IronOcr) - Adicione
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"à inicialização da aplicação - Remova
System.Net.Http,System.Text.Json,Newtonsoft.Jsonimportações dos arquivos de serviço Klippa - Exclua a classe
KlippaService(ou substitua seu corpo por chamadasIronTesseract, mantendo a interface) - Registre
IronTesseractcomo um singleton no contêiner DI - Substitua blocos de upload
MultipartFormDataContentporOcrInput.LoadImage()ouOcrInput.LoadPdf() - Exclua classes de modelo de resposta JSON (
KlippaResponse,KlippaData,KlippaParsedDocument) - Substitua cadeias de navegação JSON anuláveis (
.Data?.ParsedDocument?.Text) porresult.Text - Remova loops de repetição e tempos limite
CancellationTokenSourcedos locais de chamada Klippa - Remover o tratamento de limite de taxa (blocos de captura HTTP 429)
- Substitua
await ProcessDocumentAsync(...)porawait ocr.ReadAsync(...)ouocr.Read(...)síncrono - Adicione filtros de pré-processamento
OcrInput(Deskew,DeNoise,Contrast) para entradas de documentos de baixa qualidade - Substituir a infraestrutura de testes HTTP simulados por testes de documentos reais.
- Exclua as políticas de repetição do Polly ou o middleware de repetição personalizado com escopo para chamadas do Klippa.
Pós-migração
- Verificar se o resultado da extração de texto corresponde ao conteúdo esperado dos documentos de teste conhecidos.
- Confirme se os níveis de confiança excedem o limite aceitável (normalmente acima de 70%) para os tipos de documentos de produção.
- Teste de entrada PDF: carregue PDFs de várias páginas nativamente e verifique o acesso ao texto por página via
result.Pages - Teste de entrada de fluxo: passe
MemoryStreame verifique seOcrInput.LoadImage(stream)produz a saída correta - Verificar se os filtros de pré-processamento melhoram a precisão em digitalizações de baixa qualidade em comparação com a linha de base não processada.
- Confirme que o singleton DI-injetado
IronTesseractlida com solicitações concorrentes sem contenção - Execute os testes de integração offline (sem conexão com a rede) — todos os testes devem ser aprovados mesmo sem acesso à nuvem.
- Verifique a saída PDF pesquisável com
result.SaveAsSearchablePdf("output.pdf")para fluxos de documentos escaneados - Teste
ReadAsyncno contexto do controlador ASP.NET Core com a propagaçãoCancellationToken - Confirme que o padrão de descarte
using var input = new OcrInput()não vaza memória sob carga sustentada
Principais benefícios da migração para o IronOCR
Soberania de dados desde o primeiro dia. Após a migração, documentos financeiros sensíveis, verificações de identidade e faturas confidenciais nunca saem da sua infraestrutura. Não há nenhum processador de dados terceirizado no escopo da auditoria, nenhuma política de retenção de dados para revisar e nenhum contrato de transferência de dados para manter. As restrições HIPAA, ITAR, CMMC e FedRAMP que anteriormente tornavam o Klippa problemático são atendidas por padrão. A implantação em Docker , AWS ou Azure mantém tudo dentro dos limites da sua própria infraestrutura.
Complexidade da infraestrutura eliminada. A classe de serviço, o cliente HTTP, o código de upload do formulário, os modelos JSON, a política de repetição, a configuração de tempo limite — tudo isso existia para encapsular uma chamada de rede. Remova a chamada de rede e tudo o mais desaparecerá com ela. O código resultante é menor, mais fácil de ler e apresenta menos modos de falha. Uma única instância de IronTesseract injetada através do DI substitui toda a camada de integração HTTP.
Custo Previsível Independente do Volume. Uma licença perpétua IronOCR por $999 (Lite), $1.499 (Professional) ou $2.999 (Enterprise) cobre o processamento ilimitado de documentos. Processar 500 documentos por mês ou 500.000 por mês tem o mesmo custo. A dinâmica de cobrança por documento que tornava o Klippa caro em larga escala está estruturalmente ausente. A página de licenciamento do IronOCR detalha todos os níveis e o que cada um inclui.
Abrangência do documento sem limites. O IronOCR processa qualquer documento que contenha texto. Contratos escaneados, desenhos técnicos, formulários médicos, ordens de compra, notas manuscritas, capturas de tela, arquivos TIFF — todos tratados pela mesma chamada Read() com a mesma API. A restrição de escopo especializado que exigia um segundo sistema para documentos fora das categorias para as quais o Klippa era treinado foi eliminada. Uma biblioteca, um ponto de integração, qualquer tipo de documento.
Ambientes de rede offline e restritos agora são suportados. Aplicativos implantados em redes bancárias, sistemas governamentais, ambientes de borda ou qualquer infraestrutura com saída de dados restrita funcionam exatamente da mesma forma que em ambientes abertos. Não há verificação de conectividade, nem ping de integridade para um endpoint na nuvem, nem modo degradado quando a internet está indisponível. Implantações isoladas da internet funcionam sem modificações. O guia de implantação do Linux e o guia de implantação do Docker abrangem os caminhos de implantação em contêineres e no lado do servidor para esses ambientes.
Controle total sobre o aprimoramento de imagens. O pré-processamento na nuvem era uma caixa preta — o Klippa o aplicava, você observava os resultados, sem parâmetros para ajustar. O pipeline de pré-processamento do IronOCR é explícito e compositável: Deskew(), DeNoise(), Contrast(), Binarize(), Sharpen(), Scale(), Dilate(), DeepCleanBackgroundNoise(). Cada filtro é opcional e deve ser encomendado. As melhorias na precisão são mensuráveis, reproduzíveis e estão sob seu controle. O guia de correção da qualidade da imagem e a página de recursos de pré-processamento abrangem todo o catálogo de filtros, com orientações sobre quando aplicar cada um.
Perguntas frequentes
Por que devo migrar da API OCR do Klippa para o 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 Klippa para o IronOCR?
Substitua as sequências de inicialização do Klippa 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 Klippa 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 Klippa 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 Klippa para a IronOCR requer alterações na infraestrutura de implantação?
O IronOCR requer menos alterações de infraestrutura do que a API OCR do Klippa. 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 Klippa?
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 Klippa 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 Klippa 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.

