ABBYY FineReader対Tesseract:OCR比較
OCR.spaceにはNuGetパッケージがありません。 その単一の事実が、.NET開発者がこれを選ぶ際のすべての統合決定を定義します: 独自のhttps://api.ocr.space/parse/imageにPOSTすること、JSON応答を手動で解析すること、HTTP 429を取得すること、指数バックオフを実装すること、レート制限簿記を作成すること、独自の例外タイプを定義すること―これらの全ては、あなたのアプリケーションにテキストの単一の文字が到達する前に行わなければなりません。 この記事では、DIYによるオーバーヘッドがコード、時間、および本番環境の信頼性に実際にどれだけのコストがかかるかを検証し、単一のNuGetパッケージとして提供されるネイティブ.NETライブラリであるIronOCRと直接比較します。
OCR.spaceを理解する
OCR.spaceは、OCR.spaceが運営するリモートサーバー上で画像やPDFを処理する、フリーミアムのREST APIです。 このサービスは、無料プランを強みとしており、クレジットカード不要で月間25,000件のリクエストが可能となっている。これは、OCRを試用したり、個人的なプロジェクトを構築したりする開発者にとって魅力的な点だ。 NuGetパッケージも、公式 for .NET SDKも、どの言語においても厳密に型指定されたクライアントライブラリも存在しません。 すべて for .NETインテグレーションはHttpClientの上に手作業で構築されています。
.NET開発者にとってのアーキテクチャ上の現実:
- NuGetパッケージなし: .NETのファーストクラスのサポートは皆無です。 IntelliSense機能なし、型付きモデルなし、統合エラー処理なし。 -クラウドのみでの処理:すべてのドキュメントはお客様のインフラストラクチャから外部に送信されます。 オンプレミスオプションはなく、セルフホスト型のデプロイメントパスもありません。
- 手動によるREST統合: 開発者はファイルをbase64でエンコードし、
MultipartFormDataContentを構築し、生のJSON応答を解析します。 - DIYによるレート制限:無料プランでは、1分あたり60リクエスト、1日あたりIPアドレスごとに500リクエストという制限が適用されます。本番環境のアプリケーションでは、この制限ロジックを独自に構築する必要があります。 -ファイルサイズ制限:無料プランでは、5MBを超えるファイルは拒否されます。 複数ページのPDFファイルは、この制限を超えることが多い。 -透かし入りの PDF 出力:無料プランの検索可能な PDF 生成では、OCR.space の透かしが埋め込まれるため、出力はクライアントへの納品や文書のアーカイブには使用できません。
- SLAなし:無料プランには稼働時間に関する保証はありません。 有料プランでは上限額は高くなりますが、アーキテクチャ上の制約は同じです。
REST統合開発者が必ず書くべきこと
OCR.spaceのドキュメントは開発者に対しRESTエンドポイントを指し示すだけで、それ以外のすべては開発者自身で構築するよう指示している。 本番環境におけるセキュリティ強化を考慮する前の、必要最低限 for .NETクライアントは以下のようになります。
// OCR.space: what you build before the first line of your actual application
public class OcrSpaceApiClient : IDisposable
{
private readonly HttpClient _httpClient;
private readonly string _apiKey;
private readonly SemaphoreSlim _rateLimiter;
private const string ApiEndpoint = "https://api.ocr.space/parse/image";
private const int MaxFileSizeFree = 5 * 1024 * 1024; // 5MB — hard limit on free tier
private const int RateLimitPerMinute = 60;
public OcrSpaceApiClient(string apiKey)
{
_apiKey = apiKey ?? throw new ArgumentNullException(nameof(apiKey));
_httpClient = new HttpClient();
_httpClient.Timeout = TimeSpan.FromSeconds(120);
_rateLimiter = new SemaphoreSlim(RateLimitPerMinute, RateLimitPerMinute);
}
public async Task<OcrResult> ExtractTextFromFileAsync(
string filePath,
string language = "eng",
CancellationToken cancellationToken = default)
{
if (!File.Exists(filePath))
throw new FileNotFoundException("Image file not found", filePath);
var fileInfo = new FileInfo(filePath);
if (fileInfo.Length > MaxFileSizeFree)
{
throw new InvalidOperationException(
$"File size {fileInfo.Length / 1024 / 1024}MB exceeds free tier limit of 5MB.");
}
await _rateLimiter.WaitAsync(cancellationToken);
try
{
// Document leaves your infrastructure here
byte[] imageBytes = await File.ReadAllBytesAsync(filePath, cancellationToken);
string base64Image = Convert.ToBase64String(imageBytes);
string mimeType = GetMimeType(filePath);
var formContent = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("apikey", _apiKey),
new KeyValuePair<string, string>("base64Image", $"data:{mimeType};base64,{base64Image}"),
new KeyValuePair<string, string>("language", language),
new KeyValuePair<string, string>("isOverlayRequired", "false"),
new KeyValuePair<string, string>("filetype", Path.GetExtension(filePath).TrimStart('.')),
new KeyValuePair<string, string>("detectOrientation", "true"),
new KeyValuePair<string, string>("scale", "true"),
new KeyValuePair<string, string>("OCREngine", "2")
});
var response = await _httpClient.PostAsync(ApiEndpoint, formContent, cancellationToken);
if (!response.IsSuccessStatusCode)
{
string errorBody = await response.Content.ReadAsStringAsync(cancellationToken);
throw new OcrSpaceException(
$"OCR.space API returned {response.StatusCode}: {errorBody}",
(int)response.StatusCode);
}
string jsonResponse = await response.Content.ReadAsStringAsync(cancellationToken);
return ParseOcrResponse(jsonResponse);
}
finally
{
_ = Task.Run(async () =>
{
await Task.Delay(60000 / RateLimitPerMinute);
_rateLimiter.Release();
});
}
}
private OcrResult ParseOcrResponse(string jsonResponse)
{
using var doc = JsonDocument.Parse(jsonResponse);
var root = doc.RootElement;
if (root.TryGetProperty("IsErroredOnProcessing", out var isErrored) && isErrored.GetBoolean())
{
string errorMessage = "OCR processing failed";
if (root.TryGetProperty("ErrorMessage", out var errorMessages))
{
// Parse the array of error strings manually
var messages = new List<string>();
if (errorMessages.ValueKind == JsonValueKind.Array)
{
foreach (var msg in errorMessages.EnumerateArray())
messages.Add(msg.GetString() ?? "Unknown error");
}
errorMessage = string.Join("; ", messages);
}
throw new OcrSpaceException(errorMessage, 500);
}
var result = new OcrResult();
if (root.TryGetProperty("ParsedResults", out var parsedResults))
{
foreach (var parsedResult in parsedResults.EnumerateArray())
{
if (parsedResult.TryGetProperty("ParsedText", out var parsedText))
result.ParsedText += parsedText.GetString();
if (parsedResult.TryGetProperty("FileParseExitCode", out var exitCode))
result.ExitCodes.Add(exitCode.GetInt32());
}
}
return result;
}
private string GetMimeType(string filePath) =>
Path.GetExtension(filePath).ToLowerInvariant() switch
{
".png" => "image/png",
".jpg" or ".jpeg" => "image/jpeg",
".bmp" => "image/bmp",
".tiff" or ".tif" => "image/tiff",
".pdf" => "application/pdf",
_ => "application/octet-stream"
};
public void Dispose()
{
_httpClient?.Dispose();
_rateLimiter?.Dispose();
}
}
// Custom models — no SDK means you define these yourself
public class OcrResult
{
public string ParsedText { get; set; } = string.Empty;
public List<string> Warnings { get; } = new();
public List<int> ExitCodes { get; } = new();
}
public class OcrSpaceException : Exception
{
public int StatusCode { get; }
public OcrSpaceException(string message, int statusCode) : base(message)
=> StatusCode = statusCode;
}
// OCR.space: what you build before the first line of your actual application
public class OcrSpaceApiClient : IDisposable
{
private readonly HttpClient _httpClient;
private readonly string _apiKey;
private readonly SemaphoreSlim _rateLimiter;
private const string ApiEndpoint = "https://api.ocr.space/parse/image";
private const int MaxFileSizeFree = 5 * 1024 * 1024; // 5MB — hard limit on free tier
private const int RateLimitPerMinute = 60;
public OcrSpaceApiClient(string apiKey)
{
_apiKey = apiKey ?? throw new ArgumentNullException(nameof(apiKey));
_httpClient = new HttpClient();
_httpClient.Timeout = TimeSpan.FromSeconds(120);
_rateLimiter = new SemaphoreSlim(RateLimitPerMinute, RateLimitPerMinute);
}
public async Task<OcrResult> ExtractTextFromFileAsync(
string filePath,
string language = "eng",
CancellationToken cancellationToken = default)
{
if (!File.Exists(filePath))
throw new FileNotFoundException("Image file not found", filePath);
var fileInfo = new FileInfo(filePath);
if (fileInfo.Length > MaxFileSizeFree)
{
throw new InvalidOperationException(
$"File size {fileInfo.Length / 1024 / 1024}MB exceeds free tier limit of 5MB.");
}
await _rateLimiter.WaitAsync(cancellationToken);
try
{
// Document leaves your infrastructure here
byte[] imageBytes = await File.ReadAllBytesAsync(filePath, cancellationToken);
string base64Image = Convert.ToBase64String(imageBytes);
string mimeType = GetMimeType(filePath);
var formContent = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("apikey", _apiKey),
new KeyValuePair<string, string>("base64Image", $"data:{mimeType};base64,{base64Image}"),
new KeyValuePair<string, string>("language", language),
new KeyValuePair<string, string>("isOverlayRequired", "false"),
new KeyValuePair<string, string>("filetype", Path.GetExtension(filePath).TrimStart('.')),
new KeyValuePair<string, string>("detectOrientation", "true"),
new KeyValuePair<string, string>("scale", "true"),
new KeyValuePair<string, string>("OCREngine", "2")
});
var response = await _httpClient.PostAsync(ApiEndpoint, formContent, cancellationToken);
if (!response.IsSuccessStatusCode)
{
string errorBody = await response.Content.ReadAsStringAsync(cancellationToken);
throw new OcrSpaceException(
$"OCR.space API returned {response.StatusCode}: {errorBody}",
(int)response.StatusCode);
}
string jsonResponse = await response.Content.ReadAsStringAsync(cancellationToken);
return ParseOcrResponse(jsonResponse);
}
finally
{
_ = Task.Run(async () =>
{
await Task.Delay(60000 / RateLimitPerMinute);
_rateLimiter.Release();
});
}
}
private OcrResult ParseOcrResponse(string jsonResponse)
{
using var doc = JsonDocument.Parse(jsonResponse);
var root = doc.RootElement;
if (root.TryGetProperty("IsErroredOnProcessing", out var isErrored) && isErrored.GetBoolean())
{
string errorMessage = "OCR processing failed";
if (root.TryGetProperty("ErrorMessage", out var errorMessages))
{
// Parse the array of error strings manually
var messages = new List<string>();
if (errorMessages.ValueKind == JsonValueKind.Array)
{
foreach (var msg in errorMessages.EnumerateArray())
messages.Add(msg.GetString() ?? "Unknown error");
}
errorMessage = string.Join("; ", messages);
}
throw new OcrSpaceException(errorMessage, 500);
}
var result = new OcrResult();
if (root.TryGetProperty("ParsedResults", out var parsedResults))
{
foreach (var parsedResult in parsedResults.EnumerateArray())
{
if (parsedResult.TryGetProperty("ParsedText", out var parsedText))
result.ParsedText += parsedText.GetString();
if (parsedResult.TryGetProperty("FileParseExitCode", out var exitCode))
result.ExitCodes.Add(exitCode.GetInt32());
}
}
return result;
}
private string GetMimeType(string filePath) =>
Path.GetExtension(filePath).ToLowerInvariant() switch
{
".png" => "image/png",
".jpg" or ".jpeg" => "image/jpeg",
".bmp" => "image/bmp",
".tiff" or ".tif" => "image/tiff",
".pdf" => "application/pdf",
_ => "application/octet-stream"
};
public void Dispose()
{
_httpClient?.Dispose();
_rateLimiter?.Dispose();
}
}
// Custom models — no SDK means you define these yourself
public class OcrResult
{
public string ParsedText { get; set; } = string.Empty;
public List<string> Warnings { get; } = new();
public List<int> ExitCodes { get; } = new();
}
public class OcrSpaceException : Exception
{
public int StatusCode { get; }
public OcrSpaceException(string message, int statusCode) : base(message)
=> StatusCode = statusCode;
}
Imports System
Imports System.Collections.Generic
Imports System.IO
Imports System.Net.Http
Imports System.Text.Json
Imports System.Threading
Imports System.Threading.Tasks
' OCR.space: what you build before the first line of your actual application
Public Class OcrSpaceApiClient
Implements IDisposable
Private ReadOnly _httpClient As HttpClient
Private ReadOnly _apiKey As String
Private ReadOnly _rateLimiter As SemaphoreSlim
Private Const ApiEndpoint As String = "https://api.ocr.space/parse/image"
Private Const MaxFileSizeFree As Integer = 5 * 1024 * 1024 ' 5MB — hard limit on free tier
Private Const RateLimitPerMinute As Integer = 60
Public Sub New(apiKey As String)
_apiKey = If(apiKey, Throw New ArgumentNullException(NameOf(apiKey)))
_httpClient = New HttpClient()
_httpClient.Timeout = TimeSpan.FromSeconds(120)
_rateLimiter = New SemaphoreSlim(RateLimitPerMinute, RateLimitPerMinute)
End Sub
Public Async Function ExtractTextFromFileAsync(filePath As String, Optional language As String = "eng", Optional cancellationToken As CancellationToken = Nothing) As Task(Of OcrResult)
If Not File.Exists(filePath) Then
Throw New FileNotFoundException("Image file not found", filePath)
End If
Dim fileInfo = New FileInfo(filePath)
If fileInfo.Length > MaxFileSizeFree Then
Throw New InvalidOperationException($"File size {fileInfo.Length \ 1024 \ 1024}MB exceeds free tier limit of 5MB.")
End If
Await _rateLimiter.WaitAsync(cancellationToken)
Try
' Document leaves your infrastructure here
Dim imageBytes As Byte() = Await File.ReadAllBytesAsync(filePath, cancellationToken)
Dim base64Image As String = Convert.ToBase64String(imageBytes)
Dim mimeType As String = GetMimeType(filePath)
Dim formContent = New FormUrlEncodedContent(New KeyValuePair(Of String, String)() {
New KeyValuePair(Of String, String)("apikey", _apiKey),
New KeyValuePair(Of String, String)("base64Image", $"data:{mimeType};base64,{base64Image}"),
New KeyValuePair(Of String, String)("language", language),
New KeyValuePair(Of String, String)("isOverlayRequired", "false"),
New KeyValuePair(Of String, String)("filetype", Path.GetExtension(filePath).TrimStart("."c)),
New KeyValuePair(Of String, String)("detectOrientation", "true"),
New KeyValuePair(Of String, String)("scale", "true"),
New KeyValuePair(Of String, String)("OCREngine", "2")
})
Dim response = Await _httpClient.PostAsync(ApiEndpoint, formContent, cancellationToken)
If Not response.IsSuccessStatusCode Then
Dim errorBody = Await response.Content.ReadAsStringAsync(cancellationToken)
Throw New OcrSpaceException($"OCR.space API returned {response.StatusCode}: {errorBody}", CInt(response.StatusCode))
End If
Dim jsonResponse = Await response.Content.ReadAsStringAsync(cancellationToken)
Return ParseOcrResponse(jsonResponse)
Finally
_ = Task.Run(Async Function()
Await Task.Delay(60000 \ RateLimitPerMinute)
_rateLimiter.Release()
End Function)
End Try
End Function
Private Function ParseOcrResponse(jsonResponse As String) As OcrResult
Using doc = JsonDocument.Parse(jsonResponse)
Dim root = doc.RootElement
If root.TryGetProperty("IsErroredOnProcessing", ByRef isErrored) AndAlso isErrored.GetBoolean() Then
Dim errorMessage As String = "OCR processing failed"
If root.TryGetProperty("ErrorMessage", ByRef errorMessages) Then
' Parse the array of error strings manually
Dim messages = New List(Of String)()
If errorMessages.ValueKind = JsonValueKind.Array Then
For Each msg In errorMessages.EnumerateArray()
messages.Add(msg.GetString() OrElse "Unknown error")
Next
End If
errorMessage = String.Join("; ", messages)
End If
Throw New OcrSpaceException(errorMessage, 500)
End If
Dim result = New OcrResult()
If root.TryGetProperty("ParsedResults", ByRef parsedResults) Then
For Each parsedResult In parsedResults.EnumerateArray()
If parsedResult.TryGetProperty("ParsedText", ByRef parsedText) Then
result.ParsedText &= parsedText.GetString()
End If
If parsedResult.TryGetProperty("FileParseExitCode", ByRef exitCode) Then
result.ExitCodes.Add(exitCode.GetInt32())
End If
Next
End If
Return result
End Using
End Function
Private Function GetMimeType(filePath As String) As String
Return Path.GetExtension(filePath).ToLowerInvariant() Select Case
Case ".png" : Return "image/png"
Case ".jpg", ".jpeg" : Return "image/jpeg"
Case ".bmp" : Return "image/bmp"
Case ".tiff", ".tif" : Return "image/tiff"
Case ".pdf" : Return "application/pdf"
Case Else : Return "application/octet-stream"
End Select
End Function
Public Sub Dispose() Implements IDisposable.Dispose
_httpClient?.Dispose()
_rateLimiter?.Dispose()
End Sub
End Class
' Custom models — no SDK means you define these yourself
Public Class OcrResult
Public Property ParsedText As String = String.Empty
Public ReadOnly Property Warnings As New List(Of String)()
Public ReadOnly Property ExitCodes As New List(Of Integer)()
End Class
Public Class OcrSpaceException
Inherits Exception
Public ReadOnly Property StatusCode As Integer
Public Sub New(message As String, statusCode As Integer)
MyBase.New(message)
Me.StatusCode = statusCode
End Sub
End Class
これは、最初のビジネスロジックメソッドの前に存在する、90行以上のインフラストラクチャコードです。 OCR.spaceの統合にはすべてこの定型コード(またはそれに相当するもの)が同梱されており、それを作成するすべてのチームは、OCR.spaceが解決しなかった同じ問題を解決している。
IronOCRを理解する
IronOCR は .NET 用の商用OCRライブラリで、IronOcr NuGetパッケージを介してインストールされます。 最適化されたTesseract 5エンジンをベースに、自動前処理、ネイティブPDF入力、検索可能なPDF出力、125以上の言語パック、構造化された結果抽出機能を搭載。クラウドへの依存、APIキー、リクエストごとの料金は一切不要です。 ドキュメントは、 .NETアプリケーションを実行しているインフラストラクチャ上でローカルに処理されます。
主な特徴:
- 単一のNuGetパッケージ:
dotnet add package IronOcrが完全なインストールです。 管理すべきネイティブバイナリも、tessdataディレクトリも、環境変数もありません。 - 強く型付けされたAPI:
IronTesseract,OcrInput,OcrResult,CropRectangleはすべて、完全なIntelliSenseサポートを備えた一級 for .NETタイプです。 -自動前処理:デスクウ、ノイズ除去、コントラスト、二値化、解像度向上フィルターが、外部ライブラリを使用せずに自動的に、または必要に応じて適用されます。 -ネイティブPDFサポート:スキャンしたPDFの読み込みと、検索可能なPDFの生成の両方が組み込まれています。 利用可能なメモリ容量を超えるサイズ制限はありません。 -ローカル実行: OCR処理中にネットワーク呼び出しは発生しません。 このライブラリは完全にプロセス内で動作します。 エアギャップ環境は、設定変更なしで動作します。 - スレッドセーフ: 複数の
IronTesseractインスタンスが、ロックや競合管理なしに並行して実行されます。 - 永続ライセンス: $999 Lite / $1,499 Plus / $2,999 Professional。 リクエスト量に関わらず、リクエストごとの料金は一切かかりません。
機能比較
| フィーチャー | OCR.space | IronOCR |
|---|---|---|
| NuGetパッケージ | なし — REST APIのみ | IronOcr - 完全な.NETサポート |
| 処理場所 | OCR.space クラウドサーバー | 地域密着型インフラ |
| 無料プラン | 月間25,000件のリクエスト(限定) | 無料トライアル利用可能 |
| 有料料金 | OCR.spaceに現在の価格をお問い合わせください | $2,999 一度限りの永続 |
| 律速段階 | 60リクエスト/分、500リクエスト/日(無料) | None |
| オフライン機能 | なし | はい、完全にエアギャップされています |
| データプライバシー | 第三者に送付された文書 | ドキュメントはサーバー上に残ります |
詳細な機能比較
| フィーチャー | OCR.space | IronOCR |
|---|---|---|
| 統合。 | ||
| NuGetパッケージ | None | はい (IronOcr) |
| 厳密な型付けモデル | なし — 手動JSON解析 | はい (OcrResult, OcrInput) |
| IntelliSenseのサポート | None | フル |
| カスタム例外タイプ | 自分自身を定義しなければならない | 内蔵 |
| 再試行ロジック | 自分で築き上げなければならない | 内蔵 |
| 処理 | ||
| 処理場所 | クラウドサーバー | ローカル処理中 |
| インターネット接続が必要です | はい、すべての電話 | なし |
| エアギャップサポート | なし | はい |
| レート制限 | はい、すべてのプラン | None |
| ファイルサイズ制限 | 5MB(無料枠) | メモリのみ |
| OCR機能 | ||
| 画像OCR | はい | はい |
| PDF OCR | はい(ただし制限あり) | はい(ネイティブ、サイズ制限なし) |
| 検索可能なPDF出力 | 無料プランでは透かしが入ります | 出力はきれいで、透かしはありません。 |
| 自動前処理 | なし(サーバー側のみ) | デスクウ、ノイズ除去、コントラスト調整、二値化、解像度向上 |
| 手動前処理制御 | None | フルフィルターAPI |
| 言語サポート | 約25言語 | NuGet言語パック経由で125種類以上 |
| 文書ごとに複数の言語 | 制限的 | はい — 主要言語と副言語 |
| 構造化された出力(単語数/行数/ページ数) | いいえ — プレーンテキストのみ | はい、ページ、段落、行、座標付きの単語です。 |
| 信頼度スコア | なし | はい、結果ごと、単語ごと |
| 地域ベースのOCR | なし | はい — CropRectangle |
| OCR中のバーコード読み取り | なし | はい — ReadBarCodes = true |
| デプロイメント | ||
| Docker | 発信インターネット接続が必要です | すぐに使える |
| Linux | 発信インターネット接続が必要です | 完全サポート |
| HIPAA準拠 | リスク — 書類があなたの管理下から離れる | はい、外部送信はありません |
| GDPRに準拠 | リスク — EUのデータ転送は不明確 | はい、第三者によるデータ処理は行いません。 |
| 価格について | ||
| 価格設定モデル | 月額サブスクリプション | 1回限りの永久ライセンス |
| エントリー価格 | OCR.spaceに現在の価格をお問い合わせください | $999 一度限り |
| ドキュメントごとのコストが大規模に適用される | はい | None |
| SLA | 無料(なし)、有料(金額は変動する) | EnterpriseSLAが利用可能 |
SDKの深度と生のHTTP:統合コスト
OCR.spaceとネイティブSDKの間のギャップは、スタイルの好みの問題ではありません。 これは、開発時間、バグが発生する可能性のある領域、および各デプロイメントにおけるメンテナンスの負担という観点から測定されます。
OCR.space アプローチ
OCR.spaceを使用するすべて for .NET開発者は、独自のHTTPクライアントを作成します。 最もシンプルな実装(再試行ロジックなし、バッチ処理なし、前処理なしの基本的なテキスト抽出)でも、50行以上になります。
// OCR.space: minimum viable extraction — 50歳以上 lines before business logic
public class OcrSpaceBasicExtraction
{
private readonly HttpClient _httpClient;
private readonly string _apiKey;
public OcrSpaceBasicExtraction(string apiKey)
{
_apiKey = apiKey;
_httpClient = new HttpClient();
}
public async Task<string> ExtractText(string imagePath)
{
// Read file and encode — document leaves your infrastructure
byte[] imageBytes = File.ReadAllBytes(imagePath);
string base64 = Convert.ToBase64String(imageBytes);
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("apikey", _apiKey),
new KeyValuePair<string, string>("base64Image", $"data:image/png;base64,{base64}"),
new KeyValuePair<string, string>("language", "eng")
});
// Send to cloud
var response = await _httpClient.PostAsync(
"https://api.ocr.space/parse/image", content);
if (!response.IsSuccessStatusCode)
throw new Exception($"API error: {response.StatusCode}");
// Parse JSON manually — no typed models
string json = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(json);
return doc.RootElement
.GetProperty("ParsedResults")[0]
.GetProperty("ParsedText")
.GetString() ?? string.Empty;
}
}
// OCR.space: minimum viable extraction — 50歳以上 lines before business logic
public class OcrSpaceBasicExtraction
{
private readonly HttpClient _httpClient;
private readonly string _apiKey;
public OcrSpaceBasicExtraction(string apiKey)
{
_apiKey = apiKey;
_httpClient = new HttpClient();
}
public async Task<string> ExtractText(string imagePath)
{
// Read file and encode — document leaves your infrastructure
byte[] imageBytes = File.ReadAllBytes(imagePath);
string base64 = Convert.ToBase64String(imageBytes);
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("apikey", _apiKey),
new KeyValuePair<string, string>("base64Image", $"data:image/png;base64,{base64}"),
new KeyValuePair<string, string>("language", "eng")
});
// Send to cloud
var response = await _httpClient.PostAsync(
"https://api.ocr.space/parse/image", content);
if (!response.IsSuccessStatusCode)
throw new Exception($"API error: {response.StatusCode}");
// Parse JSON manually — no typed models
string json = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(json);
return doc.RootElement
.GetProperty("ParsedResults")[0]
.GetProperty("ParsedText")
.GetString() ?? string.Empty;
}
}
Imports System.Net.Http
Imports System.Threading.Tasks
Imports System.IO
Imports System.Text.Json
Public Class OcrSpaceBasicExtraction
Private ReadOnly _httpClient As HttpClient
Private ReadOnly _apiKey As String
Public Sub New(apiKey As String)
_apiKey = apiKey
_httpClient = New HttpClient()
End Sub
Public Async Function ExtractText(imagePath As String) As Task(Of String)
' Read file and encode — document leaves your infrastructure
Dim imageBytes As Byte() = File.ReadAllBytes(imagePath)
Dim base64 As String = Convert.ToBase64String(imageBytes)
Dim content = New FormUrlEncodedContent(New KeyValuePair(Of String, String)() {
New KeyValuePair(Of String, String)("apikey", _apiKey),
New KeyValuePair(Of String, String)("base64Image", $"data:image/png;base64,{base64}"),
New KeyValuePair(Of String, String)("language", "eng")
})
' Send to cloud
Dim response = Await _httpClient.PostAsync("https://api.ocr.space/parse/image", content)
If Not response.IsSuccessStatusCode Then
Throw New Exception($"API error: {response.StatusCode}")
End If
' Parse JSON manually — no typed models
Dim json As String = Await response.Content.ReadAsStringAsync()
Using doc = JsonDocument.Parse(json)
Return doc.RootElement _
.GetProperty("ParsedResults")(0) _
.GetProperty("ParsedText") _
.GetString() OrElse String.Empty
End Using
End Function
End Class
バッチ処理を追加すると、行数は80行以上に増えます。なぜなら、すべてのバッチリクエストには、レート制限によるスロットリング、指数バックオフを使用したリクエストごとの再試行ロジック、およびHTTP 429レスポンスの明示的な処理が必要となるからです。 PDF処理を追加すると、すべての呼び出しの前に5MBのファイルサイズチェックが加わります。なぜなら、無料プランでは、警告ではなくエラーでより大きなファイルが拒否されるからです。
IronOCRのアプローチ
NuGetからIronOCRをインストールすると、既存のメソッド呼び出しでインフラストラクチャコードがすべて置き換えられます。
// IronOCR: complete implementation
var result = new IronTesseract().Read("invoice.png");
Console.WriteLine(result.Text);
// IronOCR: complete implementation
var result = new IronTesseract().Read("invoice.png");
Console.WriteLine(result.Text);
IronTesseractクラスは、ファイルの読み取り、前処理、エンジンの実行、および結果の構築を処理します。 HTTPクライアントがありません。 Base64エンコードは使用していません。 JSON解析は行いません。 APIキーがありません。 レート制限なし。 画像からテキストを読み込むチュートリアルでは、ファイルパス、バイト配列、ストリーム、ビットマップなど、入力形式のさまざまなバリエーションを取り上げていますが、それらはすべて同じ1回の呼び出しパターンに従います。
ソースファイルから得られるコードの複雑度に関する数値は、仮説的なものではありません。
| シナリオ | OCR.スペースライン | IronOCRライン |
|---|---|---|
| 基本的な抽出 | 50歳以上 | 3 |
| PDF処理 | 60歳以上 | 12 |
| バッチ処理 | 80歳以上 | 15 |
| エラー処理 | 70歳以上 | 15 |
| 完全なクライアントインフラストラクチャ | 200以上 | 0(NuGetが提供) |
バッチ処理とレート制限
レート制限の問題は、OCR.spaceの無料プランが実際の運用環境下で最も顕著に破綻する点である。
OCR.space アプローチ
OCR.spaceの下でのバッチ処理では、毎分60リクエストの制約内に収まるようにSemaphoreSlimを構築および管理し、セマフォロジックが不正確な場合や共有インフラストラクチャがIPアドレスを共有しているときに発生するHTTP 429応答用の指数バックオフリトライロジックが必要です:
// OCR.space batch: 80歳以上 lines of rate-limit plumbing before actual work
public class OcrSpaceBatchProcessing
{
private readonly HttpClient _httpClient;
private readonly string _apiKey;
private readonly SemaphoreSlim _rateLimiter;
public OcrSpaceBatchProcessing(string apiKey)
{
_apiKey = apiKey;
_httpClient = new HttpClient();
_rateLimiter = new SemaphoreSlim(60, 60); // Free tier: 60/minute
}
public async Task<Dictionary<string, string>> ProcessBatch(string[] imagePaths)
{
var results = new Dictionary<string, string>();
foreach (var imagePath in imagePaths)
{
await _rateLimiter.WaitAsync();
try
{
string text = await ProcessWithRetry(imagePath);
results[imagePath] = text;
}
finally
{
_ = Task.Run(async () =>
{
await Task.Delay(1000); // 1 second spacing
_rateLimiter.Release();
});
}
}
return results;
}
private async Task<string> ProcessWithRetry(string imagePath, int maxRetries = 3)
{
for (int attempt = 0; attempt < maxRetries; attempt++)
{
try
{
byte[] imageBytes = File.ReadAllBytes(imagePath);
string base64 = Convert.ToBase64String(imageBytes);
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("apikey", _apiKey),
new KeyValuePair<string, string>("base64Image", $"data:image/png;base64,{base64}"),
new KeyValuePair<string, string>("language", "eng")
});
var response = await _httpClient.PostAsync(
"https://api.ocr.space/parse/image", content);
if (response.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
{
// Rate limited — exponential backoff
await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)));
continue;
}
response.EnsureSuccessStatusCode();
string json = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(json);
return doc.RootElement
.GetProperty("ParsedResults")[0]
.GetProperty("ParsedText")
.GetString() ?? string.Empty;
}
catch (HttpRequestException) when (attempt < maxRetries - 1)
{
await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)));
}
}
throw new Exception($"Failed to process {imagePath} after {maxRetries} attempts");
}
}
// OCR.space batch: 80歳以上 lines of rate-limit plumbing before actual work
public class OcrSpaceBatchProcessing
{
private readonly HttpClient _httpClient;
private readonly string _apiKey;
private readonly SemaphoreSlim _rateLimiter;
public OcrSpaceBatchProcessing(string apiKey)
{
_apiKey = apiKey;
_httpClient = new HttpClient();
_rateLimiter = new SemaphoreSlim(60, 60); // Free tier: 60/minute
}
public async Task<Dictionary<string, string>> ProcessBatch(string[] imagePaths)
{
var results = new Dictionary<string, string>();
foreach (var imagePath in imagePaths)
{
await _rateLimiter.WaitAsync();
try
{
string text = await ProcessWithRetry(imagePath);
results[imagePath] = text;
}
finally
{
_ = Task.Run(async () =>
{
await Task.Delay(1000); // 1 second spacing
_rateLimiter.Release();
});
}
}
return results;
}
private async Task<string> ProcessWithRetry(string imagePath, int maxRetries = 3)
{
for (int attempt = 0; attempt < maxRetries; attempt++)
{
try
{
byte[] imageBytes = File.ReadAllBytes(imagePath);
string base64 = Convert.ToBase64String(imageBytes);
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("apikey", _apiKey),
new KeyValuePair<string, string>("base64Image", $"data:image/png;base64,{base64}"),
new KeyValuePair<string, string>("language", "eng")
});
var response = await _httpClient.PostAsync(
"https://api.ocr.space/parse/image", content);
if (response.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
{
// Rate limited — exponential backoff
await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)));
continue;
}
response.EnsureSuccessStatusCode();
string json = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(json);
return doc.RootElement
.GetProperty("ParsedResults")[0]
.GetProperty("ParsedText")
.GetString() ?? string.Empty;
}
catch (HttpRequestException) when (attempt < maxRetries - 1)
{
await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)));
}
}
throw new Exception($"Failed to process {imagePath} after {maxRetries} attempts");
}
}
Imports System
Imports System.Collections.Generic
Imports System.IO
Imports System.Net.Http
Imports System.Text.Json
Imports System.Threading
Imports System.Threading.Tasks
Public Class OcrSpaceBatchProcessing
Private ReadOnly _httpClient As HttpClient
Private ReadOnly _apiKey As String
Private ReadOnly _rateLimiter As SemaphoreSlim
Public Sub New(apiKey As String)
_apiKey = apiKey
_httpClient = New HttpClient()
_rateLimiter = New SemaphoreSlim(60, 60) ' Free tier: 60/minute
End Sub
Public Async Function ProcessBatch(imagePaths As String()) As Task(Of Dictionary(Of String, String))
Dim results As New Dictionary(Of String, String)()
For Each imagePath In imagePaths
Await _rateLimiter.WaitAsync()
Try
Dim text As String = Await ProcessWithRetry(imagePath)
results(imagePath) = text
Finally
_ = Task.Run(Async Function()
Await Task.Delay(1000) ' 1 second spacing
_rateLimiter.Release()
End Function)
End Try
Next
Return results
End Function
Private Async Function ProcessWithRetry(imagePath As String, Optional maxRetries As Integer = 3) As Task(Of String)
For attempt As Integer = 0 To maxRetries - 1
Try
Dim imageBytes As Byte() = File.ReadAllBytes(imagePath)
Dim base64 As String = Convert.ToBase64String(imageBytes)
Dim content As New FormUrlEncodedContent(New KeyValuePair(Of String, String)() {
New KeyValuePair(Of String, String)("apikey", _apiKey),
New KeyValuePair(Of String, String)("base64Image", $"data:image/png;base64,{base64}"),
New KeyValuePair(Of String, String)("language", "eng")
})
Dim response As HttpResponseMessage = Await _httpClient.PostAsync("https://api.ocr.space/parse/image", content)
If response.StatusCode = System.Net.HttpStatusCode.TooManyRequests Then
' Rate limited — exponential backoff
Await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)))
Continue For
End If
response.EnsureSuccessStatusCode()
Dim json As String = Await response.Content.ReadAsStringAsync()
Using doc As JsonDocument = JsonDocument.Parse(json)
Return doc.RootElement _
.GetProperty("ParsedResults")(0) _
.GetProperty("ParsedText") _
.GetString() OrElse String.Empty
End Using
Catch ex As HttpRequestException When attempt < maxRetries - 1
Await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)))
End Try
Next
Throw New Exception($"Failed to process {imagePath} after {maxRetries} attempts")
End Function
End Class
無料プランの1日あたりのリクエスト数上限が500件であることも、この制限をさらに強めている。 1日に600件の文書を処理するアプリケーションは、処理の途中で失敗し、翌日まで自動的に処理が引き継がれません。
IronOCRのアプローチ
IronOCRにはレート制限がありません。 バッチ処理はループです。
// IronOCR batch: 8 lines, no rate limits, no retries needed
public Dictionary<string, string> ProcessBatch(string[] imagePaths)
{
var results = new Dictionary<string, string>();
var ocr = new IronTesseract();
foreach (var imagePath in imagePaths)
{
// なし rate limits, no retries, no cloud dependency
var result = ocr.Read(imagePath);
results[imagePath] = result.Text;
}
return results;
}
// IronOCR batch: 8 lines, no rate limits, no retries needed
public Dictionary<string, string> ProcessBatch(string[] imagePaths)
{
var results = new Dictionary<string, string>();
var ocr = new IronTesseract();
foreach (var imagePath in imagePaths)
{
// なし rate limits, no retries, no cloud dependency
var result = ocr.Read(imagePath);
results[imagePath] = result.Text;
}
return results;
}
Imports System.Collections.Generic
' IronOCR batch: 8 lines, no rate limits, no retries needed
Public Function ProcessBatch(imagePaths As String()) As Dictionary(Of String, String)
Dim results As New Dictionary(Of String, String)()
Dim ocr As New IronTesseract()
For Each imagePath In imagePaths
' なし rate limits, no retries, no cloud dependency
Dim result = ocr.Read(imagePath)
results(imagePath) = result.Text
Next
Return results
End Function
バッチ全体でIronTesseractインスタンスを再利用することで、エンジンの初期化オーバーヘッドを繰り返すことを避けられます。 処理速度は、リモートAPIのスロットリングポリシーではなく、ローカルCPUとディスクI/Oによって制限されます。 マルチスレッドの例では、スループットが重要な場合に、コア間で並列処理を行う方法を示しています。
PDF処理
PDFサポートは、2つ目の主要な構造的欠陥を示している。
OCR.space アプローチ
OCR.spaceは有料プランではPDFファイルのアップロードを受け付けており、無料プランでも一部対応していますが、無料アカウントのファイルサイズ上限が5MBであるため、ほとんどの複数ページ文書はアップロードできません。 無料プランの検索可能なPDF出力には透かしが埋め込まれます。 実装には、ページごとの結果抽出と、リクエストごとに明示的なファイルサイズチェックが必要です。
// OCR.space PDF: size check required, watermarks on free tier
public async Task<List<string>> ExtractTextFromPdf(string pdfPath)
{
var pageTexts = new List<string>();
// Reject before wasting a request quota entry
var fileInfo = new FileInfo(pdfPath);
if (fileInfo.Length > 5 * 1024 * 1024)
throw new InvalidOperationException("File exceeds 5MB free tier limit. Upgrade or split PDF.");
byte[] pdfBytes = File.ReadAllBytes(pdfPath);
string base64 = Convert.ToBase64String(pdfBytes);
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("apikey", _apiKey),
new KeyValuePair<string, string>("base64Image", $"data:application/pdf;base64,{base64}"),
new KeyValuePair<string, string>("language", "eng"),
new KeyValuePair<string, string>("filetype", "PDF"),
new KeyValuePair<string, string>("isCreateSearchablePdf", "false") // Watermarked on free tier
});
var response = await _httpClient.PostAsync("https://api.ocr.space/parse/image", content);
if (!response.IsSuccessStatusCode)
throw new Exception($"API error: {response.StatusCode}");
string json = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(json);
if (doc.RootElement.TryGetProperty("ParsedResults", out var results))
{
foreach (var result in results.EnumerateArray())
{
if (result.TryGetProperty("ParsedText", out var text))
pageTexts.Add(text.GetString() ?? string.Empty);
}
}
return pageTexts;
}
// OCR.space PDF: size check required, watermarks on free tier
public async Task<List<string>> ExtractTextFromPdf(string pdfPath)
{
var pageTexts = new List<string>();
// Reject before wasting a request quota entry
var fileInfo = new FileInfo(pdfPath);
if (fileInfo.Length > 5 * 1024 * 1024)
throw new InvalidOperationException("File exceeds 5MB free tier limit. Upgrade or split PDF.");
byte[] pdfBytes = File.ReadAllBytes(pdfPath);
string base64 = Convert.ToBase64String(pdfBytes);
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("apikey", _apiKey),
new KeyValuePair<string, string>("base64Image", $"data:application/pdf;base64,{base64}"),
new KeyValuePair<string, string>("language", "eng"),
new KeyValuePair<string, string>("filetype", "PDF"),
new KeyValuePair<string, string>("isCreateSearchablePdf", "false") // Watermarked on free tier
});
var response = await _httpClient.PostAsync("https://api.ocr.space/parse/image", content);
if (!response.IsSuccessStatusCode)
throw new Exception($"API error: {response.StatusCode}");
string json = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(json);
if (doc.RootElement.TryGetProperty("ParsedResults", out var results))
{
foreach (var result in results.EnumerateArray())
{
if (result.TryGetProperty("ParsedText", out var text))
pageTexts.Add(text.GetString() ?? string.Empty);
}
}
return pageTexts;
}
Imports System.IO
Imports System.Net.Http
Imports System.Text.Json
Imports System.Threading.Tasks
Public Class OCRSpaceClient
Private ReadOnly _apiKey As String
Private ReadOnly _httpClient As HttpClient
Public Sub New(apiKey As String, httpClient As HttpClient)
_apiKey = apiKey
_httpClient = httpClient
End Sub
Public Async Function ExtractTextFromPdf(pdfPath As String) As Task(Of List(Of String))
Dim pageTexts As New List(Of String)()
' Reject before wasting a request quota entry
Dim fileInfo As New FileInfo(pdfPath)
If fileInfo.Length > 5 * 1024 * 1024 Then
Throw New InvalidOperationException("File exceeds 5MB free tier limit. Upgrade or split PDF.")
End If
Dim pdfBytes As Byte() = File.ReadAllBytes(pdfPath)
Dim base64 As String = Convert.ToBase64String(pdfBytes)
Dim content As New FormUrlEncodedContent(New KeyValuePair(Of String, String)() {
New KeyValuePair(Of String, String)("apikey", _apiKey),
New KeyValuePair(Of String, String)("base64Image", $"data:application/pdf;base64,{base64}"),
New KeyValuePair(Of String, String)("language", "eng"),
New KeyValuePair(Of String, String)("filetype", "PDF"),
New KeyValuePair(Of String, String)("isCreateSearchablePdf", "false") ' Watermarked on free tier
})
Dim response As HttpResponseMessage = Await _httpClient.PostAsync("https://api.ocr.space/parse/image", content)
If Not response.IsSuccessStatusCode Then
Throw New Exception($"API error: {response.StatusCode}")
End If
Dim json As String = Await response.Content.ReadAsStringAsync()
Using doc As JsonDocument = JsonDocument.Parse(json)
If doc.RootElement.TryGetProperty("ParsedResults", results) Then
For Each result In results.EnumerateArray()
If result.TryGetProperty("ParsedText", text) Then
pageTexts.Add(If(text.GetString(), String.Empty))
End If
Next
End If
End Using
Return pageTexts
End Function
End Class
IronOCRのアプローチ
IronOCRはPDFをネイティブに読み取ります。 制限となるのは利用可能なメモリ容量であり、任意のファイルサイズしきい値ではない。 検索可能なPDF出力は、どのライセンスレベルでも透かしのないクリーンなファイルを生成します。
// IronOCR PDF: native support, no size limit, no watermarks
public List<string> ExtractTextFromPdf(string pdfPath)
{
var pageTexts = new List<string>();
using var input = new OcrInput();
input.LoadPdf(pdfPath); // なし 5MB ceiling
var result = new IronTesseract().Read(input);
foreach (var page in result.Pages)
pageTexts.Add(page.Text);
return pageTexts;
}
// Generate searchable PDF — no watermarks
public void CreateSearchablePdf(string inputPath, string outputPath)
{
var result = new IronTesseract().Read(inputPath);
result.SaveAsSearchablePdf(outputPath);
}
// IronOCR PDF: native support, no size limit, no watermarks
public List<string> ExtractTextFromPdf(string pdfPath)
{
var pageTexts = new List<string>();
using var input = new OcrInput();
input.LoadPdf(pdfPath); // なし 5MB ceiling
var result = new IronTesseract().Read(input);
foreach (var page in result.Pages)
pageTexts.Add(page.Text);
return pageTexts;
}
// Generate searchable PDF — no watermarks
public void CreateSearchablePdf(string inputPath, string outputPath)
{
var result = new IronTesseract().Read(inputPath);
result.SaveAsSearchablePdf(outputPath);
}
Imports System.Collections.Generic
' IronOCR PDF: native support, no size limit, no watermarks
Public Function ExtractTextFromPdf(pdfPath As String) As List(Of String)
Dim pageTexts As New List(Of String)()
Using input As New OcrInput()
input.LoadPdf(pdfPath) ' なし 5MB ceiling
Dim result = New IronTesseract().Read(input)
For Each page In result.Pages
pageTexts.Add(page.Text)
Next
End Using
Return pageTexts
End Function
' Generate searchable PDF — no watermarks
Public Sub CreateSearchablePdf(inputPath As String, outputPath As String)
Dim result = New IronTesseract().Read(inputPath)
result.SaveAsSearchablePdf(outputPath)
End Sub
パスワード保護されたPDFは単一のパラメータ: input.LoadPdf("encrypted.pdf", Password: "secret")。 PDF OCRの操作ガイドでは、ページ範囲の選択方法とパスワードの処理方法について詳しく説明しています。 検索可能なPDF生成パターンについては、検索可能なPDFの作成方法を参照してください。
エラーハンドリング
OCR.spaceはHTTPステータスコードとJSONのIsErroredOnProcessingフラグの2つの別々のチャンネルを通じてエラーを提供します。 本番コードは両方をチェックし、FileParseExitCode値を確認する必要があります。 これらはすべて型付けされていません — すべてJsonDocumentからの文字列解析です:
// OCR.space: two error layers, all string-based
public async Task<string> SafeExtract(HttpClient client, string apiKey, string imagePath)
{
try
{
byte[] imageBytes = File.ReadAllBytes(imagePath);
string base64 = Convert.ToBase64String(imageBytes);
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("apikey", apiKey),
new KeyValuePair<string, string>("base64Image", $"data:image/png;base64,{base64}")
});
var response = await client.PostAsync("https://api.ocr.space/parse/image", content);
if (!response.IsSuccessStatusCode)
{
switch (response.StatusCode)
{
case System.Net.HttpStatusCode.Unauthorized:
throw new Exception("Invalid API key");
case System.Net.HttpStatusCode.TooManyRequests:
throw new Exception("Rate limit exceeded — wait and retry");
case System.Net.HttpStatusCode.PaymentRequired:
throw new Exception("Quota exceeded — upgrade plan");
default:
throw new Exception($"API error: {response.StatusCode}");
}
}
// Second error layer: JSON-level failures
string json = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(json);
if (doc.RootElement.TryGetProperty("IsErroredOnProcessing", out var isError)
&& isError.GetBoolean())
{
var messages = new List<string>();
if (doc.RootElement.TryGetProperty("ErrorMessage", out var errors))
{
foreach (var e in errors.EnumerateArray())
messages.Add(e.GetString() ?? "Unknown error");
}
throw new Exception(string.Join("; ", messages));
}
// Third layer: per-page exit codes
if (doc.RootElement.TryGetProperty("ParsedResults", out var results))
{
foreach (var result in results.EnumerateArray())
{
if (result.TryGetProperty("FileParseExitCode", out var exitCode)
&& exitCode.GetInt32() != 1)
throw new Exception($"Page parse failed: exit code {exitCode.GetInt32()}");
}
}
return doc.RootElement
.GetProperty("ParsedResults")[0]
.GetProperty("ParsedText")
.GetString() ?? string.Empty;
}
catch (JsonException ex)
{
throw new Exception($"Invalid API response: {ex.Message}");
}
catch (HttpRequestException ex)
{
throw new Exception($"Network error: {ex.Message}");
}
}
// OCR.space: two error layers, all string-based
public async Task<string> SafeExtract(HttpClient client, string apiKey, string imagePath)
{
try
{
byte[] imageBytes = File.ReadAllBytes(imagePath);
string base64 = Convert.ToBase64String(imageBytes);
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("apikey", apiKey),
new KeyValuePair<string, string>("base64Image", $"data:image/png;base64,{base64}")
});
var response = await client.PostAsync("https://api.ocr.space/parse/image", content);
if (!response.IsSuccessStatusCode)
{
switch (response.StatusCode)
{
case System.Net.HttpStatusCode.Unauthorized:
throw new Exception("Invalid API key");
case System.Net.HttpStatusCode.TooManyRequests:
throw new Exception("Rate limit exceeded — wait and retry");
case System.Net.HttpStatusCode.PaymentRequired:
throw new Exception("Quota exceeded — upgrade plan");
default:
throw new Exception($"API error: {response.StatusCode}");
}
}
// Second error layer: JSON-level failures
string json = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(json);
if (doc.RootElement.TryGetProperty("IsErroredOnProcessing", out var isError)
&& isError.GetBoolean())
{
var messages = new List<string>();
if (doc.RootElement.TryGetProperty("ErrorMessage", out var errors))
{
foreach (var e in errors.EnumerateArray())
messages.Add(e.GetString() ?? "Unknown error");
}
throw new Exception(string.Join("; ", messages));
}
// Third layer: per-page exit codes
if (doc.RootElement.TryGetProperty("ParsedResults", out var results))
{
foreach (var result in results.EnumerateArray())
{
if (result.TryGetProperty("FileParseExitCode", out var exitCode)
&& exitCode.GetInt32() != 1)
throw new Exception($"Page parse failed: exit code {exitCode.GetInt32()}");
}
}
return doc.RootElement
.GetProperty("ParsedResults")[0]
.GetProperty("ParsedText")
.GetString() ?? string.Empty;
}
catch (JsonException ex)
{
throw new Exception($"Invalid API response: {ex.Message}");
}
catch (HttpRequestException ex)
{
throw new Exception($"Network error: {ex.Message}");
}
}
Imports System
Imports System.Collections.Generic
Imports System.IO
Imports System.Net.Http
Imports System.Text.Json
Imports System.Threading.Tasks
Public Class OCRSpace
Public Async Function SafeExtract(client As HttpClient, apiKey As String, imagePath As String) As Task(Of String)
Try
Dim imageBytes As Byte() = File.ReadAllBytes(imagePath)
Dim base64 As String = Convert.ToBase64String(imageBytes)
Dim content As New FormUrlEncodedContent(New KeyValuePair(Of String, String)() {
New KeyValuePair(Of String, String)("apikey", apiKey),
New KeyValuePair(Of String, String)("base64Image", $"data:image/png;base64,{base64}")
})
Dim response As HttpResponseMessage = Await client.PostAsync("https://api.ocr.space/parse/image", content)
If Not response.IsSuccessStatusCode Then
Select Case response.StatusCode
Case System.Net.HttpStatusCode.Unauthorized
Throw New Exception("Invalid API key")
Case System.Net.HttpStatusCode.TooManyRequests
Throw New Exception("Rate limit exceeded — wait and retry")
Case System.Net.HttpStatusCode.PaymentRequired
Throw New Exception("Quota exceeded — upgrade plan")
Case Else
Throw New Exception($"API error: {response.StatusCode}")
End Select
End If
' Second error layer: JSON-level failures
Dim json As String = Await response.Content.ReadAsStringAsync()
Using doc As JsonDocument = JsonDocument.Parse(json)
If doc.RootElement.TryGetProperty("IsErroredOnProcessing", ByRef isError) AndAlso isError.GetBoolean() Then
Dim messages As New List(Of String)()
If doc.RootElement.TryGetProperty("ErrorMessage", ByRef errors) Then
For Each e In errors.EnumerateArray()
messages.Add(e.GetString() ?? "Unknown error")
Next
End If
Throw New Exception(String.Join("; ", messages))
End If
' Third layer: per-page exit codes
If doc.RootElement.TryGetProperty("ParsedResults", ByRef results) Then
For Each result In results.EnumerateArray()
If result.TryGetProperty("FileParseExitCode", ByRef exitCode) AndAlso exitCode.GetInt32() <> 1 Then
Throw New Exception($"Page parse failed: exit code {exitCode.GetInt32()}")
End If
Next
End If
Return doc.RootElement _
.GetProperty("ParsedResults")(0) _
.GetProperty("ParsedText") _
.GetString() ?? String.Empty
End Using
Catch ex As JsonException
Throw New Exception($"Invalid API response: {ex.Message}")
Catch ex As HttpRequestException
Throw New Exception($"Network error: {ex.Message}")
End Try
End Function
End Class
IronOCRは、明確なメッセージとともに標準的な.NET例外を発生させます。 JSON解析なし、HTTPステータスコード切り替えなし、階層型エラー抽出なし:
// IronOCR: standard .NET exceptions
public string SafeExtract(string imagePath)
{
try
{
var result = new IronTesseract().Read(imagePath);
return result.Text;
}
catch (FileNotFoundException)
{
throw; // File not found — straightforward
}
catch (Exception ex) when (ex.Message.Contains("corrupt"))
{
throw new Exception($"Image file is corrupt: {imagePath}");
}
// なし JSON errors. なし HTTP errors. なし rate limit errors.
}
// IronOCR: standard .NET exceptions
public string SafeExtract(string imagePath)
{
try
{
var result = new IronTesseract().Read(imagePath);
return result.Text;
}
catch (FileNotFoundException)
{
throw; // File not found — straightforward
}
catch (Exception ex) when (ex.Message.Contains("corrupt"))
{
throw new Exception($"Image file is corrupt: {imagePath}");
}
// なし JSON errors. なし HTTP errors. なし rate limit errors.
}
Imports IronOcr
Public Function SafeExtract(imagePath As String) As String
Try
Dim result = New IronTesseract().Read(imagePath)
Return result.Text
Catch ex As FileNotFoundException
Throw ' File not found — straightforward
Catch ex As Exception When ex.Message.Contains("corrupt")
Throw New Exception($"Image file is corrupt: {imagePath}")
End Try
' なし JSON errors. なし HTTP errors. なし rate limit errors.
End Function
IronTesseract APIリファレンス と OcrResult APIリファレンス は、抽出品質を評価するためのConfidenceプロパティを含む型付き結果構造を文書化しています。
APIマッピングリファレンス
| OCR.space コンセプト | IronOCR相当値 |
|---|---|
POST https://api.ocr.space/parse/image |
new IronTesseract().Read(path) |
FormUrlEncodedContent / MultipartFormDataContent |
OcrInput |
base64Imageパラメータ |
input.LoadImage(bytes) |
languageパラメータ |
ocr.Language = OcrLanguage.English |
OCREngineパラメータ |
エンジンは内部で管理されています |
isOverlayRequiredパラメータ |
result.Words / result.Lines (常に利用可能) |
isCreateSearchablePdfパラメータ |
result.SaveAsSearchablePdf(outputPath) |
filetype=PDFパラメータ |
input.LoadPdf(path) |
ParsedResults[0].ParsedText |
result.Text |
ParsedResults[n] (ページごとに) |
result.Pages[n].Text |
IsErroredOnProcessing JSONフラグ |
標準 for .NET例外 |
FileParseExitCode ページごとのフラグ |
標準 for .NET例外 |
ProcessingTimeInMilliseconds |
暗黙的 - 追加の解析は不要 |
| HTTP 429 / レート制限 | 該当なし - レート制限なし |
カスタムOcrResult POCO (ユーザ定義) |
IronOcr.OcrResult (NuGetが提供) |
カスタムOcrSpaceException (ユーザ定義) |
標準 for .NET例外タイプ |
SemaphoreSlimレートリミッター (ユーザーが構築) |
不要 |
| すべてのリクエストにAPIキーを含める | IronOcr.License.LicenseKey (起動時一度だけ) |
チームがOCR.spaceからIronOCRへの移行を検討する際
負荷テスト中に無料ティアに到達する
OCR.spaceの1日あたりIPアドレスごとのリクエスト数500件という上限は、緩やかな勧告ではなく、厳格な制限です。 チームは、複数の開発者がオフィスのIPアドレスを共有している負荷テストやステージング環境へのデプロイ中に、この問題に気づく。 OCR.spaceに対して統合テストを実行する共有CI/CDパイプラインは、営業日終了前に1日の割り当て量を使い果たしてしまうでしょう。 その時点でアプリケーションが失敗するのは、コードに誤りがあるからではなく、サードパーティ製のカウンターが任意のしきい値に達したためです。 ローカル処理に移行すれば、この種の障害は完全に解消されます。処理はプロセス内で実行されるため、使い果たすクォータは存在しないからです。
コンプライアンスおよびデータ分類レビュー
文書を個人情報、医療情報、または財務的に機密性の高い情報として分類するセキュリティレビューは、OCR.spaceにとって二者択一の問題を生み出します。つまり、このサービスにはオンプレミス展開のパスがないのです。 HIPAAのシナリオに対応するビジネスアソシエイト契約(BAA)に相当する文書は存在せず、GDPRの下でEUのデータ転送を明確に規定するデータ処理契約の構造も存在せず、契約上の管理措置が講じられている場合でも文書の送信を防止する技術的なメカニズムも存在しない。 クラウド経由で送信される文書処理に関してコンプライアンス違反の指摘を受けたチームは、ローカルなソリューションを必要としている。 IronOCRはこの要件を設計段階から満たしています。つまり、ドキュメントはアプリケーションサーバーから決して外部に送信されません。 ライセンスページには永続ライセンスの構造が記載されており、クラウドベンダーによるレビューを対象範囲から除外することで、コンプライアンス文書の作成も簡素化されます。
統合コードの保守負担
OCR.spaceの統合機能は、その機能要件に比例して技術的負債を蓄積する。 初期のHTTPクライアントは管理しやすい。 そしてチームは再試行ロジックを追加する。 複数のサービスが同じアドレスを共有しているため、IPアドレスごとのレート制限追跡機能が追加されます。 次に、ファイルサイズの事前検証ステップを追加します。その後、エンジン1とエンジン2のレスポンス間でJSONエラー構造が異なることを発見し、分岐を追加します。 6か月後、OCR.spaceとの統合は400行ものインフラストラクチャコードとなり、新しいチームメンバーはOCR関連の作業に携わる前に、そのコードを理解しなければならない。 そのチームがメンテナンスコストを一度きりの$999 IronOCRライセンスと比較すると、算術は簡単です。
構造化された出力要件
OCR.spaceはプレーンテキストのみを返して、それ以外の情報は返しません。 回答には、単語座標、行境界、段落分割、単語ごとの信頼度スコアは含まれていません。 請求書から特定のフィールド(既知の領域にあるベンダー名、右下隅にある合計金額など)を抽出する必要があるアプリケーションは、OCR.spaceの出力を基に構築するための構造化された基盤を持っていません。 IronOCRは、ページ、段落、行、個々の単語といったあらゆる粒度レベルで、座標と信頼度値を含む完全な文書階層構造を公開します。 領域ベースの処理により、ページ全体の出力を後処理することなく、特定の文書領域を対象とすることができます。 読み取り結果の操作方法と領域ベースのOCRガイドでは、これらのパターンについて詳しく説明しています。
無料枠を超えたボリューム増加
無料プランは月間25,000件のリクエストまで対応しており、低負荷のシナリオでは十分機能します。 それ以外は有料の階層が適用されます — OCR.spaceに現在の価格をお問い合わせください。 それらのコストは時間と共に増加し、いつの日か、リクエストごとの料金が永続ライセンス$999と比較されるときまで続きます。 月間文書処理量が25,000件を超える見込みのチームにとって、現地処理のコスト削減は明確なメリットとなる。
一般的な移行の考慮事項
HTTPクライアントをメソッド呼び出しに置き換える
OCR.spaceからIronOCRへの移行は、どちらも文書からテキストを返すため、アーキテクチャ的には単純です。 HTTPクライアント、JSONパーサー、レートリミッター、およびカスタム例外タイプが消えます。 それらに代わるのは、単一のNuGetパッケージとメソッド呼び出しです。 サービス境界における変更前/変更後:
// Before: OCR.space service (simplified — actual implementation is 200以上 lines)
public class OcrSpaceService : IDisposable
{
private readonly HttpClient _client = new();
private readonly string _apiKey;
public OcrSpaceService(string apiKey) => _apiKey = apiKey;
public async Task<string> ProcessDocument(string path)
{
byte[] bytes = File.ReadAllBytes(path);
string base64 = Convert.ToBase64String(bytes);
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("apikey", _apiKey),
new KeyValuePair<string, string>("base64Image", $"data:image/png;base64,{base64}")
});
var response = await _client.PostAsync("https://api.ocr.space/parse/image", content);
response.EnsureSuccessStatusCode();
string json = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(json);
return doc.RootElement.GetProperty("ParsedResults")[0].GetProperty("ParsedText").GetString()!;
}
public void Dispose() => _client.Dispose();
}
// After: IronOCR service — no HttpClient, no JSON, no API key
public class IronOcrService
{
private readonly IronTesseract _ocr = new();
public string ProcessDocument(string path)
{
return _ocr.Read(path).Text;
}
// なし Dispose — no external connections to close
}
// Before: OCR.space service (simplified — actual implementation is 200以上 lines)
public class OcrSpaceService : IDisposable
{
private readonly HttpClient _client = new();
private readonly string _apiKey;
public OcrSpaceService(string apiKey) => _apiKey = apiKey;
public async Task<string> ProcessDocument(string path)
{
byte[] bytes = File.ReadAllBytes(path);
string base64 = Convert.ToBase64String(bytes);
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("apikey", _apiKey),
new KeyValuePair<string, string>("base64Image", $"data:image/png;base64,{base64}")
});
var response = await _client.PostAsync("https://api.ocr.space/parse/image", content);
response.EnsureSuccessStatusCode();
string json = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(json);
return doc.RootElement.GetProperty("ParsedResults")[0].GetProperty("ParsedText").GetString()!;
}
public void Dispose() => _client.Dispose();
}
// After: IronOCR service — no HttpClient, no JSON, no API key
public class IronOcrService
{
private readonly IronTesseract _ocr = new();
public string ProcessDocument(string path)
{
return _ocr.Read(path).Text;
}
// なし Dispose — no external connections to close
}
Imports System
Imports System.IO
Imports System.Net.Http
Imports System.Threading.Tasks
Imports System.Collections.Generic
Imports System.Text.Json
Public Class OcrSpaceService
Implements IDisposable
Private ReadOnly _client As New HttpClient()
Private ReadOnly _apiKey As String
Public Sub New(apiKey As String)
_apiKey = apiKey
End Sub
Public Async Function ProcessDocument(path As String) As Task(Of String)
Dim bytes As Byte() = File.ReadAllBytes(path)
Dim base64 As String = Convert.ToBase64String(bytes)
Dim content As New FormUrlEncodedContent(New KeyValuePair(Of String, String)() {
New KeyValuePair(Of String, String)("apikey", _apiKey),
New KeyValuePair(Of String, String)("base64Image", $"data:image/png;base64,{base64}")
})
Dim response As HttpResponseMessage = Await _client.PostAsync("https://api.ocr.space/parse/image", content)
response.EnsureSuccessStatusCode()
Dim json As String = Await response.Content.ReadAsStringAsync()
Using doc As JsonDocument = JsonDocument.Parse(json)
Return doc.RootElement.GetProperty("ParsedResults")(0).GetProperty("ParsedText").GetString()
End Using
End Function
Public Sub Dispose() Implements IDisposable.Dispose
_client.Dispose()
End Sub
End Class
Public Class IronOcrService
Private ReadOnly _ocr As New IronTesseract()
Public Function ProcessDocument(path As String) As String
Return _ocr.Read(path).Text
End Function
End Class
HttpClientがないからです。
APIキーの削除
OCR.spaceでは、すべてのHTTPリクエストにAPIキーが必要です。そのキーは安全に保管し、外部に公開する場合はローテーションを行い、ソースコード管理から除外する必要があります。 IronOCRは、アプリケーション起動時に一度だけ設定されるライセンスキーを使用します。このライセンスキーは通常、環境変数または設定システムから取得されます。
// At application startup — once, not per request
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE");
// At application startup — once, not per request
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE");
' At application startup — once, not per request
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE")
移行後は、APIキーのローテーションプロセス、シークレット管理の設定、およびリクエストごとのキー挿入ロジックはすべて不要になります。
移行後の前処理
OCR.spaceは結果を返す前にサーバー側で処理を行いますが、開発者はその処理内容を制御することはできません。 IronOCRは、明示的な前処理方法を公開しています。 文書の品質が懸念される場合(傾いたスキャン画像、コントラストの低いコピー、ノイズの多いファックス出力など)、前処理パイプラインは3~5つのメソッド呼び出しで構成されます。
using var input = new OcrInput();
input.LoadImage("low-quality-scan.jpg");
input.Deskew();
input.DeNoise();
input.Contrast();
input.Binarize();
input.EnhanceResolution(300);
var result = new IronTesseract().Read(input);
using var input = new OcrInput();
input.LoadImage("low-quality-scan.jpg");
input.Deskew();
input.DeNoise();
input.Contrast();
input.Binarize();
input.EnhanceResolution(300);
var result = new IronTesseract().Read(input);
Imports IronOcr
Using input As New OcrInput()
input.LoadImage("low-quality-scan.jpg")
input.Deskew()
input.DeNoise()
input.Contrast()
input.Binarize()
input.EnhanceResolution(300)
Dim result = New IronTesseract().Read(input)
End Using
画像品質補正ガイドと画像色補正ガイドには、利用可能なすべてのフィルターが記載されており、補正前後の精度差を示す例が添えられています。
非同期処理と同期処理
OCR.space の呼び出しは、HTTP の往復通信を伴うため、本質的に非同期です。 IronOCRの呼び出しはデフォルトでは同期的に行われるため、Web以外の環境ではコードが簡素化されます。 非同期実行を要求するASP.NETおよびサーバーのシナリオの場合、IronOCRはTask.Runラッピングまたは直接非同期APIを通じて非同期操作をサポートします。 非同期OCRガイドでは、パターンについて説明しています。 以前にクラウド呼び出しを待機していたサービスメソッドからawaitを削除することで、OCR.spaceがそれを必要としているコンテキストで非同期が採用された場合のコールスタックが簡素化されます。
IronOCRの追加機能
上記のセクションでは説明されていない機能で、それぞれOCR.spaceには同等の機能がないものです。
- hOCRエクスポート:
result.SaveAsHocrFile("output.hocr")は、下流の文書処理パイプラインのための標準準拠のhOCR出力を生成します。 -進捗状況の追跡:長時間実行される複数ページにわたるバッチ処理の進捗イベントを購読することで、UIアプリケーションで進捗バーや完了予定時刻の計算が可能になります。 -テーブル抽出:構造化されたテーブルデータは結果モデルを介してアクセス可能であり、列の整列が必要な請求書明細項目の抽出などのユースケースをサポートします。 - 専門的な文書読み取り: パスポートMRZゾーン、ナンバープレート、MICR小切手行、手書き認識は、同じ
IronTesseractAPIを通じてアクセス可能な目的に合った機能です。
.NETの互換性と将来の準備
IronOCRは.NET 6、 .NET 7、 .NET 8、および.NET 9を対象としており、各LTSおよびSTSリリースを積極的にサポートしています。 このライブラリは、Windows x64、Windows x86、Linux x64、macOS、Docker、AWS Lambda、Azure App Serviceで動作します。しかも、すべて同じNuGetパッケージから入手でき、プラットフォーム固有の設定は不要です。 OCR.spaceは、すべての展開環境において発信HTTPS接続を必須としています。そのため、エアギャップネットワーク、制限的な企業プロキシ、およびサードパーティAPIへの発信トラフィックがポリシーによってブロックされているインフラストラクチャは対象外となります。 IronOCRには実行時のネットワーク要件はありません。 完全にプロセス内で実行されます。 .NET 10が2026年後半のリリースに向けて進む中、IronOCRは.NETのメジャーバージョン間で互換性を維持してきた実績があり、統合の変更なしに継続性を提供します。
結論
OCR.spaceは、週末にOCR機能のプロトタイプを作成する必要がある開発者、テキスト抽出の概念を研究している学生、あるいはコンプライアンス要件のない月間25,000件以下の小規模な個人用ツールなど、真に役立つニッチな市場を占めています。 その層にとって、無料プランは真の価値を提供する。
問題は、 .NET開発者は通常、週末にプロトタイプを作成しているわけではないということだ。彼らは請求書システム、医療記録処理システム、文書アーカイブパイプライン、そしてコンプライアンスに敏感なビジネスアプリケーションなどを構築しているのだ。 そういった状況においては、OCR.spaceにNuGetパッケージが存在しないことは、単なる不便ではなく、構造的な非互換性である。 HTTPクライアント、レートリミッター、JSONパーサー、およびリトライインフラストラクチャの構築に費やす1時間は、アプリケーションの実際の要件に費やせない1時間となる。 その費用は初期費用として発生しますが、メンテナンス費用はシステム統合のライフサイクル全体にわたって発生します。
IronOCRは、OCR.spaceのあらゆる統合において共通する、真のSDKの欠如という特定の失敗モードに対処します。 NuGetパッケージ1つ、メソッド呼び出し1つ、HTTP関連の処理なし、レート制限なし、外部サーバーへのドキュメント送信なし。 $999の初期費用は1回限りの費用です。継続的なOCR.spaceサブスクリプションコストは、特にボリュームが増加すると、これを超えることになります。 コンプライアンス要件のあるチームにとっては、コストに関係なくローカル処理が唯一の選択肢です。
最終的に、この比較は一つの疑問に集約されます。アプリケーションはOCRを外部REST依存関係として必要とするのか、それともライブラリ関数として必要とするのか? 本番環境で使用される.NETアプリケーションの場合、答えはほぼ常に後者です。
よくある質問
OCR.space APIとは何ですか?
OCR.space APIは、開発者や企業が画像や文書からテキストを抽出するために使用するOCRソリューションです。.NETアプリケーション開発のためにIronOCRとともに評価されるいくつかのOCRオプションの一つです。
IronOCRは.NET開発者向けOCR.space APIと比較してどうですか?
IronOCRはNuGetネイティブ for .NET OCRライブラリで、.CoreエンジンとしてIronTesseractを使用しています。OCR.spaceAPIと比較して、よりシンプルなデプロイメント(SDKインストーラーなし)、定額制の価格設定、COMインターオプやクラウド依存のないクリーンなC# APIを提供します。
IronOCRはOCR.space APIよりセットアップが簡単ですか?
IronOCRは単一のNuGetパッケージでインストールされます。SDKインストーラー、ライセンスファイルのコピー、COMコンポーネントの登録、ランタイムバイナリの管理は必要ありません。OCRエンジン全体がパッケージにバンドルされています。
OCR.space APIとIronOCRにはどのような精度の違いがありますか?
IronOCRは、標準的なビジネス文書、請求書、領収書、スキャンしたフォームに対して高い認識精度を達成します。高度に劣化した文書や一般的でないスクリプトの場合、精度はソースの品質によって異なります。IronOCRは低品質入力の認識を向上させる画像前処理フィルターを含んでいます。
IronOCRはPDFテキスト抽出をサポートしていますか?
IronOCRは、ネイティブPDFとスキャンしたPDFイメージの両方から、一回の呼び出しでテキストを抽出します。また、複数ページのTIFFファイル、画像、ストリームもサポートします。スキャンしたPDFの場合、OCRはページごとに適用され、ページごとの結果オブジェクトを持ちます。
OCR.spaceのAPIライセンスはIronOCRと比べてどうですか?
IronOCRは、定額制の永久ライセンスで、ページ毎やスキャン毎の課金はありません。大量のドキュメントを処理する組織は、ボリュームに関係なく同じライセンス費用を支払います。詳しくはIronOCRライセンスページをご覧ください。
IronOCR はどの言語をサポートしていますか?
IronOCRは個別のNuGet言語パックにより127言語をサポートしています。言語を追加するには'dotnet add package IronOcr.Languages.{Language}'コマンドを実行するだけです。手動でのファイル配置やパス設定は必要ありません。
.NETプロジェクトにIronOCRをインストールするにはどうすればよいですか?
NuGet経由でインストールします:パッケージマネージャーコンソールで'Install-Package IronOcr'、またはCLIで'dotnet add package IronOcr'。追加の言語パックも同様にインストールされます。ネイティブSDKインストーラーは必要ありません。
IronOCRはOCR.spaceと違い、Dockerやコンテナでのデプロイに適していますか?
IronOCRはNuGetパッケージによってDockerコンテナで動作します。ライセンスキーは環境変数で設定します。OCRエンジン自体にはライセンスファイル、SDKパス、ボリュームマウントは必要ありません。
OCR.spaceと比較して、購入前にIronOCRを試すことはできますか?
IronOCRのトライアルモードでは、ドキュメントを処理し、出力に透かしをオーバーレイしたOCR結果を返します。ライセンスを購入する前に、ご自身の文書で精度を確認することができます。
IronOCRはテキスト抽出とバーコード読み取りをサポートしていますか?
IronOCRはテキスト抽出とOCRに重点を置いています。バーコード読み取りについては、Iron SoftwareはIronBarcodeをコンパニオンライブラリとして提供しています。どちらも個別に、あるいはIron Suiteのバンドルとして提供されています。
OCR.spaceAPIからIronOCRへの移行は簡単ですか?
OCR.spaceAPIからIronOCRへの移行は通常、初期化シーケンスをIronTesseractのインスタンス生成に置き換え、COMライフサイクル管理を削除し、APIコールを更新します。ほとんどの移行はコードの複雑さを大幅に軽減します。

