Jak zaimplementować OCR w języku C# przy użyciu bibliotek open source
Ten przewodnik jest przeznaczony dla programistów .NET, którzy zintegrowali REST API Klippa i przechodzą na IronOCR dla .NET w celu przetwarzania dokumentów na miejscu. Obejmuje praktyczne kroki mające na celu usunięcie infrastruktury klienta HTTP, wyeliminowanie deseryalizacji JSON oraz zastąpienie przesyłania dokumentów w chmurze lokalnymi wywołaniami OCR, które nigdy nie korzystają z sieci.
Dlaczego warto przejść z Klippa OCR
Klippa to usługa analizy dokumentów dostępna wyłącznie w chmurze, bez zestawu SDK dla platformy .NET. Każda integracja to ręcznie tworzony klient REST. Ta rzeczywistość architektoniczna ma dalsze konsekwencje, które kumulują się w trakcie cyklu życia systemu produkcyjnego.
Brak pakietu NuGet oznacza, że to Ty jesteś właścicielem warstwy integracyjnej. Nie ma nic do instalowania. Koszt wejścia to napisanie wrappera HttpClient, skonfigurowanie nagłówków uwierzytelniania X-Auth-Key, budowanie ciał żądań MultipartFormDataContent, deserializacja schematu odpowiedzi JSON Klippy i podłączenie logiki ponowienia dla tymczasowych awarii. To 2–4 dni przygotowań, zanim pierwszy dokument zostanie niezawodnie przetworzony w środowisku produkcyjnym. Kiedy Klippa aktualizuje schemat swojego API, kod deseryalizacji przestaje działać i wymaga ręcznej konserwacji.
Każde przesłanie dokumentu wiąże się z zależnością od sieci. Klippa przetwarza dokumenty wyłącznie na serwerach hostowanych w UE. Przerwy w działaniu po stronie Klippa, zwiększone opóźnienia lub jakiekolwiek zakłócenia w dostępie do Internetu z serwera aplikacji całkowicie wstrzymują przetwarzanie dokumentów. Nie ma rozwiązania awaryjnego, trybu lokalnego ani ponownej próby, które rozwiązywałyby problem niedostępności usługi w chmurze.
Wrażliwe dokumenty opuszczają Twoją infrastrukturę. Dokumenty finansowe — pokwitowania z danymi płatności, faktury z numerami VAT i kwotami, dokumenty tożsamości z danymi paszportowymi — są przesyłane na serwer strony trzeciej przy każdym wywołaniu API. Przepisy RODO dotyczące przekazywania danych uwzględniają niektóre z tych kwestii w odniesieniu do przetwarzania danych w UE, ale zakres audytu nadal obejmuje infrastrukturę Klippy, zasady przechowywania danych oraz podwykonawców przetwarzających dane. W przypadku zespołów realizujących kontrakty w sektorze opieki zdrowotnej, prawnym, usług finansowych lub rządowym sformułowanie "hostowane w UE" nie spełnia wymogu, aby dane nie opuszczały organizacji.
Ceny za dokument są skalowane bez górnego limitu. Klippa nie publikuje cennika. Przy każdej znaczącej ilości dokumentów — 10 000 rachunków miesięcznie w systemie zarządzania wydatkami, 500 faktur dziennie w procesie automatyzacji rozliczeń — model rozliczeń za dokument generuje koszty, których nigdy nie poniosłabyś w przypadku Licencji wieczystej. Kształtowanie się kosztów jest bezpośrednio powiązane ze wzrostem działalności, co jest przeciwieństwem tego, jak powinny wyglądać wydatki na infrastrukturę.
Zakres kompetencji specjalisty ulega zmianie wraz z rozszerzeniem wymagań. Klippa została przeszkolona w zakresie paragonów, faktur i dokumentów tożsamości. Aplikacja, która zaczyna jako narzędzie do zarządzania wydatkami, rzadko pozostaje tylko tym. Gdy po raz pierwszy pojawia się dokument spoza tych trzech kategorii — zeskanowana umowa o pracę, formularz medyczny, rysunek techniczny, zamówienie o niestandardowym układzie — Klippa nie zwraca żadnych użytecznych wyników.IronOCR przetwarza każdy dokument zawierający tekst, bez ograniczeń dotyczących kategorii.
Wywołania REST wyłącznie asynchroniczne powodują opóźnienia w kontekstach synchronicznych. Każde wywołanie Klippa jest operacją HTTP typu asynchronicznego. Przesłanie jednego dokumentu w obie strony zajmuje od 500 ms do 2000 ms w sieci.IronOCR przetwarza ten sam dokument lokalnie w czasie 100–400 ms bez obciążenia związanego z przetwarzaniem asynchronicznym w scenariuszach, w których przetwarzanie synchroniczne lepiej pasuje do architektury.
Podstawowy problem
Klippa nie posiada zestawu SDK. OCR oznacza skonstruowanie i wysłanie żądania HTTP, a następnie deseryalizację 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
IronOCR zastępuje to wszystko:
// 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 a Klippa OCR: porównanie funkcji
W poniższej tabeli porównano obie biblioteki pod kątem aspektów, które mają największe znaczenie przy podejmowaniu decyzji o migracji do środowiska produkcyjnego.
| Funkcja | Klippa OCR | IronOCR |
|---|---|---|
| Model wdrożenia | Wyłącznie w chmurze (serwery w UE) | Lokalna, w pełni zlocalizowana |
| Pakiet .NET SDK / NuGet | None | Pakiet NuGet IronOcr |
| Wymagane połączenie z Internetem | Tak, przy każdym wywołaniu | Nigdy |
| Dane dokumentu opuszczają sieć | Zawsze | Nigdy |
| Uniwersalne OCR | Nie (tylko pokwitowania, faktury, dokumenty tożsamości) | Tak (dowolny typ dokumentu) |
| Konfiguracja uwierzytelniania | Nagłówek HTTP X-Auth-Key |
Ciąg znaków IronOcr.License.LicenseKey |
| Wymagany klient HTTP | Tak | Nie |
| Deserializacja odpowiedzi | Ręczne parsowanie JSON | Typowany obiekt OcrResult |
| Logika ponownej próby/limitu czasu | Ręcznie przygotowane | Nie jest wymagane (połączenie lokalne) |
| Obsługa trybu offline / air-gapped | Nie | Tak |
| Plik wejściowy PDF | Tak (chmura) | Tak (język ojczysty, lokalny) |
| Wielostronicowy plik wejściowy w formacie TIFF | Nieznane | Tak |
| Formaty plików graficznych | JPG, PNG (chmura) | JPG, PNG, BMP, TIFF, GIF i inne |
| Wejście strumieniowe i tablica bajtów | Brak SDK | Tak |
| Automatyczne przetwarzanie wstępne obrazów | Po stronie chmury (nieprzejrzyste) | Tak (Deskew, DeNoise, Contrast, Binarize, Sharpen) |
| Struktura wyjściowa: współrzędne słów | Nie | Tak |
| Wyniki pewności dla poszczególnych słów | Nie | Tak |
| Wynik w formacie PDF z możliwością wyszukiwania | Nie | Tak |
| Odczytywanie BarCode podczas OCR | Nie | Tak |
| Obsługa wielu języków | Ograniczone do określonych typów dokumentów | Ponad 125 języków |
| Bezpieczeństwo wątków | N/A (wywołania HTTP) | Tak (jeden IronTesseract na wątek) |
| Wdrażanie wielopłatformowe | Niezależny od REST | Windows, Linux, macOS, Docker, Azure, AWS |
| Zgodność z HIPAA / ITAR / air-gapped | Nie | Tak |
| Model cenowy | SaaS za dokument (ceny nieopublikowane) | Licencja wieczysta od $999 |
| Koszt za stronę przy dużej skali | Tak, bez ograniczeń | None |
Szybki start: Migracja zKlippa OCRdo IronOCR
Krok 1: Zastąp pakiet NuGet
Klippa nie posiada oficjalnego pakietu NuGet. Usuń zależności klienta HTTP, które istnieją wyłącznie w celu obsługi integracji z Klippą:
# 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
Zainstaluj IronOCR z NuGet:
dotnet add package IronOcr
Krok 2: Aktualizacja przestrzeni nazw
Należy usunąć przestrzenie nazw HTTP i JSON wymagane przez integrację z Klippą. Dodaj pojedynczą przestrzeń nazw 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
Krok 3: Inicjalizacja licencji
Dodaj inicjalizację licencji raz na początek aplikacji — w Program.cs, Startup.cs lub przed pierwszym wywołaniem OCR:
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Przykłady migracji kodu
Zastąpienie klasy usługi klienta HTTP
Integracja z Klippą wymaga pełnej klasy usługowej obejmującej infrastrukturę HTTP. Nie da się tego uniknąć, ponieważ nie ma SDK.
Podejście 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
Podejście 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
Klasa usługowa Klippa istnieje wyłącznie dlatego, że API wymaga infrastruktury HTTP. RównoważnośćIronOCR sprowadza się do pojedynczego wywołania Read(). Limity czasu, nagłówki uwierzytelniające i wzorce usuwania znikają, ponieważ nie ma sieci. Opcje inicjalizacji można znaleźć w przewodniku konfiguracji IronTesseract, a działający kod w podstawowym przykładzie OCR.
Eliminacja przesyłania formularzy wieloczęściowych
Klippa otrzymuje dokumenty w formie wieloczęściowych plików przesyłanych za pomocą formularzy. Kod przesyłania jest mechaniczny, ale wrażliwy: odczyt plików, nagłówki typu zawartości, tworzenie granic i zarządzanie rozmiarem przesyłanych plików.
Podejście 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
Podejście 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
Konstrukcja MultipartFormDataContent, nagłówki typu treści oraz sam upload zniknęły.IronOCR czyta bezpośrednio z ścieżki pliku, z tablicy bajtów lub z Stream — te same dane, które Klippa przesyłała do chmury, pozostają lokalne. Przewodnik dotyczący danych wejściowych w postaci obrazów obejmuje wszystkie obsługiwane formaty wejściowe, a przewodnik dotyczący danych wejściowych w postaci strumienia obejmuje ścieżkę strumienia pamięci dla dokumentów, które docierają jako tablice bajtów z procesów nadrzędnych.
Zastąpienie deseryalizacji odpowiedzi JSON
Klippa zwraca zagnieżdżoną strukturę JSON. Nawigacja w tej strukturze wymaga modelu C# odpowiadającego lub inline JsonDocument — oba przerywają się, kiedy Klippa zmienia schemat odpowiedzi.
Podejście 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
Podejście 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 to typowy obiekt .NET. Nie ma JSON do parsowania, nie ma klasy modelu do utrzymania i nie ma ryzyka, że zmiana schematu zakłóci deseryalizację w środowisku produkcyjnym. Przewodnik odczytywania wyników dokumentuje kompletny model obiektowy OcrResult, w tym współrzędne słów, wyniki pewności i hierarchię strukturalną strony. Dla wzorców ekstrakcji pól specyficznych dla faktur zbudowanych na OcrResult, tutorial OCR faktur obejmuje logikę ekstrakcji od końca do końca.
Usunięcie infrastruktury obsługi błędów i ponownych prób
Integracja Klippa przez HTTP wymaga obsługi błędów dla każdego trybu awarii, jaki może spowodować wywołanie sieciowe: przekroczenie limitu czasu, odpowiedzi 4xx, odpowiedzi 5xx, limity szybkości i częściowy JSON. Zespoły zajmujące się integracjami produkcyjnymi dodają zasady ponownych prób przy użyciu Polly lub logiki niestandardowej. Infrastruktura ta znika wraz z zanikiem połączenia sieciowego.
Podejście 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
Podejście 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)
{
// Nie retry loop. Nie CancellationTokenSource. Nie HTTP status checks.
// Nie rate limit handling. Nie 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)
{
// Nie retry loop. Nie CancellationTokenSource. Nie HTTP status checks.
// Nie rate limit handling. Nie 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 Function ReadDocument(filePath As String) As String
' Nie retry loop. Nie CancellationTokenSource. Nie HTTP status checks.
' Nie rate limit handling. Nie partial-JSON guards.
Dim result = New IronTesseract().Read(filePath)
Return result.Text
End Function
Cała infrastruktura ponowienia — pętla, obliczenie opóźnienia, CancellationTokenSource, rozgałęzienie kodu statusu HTTP, blok catch TaskCanceledException — istnieją wyłącznie z powodu sieci. Usuń wywołanie sieciowe, a wszystko zniknie. Lokalne wywołanie OCR kończy się szybkim błędem z podanym wyjątkiem, jeśli plik wejściowy jest nieobecny lub nieczytelny, a w przeciwnym razie kończy się powodzeniem. Przewodnik po optymalizacji szybkości obejmuje dostrajanie wydajności IronOCR, jeśli po migracji pojawiają się obawy dotyczące przepustowości.
Przetwarzanie wielostronicowych plików PDF bez przesyłania do chmury
Klippa akceptuje uploady PDF poprzez ten sam punkt końcowy parseDocument. Wielostronicowe pliki PDF nadal opuszczają Twoją sieć.IronOCR odczytuje pliki PDF natywnie, w trakcie przetwarzania, z dostępem do wyników strona po stronie.
Podejście 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
Podejście 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
IronOCR odczytuje pliki PDF natywnie, bez konieczności konwersji. Każda strona jest dostępna indywidualnie wraz z pełną hierarchią wierszy, słów i znaków. Wywołanie SaveAsSearchablePdf() produkuje PDF z warstwą tekstową z zeskanowanego dokumentu — funkcjonalność, której Klippa nie oferuje. Podręcznik w formacie PDF dotyczący danych wejściowych obejmuje opcje ładowania, a podręcznik w formacie PDF z funkcją wyszukiwania obejmuje opcje wyjściowe, w tym format PDF/A zapewniający zgodność z wymogąmi archiwizacji.
Klippa OCRAPI do IronOCR– dokumentacja API dla mapowania
Klippa to REST API, a nie typowany zestaw SDK. Poniższe zestawienie przedstawia odpowiedniki funkcji integracyjnych Klippy w IronOCR.
| Koncepcja Klippa | Odpowiednik IronOCR |
|---|---|
HttpClient z nagłówkiem X-Auth-Key |
Instancja IronTesseract — brak konfiguracji uwierzytelniania |
MultipartFormDataContent |
OcrInput.LoadImage(path) lub OcrInput.LoadPdf(path) |
POST /api/v1/parseDocument |
IronTesseract.Read(input) |
await _client.PostAsync(...) |
ocr.Read(input) — synchroniczne, bez potrzeby await |
response.EnsureSuccessStatusCode() |
Niepotrzebne — brak odpowiedzi HTTP |
JsonSerializer.Deserialize<KlippaResponse>(json) |
Typowany OcrResult — bez deserializacji |
KlippaResponse.Data.ParsedDocument.Text |
OcrResult.Text |
KlippaResponse.Data.ParsedDocument.Amount |
Niestandardowy regex na OcrResult.Text lub OcrResult.Lines |
KlippaResponse.Data.ParsedDocument.Merchant |
OcrResult.Pages[0].Lines[0].Text |
Pętla ponowienia z Task.Delay |
Nie jest wymagane — brak trybu awarii sieci |
CancellationTokenSource(TimeSpan.FromSeconds(30)) |
Nie jest wymagane — lokalne wykonanie |
| Obsługa limitów szybkości (HTTP 429) | Nie jest wymagane — brak limitów stawek |
| Przekierowywanie dokumentów w chmurze na serwery w UE | Lokalne wykonywanie w trakcie procesu |
KlippaService.Dispose() / HttpClient.Dispose() |
Zarządzanie usuwaniem OcrInput poprzez instrukcję using |
| Strukturalne pola odpowiedzi JSON | OcrResult.Text + OcrResult.Pages + OcrResult.Words |
| Subskrypcja SaaS API | Ciąg znaków IronOcr.License.LicenseKey — wieczyste |
Typowe problemy związane z migracją i ich rozwiązania
Problem 1: Miejsca wywołań wyłącznie asynchronicznych po usunięciu HTTP
Klippa: Cała integracja z Klippą jest asynchroniczna, ponieważ wymagają tego wywołania HTTP. Kontrolery, serwisy i procesy w tle w całym kodzie wywołują await ProcessDocumentAsync(...). Usunięcie wywołania HTTP oznacza, że await nie jest już potrzebny, ale sygnatury metod async pozostają.
Rozwiązanie:IronOCR dostarcza zarówno synchronizowane, jak i asynchroniczne API. Dla miejsc, które muszą pozostać asynchroniczne (kontrolery ASP.NET Core, serwisy w tle z CancellationToken), użyj 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
Przewodnik asynchronicznego OCR obejmuje integrację ReadAsync i CancellationToken dla ASP.NET Core i wzorców serwisów hostowanych.
Problem 2: Rejestracja wstrzykiwania zależności
Klippa: Klasa KlippaService jest rejestrowana w DI jako singleton lub usługą skopowaną i obejmuje HttpClient. Usunięcie tego oznacza aktualizację rejestracji DI i wszystkich punktów wstrzykiwania.
Rozwiązanie: Zarejestruj IronTesseract jako singleton (jest bezpieczny dla wielu wątków) i wstrzyknij go bezpośrednio, lub stwórz cienki wrapper, który odzwierciedla istniejący interfejs serwisów:
// 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
Jedna instancja IronTesseract zarejestrowana jako singleton obsługuje równoczesne żądania. Każde wywołanie Read() jest bezpieczne dla wątków.
Problem 3: Pobieranie ustrukturyzowanych pól bez wstępnego parsowania JSON
Klippa: Klippa zwraca amount, merchant, date i vat_amount jako typowane właściwości JSON. Przejście na IronOCR oznacza, że pola te nie są już dostarczane w postaci wstępnie przetworzonej.
Rozwiązanie: OcrResultIronOCR dostarcza surowego tekstu i współrzędnych na poziomie słowa do zbudowania równoważnej ekstrakcji. W przypadku dokumentów o przewidywalnym układzie, OCR oparty na regionach kieruje się bezpośrednio do określonych pól:
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
Przewodnik optymalizacji regionu OCR szczegółowo opisuje użycie CropRectangle. Aby uzyskać pełne wzorce wyodrębniania danych z różnych układów paragonów i faktur, samouczek dotyczący skanowania paragonów zawiera kompletny, działający kod.
Problem 4: Dokumenty przychodzące jako strumienie z usług nadrzędnych
Klippa: Klippa odbiera dokumenty w postaci wieloczęściowych przesyłanych formularzy — bajtów plików zawartych w treści formularza HTTP. Jeśli Twoja aplikacja odbiera dokumenty jako strumienie z S3, Azure Blob Storage lub wewnętrznych interfejsów API, odczytywałeś strumień do bajtów, a następnie przesyłałeś te bajty do Klippa.
Rozwiązanie:IronOCR akceptuje obiekty Stream bezpośrednio. Krok konwersji bajtów znika:
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
Brak ReadAllBytes, brak konstrukcji MultipartFormDataContent, brak HTTP POST. Strumień wchodzi bezpośrednio do OcrInput. Przewodnik po danych wejściowych strumieniowych obejmuje typy strumieni i wzorce usuwania.
Problem 5: Testy integracyjne zależne od symulacji HTTP
Klippa: Testy integracji kodu Klippa symulują HttpClient lub używają interfejsów HTTP (np. WireMock, MockHttp) do symulacji odpowiedzi API. Testy te symulują warstwę HTTP, a nie logikę OCR.
Rozwiązanie: Testy IronOCR wykorzystują prawdziwe dokumenty o znanym oczekiwanym wyniku. Nie jest wymagana infrastruktura testowa. Testy przeprowadzane w trybie 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
Testy, które wcześniej wymagały aktywnego połączenia z Klippą lub skomplikowanej konfiguracji mocków HTTP, teraz działają w CI bez dostępu do sieci.
Problem 6: Dokumenty niskiej jakości, które Klippa ulepszyła po stronie serwera
Klippa: Przetwarzanie w chmurze stosuje poprawę jakości obrazu przed rozpoznaniem. Programiści nigdy tego nie konfigurują — dzieje się to automatycznie na serwerach Klippa. Podczas migracji dokumenty, które Klippa przetwarzała w tle, mogą charakteryzować się niższą dokładnością bez wyraźnego przetwarzania wstępnego w IronOCR.
Rozwiązanie: Należy wyraźnie zastosować filtry przetwarzania wstępnego IronOCR. Zestaw filtrów odzwierciedla to, co usługi w chmurze stosują po stronie serwera:
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
Przewodnik po korekcji jakości obrazu obejmuje wszystkie filtry przetwarzania wstępnego oraz kolejność ich stosowania w przypadku różnych rodzajów uszkodzeń dokumentów.
Lista kontrolna migracji Klippa OCR
Przed migracją
Przed usunięciem czegokolwiek sprawdź swój kod źródłowy, aby zlokalizować cały kod specyficzny dla 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" .
Uwagi dotyczące zasobów:
- Zarejestruj każdą klasę, która opakowuje
HttpClientdla wywołań Klippa - Wylistuj wszystkie modelowe klasy deserializacji JSON (
KlippaResponse,KlippaParsedDocument, itd.) - Udokumentuj wszystkie mapowania pól, które wykorzystują wstępnie przeanalizowane właściwości JSON Klippy
- Zwróć uwagę na wszelkie zasady ponownych prób Polly lub niestandardowe pętle ponownych prób stworzone dla Klippa
Migracja kodu
- Zainstaluj pakiet NuGet
IronOcr(dotnet add package IronOcr) - Dodaj
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"na początek aplikacji - Usuń importy
System.Net.Http,System.Text.Json,Newtonsoft.Jsonz plików serwisowych Klippa - Usuń klasę
KlippaService(lub zastąp jej ciało wywołaniamiIronTesseract, zachowując interfejs) - Zarejestruj
IronTesseractjako singleton w pojemniku DI - Zastąp bloki uploadu
MultipartFormDataContentzOcrInput.LoadImage()lubOcrInput.LoadPdf() - Usuń klasy modelowe odpowiedzi JSON (
KlippaResponse,KlippaData,KlippaParsedDocument) - Zastąp łańcuchy nawigacyjne nullable JSON (
.Data?.ParsedDocument?.Text) zresult.Text - Usuń pętle ponowienia i limity czasowe
CancellationTokenSourcez miejsc wywołań Klippa - Usunąć obsługę limitów szybkości (bloki przechwytujące HTTP 429)
- Zastąp
await ProcessDocumentAsync(...)przezawait ocr.ReadAsync(...)lub synchroniczneocr.Read(...) - Dodaj filtry preprocesowania
OcrInput(Deskew,DeNoise,Contrast) dla wpisów o niskiej jakości dokumentach - Zastąp infrastrukturę testów mock HTTP prawdziwymi testami z wykorzystaniem dokumentów
- Usuń zasady ponownych prób Polly lub niestandardowe oprogramowanie pośredniczące do ponownych prób w zakresie wywołań Klippa
Po migracji
- Sprawdź, czy wynik wyodrębnienia tekstu odpowiada oczekiwanej treści znanych dokumentów testowych
- Sprawdź, czy wyniki pewności przekraczają dopuszczalny próg (zazwyczaj 70%+) dla typów dokumentów produkcyjnych
- Testuj wejście PDF: załaduj wielostronicowe PDF-y natywnie i zweryfikuj dostęp do tekstu na stronę poprzez
result.Pages - Testuj wejście strumienia: przekaż
MemoryStreami zweryfikuj, żeOcrInput.LoadImage(stream)produkuje poprawny wynik - Filtry przetwarzania wstępnego zwiększają dokładność skanów o niskiej jakości w porównaniu z nieprzetworzonymi skanami bazowymi
- Potwierdź, że wstrzyknięty przez DI singleton
IronTesseractobsługuje równoczesne żądania bez sporów - Uruchom testy integracyjne w trybie offline (bez połączenia z siecią) — wszystkie testy powinny zakończyć się powodzeniem bez dostępu do chmury
- Zweryfikuj wyjście wyszukiwanego PDF z
result.SaveAsSearchablePdf("output.pdf")dla przepływów dokumentów zeskanowanych - Testuj
ReadAsyncw kontekście kontrolera ASP.NET Core z propagacjąCancellationToken - Potwierdź, że wzorzec usuwania
using var input = new OcrInput()nie wycieka pamięci przy ciągłym obciążeniu
Kluczowe korzyści z migracji do IronOCR
Suwerenność danych od pierwszego dnia. Po migracji poufne dokumenty finansowe, skany dokumentów tożsamości i poufne faktury nigdy nie opuszczają Twojej infrastruktury. W zakresie audytu nie ma podmiotów przetwarzających dane, nie ma polityki przechowywania danych do przeglądu ani umowy o przekazywaniu danych do utrzymania. Ograniczenia HIPAA, ITAR, CMMC i FedRAMP, które wcześniej sprawiały, że korzystanie z Klippy było problematyczne, są domyślnie spełnione. Wdrożenie na Dockerze, AWS lub Azure pozwala utrzymać wszystko w granicach własnej infrastruktury.
Eliminacja złożoności infrastruktury. Klasa usługowa, klient HTTP, kod przesyłania formularzy, modele JSON, zasady ponawiania prób, konfiguracja limitów czasu — wszystko to istniało po to, aby opakować wywołanie sieciowe. Usuń wywołanie sieciowe, a wszystko inne pójdzie za nim. Powstały kod jest mniejszy, łatwiejszy do odczytania i ma mniej trybów awarii. Jedna instancja IronTesseract wstrzyknięta przez DI zastępuje całą warstwę integracji HTTP.
Przewidywalny koszt bez względu na ilość. Wieczysta licencja IronOCR w $999 (Lite), $1 499 (Professional) lub $2 999 (Enterprise) obejmuje nieograniczone przetwarzanie dokumentów. Przetwarzanie 500 dokumentów miesięcznie lub 500 000 dokumentów miesięcznie kosztuje tyle samo. Brakuje struktury rozliczeń za dokument, która sprawiała, że Klippa była droga przy większej skali. Strona licencyjna IronOCR zawiera szczegółowe informacje na temat wszystkich poziomów i tego, co każdy z nich obejmuje.
Nieograniczony zakres dokumentów.IronOCR przetwarza każdy dokument zawierający tekst. Skanowane umowy, rysunki techniczne, formularze medyczne, zlecenia zakupu, odręczne notatki, zrzuty ekranu, archiwa TIFF — wszystko obsługiwane przez to samo wywołanie Read() z tym samym API. Zniknęło ograniczenie zakresu specjalistycznego, które wymagało drugiego systemu dla dokumentów spoza kategorii, na których przeszkolono Klippę. Jedna biblioteka, jeden punkt integracji, dowolny typ dokumentu.
Obsługa środowisk offline i sieci o ograniczonym dostępie. Aplikacje wdrożone w sieciach bankowych, systemach rządowych, środowiskach brzegowych lub dowolnej infrastrukturze z ograniczonym dostępem do sieci zewnętrznej działają dokładnie tak samo, jak w środowiskach otwartych. Nie ma sprawdzania łączności, nie ma pingowania stanu punktu końcowego w chmurze ani trybu awaryjnego, gdy internet jest niedostępny. Wdrożenia typu air-gapped działają bez modyfikacji. Przewodnik wdrożeniowy dla systemu Linux oraz przewodnik wdrożeniowy dla Docker opisują ścieżki wdrożenia w środowiskach kontenerowych i po stronie serwera.
Pełna kontrola nad poprawianiem obrazów. Przetwarzanie wstępne w chmurze było czarną skrzynką — Klippa je stosowała, Ty obserwowałeś wyniki, nie miałeś żadnych parametrów do dostosowania. Pipeline przetwarzania wstępnego IronOCR jest jawny i kompozycyjny: Deskew(), DeNoise(), Contrast(), Binarize(), Sharpen(), Scale(), Dilate(), DeepCleanBackgroundNoise(). Każdy filtr jest opcjonalny i ma określoną kolejność. Poprawa dokładności jest mierzalna, powtarzalna i pozostaje pod Państwa kontrolą. Przewodnik po korekcji jakości obrazu oraz strona poświęcona funkcjom przetwarzania wstępnego obejmują pełny katalog filtrów wraz z wskazówkami dotyczącymi tego, kiedy należy stosować poszczególne z nich.
Często Zadawane Pytania
Dlaczego warto przejść z Klippa OCR API na IronOCR?
Typowe czynniki motywujące to eliminacja złożoności interoperacyjności COM, zastąpienie zarządzania licencjami opartego na plikach, uniknięcie rozliczeń za stronę, umożliwienie wdrażania w Dockerze/kontenerach oraz przyjęcie natywnego dla NuGet przepływu pracy, który integruje się ze standardowymi narzędziami .NET.
Jakie są główne zmiany w kodzie podczas migracji z Klippa OCR API do IronOCR?
Zastąp sekwencje inicjalizacji Klippa instancjonowaniem IronTesseract, usuń zarządzanie cyklem życia COM (jawne wzorce Create/Load/Close) i zaktualizuj nazwy właściwości wyników. W rezultacie znacznie zmniejsza się liczba powtarzających się linii kodu.
Jak zainstalować IronOCR, aby rozpocząć migrację?
Uruchom polecenie „Install-Package IronOcr” w konsoli menedżera pakietów lub „dotnet add package IronOcr” w interfejsie CLI. Pakiety językowe są oddzielnymi pakietami: na przykład „dotnet add package IronOcr.Languages.French” dla języka francuskiego.
Czy IronOCR dorównuje dokładnością OCR interfejsowi API Klippa OCR w przypadku standardowych dokumentów biznesowych?
IronOCR zapewnia wysoką dokładność w przypadku standardowych treści biznesowych, w tym faktur, umów, paragonów i formularzy wypełnionych na komputerze. Filtry przetwarzania wstępnego obrazu (prostowanie, usuwanie szumów, wzmacnianie kontrastu) dodatkowo poprawiają rozpoznawanie w przypadku pogorszonej jakości danych wejściowych.
W jaki sposób IronOCR obsługuje dane językowe, które Klippa OCR API instaluje oddzielnie?
Dane językowe w IronOCR są dystrybuowane jako pakiety NuGet. Polecenie „dotnet add package IronOcr.Languages.German” instaluje obsługę języka niemiećkiego. Nie wymaga to ręcznego umieszczania plików ani podawania ścieżek katalogów.
Czy migracja z Klippa OCR API do IronOCR wymaga zmian w infrastrukturze wdrożeniowej?
IronOCR wymaga mniej zmian w infrastrukturze niż Klippa OCR API. Nie ma ścieżek binarnych SDK, lokalizacji plików licencyjnych ani konfiguracji serwerów licencyjnych. Pakiet NuGet zawiera kompletny silnik OCR, a klucz licencyjny jest ciągiem znaków ustawionym w kodzie aplikacji.
Jak skonfigurować licencjonowanie IronOCR po migracji?
W kodzie uruchamiającym aplikację przypisz IronOcr.License.LicenseKey = „YOUR-KEY”. W Dockerze lub Kubernetesie zapisz klucz jako zmienną środowiskową i odczytaj go podczas uruchamiania. Użyj License.IsValidLicense do sprawdzenia ważności przed przyjęciem ruchu.
Czy IronOCR może przetwarzać pliki PDF w taki sam sposób jak Klippa?
Tak. IronOCR odczytuje zarówno natywne, jak i zeskanowane pliki PDF. Należy utworzyć instancję IronTesseract, wywołać ocr.Read(input), gdzie input jest ścieżką do pliku PDF lub obiektem OcrPdfInput, a następnie iterować strony OcrResult. Nie jest wymagany oddzielny proces renderowania plików PDF.
W jaki sposób IronOCR radzi sobie z wątkami podczas przetwarzania dużych ilości danych?
IronTesseract można bezpiecznie instancjonować dla każdego wątku. Uruchom jedną instancję na wątek w Parallel.ForEach lub puli zadań, uruchom OCR równolegle i usuń każdą instancję po zakończeniu. Nie jest wymagany żaden stan globalny ani blokowanie.
Jakie formaty wyjściowe obsługuje IronOCR po wyodrębnieniu tekstu?
IronOCR zwraca ustrukturyzowane wyniki, w tym tekst, współrzędne słów, wyniki pewności i strukturę strony. Opcje eksportu obejmują zwykły tekst, PDF z możliwością wyszukiwania oraz obiekty wyników ustrukturyzowanych do dalszego przetwarzania.
Czy ceny IronOCR są bardziej przewidywalne niż ceny Klippa OCR API w przypadku skalowania obciążeń?
IronOCR stosuje licencję wieczystą z opłatą ryczałtową, bez opłat za stronę lub wolumen. Niezależnie od tego, czy przetwarzasz 10 000, czy 10 milionów stron, koszt licencji pozostaje stały. Opcje licencji wolumenowych i zespołowych znajdują się na stronie z cennikiem IronOCR.
Co stanie się z moimi istniejącymi testami po migracji z Klippa OCR API do IronOCR?
Testy sprawdzające wyodrębnioną treść tekstową powinny nadal przechodzić pomyślnie po migracji. Testy weryfikujące wzorce wywołań API lub cykl życia obiektów COM będą wymagały aktualizacji, aby odzwierciedlały prostszy model inicjalizacji i wyników IronOCR.

