如何使用开源库在C#中实现OCR
本指南面向已集成 Klippa 的 REST API 并正在迁移到IronOCR进行本地文档处理的.NET开发人员。 它涵盖了移除 HTTP 客户端基础架构、消除 JSON 反序列化以及用从不接触网络的本地 OCR 调用替换依赖云的文档上传的实际步骤。
为什么要从 剪切 OCR 迁移?
Klippa 是一款纯云端文档智能服务,不包含.NET SDK。 每个集成都是手工构建的 REST 客户端。 这种架构现实会产生下游影响,并在生产系统的整个生命周期内不断累积。
**没有NuGet包意味着您拥有集成层。**无需安装任何内容。 进入成本是编写一个MultipartFormDataContent请求体,反序列化Klippa的JSON响应架构,并为瞬时故障安装重试逻辑。 这意味着在第一个文档能够可靠地在生产环境中处理之前,需要 2-4 天的准备工作。 当 Klippa 更新其 API 架构时,您的反序列化代码会失效,需要手动维护。
每次文档上传都依赖于网络。Klippa仅在欧盟境内的服务器上处理文档。 Klippa 端生产中断、延迟升高或应用程序服务器出站互联网访问中断都会导致文档处理完全停止。 如果云服务不可用,则没有回退机制、本地模式或重试机制来解决该问题。
**敏感文件离开您的基础设施。**财务文件——包含付款详情的收据、包含增值税号和金额的发票、包含护照信息的身份证件——在每次 API 调用时都会传输到第三方服务器。 GDPR 的数据传输条款解决了欧盟境内处理的部分问题,但审计范围仍然扩展到 Klippa 的基础设施、数据保留政策和子处理者。 对于拥有医疗保健、法律、金融服务或政府合同的团队而言,"欧盟托管"并不符合数据不得离开组织的要求。
按文档计费,无上限。Klippa不公开定价。 在任何有意义的文档量下——例如费用管理系统中每月 10,000 张收据,或应付账款自动化工作流程中每天 500 张发票——按文档计费模式都会产生永久许可证永远不会产生的成本。 成本走势与业务增长直接相关,这与基础设施支出的初衷背道而驰。
当需求扩大时,专家的职责范围就会受到限制。Klippa接受过收据、发票和身份证明文件方面的培训。 一款最初定位为费用管理的应用,很少会一直停留在费用管理领域。 如果首次出现这三类文件之外的文件类型(例如扫描的雇佣协议、医疗表格、技术图纸、非标准布局的采购订单),Klippa 将不会返回任何有用的信息。 IronOCR可以处理任何包含文本的文档,没有类别限制。
**仅支持异步的 REST 调用会增加同步上下文中的延迟。**每个 Klippa 调用都是一个异步 HTTP 操作。 单个文档在网络上的往返传输时间为 500 毫秒至 2000 毫秒。 IronOCR在本地处理同一文档只需 100-400 毫秒,无需异步开销,这在同步处理更适合架构的场景中尤为如此。
基本问题
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
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与 Klippa OCR:功能对比
下表从对生产环境迁移决策最重要的几个方面对这两个库进行了比较。
| 特征 | 剪切 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 | 是 |
| 自动图像预处理 | 云侧(不透明) | 是的(桌面倾斜校正、降噪、对比度调整、二值化、锐化) |
| 结构化输出:词坐标 | 否 | 是 |
| 每个词的置信度得分 | 否 | 是 |
| 可搜索的 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
从NuGet安装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;
步骤 3:初始化许可证
在应用程序启动时添加许可证初始化—在Startup.cs或首次OCR调用之前:
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"代码迁移示例
替换 HTTP 客户端服务类
Klippa 集成需要一个完整的服务类来封装 HTTP 基础架构。 由于没有SDK,所以无法避免这种情况。
克利帕方法:
// 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();
}
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
Klippa 服务类的存在完全是因为 API 需要 HTTP 基础设施。 IronOCR等价于简化为单个Read()调用。 由于没有网络连接,超时、身份验证标头和处置模式都会消失。 有关初始化选项,请参阅IronTesseract 设置指南;有关可运行的代码,请参阅基本 OCR 示例。
取消多部分表单上传
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 });
}
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);
MultipartFormDataContent构造、内容类型头和上传本身都已消失。 IronOCR直接从文件路径读取,从字节数组中读取,或者从Stream中读取—Klippa传输到云端的相同数据保留在本地。 图像输入指南涵盖所有支持的输入格式,流输入指南涵盖从上游进程以字节数组形式到达的文档的内存流路径。
替换 JSON 响应反序列化
Klippa 返回一个嵌套的 JSON 结构。 导航这种结构需要匹配的C#模型或内联JsonDocument遍历—当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;
}
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}");
}
}
OcrResult是一个类型化的.NET对象。 无需解析 JSON,无需维护模型类,也不存在因模式漂移而导致生产环境反序列化失败的风险。 读取结果指南记录了完整的OcrResult对象模型,包括单词坐标、置信度分数和结构化页面层次。 对于基于OcrResult的发票特定字段提取模式,发票OCR教程涵盖了端到端提取逻辑。
移除错误处理和重试基础架构
通过 HTTP 集成 Klippa 需要对网络调用可能产生的每种故障模式进行错误处理:超时、4xx 响应、5xx 响应、速率限制和部分 JSON。 运行生产集成的团队使用 Polly 或自定义逻辑添加重试策略。 当网络呼叫消失时,该基础设施也会消失。
克利帕方法:
// 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");
}
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;
}
整个重试基础设施—循环、延迟计算、TaskCanceledException捕获块—仅仅因为网络而存在。 移除网络通话后,所有问题都会消失。 如果输入文件缺失或无法读取,本地 OCR 调用会快速失败并抛出异常;否则,调用会成功。如果迁移后吞吐量成为问题,速度优化指南将介绍IronOCR 的性能调优方法。
无需云上传即可处理多页 PDF
Klippa通过相同的parseDocument端点接受PDF上传。 多页PDF文件仍然会从您的网络中传输出去。 IronOCR可直接读取 PDF 文件,并支持逐页访问结果。
克利帕方法:
// 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;
}
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可直接读取 PDF 文件,无需任何转换步骤。每一页都可单独访问,包含完整的行、单词和字符层级结构。 SaveAsSearchablePdf()调用从扫描文档生成文本层PDF—这是Klippa不提供的功能。 PDF 输入指南涵盖加载选项,可搜索 PDF 指南涵盖输出选项,包括符合存档要求的 PDF/A。
剪切 OCR API 到IronOCR映射参考
Klippa 是一个 REST API,而不是一个类型化的 SDK。 下面这张映射图将 Klippa 的积分曲面转换为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) | 无需限制——无速率限制 |
| 云文档路由到欧盟服务器 | 本地进程内执行 |
KlippaService.Dispose() / HttpClient.Dispose() | using语句处理清理 |
| 结构化 JSON 响应字段 | OcrResult.Text + OcrResult.Pages + OcrResult.Words |
| SaaS API 订阅 | IronOcr.License.LicenseKey字符串—永久 |
常见迁移问题和解决方案
问题 1:HTTP 移除后的仅异步调用站点
**Klippa:**所有 Klippa 集成都是异步的,因为 HTTP 调用需要异步操作。 控制器、服务和整个代码库中的后台工作线程调用await ProcessDocumentAsync(...)。 删除HTTP调用意味着不再需要async方法签名仍然存在。
**解决方案:**IronOCR提供同步和异步API。对于必须保持异步的调用点(ASP.NET Core控制器、具有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;
}
异步OCR指南涵盖了CancellationToken在ASP.NET Core和托管服务模式中的集成。
问题 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;
}
一个注册为单例的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();
基于区域的OCR指南详细介绍CropRectangle的用法。 收据扫描教程提供了完整的可运行代码,用于提取收据和发票布局的完整模式。
问题 4:来自上游服务的流式文档
Klippa: Klippa 接收以 multipart 表单上传形式提供的文档——文件字节封装在 HTTP 表单内容中。 如果您的应用程序从 S3、Azure Blob 存储或内部 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;
}
没有MultipartFormDataContent构造,没有HTTP POST。流直接进入OcrInput。 河流输入指南涵盖河流类型和排放模式。
问题 5:依赖 HTTP 模拟的集成测试
**Klippa:**Klippa代码的集成测试模拟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);
}
以前需要实时 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}%");
图像质量校正指南涵盖所有预处理滤镜以及针对不同文档劣化类型应用这些滤镜的顺序。
剪切 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" .
库存记录:
- 记录每个包装
HttpClient用于Klippa调用的类 - 列出所有JSON反序列化模型类(
KlippaParsedDocument等) - 记录所有使用 Klippa 预解析 JSON 属性的字段映射。
- 注意任何 Polly 重试策略或为 Klippa 构建的自定义重试循环
代码迁移
- 安装
IronOcrNuGet包(dotnet add package IronOcr) - 将
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"添加到应用程序启动中 - 从Klippa服务文件中移除
Newtonsoft.Json导入 - 删除
IronTesseract调用替换其主体,保留接口) - 在DI容器中将
IronTesseract注册为单例 - 将
OcrInput.LoadPdf() - 删除JSON响应模型类(
KlippaParsedDocument) - 将可空JSON导航链(
result.Text - 移除Klippa调用点的重试循环和
CancellationTokenSource超时 - 移除速率限制处理(HTTP 429 捕获块)
- 将
ocr.Read(...) - 为低质量的文档输入添加
Contrast) - 用真实文档测试用例替换 HTTP 模拟测试基础设施
- 删除 Polly 重试策略或作用于 Klippa 调用的自定义重试中间件
后迁移
- 验证文本提取输出是否与已知测试文档中的预期内容相符
- 确认生产文档类型的置信度评分超过可接受阈值(通常为 70% 以上)
- 测试PDF输入:本地加载多页PDF并通过
result.Pages验证每页文本访问 - 测试流输入:传递
OcrInput.LoadImage(stream)产生正确的输出 - 验证预处理滤波器是否能提高低质量扫描的准确率,与未经处理的基线相比。
- 确认DI注入的
IronTesseract单例处理并发请求而无争用 - 离线运行集成测试(无网络连接)——所有测试都应在不访问云的情况下通过
- 使用
result.SaveAsSearchablePdf("output.pdf")验证扫描文档流程的可搜索PDF输出 - 在ASP.NET Core控制器上下文中测试
CancellationToken - 确认
using var input = new OcrInput()处理模式在持续负载下不泄漏内存
迁移到IronOCR的主要优势
**从一开始就保障数据主权。**迁移后,敏感财务文件、身份扫描件和机密发票永远不会离开您的基础架构。 审计范围内没有第三方处理者,没有数据保留政策需要审查,也没有数据传输协议需要维护。 以前使 Klippa 出现问题的 HIPAA、ITAR、CMMC 和 FedRAMP 限制现在默认得到满足。 在Docker 、 AWS或Azure上部署可以将所有内容都控制在您自己的基础设施边界内。
**基础设施复杂性得以消除。**服务类、HTTP客户端、表单上传代码、JSON模型、重试策略、超时配置——所有这些都只是为了封装一个网络调用。 移除网络呼叫,所有相关数据也会随之消失。 生成的代码库更小、更易读,并且故障模式更少。 通过DI注入的单个IronTesseract实例替换整个HTTP集成层。
**无论卷是多少,成本都是可预测的。**一个在$999(Lite),$1,499(Professional)或$2,999(Enterprise)的永久IronOCR许可证涵盖无限量的文档处理。 每月处理 500 份文件和每月处理 50 万份文件的成本相同。 Klippa 曾经因按文档计费而导致规模化成本高昂,但这种计费方式在结构上已经不存在了。 IronOCR许可页面详细列出了所有级别以及每个级别包含的内容。
文档范围不受限制。IronOCR可以处理任何包含文本的文档。 扫描的合同、技术图纸、医疗表格、购买订单、手写便条、截图、TIFF存档—全都由相同的Read()调用使用相同的API处理。 之前针对 Klippa 培训类别之外的文件需要第二个系统的专业范围限制已经取消。 一个库,一个集成点,支持任何文档类型。
**现在支持离线和受限网络环境。**部署在银行网络、政府系统、边缘环境或任何出站流量受限的基础设施中的应用程序,其运行方式与在开放环境中完全相同。 没有连接性检查,没有对云端点进行健康检查,也没有在互联网不可用时进入降级模式。 物理隔离部署无需修改即可正常工作。 Linux 部署指南和Docker 部署指南涵盖了这些环境的容器化和服务器端部署路径。
**完全掌控图像增强。**云端预处理就像一个黑匣子——Klippa 应用预处理,你只能观察结果,无法调整任何参数。 IronOCR的预处理管道是明确和可组合的:DeepCleanBackgroundNoise()。 每个过滤器都是可选的,需要按顺序排列。 准确率的提高是可衡量的、可重复的,并且在您的控制之下。 图像质量校正指南和预处理功能页面涵盖了完整的滤镜目录,并指导何时应用每个滤镜。
