如何在C#中使用開源庫實現OCR
本指南適用於已整合Klippa的REST API並希望轉往使用IronOCR進行內部文件處理的.NET開發者。 它涵蓋了實際步驟,如移除HTTP客戶端基礎架構、消除JSON反序列化,以及將依賴雲端的文件上傳替換成從不接觸網路的本地OCR調用。
為什麼要從Klippa OCR遷移
Klippa是一個僅限於雲端的文件智能服務,沒有.NET的SDK。 每個整合都是一個手工建立的REST客戶端。 這種架構現實對生產系統的生命週期有著下游影響。
沒有NuGet包意味著您擁有整合層。 沒有什麼需要安裝的。 入口成本是寫MultipartFormDataContent請求主體、反序列化Klippa的JSON響應架構,以及為瞬時失敗設定重試邏輯。 這需要2-4天的管道工作,才能在生產中可靠地處理第一個文件。 當Klippa更新他們的API架構時,您的反序列化程式碼會崩潰,需要手動維護。
每次文件上傳都是一個網路依賴。 Klippa僅在託管於EU的伺服器上處理文件。 Klippa端的生產停機、升高的延遲,或者任何出站互聯網存取中斷,會使您的應用伺服器完全停止處理文件。 沒有備援,沒有本地模式,沒有解決雲服務不可用的重試方法。
敏感文件離開您的基礎架構。 財務文件——帶有支付細節的收據、帶有VAT號碼和金額的發票、帶有護照資料的身份文件——在每次API調用時會傳送到第三方伺服器。 GDPR的資料傳輸規定解決了一些EU基於處理的問題,但審計範圍仍延伸到Klippa的基礎架構、資料保留政策和次處理器。 對於擁有醫療、法律、金融服務或政府合同的團隊來說,"EU託管"並不滿足資料不得離開組織的要求。
按文件計費模式無上限地擴展。 Klippa不公佈價格。 在任何有意義的文件量——每月10,000張收據的費用管理系統,每天500張發票的AP自動化工作流程——按文件計費模式產生的成本是永久授權永遠不會產生的。 成本軌跡直接與業務增長綁定,這與基礎架構支出應有的做法相反。
當需求擴展時,專家範圍破裂。 Klippa在收據、發票和身份文件上受過訓練。 一個作為費用管理開始的應用很少會停留在那裡。 第一次出現這三類別以外的文件型別時——掃描的就業合同、醫療表單、技術圖紙、具有非標準佈局的採購訂單——Klippa返回的都是無用的。 IronOCR處理任何包含文字的文件,沒有類別限制。
僅限非同步的REST調用在同步上下文中增加延遲。 每次Klippa調用都是一個非同步HTTP操作。 一個文件來回的網路往返時間為500ms至2000ms。 IronOCR本地處理相同文件僅需100-400ms,沒有非同步的負擔,當同步處理更符合架構時。
根本問題
Klippa沒有SDK。 OCR意味著構建和傳送一個HTTP請求,然後反序列化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替換了這一切:
// 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和Klippa OCR:功能比較
下表比較了這兩個庫在生產遷移決策中最重要的維度。
| 功能 | Klippa OCR | IronOCR |
|---|---|---|
| 部署模型 | 僅限雲端(歐盟伺服器) | 本地、完全本地 |
| .NET SDK / NuGet套件 | None | IronOcr NuGet 套件 |
| 需要網際網路 | 是的,在每次調用中 | 絕不需要 |
| 文件資料離開網路 | 總是需要 | 絕不需要 |
| 通用OCR | 否(僅收據、發票、身份證) | 是(任何文件型別) |
| 驗證設置 | X-Auth-Key HTTP 標頭 |
IronOcr.License.LicenseKey 字串 |
| 需要HTTP客戶端 | 是 | 不是 |
| 響應反序列化 | 手動JSON解析 | 型別化的OcrResult 物件 |
| 重試/超時邏輯 | 手工撰寫 | 不需要(本地調用) |
| 離線/氣隙支持 | 不是 | 是 |
| PDF 輸入 | 是的(雲端) | 是的(本地,內生) |
| 多頁TIFF輸入 | 未知 | 是 |
| 圖像輸入格式 | JPG, PNG(雲端) | JPG、PNG、BMP、TIFF、GIF等 |
| 流與字節陣列輸入 | 無SDK | 是 |
| 自動圖像預處理 | 雲端端(不透明) | 是的(Deskew, DeNoise, Contrast, Binarize, Sharpen) |
| 結構化輸出:單詞坐標 | 不是 | 是 |
| 每個單詞的置信度分數 | 不是 | 是 |
| 可搜尋的 PDF 輸出 | 不是 | 是 |
| OCR期間的條碼讀取 | 不是 | 是 |
| 多語言支持 | 僅限於受訓文件型別 | 125+種語言 |
| 執行緒安全性 | 不適用(HTTP調用) | 是的(一個IronTesseract每個執行緒) |
| 跨平台部署 | 與REST無關 | Windows、Linux、macOS、Docker、Azure、AWS |
| HIPAA / ITAR / 良好氣隙合規 | 不是 | 是 |
| 定價模型 | 每文件SaaS(未公佈率) | 從$999的永久授權 |
| 大規模時的每頁成本 | 是的,無邊界 | None |
快速入門:從Klippa OCR遷移到IronOCR
步驟1:替換NuGet包
Klippa沒有官方的NuGet包。 移除僅為支持Klippa整合而存在的HTTP客戶端依賴:
# 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
從NuGet安裝IronOCR:
dotnet add package IronOcr
步驟2:更新命名空間
移除Klippa整合所需的HTTP和JSON命名空間。 新增單一的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
步驟3:初始化許可證
在應用啟動時新增一次性授權初始化——在Startup.cs中或首次OCR調用之前:
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
程式碼遷移範例
替換HTTP客戶端服務類
Klippa整合需要完整的服務類來包裝HTTP基礎架構。 這是不可避免的,因為沒有SDK。
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
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
Klippa服務類完全因為API需要HTTP基礎架構而存在。 IronOCR的一個等價縮減到一個Read()調用。 超時、驗證標頭和處置模式全都消失,因為沒有網路。 參見IronTesseract設置指南以瞭解初始化選項,並參考基本OCR範例以瞭解工作程式碼。
消除多部分表單上傳
Klippa通過多部分表單上傳接收文件。 上傳程式碼是機械式但脆弱的:文件讀取,內容型別標頭,邊界構建和上傳大小管理。
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
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
MultipartFormDataContent構建、內容型別標頭和上傳本身都消失了。 IronOCR直接從文件路徑、字節陣列或Stream中讀取——Klippa傳輸到雲端的相同資料保持在本地。 圖像輸入指南涵蓋了所有支持的輸入格式,而流輸入指南涵盖字節陣列從上游過程來的文件的記憶體流路徑。
替換JSON響應反序列化
Klippa返回一個巢狀的JSON結構。 導航該結構需要匹配的C#模型或內聯JsonDocument遍歷——Klippa變更其響應架構時,這兩者都會崩潰。
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
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是一個型別化的.NET物件。 沒有JSON可解析,沒有模型類需要維護,沒有架構漂移破壞生產反序列化的風險。 讀取結果指南記錄了完整的OcrResult物件模型,包括單詞坐標、自信度分數和結構化頁面層次結構。 對於基於OcrResult構建的發票特定字段提取模式,發票OCR教程涵蓋了端到端的提取邏輯。
移除錯誤處理和重試基礎架構
Klippa整合需要對HTTP產生的每一種錯誤模式進行錯誤處理:超時、4xx響應、5xx響應、速率限制和部分JSON。 運行生產整合的團隊使用Polly或自定邏輯新增重試政策。 當網路調用消失時,這種基礎架構便消失了。
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
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)
{
//不是retry loop.不是CancellationTokenSource.不是HTTP status checks.
//不是rate limit handling.不是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)
{
//不是retry loop.不是CancellationTokenSource.不是HTTP status checks.
//不是rate limit handling.不是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
'不是retry loop.不是CancellationTokenSource.不是HTTP status checks.
'不是rate limit handling.不是partial-JSON guards.
Dim result = New IronTesseract().Read(filePath)
Return result.Text
End Function
整個重試基礎架構——迴圈、延遲計算、TaskCanceledException捕獲塊——僅因為網路而存在。 移除網路調用,所有這些都會消失。 如果輸入文件缺失或無法讀取,本地OCR調用會快速失敗並拋出型別化異常,否則成功。速度優化指南涵蓋了如果在遷移後吞吐量是問題時IronOCR性能調校的措施。
處理不需雲端上傳的多頁PDF
Klippa通過相同的parseDocument端點接受PDF上傳。 多頁的PDF仍然離開您的網路。 IronOCR原生讀取PDF,處理時可逐頁獲取結果。
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
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原生讀取PDF文件,不需要任何轉換步驟。每頁都能獨立存取,具有完整的行、單詞、和字元層次結構。 SaveAsSearchablePdf()調用從掃描文件中生成一個文字層PDF——Klippa不提供這種能力。 PDF輸入指南涵蓋了載入選項,而可搜尋PDF指南涵蓋了包括PDF/A的存檔符合選項。
Klippa OCRAPI到IronOCR的映射參考
Klippa是一個REST API,而不是一個型別化的SDK。 下方映射將Klippa's整合介面翻譯為IronOCR的等效物。
| Klippa概念 | IronOCR 等效 |
|---|---|
X-Auth-Key標頭一起 |
IronTesseract實例——不需要驗證設置 |
MultipartFormDataContent |
OcrInput.LoadPdf(path) |
POST /api/v1/parseDocument |
IronTesseract.Read(input) |
await _client.PostAsync(...) |
await |
response.EnsureSuccessStatusCode() |
不需要——無HTTP響應 |
JsonSerializer.Deserialize<KlippaResponse>(json) |
型別化的OcrResult——無需反序列化 |
KlippaResponse.Data.ParsedDocument.Text |
OcrResult.Text |
KlippaResponse.Data.ParsedDocument.Amount |
自訂正則表達式於OcrResult.Lines |
KlippaResponse.Data.ParsedDocument.Merchant |
OcrResult.Pages[0].Lines[0].Text |
帶Task.Delay的重試迴圈 |
不需要——無網路失敗模式 |
CancellationTokenSource(TimeSpan.FromSeconds(30)) |
不需要——本地執行 |
| 速率限制處理(HTTP 429) | 不需要——無速率限制 |
| 雲端文件路由至EU伺服器 | 本地內處理執行 |
KlippaService.Dispose() / HttpClient.Dispose() |
using語句處置 |
| 結構化的JSON響應字段 | OcrResult.Text + OcrResult.Pages + OcrResult.Words |
| SaaS API訂閱 | IronOcr.License.LicenseKey字串——永久 |
常見的遷移問題与解決方案
問題1:HTTP消除後的僅限非同步調用站點
Klippa:因為HTTP調用需要,所以Klippa整合全部是非同步的。 貴方程式碼庫中的控制器、服務、後台工作者全部調用await ProcessDocumentAsync(...)。 刪除HTTP調用意味著不再需要async的方法簽名仍然保留。
解決方案:IronOCR提供同步和非同步API。對於必須保持非同步的調用站點(ASP.NET核心控制器,采用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
非同步OCR指南涵蓋了對ASP.NET核心和託管服務模式中CancellationToken的整合。
問題2:依賴注入註冊
Klippa:HttpClient。 移除它意味著要更新DI註冊和所有注入點。
解決方案:將IronTesseract註冊為單件(這是執行緒安全的),直接注入它,或建立一個薄包裝器以反映您現有的服務介面。
// 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
一個IronTesseract實例註冊為單件,處理並發請求。 每次調用Read()都是執行緒安全的。
問題3:沒有預先解析的JSON的結構化字段提取
Klippa:Klippa返回vat_amount作為型別化JSON屬性。 遷移到IronOCR意味著這些字段不再預先解析到達。
解決方案:IronOCR的OcrResult提供原始文字和單詞級別坐標來構建等效的提取。 對於具有可預見佈局的文件,基於區域的OCR直接針對特定字段:
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
基於區域的OCR指南詳細說明了CropRectangle的運用。 對於在收據和發票佈局上的完整提取模式,收據掃描教程提供了完整的工作程式碼。
問題4:來自上游服務作為流的文件
Klippa:Klippa作為多部分表單上傳接收文件——HTTP表單內容包裹的文件字節。 如果您的應用程式從S3、Azure Blob Storage或內部API接收文件作為流,您則是將流讀取為字節,然後將那些字節上傳到Klippa。
解決方案:IronOCR直接接受Stream物件。 字節轉換步驟消失:
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
無MultipartFormDataContent構建,無HTTP POST。流直接進入OcrInput。 流輸入指南涵蓋了流型別和處置模式。
問題5:依賴於HTTP Mocking的整合測試
Klippa:Klippa程式碼的整合測試mock了WireMock, MockHttp)來模擬API回應。 這些測試模仿HTTP層,而不是OCR邏輯。
解決方案:IronOCR測試使用具有已知預期輸出的真實文件。 不需要模擬基礎架構。 測試離線運行:
[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
以前需要實時Klippa連接或複雜的HTTP模擬設置的測試現在在CI中無需網路存取即可運行。
問題6:Klippa伺服器端增強的低質量文件
Klippa:雲端處理在識別之前應用圖像增強。 開發者從未配置過這個——它在Klippa的伺服器上自動發生。 遷移時,Klippa無聲處理的文件可能在沒有在IronOCR中進行顯式預處理的情況下產生較低的準確性。
解決方案: 顯式應用IronOCR的預處理過濾器。 過濾器集合反映了雲服務在伺服器端應用的內容:
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
圖像質量校正指南涵蓋了所有預處理過濾器,以及應用它們的順序以應對不同的文件劣化型別。
Klippa OCR遷移清單
遷移前
審計您的程式碼庫,以在移除任何東西之前找到所有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" .
清單註釋:
- 記錄每個包裝
HttpClient為Klippa調用的類 - 列出所有JSON反序列化模型類(
KlippaParsedDocument等) - 記錄所有消耗Klippa預先解析JSON屬性的字段映射
- 註明任何專為Klippa構建的Polly重試政策或自定重試迴圈
程式碼遷移
- 安裝
IronOcrNuGet包(dotnet add package IronOcr) - 在應用啟動時新增
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY" - 從Klippa服務文件中移除
Newtonsoft.Json的引入 - 刪除
IronTesseract調用替換其主體,保持介面) - 在DI容器中註冊
IronTesseract為單件 - 用
MultipartFormDataContent上傳塊 - 刪除JSON響應模型類(
KlippaParsedDocument) - 用
.Data?.ParsedDocument?.Text) - 移除Klippa調用站點的重試迴圈和
CancellationTokenSource超時 - 移除速率限制處理(HTTP 429 捕獲塊)
- 用
await ProcessDocumentAsync(...) - 為低質量文件輸入新增
Contrast) - 用真實文件樣板測試替換HTTP模擬測試基礎設施
- 刪除針對Klippa調用限定的Polly重試政策或自定重試中介件
遷移後
- 驗證文字提取輸出與已知測試文件的預期內容相符
- 確認信心分數超過可接受門檻(通常是70%以上)以滿足生產文件型別
- 測試PDF輸入:本地載入多頁PDF並通過
result.Pages驗證每頁文字存取 - 測試流輸入:傳遞
OcrInput.LoadImage(stream)產生正確的輸出 - 驗證預處理過濾器比未處理基線提高對低質量掃描的準確性
- 確認DI注入的
IronTesseract單例能無爭議地處理並發請求 - 離線運行整合測試(無網路連接)——所有測試應該在不存取雲端的情況下通過
- 使用
result.SaveAsSearchablePdf("output.pdf")驗證可搜PDF輸出以滿足掃描文件流 - 在ASP.NET核心控制器上下文中測試
CancellationToken傳播 - 確認
using var input = new OcrInput()處置模式不會在持續負載下導致記憶體洩漏
遷移至IronOCR的主要好處
從第一天起就具備資料主權。 遷移後,敏感的財務文件、身份掃描和機密發票不再離開您的基礎架構。 審計範圍內沒有第三方處理器,沒有資料保留政策需要審查,沒有資料轉移協議需要維護。 先前使Klippa成為問題的HIPAA、ITAR、CMMC和FedRAMP限制現在預設滿足。 部署在Docker、AWS或Azure上,將所有東西都保持在您自己的基礎架構邊界內。
基礎架構復雜性消除。 服務類、HTTP客戶端、表單上傳程式碼、JSON模型、重試政策、超時配置——它們全都是為了包裝一個網路調用而存在的。 移除網路調用,所有這些也會消失。 結果程式碼庫更小、更容易閱讀,而且故障模式更少。 通過DI注入的一個IronTesseract實例替換整個HTTP整合層。
無論量有多大,成本可預測。 在$999 (Lite) 一個永久的IronOCR授權,$1,499 (Professional) 或 $2,999 (Enterprise) 可覆盖無限的文件處理。 每月處理500個文件或500,000個文件,成本相同。 按文件計費動態在大規模上曾使Klippa昂貴的情形在架構上不存在。 IronOCR授權頁面詳細說明了所有級別及其包括的內容。
沒有邊界的文件範圍。 IronOCR處理任何包含文字的文件。 掃描的合同、技術圖紙、醫療表單、採購訂單、手寫筆記、截圖、TIFF存檔——所有這些都由相同的Read()調用並由相同的API處理。 需要第二個系統來處理Klippa受訓類別之外的文件的專家範圍限制不復存在。 一個庫,一個整合點,任何文件型別。
現在支持離線和受限網路環境。 部署在銀行網路、政府系統、邊緣環境或任何具有受限出境流量的基礎架構中的應用程式運行方式與在開放環境中一樣。 沒有連接性檢查,沒有健康ping到雲端端點,也不會在網路不可用時降級模式。 無需修改即可工作在氣隙部署。 Linux部署指南和Docker部署指南涵蓋了這些環境的容器化和伺服器端部署路徑。
完全控制影像增強。 雲端預處理是一個黑箱——Klippa應用了它,您觀察結果,您沒有參數可調整。 IronOCR的預處理管道是顯式和可組合的:Deskew(), DeNoise(), Contrast(), Binarize(), Sharpen(), Scale(), Dilate(), DeepCleanBackgroundNoise()。 每個濾鏡都是可選的並且有順序。 準確性改進是可衡量的、可再現的,並且在您的控制之下。 圖像質量校正指南和預處理功能頁面涵蓋了完整的濾鏡目錄,輔以何時應用每一種的指南。
常見問題
我為什麼應該從Klippa OCR API遷移到IronOCR?
常見的驅動因素包括消除COM互操作複雜性、更換基於文件的許可證管理、避免按頁收費、啟用Docker/容器部署,以及採取與標準.NET工具整合的NuGet本地工作流。
從Klippa OCR API遷移到IronOCR的主要程式碼變更是什麼?
用IronTesseract實例化替換Klippa初始化序列,移除COM生命週期管理(顯式Create/Load/Close模式),並更新結果屬性名稱。結果是大幅減少模板程式碼行數。
如何安裝IronOCR以開始遷移?
在套件管理器控制台中運行 'Install-Package IronOcr' 或在CLI中運行 'dotnet add package IronOcr'。語言包是單獨的包:例如 'dotnet add package IronOcr.Languages.French' 安裝法語支持。
IronOCR在標準商業文件的OCR準確性上可媲美Klippa OCR API嗎?
IronOCR在包括發票、合同、收據和鍵入的表單等標準業務內容上達到高準確性。圖像預處理過濾器(糾偏、噪音去除、對比度增強)進一步提高了變質輸入的識別。
IronOCR如何處理Klippa OCR API分開安裝的語言資料?
IronOCR中的語言資料分發為NuGet包。'dotnet add package IronOcr.Languages.German' 安裝德語支持。不涉及手動文件放置或目錄路徑。
從Klippa OCR API遷移到IronOCR是否需要更改部署基礎設施?
相比於Klippa OCR API,IronOCR需要的基礎設施變更較少。無需SDK二進制路徑、授權文件放置或授權伺服器設定。NuGet套件包含完整的OCR引擎,授權金鑰是設置在應用程式程式碼中的字串。
遷移後如何配置IronOCR許可證?
在應用啟動程式碼中分配IronOcr.License.LicenseKey = "YOUR-KEY"。在Docker或Kubernetes中,將金鑰儲存為環境變數並在啟動時讀取。在接受流量之前,使用License.IsValidLicense驗證。
IronOCR能以Klippa相同的方式處理PDF嗎?
是的。IronOCR能讀取本地和掃描的PDF。實例化IronTesseract,調用ocr.Read(input),其中input是PDF路徑或OcrPdfInput,並迭代OcrResult頁面。不需要單獨的PDF渲染管道。
IronOCR如何處理大批量處理中的多執行緒?
IronTesseract可以安全地在每個執行緒中實例化。在Parallel.ForEach或Task池中為每個執行緒建立一個實例,並發併運行OCR,當完成時處置每個實例。不需要全域狀態或鎖定。
IronOCR在文字提取後支持哪些輸出格式?
IronOCR返回結構化結果,包括文字、單詞坐標、信心分數和頁面結構。導出選項包括純文字、可搜索PDF以及下游處理的結構化結果物件。
在擴展工作負載時,IronOCR的定價是否比Klippa OCR API更具可預測性?
IronOCR使用固定費率的永久性許可證,沒有按頁或按量收費。無論您處理10000頁還是1000萬頁,許可證費用保持不變。團隊和批量許可證選項可在IronOCR定價頁面上找到。
從Klippa OCR API遷移到IronOCR後,我現有的測試會怎樣?
遷移後,斷言提取的文字內容的測試應繼續通過。需要更新的測試是驗證API調用模式或COM物件生命週期,這需要反映IronOCR較簡單的初始化和結果模型。

