オープンソースライブラリを使用してC#でOCRを実装する方法
このガイドは、KlippaのREST APIを統合済みで、オンプレミスでの文書処理にIronOCRへの移行を検討している.NET開発者向けです。 本書では、HTTPクライアントインフラストラクチャの削除、JSON逆シリアル化の排除、クラウドに依存するドキュメントアップロードをネットワークを一切使用しないローカルOCR呼び出しに置き換えるための実践的な手順について解説します。
Klippa OCRから移行する理由
Klippaは、 .NET SDKを必要としない、クラウド専用のドキュメントインテリジェンスサービスです。 すべての統合は、手作業で構築されたRESTクライアントです。 そのアーキテクチャ上の現実がもたらす影響は、生産システムのライフサイクル全体にわたって累積していく。
NuGetパッケージが不要なということは、統合レイヤーを所有しているということです。インストールするものは何もありません。 エントリーコストは、HttpClient ラッパーの作成、X-Auth-Key 認証ヘッダーの設定、MultipartFormDataContent リクエストボディの構築、Klippa の JSON レスポンススキーマのデシリアライズ、および一時的な障害のためのリトライロジックの設定です。 つまり、最初の文書が確実に本番環境で処理されるまでに、2~4日間の準備期間が必要になるということです。 KlippaがAPIスキーマを更新すると、デシリアライゼーションコードが動作しなくなり、手動でのメンテナンスが必要になります。
ドキュメントのアップロードはすべてネットワーク依存です。Klippaはドキュメント処理をEU域内のサーバーのみで行います。 Klippa側のシステム障害、レイテンシの上昇、またはアプリケーションサーバーからのインターネット接続の中断が発生すると、ドキュメント処理が完全に停止します。 クラウドサービスが利用できない場合でも、代替手段、ローカルモード、再試行機能は一切ありません。
機密文書がお客様のインフラストラクチャから送信されます。支払い詳細が記載された領収書、VAT番号と金額が記載された請求書、パスポートデータが記載された身分証明書などの財務文書は、API呼び出しのたびにサードパーティのサーバーに送信されます。 GDPRのデータ転送に関する規定は、EU域内での処理に関してこれらの問題の一部に対処していますが、監査の範囲は依然としてKlippaのインフラストラクチャ、データ保持ポリシー、およびサブプロセッサーにまで及びます。 医療、法律、金融サービス、または政府との契約を持つチームにとって、"EUホスト型"では、データが組織外に持ち出されないという要件を満たしません。
ドキュメントごとの料金体系に上限はありません。Klippaは料金表を公開していません。 経費管理システムで月間1万件の領収書、買掛金自動化ワークフローで1日500件の請求書など、ある程度の量の文書を処理する場合、文書ごとの課金モデルでは、永久ライセンスでは決して発生しないコストが発生します。 コストの推移は事業成長に直接的に連動しており、これはインフラ投資が本来果たすべき役割とは正反対である。
専門家の業務範囲は、要件が拡大すると途切れる。クリッパは、領収書、請求書、身分証明書類に関する研修を受けている。 経費管理アプリとして始まったものが、そのまま経費管理にとどまることは稀である。 これら3つのカテゴリ以外の文書タイプ(スキャンされた雇用契約書、医療フォーム、技術図面、非標準レイアウトの発注書など)が初めて出現した場合、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
// 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:機能比較
以下の表は、本番環境への移行を決定する上で最も重要な要素に基づいて、2つのライブラリを比較したものです。
| フィーチャー | クリッパOCR | IronOCR |
|---|---|---|
| 展開モデル | クラウド専用(EUサーバー) | オンプレミス、完全ローカル |
| .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
# 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: 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: 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
構築、コンテンツタイプのヘッダー、アップロード自体がすべてなくなりました。 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;
}
// 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 チュートリアルは、エンドツーエンドの抽出ロジックを網羅しています。
エラー処理と再試行インフラストラクチャの削除
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");
}
// 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
全体のリトライインフラストラクチャ—ループ、遅延計算、CancellationTokenSource、HTTP ステータスコードの分岐、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;
}
// 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を含む出力オプションが記載されています。
クリッパOCRAPIからIronOCRへのマッピングリファレンス
KlippaはREST APIであり、型付きSDKではありません。 以下のマッピングは、Klippaの統合面をIronOCRの同等の領域に変換したものです。
| クリッパコンセプト | IronOCR相当値 |
|---|---|
HttpClient と X-Auth-Key ヘッダー |
IronTesseract インスタンス—認証セットアップ不要 |
MultipartFormDataContent |
OcrInput.LoadImage(path) または OcrInput.LoadPdf(path) |
POST /api/v1/parseDocument |
IronTesseract.Read(input) |
await _client.PostAsync(...) |
ocr.Read(input) — 同期的で、await 不要 |
response.EnsureSuccessStatusCode() |
不要 — HTTPレスポンスなし |
JsonSerializer.Deserialize<KlippaResponse>(json) |
型指定された OcrResult — デシリアライズが不要 |
KlippaResponse.Data.ParsedDocument.Text |
OcrResult.Text |
KlippaResponse.Data.ParsedDocument.Amount |
OcrResult.Text または 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() |
OcrInput の using ステートメントによる処分 |
| 構造化されたJSONレスポンスフィールド | OcrResult.Text + OcrResult.Pages + OcrResult.Words |
| SaaS APIサブスクリプション | IronOcr.License.LicenseKey 文字列—永続的 |
一般的な移行の問題と解決策
問題1:HTTP削除後の非同期専用呼び出しサイト
Klippa: HTTP呼び出しには非同期処理が必要なため、Klippaとの統合はすべて非同期で行われます。 コードベース全体でawait ProcessDocumentAsync(...) を呼び出すコントローラー、サービス、およびバックグラウンドワーカー HTTP 呼び出しの削除は、await が不要になることを意味しますが、async のメソッドシグネチャは残ります。
解決策: IronOCR は同期および非同期 API の両方を提供します。非同期である必要がある呼び出しサイト (ASP.NET Core コントローラー、CancellationToken を使用したバックグラウンドサービス) には、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 Core およびホストサービスパターンのための ReadAsync と CancellationToken の統合を網羅しています。
課題2:依存性注入の登録
Klippa: KlippaService クラスはシングルトンまたはスコープサービスとして DI に登録され、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 は date、および 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
ReadAllBytes なし、MultipartFormDataContent 構築なし、HTTP POST なし。ストリームは OcrInput に直接入ります。 河川流入ガイドでは、河川の種類と排水パターンについて説明しています。
課題5:HTTPモックに依存する統合テスト
Klippa: Klippa コードの統合テストは HttpClient をモックしたり、API レスポンスをシミュレートするために HTTP インターセプター (例:MockHttp) を使用します。 これらのテストは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
画像品質補正ガイドでは、すべての前処理フィルターと、さまざまな文書劣化タイプに対するそれらの適用順序について説明しています。
クリッパ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" .
在庫に関する注記:
- Klippa 呼び出しのために
HttpClientをラップするすべてのクラスを記録してください - すべての JSON デシリアライズモデルクラスをリストアップしてください (例:
KlippaParsedDocumentなど) - Klippaの事前解析済みJSONプロパティを使用するすべてのフィールドマッピングを文書化する
- PollyのリトライポリシーやKlippa用に構築されたカスタムリトライループに注意してください。
コードの移行
IronOcrNuGet パッケージをインストールしてください (例:dotnet add package IronOcr)- アプリケーション起動時に
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"を追加してください - Klippa サービスファイルから
Newtonsoft.Jsonのインポートを削除してください KlippaServiceクラスを削除 (またはその本体をIronTesseract呼び出しで置き換え、インターフェースを保持)IronTesseractを DI コンテナにシングルトンとして登録してくださいMultipartFormDataContentアップロードブロックをOcrInput.LoadImage()またはOcrInput.LoadPdf()に置き換えてください- JSON レスポンスモデルクラスを削除してください (例:
KlippaParsedDocument) - NULL 可能な JSON ナビゲーションチェーン (例:
.Data?.ParsedDocument?.Text) をresult.Textに置き換えてください - Klippa 呼び出しサイトからリトライループと
CancellationTokenSourceタイムアウトを削除してください - レート制限処理(HTTP 429 キャッチブロック)を削除する
await ProcessDocumentAsync(...)をawait ocr.ReadAsync(...)または同期的なocr.Read(...)に置き換えてください- 低品質の文書入力のために
OcrInput前処理フィルター (Contrast) を追加してください - HTTPモックテストインフラストラクチャを実際のドキュメントフィクスチャテストに置き換える
- Klippa呼び出しにスコープされたPollyリトライポリシーまたはカスタムリトライミドルウェアを削除します。
移行後
- テキスト抽出結果が既知のテスト文書の想定される内容と一致することを確認する
- 生産文書の種類について、信頼度スコアが許容しきい値(通常70%以上)を超えていることを確認する
- PDF 入力のテスト:ネイティブにマルチページ PDF を読み込み、
result.Pagesを介した各ページのテキストアクセスを検証する - ストリーム入力のテスト:
MemoryStreamを通過させ、OcrInput.LoadImage(stream)が正しい出力を生成することを検証する - 前処理フィルターが未処理のベースラインと比較して低品質スキャンの精度を向上させることを検証する
- DI で注入された
IronTesseractシングルトンが同時リクエストをコンテンションなしで処理することを確認する - オフライン(ネットワーク接続なし)で統合テストを実行する — クラウドアクセスなしで全てのテストが合格するはずです
- スキャンされた文書フローのために
result.SaveAsSearchablePdf("output.pdf")を使った検索可能な PDF 出力を確認する - ASP.NET Core コントローラーコンテキストで
ReadAsyncをCancellationToken伝播とともにテストする - 持続的な負荷の下でメモリリークのない
using var input = new OcrInput()廃棄パターンを確認する
IronOCRへの移行の主なメリット
データ主権は初日から確保されます。移行後、機密性の高い財務書類、本人確認書類、機密性の高い請求書は、お客様のインフラストラクチャから外部に持ち出されることはありません。 監査対象には第三者処理業者は含まれておらず、レビューすべきデータ保持ポリシーも、維持すべきデータ転送契約もありません。 これまでKlippaに問題を引き起こしていたHIPAA、ITAR、CMMC、およびFedRAMPの制約は、デフォルトで満たされています。 Docker 、 AWS 、またはAzureへのデプロイにより、すべてが自社のインフラストラクチャ境界内に収まります。
インフラストラクチャの複雑さが解消されました。サービス クラス、HTTP クライアント、フォームアップロード コード、JSON モデル、再試行ポリシー、タイムアウト設定など、すべてはネットワーク呼び出しをラップするために存在していました。 ネットワーク呼び出しを削除すれば、それらすべてが削除されます。 その結果、コードベースはより小さく、読みやすく、障害発生箇所も少なくなる。 DI を通じて注入された単一の IronTesseract インスタンスは、すべての HTTP 統合レイヤーを置き換えます。
ボリュームにかかわらず予測可能なコスト。 $499 (Lite)、$1,499 (Professional)、または $2,999 (Enterprise) の IronOCR 永続ライセンスは、無制限の文書処理をカバーします。 1ヶ月に500件の文書を処理する場合も、50万件を処理する場合も、費用は同じです。 Klippaを大規模に利用する際のコスト要因となっていた、ドキュメントごとの課金システムは、構造的に排除されている。 IronOCRのライセンスページには、すべてのティアとそれぞれの内容が詳しく記載されています。
文書の範囲に制限はありません。IronOCRは、テキストを含むあらゆる文書を処理できます。 スキャンされた契約書、技術図面、医療フォーム、注文書、手書きのメモ、スクリーンショット、TIFF アーカイブ — すべて同じ Read() 呼び出しと同じ API で処理されます。 Klippaの専門分野外の文書については別のシステムが必要だったという、専門分野に関する制約は解消された。 1つのライブラリ、1つの統合ポイント、あらゆるドキュメントタイプに対応。
オフライン環境および制限付きネットワーク環境がサポートされるようになりました。銀行ネットワーク、政府システム、エッジ環境、または外部への通信が制限されているあらゆるインフラストラクチャに展開されたアプリケーションは、オープン環境とまったく同じように動作します。 接続チェックも、クラウドエンドポイントへのヘルスチェックピングも、インターネット接続が利用できない場合の劣化モードもありません。 エアギャップ方式の展開は、変更なしで機能します。 Linux導入ガイドとDocker導入ガイドでは、これらの環境におけるコンテナ化およびサーバーサイド導入の手順について説明しています。
画像補正を完全に制御できます。クラウド前処理はブラックボックスでした。Klippaが前処理を適用し、ユーザーは結果を確認するだけで、調整できるパラメータはありませんでした。 IronOCR の前処理パイプラインは明示的かつ組み合わせ可能です:DeepCleanBackgroundNoise()。 各フィルターはオプションであり、順番に並んでいます。 精度向上は測定可能で再現性があり、かつあなたの管理下にあります。 画像品質補正ガイドと前処理機能のページでは、すべてのフィルターカタログを網羅し、それぞれのフィルターを適用するタイミングに関するガイダンスを提供しています。
よくある質問
なぜKlippa OCR APIからIronOCRに移行する必要があるのですか?
一般的な推進要因には、COM相互接続の複雑さの解消、ファイルベースのライセンス管理の置き換え、ページ単位の課金の回避、Docker/コンテナデプロイの有効化、標準的な.NETツールと統合するNuGetネイティブワークフローの採用などがあります。
Klippa OCR APIからIronOCRに移行する際の主なコード変更は何ですか?
Klippaの初期化シーケンスをIronTesseractのインスタンス化に置き換え、COMのライフサイクル管理(明示的なCreate/Load/Closeパターン)を削除し、結果のプロパティ名を更新します。その結果、定型的な行が大幅に減りました。
IronOCRをインストールして移行を開始するにはどうすればいいですか?
パッケージマネージャーコンソールで'Install-Package IronOcr'を実行するか、CLIで'dotnet add package IronOcr'を実行してください。言語パックは別のパッケージです:例えばフランス語の場合は'dotnet add package IronOcr.Languages.French'となります。
IronOCRはKlippa OCR APIの標準的なビジネス文書のOCR精度に匹敵しますか?
IronOCRは、請求書、契約書、領収書、入力フォームなどの標準的なビジネスコンテンツで高い精度を達成しています。画像前処理フィルター(傾き補正、ノイズ除去、コントラスト強調)により、劣化した入力の認識をさらに向上させます。
IronOCR はKlippa OCR APIが別途インストールする言語データをどのように扱うのですか?
IronOCRの言語データはNuGetパッケージとして配布されます。'dotnet add package IronOcr.Languages.German'でドイツ語のサポートがインストールされます。手動でのファイル配置やディレクトリパスは必要ありません。
Klippa OCR APIからIronOCRに移行する場合、導入インフラの変更が必要ですか?
IronOCRはKlippa OCR APIよりもインフラの変更が少なくて済みます。SDKのバイナリパス、ライセンスファイルの配置、ライセンスサーバーの設定は必要ありません。NuGetパッケージには完全なOCRエンジンが含まれており、ライセンスキーはアプリケーションコードに設定された文字列です。
移行後のIronOCRライセンスの設定方法は?
アプリケーションの起動コードでIronOcr.License.LicenseKey = "YOUR-KEY "を割り当てます。DockerまたはKubernetesでは、キーを環境変数として保存し、スタートアップで読み込む。License.IsValidLicenseを使用して、トラフィックを受け入れる前にバリデーションを行う。
IronOCRはKlippaと同じようにPDFを処理できますか?
IronOCRはネイティブPDFもスキャンしたPDFも読み取ります。IronTesseractをインスタンス化し、ocr.Read(input)を呼び出し、入力はPDFパスまたはOcrPdfInputであり、OcrResultページを反復します。別のPDFレンダリングパイプラインは必要ありません。
IronOCRはどのように大量処理のスレッディングを処理するのですか?
IronTesseractはスレッド毎にインスタンス化しても安全です。Parallel.ForEachまたはタスクプールでスレッドごとにインスタンスを1つスピンアップし、OCRを同時に実行し、完了したら各インスタンスを破棄します。グローバルステートやロックは必要ありません。
IronOCRはテキスト抽出後、どのような出力形式をサポートしていますか?
IronOCRはテキスト、単語座標、信頼度スコア、ページ構造を含む構造化された結果を返します。エクスポートオプションには、プレーンテキスト、検索可能なPDF、下流処理用の構造化結果オブジェクトが含まれます。
IronOCRの価格はKlippa OCR APIよりもワークロードのスケーリングが予測できますか?
IronOCRは、定額制の永久ライセンスを採用しており、ページ単位やボリューム単位の料金はかかりません。10,000ページでも1,000万ページでも、ライセンスコストは一定です。ボリュームライセンスとチームライセンスのオプションはIronOCRの価格ページをご覧ください。
Klippa OCR APIからIronOCRに移行した後、既存のテストはどうなりますか?
抽出されたテキストコンテンツをアサートするテストは、移行後もパスし続ける必要があります。APIコールパターンやCOMオブジェクトのライフサイクルを検証するテストは、IronOCRのシンプルな初期化と結果モデルを反映するように更新する必要があります。

