Azure Computer Vision OCRから以下への移行
このガイドでは、 .NET開発者向けに、Azure Computer Vision OCRをIronOCRに置き換える手順を説明します。IronOCRは、クラウドインフラストラクチャを使用せずにローカルでドキュメントを処理するオンプレミスのOCRライブラリです。 このドキュメントでは、 NuGetパッケージと名前空間の交換、Azure の非同期ポーリング モデルを同期ローカル呼び出しに変換するなどの機械的な手順、および移行中に最も注意が必要な特定のパターン(依存性注入の配線、フォーム認識ポーリング ループ、複数ページ TIFF 処理、バッチ スループット)の処理について説明します。
Azure コンピュータービジョン OCRから移行する理由
移住を支持する根拠は抽象的なものではない。 チームは通常、本番環境でAzure Computer Visionにおいて1つ以上の具体的な運用上の問題に遭遇した後に、この決定に至ります。
エンドポイントとAPIキーの管理は終わりがありません。開発、ステージング、本番、災害復旧など、あらゆるデプロイ環境において、プロビジョニングされたAzure Cognitive Servicesリソース、エンドポイントURL、および少なくとも1つのAPIキーが必要です。 キーは回転させる必要があります。 リソースがリージョンを移動すると、エンドポイントも変更されます。 すべての環境は、cognitiveservices.azure.comに到達するためのアウトバウンドファイアウォールルールが必要です。 運用範囲は、環境やチーム内の開発者が増えるにつれて拡大する。 IronOCRは、アプリケーション起動時に一度だけ設定される単一の文字列ライセンスキーで、これらすべてを置き換えます。ライセンスキーのローテーションスケジュールも、アウトバウンドネットワークの要件もありません。
ページ単位の課金では、複数ページのドキュメントは不利になります。Azure Computer Vision では、PDF の各ページが個別のトランザクションとしてカウントされます。 20ページの契約書は、20回の通話料が発生することを意味します。 1,000件の取引につき1ドルとすると、1件あたり平均4ページの複数ページ文書を月に50,000件処理するチームは、200,000件の取引を発生させることになります。これは、無料期間終了後は月額195ドル、年間2,340ドルに相当します。 これは、IronOCR Lite ($999) に対する損益分岐点であり、4か月未満で、それ以後はページごとの追加料金がかかりません。
非同期処理の伝播はコールスタック全体に及びます。Azure Computer Vision は同期的に応答を返すことができません。クラウド I/O にはネットワーク遅延が発生するためです。 await の要件は AnalyzeAsync にあり、すべての呼び出しメソッドを非同期にすることを強制し、このパターンをサービス層からコントローラー、バックグラウンドワーカー、必要に応じてリファクタリングされる同期コードにまで波及させます。 Form Recognizer のポーリングベースの操作はこれを複合化します: WaitUntil.Completed がスレッドをブロックし、真のノンブロッキング動作には、UpdateStatusAsync ポーリングループを手動で管理する必要があります。
文書は通話のたびにネットワークから送信されます。HIPAAで保護された医療情報、ITARで規制された防衛文書、弁護士と依頼人の間の秘匿特権のある通信、またはデータ所在地の規則の対象となるあらゆる種類の文書を処理するチームにとって、クラウドへの強制的な送信は、トレードオフではなく、アーキテクチャ上の非互換性となります。 ドキュメントの内容をマイクロソフトのデータセンターに送信しないようにするAzure Computer Visionモードは存在しません。
レート制限はスループットの上限を設定します。Azure Computer Vision の S1 ティアでは、1 秒あたり 10 トランザクションが上限となっています。 1時間に3,600枚の画像を処理するバッチ処理は、まさに処理能力の上限に達します。 これを超えるとHTTP 429応答が返されるため、すべての呼び出しパスで指数バックオフによる再試行ロジックが必要になります。 IronOCRのスループットの上限はホスティングハードウェアの性能によって決まります。サービス側による制限はなく、再試行のためのインフラストラクチャも必要ありません。
画像OCRとPDF OCRは、2つの別々のサービスが必要です。 標準の画像OCRはAzure.AI.Vision.ImageAnalysisから使用します。 完全なPDF処理にはAzure.AI.FormRecognizer.DocumentAnalysisから必要です — 異なるNuGetパッケージ、異なるAzureリソース、異なるエンドポイント、および異なる結果スキーマです。 画像とPDFの両方を処理するアプリケーションはすべて、この二重の構成オーバーヘッドを抱えることになる。 IronOCRはOcrInputローダーで両方を処理します。
基本的な問題
// Azure: endpoint URL + API key + async + nested block traversal — before a single character
var client = new ImageAnalysisClient(new Uri(endpoint), new AzureKeyCredential(apiKey));
using var stream = File.OpenRead(imagePath);
var result = await client.AnalyzeAsync(BinaryData.FromStream(stream), VisualFeatures.Read);
var text = string.Join("\n", result.Value.Read.Blocks.SelectMany(b => b.Lines).Select(l => l.Text));
// IronOCR: no endpoint, no key rotation, no async, no traversal
var text = new IronTesseract().Read(imagePath).Text;
// Azure: endpoint URL + API key + async + nested block traversal — before a single character
var client = new ImageAnalysisClient(new Uri(endpoint), new AzureKeyCredential(apiKey));
using var stream = File.OpenRead(imagePath);
var result = await client.AnalyzeAsync(BinaryData.FromStream(stream), VisualFeatures.Read);
var text = string.Join("\n", result.Value.Read.Blocks.SelectMany(b => b.Lines).Select(l => l.Text));
// IronOCR: no endpoint, no key rotation, no async, no traversal
var text = new IronTesseract().Read(imagePath).Text;
Imports System
Imports System.IO
Imports Azure
Imports Azure.AI.Vision
Imports IronOcr
' Azure: endpoint URL + API key + async + nested block traversal — before a single character
Dim client As New ImageAnalysisClient(New Uri(endpoint), New AzureKeyCredential(apiKey))
Using stream As FileStream = File.OpenRead(imagePath)
Dim result = Await client.AnalyzeAsync(BinaryData.FromStream(stream), VisualFeatures.Read)
Dim text As String = String.Join(vbLf, result.Value.Read.Blocks.SelectMany(Function(b) b.Lines).Select(Function(l) l.Text))
End Using
' IronOCR: no endpoint, no key rotation, no async, no traversal
Dim text As String = New IronTesseract().Read(imagePath).Text
IronOCRとAzure Computer Vision OCR:機能比較
以下の表は、この移行を評価するチームにとって最も関連性の高い機能を網羅しています。
| フィーチャー | Azure コンピュータービジョン OCR | IronOCR |
|---|---|---|
| 処理場所 | Microsoft Azureクラウド | 地元、店内 |
| インターネット接続が必要です | はい、すべてのリクエスト | なし |
| Azure サブスクリプションが必要です | はい | なし |
| 価格設定モデル | 取引ごと(1,000につき1.00ドル) | 永続ライセンス($999より) |
| 複数ページPDFにおけるページ単位課金 | はい、各ページは1件の取引に相当します。 | ページごとの料金はかかりません |
| 無料プラン | 月間5,000件の取引 | 体験版(透かし入り) |
| 画像OCR API | AnalyzeAsync (非同期のみ) |
Read() (同期) |
| PDF OCR | 別フォーム認識サービス | 組み込み、同じRead()呼び出し |
| パスワードで保護されたPDF | フォーム認識機能経由 | input.LoadPdf(path, Password: "x") |
| 検索可能なPDF出力 | 手作業による組み立て | result.SaveAsSearchablePdf() |
| 複数ページTIFF | サポートされていません | input.LoadImageFrames() |
| 画像の自動前処理 | 不透明なサーバーサイド、設定不可 | 傾き補正、ノイズ除去、コントラスト調整、二値化、シャープ化、スケール調整 |
| ディープノイズ除去 | なし | input.DeepCleanBackgroundNoise() |
| OCR中のバーコード読み取り | 独立した画像解析機能 | ocr.Configuration.ReadBarCodes = true |
| 地域ベースのOCR | 直接ではない(アップロード前に手動でトリミングする) | OcrInput |
| レート制限 | S1ティアで10 TPS | ハードウェア依存のみ |
| 再試行ロジックが必要です | はい(HTTP 429、5xx) | なし |
| エアギャップ展開 | 不可能 | 完全サポート |
| 対応言語 | 164+(サーバー管理) | 125以上(NuGet言語パック) |
| 多言語同時通訳 | はい | はい (OcrLanguage.French + OcrLanguage.German) |
| 単語の境界ボックス | 多角形(頂点数が可変) | 長方形(x座標、y座標、幅、高さ) |
| 信頼度スコア | 単語ごとの浮動小数点数(0.0~1.0) | 単語ごとおよび総合評価(0~100点) |
| hOCRエクスポート | なし | result.SaveAsHocrFile() |
| 構造化された出力階層 | ブロック/線/単語 | ページ数/段落数/行数/単語数/文字数 |
| .NET互換性 | .NET Standard 2.0以降 | .NET Framework 4.6.2以降、 .NET Core、 .NET 5~9 |
| クロスプラットフォーム | Windows、Linux、macOS(クラウド経由) | Windows、Linux、macOS、Docker、ARM64 |
| 商用サポート | Azure サポートプラン | IronOCRのサポートはライセンスに含まれています |
クイックスタート:Azure Computer Vision OCRからIronOCRへの移行
ステップ 1: NuGet パッケージを置き換える
Azure Computer Vision パッケージを削除します。
dotnet remove package Azure.AI.Vision.ImageAnalysis
dotnet remove package Azure.AI.Vision.ImageAnalysis
プロジェクトでPDF処理にForm Recognizerも使用している場合は、そのパッケージも削除してください。
dotnet remove package Azure.AI.FormRecognizer
dotnet remove package Azure.AI.FormRecognizer
NuGetからIronOCRをインストールしてください。
dotnet add package IronOcr
ステップ 2: 名前空間の更新
// Before (Azure Computer Vision)
using Azure;
using Azure.AI.Vision.ImageAnalysis;
// For PDF processing:
// using Azure.AI.FormRecognizer.DocumentAnalysis;
// After (IronOCR)
using IronOcr;
// Before (Azure Computer Vision)
using Azure;
using Azure.AI.Vision.ImageAnalysis;
// For PDF processing:
// using Azure.AI.FormRecognizer.DocumentAnalysis;
// After (IronOCR)
using IronOcr;
Imports IronOcr
' Before (Azure Computer Vision)
' Imports Azure
' Imports Azure.AI.Vision.ImageAnalysis
' For PDF processing:
' Imports Azure.AI.FormRecognizer.DocumentAnalysis
' After (IronOCR)
ステップ 3: ライセンスの初期化
ライセンスキーは、OCR呼び出しを行う前に、アプリケーション起動時に一度だけ追加してください。
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
本番環境では、キーを環境変数に保存してください。
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE");
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE");
Imports System
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE")
コード移行の例
依存性注入された Azure クライアント構成の置き換え
推奨されるAzure SDKパターンに従うチームは、DIコンテナにIConfigurationバインディングを使用して登録します。 この配線はappsettings.jsonからエンドポイントURLとAPIキーを引き出し、各デプロイメント環境でのアウトバウンドネットワーク構成を必要とします。
Azure コンピュータビジョンのアプローチ:
// appsettings.json binds to this class
public class AzureComputerVisionOptions
{
public string Endpoint { get; set; } // "https://your-resource.cognitiveservices.azure.com/"
public string ApiKey { get; set; } // rotated periodically
}
// Program.cs / Startup.cs
services.Configure<AzureComputerVisionOptions>(
configuration.GetSection("AzureComputerVision"));
services.AddSingleton<ImageAnalysisClient>(sp =>
{
var opts = sp.GetRequiredService<IOptions<AzureComputerVisionOptions>>().Value;
return new ImageAnalysisClient(
new Uri(opts.Endpoint),
new AzureKeyCredential(opts.ApiKey));
});
services.AddScoped<IOcrService, AzureOcrService>();
// appsettings.json binds to this class
public class AzureComputerVisionOptions
{
public string Endpoint { get; set; } // "https://your-resource.cognitiveservices.azure.com/"
public string ApiKey { get; set; } // rotated periodically
}
// Program.cs / Startup.cs
services.Configure<AzureComputerVisionOptions>(
configuration.GetSection("AzureComputerVision"));
services.AddSingleton<ImageAnalysisClient>(sp =>
{
var opts = sp.GetRequiredService<IOptions<AzureComputerVisionOptions>>().Value;
return new ImageAnalysisClient(
new Uri(opts.Endpoint),
new AzureKeyCredential(opts.ApiKey));
});
services.AddScoped<IOcrService, AzureOcrService>();
' appsettings.json binds to this class
Public Class AzureComputerVisionOptions
Public Property Endpoint As String ' "https://your-resource.cognitiveservices.azure.com/"
Public Property ApiKey As String ' rotated periodically
End Class
' Program.vb / Startup.vb
services.Configure(Of AzureComputerVisionOptions)(
configuration.GetSection("AzureComputerVision"))
services.AddSingleton(Of ImageAnalysisClient)(Function(sp)
Dim opts = sp.GetRequiredService(Of IOptions(Of AzureComputerVisionOptions))().Value
Return New ImageAnalysisClient(
New Uri(opts.Endpoint),
New AzureKeyCredential(opts.ApiKey))
End Function)
services.AddScoped(Of IOcrService, AzureOcrService)()
// AzureOcrService.cs
public class AzureOcrService : IOcrService
{
private readonly ImageAnalysisClient _client;
public AzureOcrService(ImageAnalysisClient client)
{
_client = client;
}
public async Task<string> ReadAsync(string imagePath)
{
using var stream = File.OpenRead(imagePath);
var data = BinaryData.FromStream(stream);
var result = await _client.AnalyzeAsync(data, VisualFeatures.Read);
return string.Join("\n",
result.Value.Read.Blocks
.SelectMany(b => b.Lines)
.Select(l => l.Text));
}
}
// AzureOcrService.cs
public class AzureOcrService : IOcrService
{
private readonly ImageAnalysisClient _client;
public AzureOcrService(ImageAnalysisClient client)
{
_client = client;
}
public async Task<string> ReadAsync(string imagePath)
{
using var stream = File.OpenRead(imagePath);
var data = BinaryData.FromStream(stream);
var result = await _client.AnalyzeAsync(data, VisualFeatures.Read);
return string.Join("\n",
result.Value.Read.Blocks
.SelectMany(b => b.Lines)
.Select(l => l.Text));
}
}
Imports System.IO
Imports System.Threading.Tasks
Public Class AzureOcrService
Implements IOcrService
Private ReadOnly _client As ImageAnalysisClient
Public Sub New(client As ImageAnalysisClient)
_client = client
End Sub
Public Async Function ReadAsync(imagePath As String) As Task(Of String) Implements IOcrService.ReadAsync
Using stream = File.OpenRead(imagePath)
Dim data = BinaryData.FromStream(stream)
Dim result = Await _client.AnalyzeAsync(data, VisualFeatures.Read)
Return String.Join(vbLf, result.Value.Read.Blocks _
.SelectMany(Function(b) b.Lines) _
.Select(Function(l) l.Text))
End Using
End Function
End Class
IronOCRのアプローチ:
// Program.cs / Startup.cs
// One-time license key — no endpoint, no credential class, no options binding
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE");
// Register IronTesseract as a singleton — it is thread-safe
services.AddSingleton<IronTesseract>();
services.AddScoped<IOcrService, IronOcrService>();
// Program.cs / Startup.cs
// One-time license key — no endpoint, no credential class, no options binding
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE");
// Register IronTesseract as a singleton — it is thread-safe
services.AddSingleton<IronTesseract>();
services.AddScoped<IOcrService, IronOcrService>();
' Program.vb / Startup.vb
' One-time license key — no endpoint, no credential class, no options binding
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE")
' Register IronTesseract as a singleton — it is thread-safe
services.AddSingleton(Of IronTesseract)()
services.AddScoped(Of IOcrService, IronOcrService)()
// IronOcrService.cs
public class IronOcrService : IOcrService
{
private readonly IronTesseract _ocr;
public IronOcrService(IronTesseract ocr)
{
_ocr = ocr;
}
public string Read(string imagePath)
{
return _ocr.Read(imagePath).Text;
}
}
// IronOcrService.cs
public class IronOcrService : IOcrService
{
private readonly IronTesseract _ocr;
public IronOcrService(IronTesseract ocr)
{
_ocr = ocr;
}
public string Read(string imagePath)
{
return _ocr.Read(imagePath).Text;
}
}
' IronOcrService.vb
Public Class IronOcrService
Implements IOcrService
Private ReadOnly _ocr As IronTesseract
Public Sub New(ocr As IronTesseract)
_ocr = ocr
End Sub
Public Function Read(imagePath As String) As String Implements IOcrService.Read
Return _ocr.Read(imagePath).Text
End Function
End Class
DI配線は、2つのコンフィギュレーションクラス(オプション + クライアントファクトリー)から単一のAddSingleton<IronTesseract>()呼び出しに減少します。 Azureのcognitiveservices.azure.comのアウトバウンドファイアウォールルールはすべて削除されます。 シングルトンインスタンスで利用可能な設定オプションについては、 IronTesseractのセットアップガイドを参照してください。
フォーム認識ポーリングループの排除
Form RecognizerのLongRunningOperationを返します。 WaitUntil.Completedはクラウドジョブが完了するまで呼び出しスレッドをブロックします — 通常はドキュメントごとに2〜10秒です。 ノンブロッキング動作では、チームはUpdateStatusAsyncポーリングループを遅延付きで作成し、OCRロジックが含まれない30〜50行のインフラコードを追加します。
Azure コンピュータビジョンのアプローチ:
// DocumentAnalysisClient — separate from ImageAnalysisClient, separate resource
public class FormRecognizerPdfService
{
private readonly DocumentAnalysisClient _client;
public FormRecognizerPdfService(string endpoint, string apiKey)
{
_client = new DocumentAnalysisClient(
new Uri(endpoint),
new AzureKeyCredential(apiKey));
}
// Blocking wait — thread is held for the duration of cloud processing
public async Task<string> ExtractPdfTextBlocking(string pdfPath)
{
using var stream = File.OpenRead(pdfPath);
var operation = await _client.AnalyzeDocumentAsync(
WaitUntil.Completed, // blocks until Azure finishes
"prebuilt-read",
stream);
var docResult = operation.Value;
var sb = new StringBuilder();
foreach (var page in docResult.Pages)
{
foreach (var line in page.Lines)
{
sb.AppendLine(line.Content); // .Content, not .Text
}
}
return sb.ToString();
}
// True async — manual polling loop required
public async Task<string> ExtractPdfTextNonBlocking(string pdfPath)
{
using var stream = File.OpenRead(pdfPath);
var operation = await _client.AnalyzeDocumentAsync(
WaitUntil.Started, // returns immediately, not complete yet
"prebuilt-read",
stream);
// Poll every 500ms until the operation finishes
while (!operation.HasCompleted)
{
await Task.Delay(500);
await operation.UpdateStatusAsync();
}
var docResult = operation.Value;
var sb = new StringBuilder();
foreach (var page in docResult.Pages)
{
foreach (var line in page.Lines)
{
sb.AppendLine(line.Content);
}
}
return sb.ToString();
}
}
// DocumentAnalysisClient — separate from ImageAnalysisClient, separate resource
public class FormRecognizerPdfService
{
private readonly DocumentAnalysisClient _client;
public FormRecognizerPdfService(string endpoint, string apiKey)
{
_client = new DocumentAnalysisClient(
new Uri(endpoint),
new AzureKeyCredential(apiKey));
}
// Blocking wait — thread is held for the duration of cloud processing
public async Task<string> ExtractPdfTextBlocking(string pdfPath)
{
using var stream = File.OpenRead(pdfPath);
var operation = await _client.AnalyzeDocumentAsync(
WaitUntil.Completed, // blocks until Azure finishes
"prebuilt-read",
stream);
var docResult = operation.Value;
var sb = new StringBuilder();
foreach (var page in docResult.Pages)
{
foreach (var line in page.Lines)
{
sb.AppendLine(line.Content); // .Content, not .Text
}
}
return sb.ToString();
}
// True async — manual polling loop required
public async Task<string> ExtractPdfTextNonBlocking(string pdfPath)
{
using var stream = File.OpenRead(pdfPath);
var operation = await _client.AnalyzeDocumentAsync(
WaitUntil.Started, // returns immediately, not complete yet
"prebuilt-read",
stream);
// Poll every 500ms until the operation finishes
while (!operation.HasCompleted)
{
await Task.Delay(500);
await operation.UpdateStatusAsync();
}
var docResult = operation.Value;
var sb = new StringBuilder();
foreach (var page in docResult.Pages)
{
foreach (var line in page.Lines)
{
sb.AppendLine(line.Content);
}
}
return sb.ToString();
}
}
Imports System
Imports System.IO
Imports System.Text
Imports System.Threading.Tasks
Imports Azure
Imports Azure.AI.FormRecognizer.DocumentAnalysis
' DocumentAnalysisClient — separate from ImageAnalysisClient, separate resource
Public Class FormRecognizerPdfService
Private ReadOnly _client As DocumentAnalysisClient
Public Sub New(endpoint As String, apiKey As String)
_client = New DocumentAnalysisClient(
New Uri(endpoint),
New AzureKeyCredential(apiKey))
End Sub
' Blocking wait — thread is held for the duration of cloud processing
Public Async Function ExtractPdfTextBlocking(pdfPath As String) As Task(Of String)
Using stream = File.OpenRead(pdfPath)
Dim operation = Await _client.AnalyzeDocumentAsync(
WaitUntil.Completed, ' blocks until Azure finishes
"prebuilt-read",
stream)
Dim docResult = operation.Value
Dim sb = New StringBuilder()
For Each page In docResult.Pages
For Each line In page.Lines
sb.AppendLine(line.Content) ' .Content, not .Text
Next
Next
Return sb.ToString()
End Using
End Function
' True async — manual polling loop required
Public Async Function ExtractPdfTextNonBlocking(pdfPath As String) As Task(Of String)
Using stream = File.OpenRead(pdfPath)
Dim operation = Await _client.AnalyzeDocumentAsync(
WaitUntil.Started, ' returns immediately, not complete yet
"prebuilt-read",
stream)
' Poll every 500ms until the operation finishes
While Not operation.HasCompleted
Await Task.Delay(500)
Await operation.UpdateStatusAsync()
End While
Dim docResult = operation.Value
Dim sb = New StringBuilder()
For Each page In docResult.Pages
For Each line In page.Lines
sb.AppendLine(line.Content)
Next
Next
Return sb.ToString()
End Using
End Function
End Class
IronOCRのアプローチ:
// One class handles both images and PDFs — no second client or second resource
public class IronOcrDocumentService
{
private readonly IronTesseract _ocr;
public IronOcrDocumentService(IronTesseract ocr)
{
_ocr = ocr;
}
// Synchronous — returns immediately when local processing completes
public string ExtractPdfText(string pdfPath)
{
using var input = new OcrInput();
input.LoadPdf(pdfPath);
return _ocr.Read(input).Text;
}
// Specific page range — no per-page billing penalty
public string ExtractPageRange(string pdfPath, int startPage, int endPage)
{
using var input = new OcrInput();
input.LoadPdfPages(pdfPath, startPage, endPage);
return _ocr.Read(input).Text;
}
// If an async signature is required by an interface or controller
public Task<string> ExtractPdfTextAsync(string pdfPath)
{
return Task.Run(() => ExtractPdfText(pdfPath));
}
}
// One class handles both images and PDFs — no second client or second resource
public class IronOcrDocumentService
{
private readonly IronTesseract _ocr;
public IronOcrDocumentService(IronTesseract ocr)
{
_ocr = ocr;
}
// Synchronous — returns immediately when local processing completes
public string ExtractPdfText(string pdfPath)
{
using var input = new OcrInput();
input.LoadPdf(pdfPath);
return _ocr.Read(input).Text;
}
// Specific page range — no per-page billing penalty
public string ExtractPageRange(string pdfPath, int startPage, int endPage)
{
using var input = new OcrInput();
input.LoadPdfPages(pdfPath, startPage, endPage);
return _ocr.Read(input).Text;
}
// If an async signature is required by an interface or controller
public Task<string> ExtractPdfTextAsync(string pdfPath)
{
return Task.Run(() => ExtractPdfText(pdfPath));
}
}
Imports System.Threading.Tasks
' One class handles both images and PDFs — no second client or second resource
Public Class IronOcrDocumentService
Private ReadOnly _ocr As IronTesseract
Public Sub New(ocr As IronTesseract)
_ocr = ocr
End Sub
' Synchronous — returns immediately when local processing completes
Public Function ExtractPdfText(pdfPath As String) As String
Using input As New OcrInput()
input.LoadPdf(pdfPath)
Return _ocr.Read(input).Text
End Using
End Function
' Specific page range — no per-page billing penalty
Public Function ExtractPageRange(pdfPath As String, startPage As Integer, endPage As Integer) As String
Using input As New OcrInput()
input.LoadPdfPages(pdfPath, startPage, endPage)
Return _ocr.Read(input).Text
End Using
End Function
' If an async signature is required by an interface or controller
Public Function ExtractPdfTextAsync(pdfPath As String) As Task(Of String)
Return Task.Run(Function() ExtractPdfText(pdfPath))
End Function
End Class
ポーリングループとその遅延ロジックは完全に消滅する。 LoadPdfPagesはページ範囲の選択を処理します — ページごとの個別の呼び出しやトランザクションカウントはありません。 PDF入力ガイドでは、ストリーム入力、バイト配列の読み込み、ページ範囲パラメータについて詳細に解説しています。
Azureの単語レベルの結果をIronOCRの構造化出力にマッピングする
Azure Computer Vision は、単語の境界ボックスを、頂点数が可変の多角形として返します。通常は 4 つですが、必ずしもそうとは限りません。 結果の階層はfloatです。 単語の位置を読み取るコードは、ポリゴンの頂点配列を処理し、閾値比較のための信頼度スケールを正規化する必要があります。
Azure コンピュータビジョンのアプローチ:
public async Task<List<WordResult>> ExtractWordPositionsAsync(string imagePath)
{
using var stream = File.OpenRead(imagePath);
var imageData = BinaryData.FromStream(stream);
var response = await _client.AnalyzeAsync(imageData, VisualFeatures.Read);
var words = new List<WordResult>();
foreach (var block in response.Value.Read.Blocks)
{
foreach (var line in block.Lines)
{
foreach (var word in line.Words)
{
// BoundingPolygon is a list of ImagePoint — variable vertex count
var polygon = word.BoundingPolygon;
int minX = polygon.Min(p => p.X);
int minY = polygon.Min(p => p.Y);
int maxX = polygon.Max(p => p.X);
int maxY = polygon.Max(p => p.Y);
words.Add(new WordResult
{
Text = word.Text,
// Azure confidence: 0.0 to 1.0 — multiply by 100 for comparison
Confidence = (double)word.Confidence * 100.0,
X = minX,
Y = minY,
Width = maxX - minX,
Height = maxY - minY
});
}
}
}
return words;
}
public record WordResult(string Text, double Confidence, int X, int Y, int Width, int Height);
public async Task<List<WordResult>> ExtractWordPositionsAsync(string imagePath)
{
using var stream = File.OpenRead(imagePath);
var imageData = BinaryData.FromStream(stream);
var response = await _client.AnalyzeAsync(imageData, VisualFeatures.Read);
var words = new List<WordResult>();
foreach (var block in response.Value.Read.Blocks)
{
foreach (var line in block.Lines)
{
foreach (var word in line.Words)
{
// BoundingPolygon is a list of ImagePoint — variable vertex count
var polygon = word.BoundingPolygon;
int minX = polygon.Min(p => p.X);
int minY = polygon.Min(p => p.Y);
int maxX = polygon.Max(p => p.X);
int maxY = polygon.Max(p => p.Y);
words.Add(new WordResult
{
Text = word.Text,
// Azure confidence: 0.0 to 1.0 — multiply by 100 for comparison
Confidence = (double)word.Confidence * 100.0,
X = minX,
Y = minY,
Width = maxX - minX,
Height = maxY - minY
});
}
}
}
return words;
}
public record WordResult(string Text, double Confidence, int X, int Y, int Width, int Height);
Imports System.IO
Imports System.Threading.Tasks
Imports System.Linq
Public Class ImageAnalyzer
Private _client As SomeClientType ' Replace with the actual type of _client
Public Async Function ExtractWordPositionsAsync(imagePath As String) As Task(Of List(Of WordResult))
Using stream = File.OpenRead(imagePath)
Dim imageData = BinaryData.FromStream(stream)
Dim response = Await _client.AnalyzeAsync(imageData, VisualFeatures.Read)
Dim words = New List(Of WordResult)()
For Each block In response.Value.Read.Blocks
For Each line In block.Lines
For Each word In line.Words
' BoundingPolygon is a list of ImagePoint — variable vertex count
Dim polygon = word.BoundingPolygon
Dim minX = polygon.Min(Function(p) p.X)
Dim minY = polygon.Min(Function(p) p.Y)
Dim maxX = polygon.Max(Function(p) p.X)
Dim maxY = polygon.Max(Function(p) p.Y)
words.Add(New WordResult With {
.Text = word.Text,
' Azure confidence: 0.0 to 1.0 — multiply by 100 for comparison
.Confidence = CDbl(word.Confidence) * 100.0,
.X = minX,
.Y = minY,
.Width = maxX - minX,
.Height = maxY - minY
})
Next
Next
Next
Return words
End Using
End Function
End Class
Public Class WordResult
Public Property Text As String
Public Property Confidence As Double
Public Property X As Integer
Public Property Y As Integer
Public Property Width As Integer
Public Property Height As Integer
End Class
IronOCRのアプローチ:
public List<WordResult> ExtractWordPositions(string imagePath)
{
var result = new IronTesseract().Read(imagePath);
var words = new List<WordResult>();
foreach (var page in result.Pages)
{
foreach (var line in page.Lines)
{
foreach (var word in line.Words)
{
// Rectangle-based bounding box — no polygon math required
// Confidence is already 0–100, matching the converted Azure scale
words.Add(new WordResult
{
Text = word.Text,
Confidence = word.Confidence, // 0–100, no conversion needed
X = word.X,
Y = word.Y,
Width = word.Width,
Height = word.Height
});
}
}
}
return words;
}
// Filter to only high-confidence words — common post-processing pattern
public IEnumerable<string> ExtractHighConfidenceWords(string imagePath, double threshold = 80.0)
{
var result = new IronTesseract().Read(imagePath);
return result.Words
.Where(w => w.Confidence >= threshold)
.Select(w => w.Text);
}
public record WordResult(string Text, double Confidence, int X, int Y, int Width, int Height);
public List<WordResult> ExtractWordPositions(string imagePath)
{
var result = new IronTesseract().Read(imagePath);
var words = new List<WordResult>();
foreach (var page in result.Pages)
{
foreach (var line in page.Lines)
{
foreach (var word in line.Words)
{
// Rectangle-based bounding box — no polygon math required
// Confidence is already 0–100, matching the converted Azure scale
words.Add(new WordResult
{
Text = word.Text,
Confidence = word.Confidence, // 0–100, no conversion needed
X = word.X,
Y = word.Y,
Width = word.Width,
Height = word.Height
});
}
}
}
return words;
}
// Filter to only high-confidence words — common post-processing pattern
public IEnumerable<string> ExtractHighConfidenceWords(string imagePath, double threshold = 80.0)
{
var result = new IronTesseract().Read(imagePath);
return result.Words
.Where(w => w.Confidence >= threshold)
.Select(w => w.Text);
}
public record WordResult(string Text, double Confidence, int X, int Y, int Width, int Height);
Imports System.Collections.Generic
Imports System.Linq
Public Class WordExtractor
Public Function ExtractWordPositions(imagePath As String) As List(Of WordResult)
Dim result = New IronTesseract().Read(imagePath)
Dim words = New List(Of WordResult)()
For Each page In result.Pages
For Each line In page.Lines
For Each word In line.Words
' Rectangle-based bounding box — no polygon math required
' Confidence is already 0–100, matching the converted Azure scale
words.Add(New WordResult With {
.Text = word.Text,
.Confidence = word.Confidence, ' 0–100, no conversion needed
.X = word.X,
.Y = word.Y,
.Width = word.Width,
.Height = word.Height
})
Next
Next
Next
Return words
End Function
' Filter to only high-confidence words — common post-processing pattern
Public Function ExtractHighConfidenceWords(imagePath As String, Optional threshold As Double = 80.0) As IEnumerable(Of String)
Dim result = New IronTesseract().Read(imagePath)
Return result.Words _
.Where(Function(w) w.Confidence >= threshold) _
.Select(Function(w) w.Text)
End Function
End Class
Public Class WordResult
Public Property Text As String
Public Property Confidence As Double
Public Property X As Integer
Public Property Y As Integer
Public Property Width As Integer
Public Property Height As Integer
End Class
多角形から長方形への変換は行われなくなります。 Azureの0.0~1.0の値を100倍すると、信頼度値が直接一致します。既存のしきい値ロジックには、この1つの調整が必要です。 構造化データ出力ガイドには、完全な階層構造と座標プロパティが記載されています。 信頼度スコアリングモデルの詳細については、信頼度スコアリングガイドを参照してください。
クラウドアップロードなしで複数ページTIFFを処理する
Azure Computer Vision のImageAnalysisClientは単一画像を受け入れます。 文書スキャンワークフロー、ファックスアーカイブ、医療画像処理パイプラインなどでよく使用されるマルチフレームTIFFファイルは、アップロード前にTIFFファイルを個々の画像に分割するか(フレームごとに1回のトランザクション)、または個別の設定を備えたForm Recognizerに切り替える必要があります。 どちらの道も安全とは言えない。
Azure コンピュータビジョンのアプローチ:
// Azure does not support multi-frame TIFF directly via ImageAnalysisClient
// Must split frames manually and upload each as a separate transaction
public async Task<string> ExtractMultiFrameTiffAsync(string tiffPath)
{
// Load TIFF using System.Drawing or a third-party library
using var bitmap = new System.Drawing.Bitmap(tiffPath);
int frameCount = bitmap.GetFrameCount(
System.Drawing.Imaging.FrameDimension.Page);
var allText = new StringBuilder();
for (int i = 0; i < frameCount; i++)
{
// Select frame, save to temporary PNG, upload to Azure
bitmap.SelectActiveFrame(System.Drawing.Imaging.FrameDimension.Page, i);
using var ms = new MemoryStream();
bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
ms.Position = 0;
// Each frame = 1 Azure transaction = $0.001
var imageData = BinaryData.FromStream(ms);
var result = await _client.AnalyzeAsync(imageData, VisualFeatures.Read);
foreach (var block in result.Value.Read.Blocks)
foreach (var line in block.Lines)
allText.AppendLine(line.Text);
}
return allText.ToString();
}
// Azure does not support multi-frame TIFF directly via ImageAnalysisClient
// Must split frames manually and upload each as a separate transaction
public async Task<string> ExtractMultiFrameTiffAsync(string tiffPath)
{
// Load TIFF using System.Drawing or a third-party library
using var bitmap = new System.Drawing.Bitmap(tiffPath);
int frameCount = bitmap.GetFrameCount(
System.Drawing.Imaging.FrameDimension.Page);
var allText = new StringBuilder();
for (int i = 0; i < frameCount; i++)
{
// Select frame, save to temporary PNG, upload to Azure
bitmap.SelectActiveFrame(System.Drawing.Imaging.FrameDimension.Page, i);
using var ms = new MemoryStream();
bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
ms.Position = 0;
// Each frame = 1 Azure transaction = $0.001
var imageData = BinaryData.FromStream(ms);
var result = await _client.AnalyzeAsync(imageData, VisualFeatures.Read);
foreach (var block in result.Value.Read.Blocks)
foreach (var line in block.Lines)
allText.AppendLine(line.Text);
}
return allText.ToString();
}
Imports System.Drawing
Imports System.Drawing.Imaging
Imports System.IO
Imports System.Text
Imports System.Threading.Tasks
Public Class TiffProcessor
Private _client As ImageAnalysisClient
Public Async Function ExtractMultiFrameTiffAsync(tiffPath As String) As Task(Of String)
' Load TIFF using System.Drawing or a third-party library
Using bitmap As New Bitmap(tiffPath)
Dim frameCount As Integer = bitmap.GetFrameCount(FrameDimension.Page)
Dim allText As New StringBuilder()
For i As Integer = 0 To frameCount - 1
' Select frame, save to temporary PNG, upload to Azure
bitmap.SelectActiveFrame(FrameDimension.Page, i)
Using ms As New MemoryStream()
bitmap.Save(ms, Imaging.ImageFormat.Png)
ms.Position = 0
' Each frame = 1 Azure transaction = $0.001
Dim imageData As BinaryData = BinaryData.FromStream(ms)
Dim result = Await _client.AnalyzeAsync(imageData, VisualFeatures.Read)
For Each block In result.Value.Read.Blocks
For Each line In block.Lines
allText.AppendLine(line.Text)
Next
Next
End Using
Next
Return allText.ToString()
End Using
End Function
End Class
IronOCRのアプローチ:
// IronOCR handles multi-frame TIFF natively — single call, no frame splitting
public string ExtractMultiFrameTiff(string tiffPath)
{
using var input = new OcrInput();
input.LoadImageFrames(tiffPath); // all frames loaded automatically
var result = new IronTesseract().Read(input);
return result.Text;
}
// Access per-page data for frame-level reporting
public void ExtractTiffWithPageStats(string tiffPath)
{
using var input = new OcrInput();
input.LoadImageFrames(tiffPath);
var ocr = new IronTesseract();
var result = ocr.Read(input);
Console.WriteLine($"Total frames processed: {result.Pages.Length}");
foreach (var page in result.Pages)
{
Console.WriteLine($"Frame {page.PageNumber}: " +
$"{page.Words.Length} words, " +
$"confidence {page.Confidence:F1}%");
}
}
// Combine with preprocessing for scanned TIFF archives
public string ExtractLowQualityTiffArchive(string tiffPath)
{
using var input = new OcrInput();
input.LoadImageFrames(tiffPath);
input.Deskew();
input.DeNoise();
input.Contrast();
var result = new IronTesseract().Read(input);
return result.Text;
}
// IronOCR handles multi-frame TIFF natively — single call, no frame splitting
public string ExtractMultiFrameTiff(string tiffPath)
{
using var input = new OcrInput();
input.LoadImageFrames(tiffPath); // all frames loaded automatically
var result = new IronTesseract().Read(input);
return result.Text;
}
// Access per-page data for frame-level reporting
public void ExtractTiffWithPageStats(string tiffPath)
{
using var input = new OcrInput();
input.LoadImageFrames(tiffPath);
var ocr = new IronTesseract();
var result = ocr.Read(input);
Console.WriteLine($"Total frames processed: {result.Pages.Length}");
foreach (var page in result.Pages)
{
Console.WriteLine($"Frame {page.PageNumber}: " +
$"{page.Words.Length} words, " +
$"confidence {page.Confidence:F1}%");
}
}
// Combine with preprocessing for scanned TIFF archives
public string ExtractLowQualityTiffArchive(string tiffPath)
{
using var input = new OcrInput();
input.LoadImageFrames(tiffPath);
input.Deskew();
input.DeNoise();
input.Contrast();
var result = new IronTesseract().Read(input);
return result.Text;
}
Imports System
' IronOCR handles multi-frame TIFF natively — single call, no frame splitting
Public Function ExtractMultiFrameTiff(tiffPath As String) As String
Using input As New OcrInput()
input.LoadImageFrames(tiffPath) ' all frames loaded automatically
Dim result = New IronTesseract().Read(input)
Return result.Text
End Using
End Function
' Access per-page data for frame-level reporting
Public Sub ExtractTiffWithPageStats(tiffPath As String)
Using input As New OcrInput()
input.LoadImageFrames(tiffPath)
Dim ocr As New IronTesseract()
Dim result = ocr.Read(input)
Console.WriteLine($"Total frames processed: {result.Pages.Length}")
For Each page In result.Pages
Console.WriteLine($"Frame {page.PageNumber}: " &
$"{page.Words.Length} words, " &
$"confidence {page.Confidence:F1}%")
Next
End Using
End Sub
' Combine with preprocessing for scanned TIFF archives
Public Function ExtractLowQualityTiffArchive(tiffPath As String) As String
Using input As New OcrInput()
input.LoadImageFrames(tiffPath)
input.Deskew()
input.DeNoise()
input.Contrast()
Dim result = New IronTesseract().Read(input)
Return result.Text
End Using
End Function
フレームごとのトランザクションコストはゼロになります。 System.Drawingフレーム抽出ループとその一時的なPNGシリアライゼーションステップは完全に排除されます。 文書の品質にばらつきがあるファックスアーカイブのワークフローについては、 TIFFおよびGIF入力ガイドでフレーム選択オプションについて説明し、画像品質補正ガイドで前処理フィルタセット全体について説明しています。
レート制限キューイングなしの並列バッチ処理
Azure Computer VisionのS1ティアでは、スループットは1秒あたり10トランザクションに制限されています。 このレートを超えるバッチジョブは、HTTP 429応答を受け取ります。 製造実装にはレート制限ラッパー、セマフォ、またはキューイングレイヤーが必要ですが、IronOCR APIは設計上スレッドセーフ — スレッドごとに1つのParallel.ForEachで実行します。
Azure コンピュータビジョンのアプローチ:
// Must throttle to 10 TPS to avoid 429 errors on S1 tier
public class ThrottledAzureBatchProcessor
{
private readonly ImageAnalysisClient _client;
// Semaphore limits concurrent Azure calls to stay under 10 TPS
private readonly SemaphoreSlim _throttle = new SemaphoreSlim(10, 10);
public async Task<Dictionary<string, string>> ProcessBatchAsync(
IEnumerable<string> imagePaths)
{
var results = new ConcurrentDictionary<string, string>();
var tasks = imagePaths.Select(async path =>
{
await _throttle.WaitAsync();
try
{
using var stream = File.OpenRead(path);
var data = BinaryData.FromStream(stream);
var response = await _client.AnalyzeAsync(data, VisualFeatures.Read);
var text = string.Join("\n",
response.Value.Read.Blocks
.SelectMany(b => b.Lines)
.Select(l => l.Text));
results[path] = text;
// Respect 1-second window for the 10 TPS ceiling
await Task.Delay(100);
}
catch (RequestFailedException ex) when (ex.Status == 429)
{
// Rate limited despite throttling — back off and retry
await Task.Delay(2000);
// Re-queue or log failure — simplified here
results[path] = string.Empty;
}
finally
{
_throttle.Release();
}
});
await Task.WhenAll(tasks);
return new Dictionary<string, string>(results);
}
}
// Must throttle to 10 TPS to avoid 429 errors on S1 tier
public class ThrottledAzureBatchProcessor
{
private readonly ImageAnalysisClient _client;
// Semaphore limits concurrent Azure calls to stay under 10 TPS
private readonly SemaphoreSlim _throttle = new SemaphoreSlim(10, 10);
public async Task<Dictionary<string, string>> ProcessBatchAsync(
IEnumerable<string> imagePaths)
{
var results = new ConcurrentDictionary<string, string>();
var tasks = imagePaths.Select(async path =>
{
await _throttle.WaitAsync();
try
{
using var stream = File.OpenRead(path);
var data = BinaryData.FromStream(stream);
var response = await _client.AnalyzeAsync(data, VisualFeatures.Read);
var text = string.Join("\n",
response.Value.Read.Blocks
.SelectMany(b => b.Lines)
.Select(l => l.Text));
results[path] = text;
// Respect 1-second window for the 10 TPS ceiling
await Task.Delay(100);
}
catch (RequestFailedException ex) when (ex.Status == 429)
{
// Rate limited despite throttling — back off and retry
await Task.Delay(2000);
// Re-queue or log failure — simplified here
results[path] = string.Empty;
}
finally
{
_throttle.Release();
}
});
await Task.WhenAll(tasks);
return new Dictionary<string, string>(results);
}
}
Imports System.Collections.Concurrent
Imports System.IO
Imports System.Threading
Imports System.Threading.Tasks
' Must throttle to 10 TPS to avoid 429 errors on S1 tier
Public Class ThrottledAzureBatchProcessor
Private ReadOnly _client As ImageAnalysisClient
' Semaphore limits concurrent Azure calls to stay under 10 TPS
Private ReadOnly _throttle As New SemaphoreSlim(10, 10)
Public Async Function ProcessBatchAsync(imagePaths As IEnumerable(Of String)) As Task(Of Dictionary(Of String, String))
Dim results As New ConcurrentDictionary(Of String, String)()
Dim tasks = imagePaths.Select(Async Function(path)
Await _throttle.WaitAsync()
Try
Using stream = File.OpenRead(path)
Dim data = BinaryData.FromStream(stream)
Dim response = Await _client.AnalyzeAsync(data, VisualFeatures.Read)
Dim text = String.Join(vbLf,
response.Value.Read.Blocks _
.SelectMany(Function(b) b.Lines) _
.Select(Function(l) l.Text))
results(path) = text
' Respect 1-second window for the 10 TPS ceiling
Await Task.Delay(100)
End Using
Catch ex As RequestFailedException When ex.Status = 429
' Rate limited despite throttling — back off and retry
Await Task.Delay(2000)
' Re-queue or log failure — simplified here
results(path) = String.Empty
Finally
_throttle.Release()
End Try
End Function)
Await Task.WhenAll(tasks)
Return New Dictionary(Of String, String)(results)
End Function
End Class
IronOCRのアプローチ:
// なし rate limiting needed — throughput is hardware-bound
public Dictionary<string, string> ProcessBatch(IEnumerable<string> imagePaths)
{
var results = new ConcurrentDictionary<string, string>();
Parallel.ForEach(imagePaths, imagePath =>
{
// Each thread gets its own IronTesseract instance — fully thread-safe
var ocr = new IronTesseract();
var result = ocr.Read(imagePath);
results[imagePath] = result.Text;
});
return new Dictionary<string, string>(results);
}
// With controlled parallelism for memory-constrained environments
public Dictionary<string, string> ProcessBatchControlled(
IEnumerable<string> imagePaths, int maxDegreeOfParallelism = 4)
{
var results = new ConcurrentDictionary<string, string>();
var options = new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism };
Parallel.ForEach(imagePaths, options, imagePath =>
{
var ocr = new IronTesseract();
var result = ocr.Read(imagePath);
results[imagePath] = result.Text;
});
return new Dictionary<string, string>(results);
}
// なし rate limiting needed — throughput is hardware-bound
public Dictionary<string, string> ProcessBatch(IEnumerable<string> imagePaths)
{
var results = new ConcurrentDictionary<string, string>();
Parallel.ForEach(imagePaths, imagePath =>
{
// Each thread gets its own IronTesseract instance — fully thread-safe
var ocr = new IronTesseract();
var result = ocr.Read(imagePath);
results[imagePath] = result.Text;
});
return new Dictionary<string, string>(results);
}
// With controlled parallelism for memory-constrained environments
public Dictionary<string, string> ProcessBatchControlled(
IEnumerable<string> imagePaths, int maxDegreeOfParallelism = 4)
{
var results = new ConcurrentDictionary<string, string>();
var options = new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism };
Parallel.ForEach(imagePaths, options, imagePath =>
{
var ocr = new IronTesseract();
var result = ocr.Read(imagePath);
results[imagePath] = result.Text;
});
return new Dictionary<string, string>(results);
}
Imports System.Collections.Concurrent
Imports System.Collections.Generic
Imports System.Threading.Tasks
Public Class ImageProcessor
' なし rate limiting needed — throughput is hardware-bound
Public Function ProcessBatch(imagePaths As IEnumerable(Of String)) As Dictionary(Of String, String)
Dim results = New ConcurrentDictionary(Of String, String)()
Parallel.ForEach(imagePaths, Sub(imagePath)
' Each thread gets its own IronTesseract instance — fully thread-safe
Dim ocr = New IronTesseract()
Dim result = ocr.Read(imagePath)
results(imagePath) = result.Text
End Sub)
Return New Dictionary(Of String, String)(results)
End Function
' With controlled parallelism for memory-constrained environments
Public Function ProcessBatchControlled(imagePaths As IEnumerable(Of String), Optional maxDegreeOfParallelism As Integer = 4) As Dictionary(Of String, String)
Dim results = New ConcurrentDictionary(Of String, String)()
Dim options = New ParallelOptions With {.MaxDegreeOfParallelism = maxDegreeOfParallelism}
Parallel.ForEach(imagePaths, options, Sub(imagePath)
Dim ocr = New IronTesseract()
Dim result = ocr.Read(imagePath)
results(imagePath) = result.Text
End Sub)
Return New Dictionary(Of String, String)(results)
End Function
End Class
セマフォ、100msの遅延、およびHTTP 429のキャッチブロックはすべて削除されました。 並列処理は、CPUコア数と利用可能なメモリ容量によってのみ制限され、サービス層によって制限されることはありません。 マルチスレッドの例では、タイミング比較を含む完全なパターンを示しており、速度最適化ガイドでは、バッチワークロード向けのエンジン構成チューニングについて解説しています。
Azureが拒否する低品質スキャンの前処理
Azure Computer Visionはサーバー側で画像強調処理を実行しますが、その処理は不透明であり、構成変更はできません。 歪みが大きすぎたり、ノイズが多すぎたり、コントラストが低すぎたりする文書は、信頼性の低い結果を返すか、介入手段のない空のテキストを返します。 IronOCRはOcrInputに直接前処理パイプラインを公開します。
Azure コンピュータビジョンのアプローチ:
// なし client-side preprocessing API — must preprocess externally before upload
public async Task<string> ExtractFromLowQualityScanAsync(string imagePath)
{
// Option 1: Accept whatever Azure returns (may be empty or low-quality)
using var stream = File.OpenRead(imagePath);
var imageData = BinaryData.FromStream(stream);
var result = await _client.AnalyzeAsync(imageData, VisualFeatures.Read);
// なし way to know if the server applied enhancement
// なし confidence on the overall result — only per-word
var text = string.Join("\n",
result.Value.Read.Blocks
.SelectMany(b => b.Lines)
.Select(l => l.Text));
if (string.IsNullOrWhiteSpace(text))
{
// Option 2: Apply external preprocessing using System.Drawing, SkiaSharp,
// or ImageMagick, re-serialize to stream, re-upload — second billable transaction
throw new Exception("Azure returned empty result; manual preprocessing needed");
}
return text;
}
// なし client-side preprocessing API — must preprocess externally before upload
public async Task<string> ExtractFromLowQualityScanAsync(string imagePath)
{
// Option 1: Accept whatever Azure returns (may be empty or low-quality)
using var stream = File.OpenRead(imagePath);
var imageData = BinaryData.FromStream(stream);
var result = await _client.AnalyzeAsync(imageData, VisualFeatures.Read);
// なし way to know if the server applied enhancement
// なし confidence on the overall result — only per-word
var text = string.Join("\n",
result.Value.Read.Blocks
.SelectMany(b => b.Lines)
.Select(l => l.Text));
if (string.IsNullOrWhiteSpace(text))
{
// Option 2: Apply external preprocessing using System.Drawing, SkiaSharp,
// or ImageMagick, re-serialize to stream, re-upload — second billable transaction
throw new Exception("Azure returned empty result; manual preprocessing needed");
}
return text;
}
Imports System.IO
Imports System.Threading.Tasks
Public Class Example
Private _client As SomeClientType ' Replace with the actual type of _client
' なし client-side preprocessing API — must preprocess externally before upload
Public Async Function ExtractFromLowQualityScanAsync(imagePath As String) As Task(Of String)
' Option 1: Accept whatever Azure returns (may be empty or low-quality)
Using stream = File.OpenRead(imagePath)
Dim imageData = BinaryData.FromStream(stream)
Dim result = Await _client.AnalyzeAsync(imageData, VisualFeatures.Read)
' なし way to know if the server applied enhancement
' なし confidence on the overall result — only per-word
Dim text = String.Join(vbLf, result.Value.Read.Blocks.SelectMany(Function(b) b.Lines).Select(Function(l) l.Text))
If String.IsNullOrWhiteSpace(text) Then
' Option 2: Apply external preprocessing using System.Drawing, SkiaSharp,
' or ImageMagick, re-serialize to stream, re-upload — second billable transaction
Throw New Exception("Azure returned empty result; manual preprocessing needed")
End If
Return text
End Using
End Function
End Class
IronOCRのアプローチ:
// Preprocessing is part of the same call — no re-upload, no second transaction
public string ExtractFromLowQualityScan(string imagePath)
{
using var input = new OcrInput();
input.LoadImage(imagePath);
// Correct common scanning defects before OCR
input.Deskew(); // Fix rotated documents
input.DeNoise(); // Remove scanner noise
input.Contrast(); // Improve contrast for faded documents
input.Binarize(); // Convert to black and white
var ocr = new IronTesseract();
var result = ocr.Read(input);
Console.WriteLine($"Confidence after preprocessing: {result.Confidence:F1}%");
return result.Text;
}
// For severely degraded documents
public string ExtractFromDegradedDocument(string imagePath)
{
using var input = new OcrInput();
input.LoadImage(imagePath);
input.DeepCleanBackgroundNoise(); // Deep learning-based noise removal
input.Deskew();
input.Scale(150); // Upscale for better character resolution
var result = new IronTesseract().Read(input);
return result.Text;
}
// Preprocessing is part of the same call — no re-upload, no second transaction
public string ExtractFromLowQualityScan(string imagePath)
{
using var input = new OcrInput();
input.LoadImage(imagePath);
// Correct common scanning defects before OCR
input.Deskew(); // Fix rotated documents
input.DeNoise(); // Remove scanner noise
input.Contrast(); // Improve contrast for faded documents
input.Binarize(); // Convert to black and white
var ocr = new IronTesseract();
var result = ocr.Read(input);
Console.WriteLine($"Confidence after preprocessing: {result.Confidence:F1}%");
return result.Text;
}
// For severely degraded documents
public string ExtractFromDegradedDocument(string imagePath)
{
using var input = new OcrInput();
input.LoadImage(imagePath);
input.DeepCleanBackgroundNoise(); // Deep learning-based noise removal
input.Deskew();
input.Scale(150); // Upscale for better character resolution
var result = new IronTesseract().Read(input);
return result.Text;
}
Imports System
Public Class OcrProcessor
' Preprocessing is part of the same call — no re-upload, no second transaction
Public Function ExtractFromLowQualityScan(imagePath As String) As String
Using input As New OcrInput()
input.LoadImage(imagePath)
' Correct common scanning defects before OCR
input.Deskew() ' Fix rotated documents
input.DeNoise() ' Remove scanner noise
input.Contrast() ' Improve contrast for faded documents
input.Binarize() ' Convert to black and white
Dim ocr As New IronTesseract()
Dim result = ocr.Read(input)
Console.WriteLine($"Confidence after preprocessing: {result.Confidence:F1}%")
Return result.Text
End Using
End Function
' For severely degraded documents
Public Function ExtractFromDegradedDocument(imagePath As String) As String
Using input As New OcrInput()
input.LoadImage(imagePath)
input.DeepCleanBackgroundNoise() ' Deep learning-based noise removal
input.Deskew()
input.Scale(150) ' Upscale for better character resolution
Dim result = New IronTesseract().Read(input)
Return result.Text
End Using
End Function
End Class
外部の前処理依存性 — System.Drawing、SkiaSharp、またはImageMagick — は削除されます。 再アップロードと2回目の取引手数料は発生しません。 前処理パイプラインはOcrInputライフサイクルの一部なので、OCRエンジンが画像を見る前に適用されます。 画像フィルターのチュートリアルでは、各フィルターについて、適用前後の精度比較を交えながら解説しています。
Azure コンピュータービジョン OCR API からIronOCRへのマッピング リファレンス
| Azure コンピュータービジョン | IronOCR相当値 |
|---|---|
ImageAnalysisClient |
IronTesseract |
new AzureKeyCredential(apiKey) |
IronOcr.License.LicenseKey = key |
new Uri(endpoint) |
不要 |
client.AnalyzeAsync(data, VisualFeatures.Read) |
ocr.Read(imagePath) |
BinaryData.FromStream(stream) |
input.LoadImage(stream) |
BinaryData.FromBytes(bytes) |
input.LoadImage(bytes) |
result.Value.Read.Blocks |
result.Pages[i].Paragraphs |
block.Lines |
result.Pages[i].Lines |
line.Text |
line.Text |
line.Words |
line.Words |
word.Text |
word.Text |
word.Confidence (0.0–1.0 float) |
word.Confidence (0–100 double) |
word.BoundingPolygon |
word.X, word.Y, word.Width, word.Height |
DocumentAnalysisClient |
IronTesseract + OcrInput |
AnalyzeDocumentAsync(WaitUntil.Completed, "prebuilt-read", stream) |
input.LoadPdf(path) |
operation.Value.Pages |
result.Pages |
page.Lines[i].Content |
result.Lines[i].Text |
UpdateStatusAsync()ポーリングループ |
不要 — 同期結果 |
RequestFailedException (ステータス429) |
該当なし - レート制限なし |
RequestFailedException (ステータス5xx) |
該当なし - サービスエラーなし |
VisualFeatures.Read列挙フラグ |
暗黙的 — Read()は常にテキストを抽出します |
Form Recognizer prebuilt-readモデル |
内蔵OCRエンジン(機種選択不要) |
appsettings.jsonのAzureエンドポイントURL |
不要 |
| APIキーローテーション手順 | 不要 |
一般的な移行の問題と解決策
問題1:変更できない非同期インターフェース契約
Azure Computer Vision: サービスインターフェースは、Azureが非同期を義務付けているため、しばしばTask<string>戻り値の型を宣言します。 呼び出し元のコード、コントローラー、バックグラウンドワーカーはすべて非同期メソッドとして記述されます。 IronOCRに切り替えることでOCRレイヤーにおける非同期処理の必要性はなくなりますが、大規模なコードベースではすべてのインターフェースシグネチャを変更することは必ずしも現実的ではありません。
解決策: 既存のインターフェースをカスケードリファクタリングなしで満たすために、Task.Runで同期IronOCR呼び出しをラップします:
// Existing interface — do not change it
public interface IOcrService
{
Task<string> ReadAsync(string imagePath);
}
// New IronOCR implementation — fulfills the contract
public class IronOcrService : IOcrService
{
private readonly IronTesseract _ocr;
public IronOcrService(IronTesseract ocr) => _ocr = ocr;
public Task<string> ReadAsync(string imagePath)
{
// Task.Run offloads to thread pool — no await chain needed
return Task.Run(() => _ocr.Read(imagePath).Text);
}
}
// Existing interface — do not change it
public interface IOcrService
{
Task<string> ReadAsync(string imagePath);
}
// New IronOCR implementation — fulfills the contract
public class IronOcrService : IOcrService
{
private readonly IronTesseract _ocr;
public IronOcrService(IronTesseract ocr) => _ocr = ocr;
public Task<string> ReadAsync(string imagePath)
{
// Task.Run offloads to thread pool — no await chain needed
return Task.Run(() => _ocr.Read(imagePath).Text);
}
}
Imports System.Threading.Tasks
' Existing interface — do not change it
Public Interface IOcrService
Function ReadAsync(imagePath As String) As Task(Of String)
End Interface
' New IronOCR implementation — fulfills the contract
Public Class IronOcrService
Implements IOcrService
Private ReadOnly _ocr As IronTesseract
Public Sub New(ocr As IronTesseract)
_ocr = ocr
End Sub
Public Function ReadAsync(imagePath As String) As Task(Of String) Implements IOcrService.ReadAsync
' Task.Run offloads to thread pool — no await chain needed
Return Task.Run(Function() _ocr.Read(imagePath).Text)
End Function
End Class
これは有効な中間ステップです。非同期OCRガイドでは、完全な非同期統合が望ましいシナリオ向けに、IronOCRに組み込まれている非同期サポートについて説明しています。
問題2:信頼度閾値ロジックが誤った結果を生み出す
Azure Computer Vision: Azureは単語の信頼度を0.0から1.0のword.Confidence > 0.85fなどのしきい値を使用します。 移行後、 IronOCRの信頼度は0~100であり、0~1ではないため、これらの比較は常に偽と評価されます。
解決策:フィルタリングロジックを更新する際に、既存のAzureしきい値を100倍します。
// Before: Azure threshold (0.0 - 1.0 scale)
var highConfidenceWords = azureWords
.Where(w => w.Confidence > 0.85f)
.Select(w => w.Text);
// After: IronOCR threshold (0 - 100 scale)
var result = new IronTesseract().Read(imagePath);
var highConfidenceWords = result.Words
.Where(w => w.Confidence > 85.0)
.Select(w => w.Text);
// Overall document confidence — also on 0-100 scale
if (result.Confidence < 70.0)
{
// Document may need preprocessing or manual review
}
// Before: Azure threshold (0.0 - 1.0 scale)
var highConfidenceWords = azureWords
.Where(w => w.Confidence > 0.85f)
.Select(w => w.Text);
// After: IronOCR threshold (0 - 100 scale)
var result = new IronTesseract().Read(imagePath);
var highConfidenceWords = result.Words
.Where(w => w.Confidence > 85.0)
.Select(w => w.Text);
// Overall document confidence — also on 0-100 scale
if (result.Confidence < 70.0)
{
// Document may need preprocessing or manual review
}
Imports System.Linq
' Before: Azure threshold (0.0 - 1.0 scale)
Dim highConfidenceWords = azureWords _
.Where(Function(w) w.Confidence > 0.85F) _
.Select(Function(w) w.Text)
' After: IronOCR threshold (0 - 100 scale)
Dim result = New IronTesseract().Read(imagePath)
Dim highConfidenceWords = result.Words _
.Where(Function(w) w.Confidence > 85.0) _
.Select(Function(w) w.Text)
' Overall document confidence — also on 0-100 scale
If result.Confidence < 70.0 Then
' Document may need preprocessing or manual review
End If
問題3:フォーム認識の事前構築済みモデルフィールド抽出には、 IronOCRに直接相当する機能がない
Azure Computer Vision: Form Recognizerの事前構築された請求書および領収書モデルは、ページ上のフィールドの表示位置を指定せずに、名前付きフィールドを自動的に抽出します — InvoiceDate。 抽出ロジックはAzureモデルに組み込まれています。
解決策: モデルベースのフィールド抽出を、CropRectangleを使用した領域ベースのOCRに置き換えます。 これにはドキュメントのレイアウトを知る必要がありますが、実際の導入環境ではほとんどの場合、固定テンプレートが用意されています。
var ocr = new IronTesseract();
// Define extraction zones for a known invoice template
var headerZone = new CropRectangle(50, 40, 400, 60);
var totalZone = new CropRectangle(350, 600, 250, 50);
var dateZone = new CropRectangle(400, 100, 200, 40);
string header, total, date;
using (var input = new OcrInput())
{
input.LoadImage("invoice.jpg", headerZone);
header = ocr.Read(input).Text.Trim();
}
using (var input = new OcrInput())
{
input.LoadImage("invoice.jpg", totalZone);
total = ocr.Read(input).Text.Trim();
}
using (var input = new OcrInput())
{
input.LoadImage("invoice.jpg", dateZone);
date = ocr.Read(input).Text.Trim();
}
var ocr = new IronTesseract();
// Define extraction zones for a known invoice template
var headerZone = new CropRectangle(50, 40, 400, 60);
var totalZone = new CropRectangle(350, 600, 250, 50);
var dateZone = new CropRectangle(400, 100, 200, 40);
string header, total, date;
using (var input = new OcrInput())
{
input.LoadImage("invoice.jpg", headerZone);
header = ocr.Read(input).Text.Trim();
}
using (var input = new OcrInput())
{
input.LoadImage("invoice.jpg", totalZone);
total = ocr.Read(input).Text.Trim();
}
using (var input = new OcrInput())
{
input.LoadImage("invoice.jpg", dateZone);
date = ocr.Read(input).Text.Trim();
}
Imports IronOcr
Dim ocr As New IronTesseract()
' Define extraction zones for a known invoice template
Dim headerZone As New CropRectangle(50, 40, 400, 60)
Dim totalZone As New CropRectangle(350, 600, 250, 50)
Dim dateZone As New CropRectangle(400, 100, 200, 40)
Dim header As String
Dim total As String
Dim date As String
Using input As New OcrInput()
input.LoadImage("invoice.jpg", headerZone)
header = ocr.Read(input).Text.Trim()
End Using
Using input As New OcrInput()
input.LoadImage("invoice.jpg", totalZone)
total = ocr.Read(input).Text.Trim()
End Using
Using input As New OcrInput()
input.LoadImage("invoice.jpg", dateZone)
date = ocr.Read(input).Text.Trim()
End Using
地域ベースのOCRガイドでは、座標系の詳細と複数地域へのバッチ処理について解説しています。
問題4:hOCRと構造化エクスポートが欠落している
Azure Computer Vision: AzureはhOCR出力を提供していません。 下流の文書分析ツール用に標準化されたレイアウトデータが必要なチームは、Azureからの応答からバウンディングボックスを手動で抽出し、独自の出力形式を構築します。
解決策: IronOCRは、1回の呼び出しで標準規格に準拠したhOCRを生成します。
var result = new IronTesseract().Read("document.jpg");
// Write hOCR file — recognized by most document analysis tools
result.SaveAsHocrFile("document.hocr");
// Searchable PDF — alternative for archive and search indexing workflows
result.SaveAsSearchablePdf("document-searchable.pdf");
var result = new IronTesseract().Read("document.jpg");
// Write hOCR file — recognized by most document analysis tools
result.SaveAsHocrFile("document.hocr");
// Searchable PDF — alternative for archive and search indexing workflows
result.SaveAsSearchablePdf("document-searchable.pdf");
Dim result = (New IronTesseract()).Read("document.jpg")
' Write hOCR file — recognized by most document analysis tools
result.SaveAsHocrFile("document.hocr")
' Searchable PDF — alternative for archive and search indexing workflows
result.SaveAsSearchablePdf("document-searchable.pdf")
問題5:Azure SDKのバージョンが他のAzureパッケージと競合する
Azure Computer Vision: 複数のAzure SDKパッケージ (Azure.KeyVault.Secrets) を使用するプロジェクトは、Azure.Core 間のトランジティブ依存関係間でバージョン競合に遭遇する可能性があります。 Azure SDK のモノレポ バージョン管理ポリシーは役立ちますが、特に一般提供版とプレビュー版の SDK バージョンを混在させる場合など、すべての競合を解消するわけではありません。
解決策: Azure.AI.FormRecognizerを削除することで、それらのSDKパッケージを依存関係ツリーから排除します。 プロジェクトがOCRにAzureのみを使用する場合、Azure.*依存関係セット全体が削除されます。 他のAzureサービスが残っている場合、パッケージ数の削減により競合が発生する可能性が低くなります。
# Remove only the OCR-related Azure packages
dotnet remove package Azure.AI.Vision.ImageAnalysis
dotnet remove package Azure.AI.FormRecognizer
# Verify remaining Azure packages have no new conflicts
dotnet restore
dotnet build
# Remove only the OCR-related Azure packages
dotnet remove package Azure.AI.Vision.ImageAnalysis
dotnet remove package Azure.AI.FormRecognizer
# Verify remaining Azure packages have no new conflicts
dotnet restore
dotnet build
問題6:スキャンされたドキュメントの品質がAzureの許容基準を下回っている
Azure Computer Vision:解像度が非常に低い画像 (約 150 DPI 未満) や、著しく歪んだスキャン画像の場合、Azure のサーバー側パイプラインから最小限のテキストまたは空のテキストが返され、どのような強化処理が試みられたかについてのフィードバックがありません。 呼び出し元は、外部の前処理なしに結果を改善する方法はありません。
解決策: IronOCRのプリプロセスパイプラインを使用して、OCR処理前に画像を準備します。
using var input = new OcrInput();
input.LoadImage("low-quality-scan.jpg");
input.Deskew();
input.DeNoise();
input.Scale(200); // Upscale to improve character resolution
input.Contrast();
input.Sharpen();
var result = new IronTesseract().Read(input);
Console.WriteLine($"Extraction confidence: {result.Confidence:F1}%");
using var input = new OcrInput();
input.LoadImage("low-quality-scan.jpg");
input.Deskew();
input.DeNoise();
input.Scale(200); // Upscale to improve character resolution
input.Contrast();
input.Sharpen();
var result = new IronTesseract().Read(input);
Console.WriteLine($"Extraction confidence: {result.Confidence:F1}%");
Imports IronOcr
Using input As New OcrInput()
input.LoadImage("low-quality-scan.jpg")
input.Deskew()
input.DeNoise()
input.Scale(200) ' Upscale to improve character resolution
input.Contrast()
input.Sharpen()
Dim result = New IronTesseract().Read(input)
Console.WriteLine($"Extraction confidence: {result.Confidence:F1}%")
End Using
画像方向補正ガイドでは、特に傾きと回転の検出について解説しています。
Azure コンピュータービジョン OCR移行チェックリスト
移行前
コードベース内のAzure Computer VisionおよびForm Recognizerの使用箇所をすべて特定します。
# Find all Azure OCR-related using statements
grep -r "Azure.AI.Vision.ImageAnalysis" --include="*.cs" .
grep -r "Azure.AI.FormRecognizer" --include="*.cs" .
grep -r "ImageAnalysisClient" --include="*.cs" .
grep -r "DocumentAnalysisClient" --include="*.cs" .
grep -r "AnalyzeAsync" --include="*.cs" .
grep -r "AnalyzeDocumentAsync" --include="*.cs" .
grep -r "VisualFeatures.Read" --include="*.cs" .
grep -r "WaitUntil.Completed" --include="*.cs" .
grep -r "UpdateStatusAsync" --include="*.cs" .
grep -r "AzureKeyCredential" --include="*.cs" .
# Find all Azure OCR-related using statements
grep -r "Azure.AI.Vision.ImageAnalysis" --include="*.cs" .
grep -r "Azure.AI.FormRecognizer" --include="*.cs" .
grep -r "ImageAnalysisClient" --include="*.cs" .
grep -r "DocumentAnalysisClient" --include="*.cs" .
grep -r "AnalyzeAsync" --include="*.cs" .
grep -r "AnalyzeDocumentAsync" --include="*.cs" .
grep -r "VisualFeatures.Read" --include="*.cs" .
grep -r "WaitUntil.Completed" --include="*.cs" .
grep -r "UpdateStatusAsync" --include="*.cs" .
grep -r "AzureKeyCredential" --include="*.cs" .
Azure OCRエンドポイントとキーを含む構成ファイルを特定します。
grep -r "cognitiveservices.azure.com" --include="*.json" .
grep -r "AzureComputerVision\|FormRecognizer" --include="*.json" .
grep -r "ComputerVision\|FormRecognizer" --include="appsettings*.json" .
grep -r "cognitiveservices.azure.com" --include="*.json" .
grep -r "AzureComputerVision\|FormRecognizer" --include="*.json" .
grep -r "ComputerVision\|FormRecognizer" --include="appsettings*.json" .
コーディング開始前の在庫品目:
- Azure OCR サービスパターンを実装するクラスの数
- OCR呼び出しから伝播する非同期メソッドチェーンの数
- 0.0~1.0のAzureスケールを使用して、単語の信頼度閾値を特定します。
- フォーム認識機能の事前構築済みモデルの使用箇所(請求書、領収書、身分証明書)を特定し、地域ベースの置換が必要
- 現在フレームごとにアップロードするために分割されているマルチフレームTIFF入力を識別する
- 不要になった送信Azureネットワークルールについて、DockerおよびCI/CD構成を確認する。
コードの移行
Azure.AI.Vision.ImageAnalysisNuGetパッケージを、使用するすべてのプロジェクトから削除しますAzure.AI.FormRecognizerNuGetパッケージを、使用するすべてのプロジェクトから削除します- 影響を受ける各プロジェクトで
dotnet add package IronOcrを実行します - アプリケーションの起動時に
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE")を追加します using Azure;を置き換えます。 using Azure.AI.Vision.ImageAnalysis;withusing IronOcr;`IronTesseractをDI (::services.AddSingleton<IronTesseract>())内でシングルトンとして登録しますAzureComputerVisionOptionsまたは同等の構成クラスを削除します;appsettings.jsonAzure OCRセクションを削除します- すべてのサービスクラスで
ImageAnalysisClientコンストラクタインジェクションを置き換えます OcrInputで置き換えます- すべての
WaitUntil.Completedパターンを削除します;IronTesseract+OcrInput.LoadPdf()で置き換えます - 単語の信頼度しきい値の比較を更新する: Azure の 0.0~1.0 の値をすべて 100 倍する
- ポリゴンバウンディングボックスアクセス(
word.Heightプロパティに置き換えます - マルチフレームTIFFフレームごとのアップロードループを
input.LoadImageFrames(tiffPath)に置き換えます - 既知のドキュメントテンプレートのためにForm Recognizerの事前構築モデルフィールド抽出を
CropRectangleベースの領域OCRに変換します - HTTP 429と5xxの
RequestFailedExceptionキャッチブロックを削除します; エラー処理をファイルシステムと入力検証例外のみに簡素化する。
移行後
- 20以上の代表的なドキュメントのサンプルについて、プレーンテキスト抽出の出力がAzureの結果と一致するか、またはそれを上回ることを検証する
- 複数ページのPDF処理で、監視画面にページごとの課金カウンターが表示されずに、すべてのページにテキストが生成されることを確認します。
- マルチフレームTIFF処理のテストで、ソースフレーム数と同じページ数が返される。 単語の信頼度値が0~100の範囲内に収まり、閾値比較が正しく機能することを確認する。
- 単語の境界ボックスの座標が、元の画像の座標系と正しく一致していることを確認します。 バッチ処理パスを実行し、レート制限エラーやセマフォ競合が発生しずに完了することを確認します。
- 外部インターネットアクセスがない環境でテストを行い、Azureエンドポイント呼び出しが発生しないことを確認します。
- アウトバウンドファイアウォールルールがない状態で、Dockerおよびコンテナデプロイメントが正常に開始することを確認します
cognitiveservices.azure.com - DIコンテナの構築が成功し、
IronTesseractシングルトンが正しく解決されることを確認します - すべてのデプロイメント環境で
IRONOCR_LICENSE環境変数が設定されていること、およびOCRがライセンス済み(透かしなし)出力を生成することを確認します
IronOCRへの移行の主なメリット
コストが予測可能になります。Azure Computer Visionのトランザクションごとのメーターは継続的に動作します。 文書処理量が多い月は、1ヶ月でIronOCRの年間ライセンス費用を超える可能性がある。 移行後、OCRの予算は固定項目となる。 処理量は、業務の拡大、バルク再処理、または一時的なスパイクにより増加する可能性があり、より大きな請求書を生成することなく増加します。"IronOCRライセンスページ"には、1人の開発者ライセンスから$2,999で無制限の開発者用のティアオプションが表示されます。
アプリケーションスタックが簡素化されます。 Azure.AI.FormRecognizerを削除することで、2つのNuGetパッケージ、2つのAzureリソース設定、2つの資格情報セット、非同期ポーリングインフラストラクチャ全体が取り除かれます。 クラウドI/Oのために以前はasync Task<string>署名が必要だったサービスクラスが同期メソッドになります。 エラー処理の範囲は、ネットワーク障害、レート制限、サービス可用性から、ファイルシステムと入力検証へと絞り込まれる。 このコードベースで働く将来のすべての開発者は、理解すべきコードが少なくなる。
文書データは組織の管理下に置かれます。移行後、OCR処理はローカル環境で行われます。 文書は組織の境界を越えることはなく、Azure テレメトリには表示されず、Microsoft のデータ保持ポリシーや処理ポリシーの対象にもなりません。 HIPAAの適用対象となる事業体、ITAR契約業者、GDPRの規制対象となる組織、およびデータ所在地の要件を持つあらゆるチームは、クラウドの第三者によるコンプライアンス審査を受けることなく文書を処理できます。 Linux導入ガイドとDocker導入ガイドでは、制限された環境にIronOCRを導入する方法を示しています。
バッチ処理のスループットはハードウェアに依存します。AzureのS1ティアにおける10 TPSの上限は厳密な制限であり、これを回避するにはキューイング、スロットリング、またはティアのアップグレードが必要です。 移行後、並行OCRジョブはサービスによって設定された制限なしで利用可能なCPUコアを飽和させます。4コアサーバーは4つの並行したIronTesseractインスタンスを同時に実行できます。 マルチスレッドの例は、そのパターンを示し、コア数に応じてスループットがスケーリングすることを示しています。
前処理の不具合はコードで解決できます。Azure Computer Visionのサーバー側画像強調処理はブラックボックスです。 スキャンが空または低信頼度の出力を返すとき、唯一のオプションはそれを受け入れるか、追加料金で再アップロードする前に外部で前処理することです。IronOCRのOcrInputパイプラインは、デスクュー、デノイズ、コントラスト、バイナリ化、スケール、シャープン、ディープノイズ除去を一級のメソッドとして公開します。 問題のあるスキャンタイプは、調整可能なパラメータとなる。 前処理機能のページには、フィルターセット全体が一覧表示され、どのフィルターがどのスキャン欠陥に対処するかについてのガイダンスが記載されています。
よくある質問
なぜAzure Computer Vision OCRからIronOCRに移行する必要があるのですか?
一般的な推進要因には、COM相互接続の複雑さの解消、ファイルベースのライセンス管理の置き換え、ページ単位の課金の回避、Docker/コンテナデプロイの有効化、標準的な.NETツールと統合するNuGetネイティブワークフローの採用などがあります。
Azure Computer Vision OCRからIronOCRに移行する際の主なコード変更は何ですか?
Azure Computer Visionの初期化シーケンスをIronTesseractのインスタンス化に置き換え、COMのライフサイクル管理(明示的なCreate/Load/Closeパターン)を削除し、結果のプロパティ名を更新します。その結果、定型的な行が大幅に減りました。
IronOCRをインストールして移行を開始するにはどうすればいいですか?
パッケージマネージャーコンソールで'Install-Package IronOcr'を実行するか、CLIで'dotnet add package IronOcr'を実行してください。言語パックは別のパッケージです:例えばフランス語の場合は'dotnet add package IronOcr.Languages.French'となります。
IronOCRはAzure Computer Vision OCRの標準的なビジネス文書のOCR精度に匹敵しますか?
IronOCRは、請求書、契約書、領収書、入力フォームなどの標準的なビジネスコンテンツで高い精度を達成しています。画像前処理フィルター(傾き補正、ノイズ除去、コントラスト強調)により、劣化した入力の認識をさらに向上させます。
IronOCRはAzure Computer Vision OCRが別途インストールする言語データをどのように扱うのですか?
IronOCRの言語データはNuGetパッケージとして配布されます。'dotnet add package IronOcr.Languages.German'でドイツ語のサポートがインストールされます。手動でのファイル配置やディレクトリパスは必要ありません。
Azure Computer Vision OCRからIronOCRへの移行には、導入インフラの変更が必要ですか?
IronOCRは、Azure Computer Vision OCRよりもインフラの変更が少なくて済みます。SDKのバイナリパス、ライセンスファイルの配置、ライセンスサーバーの設定はありません。NuGetパッケージには完全なOCRエンジンが含まれており、ライセンスキーはアプリケーションコードに設定された文字列です。
移行後のIronOCRライセンスの設定方法は?
アプリケーションの起動コードでIronOcr.License.LicenseKey = "YOUR-KEY "を割り当てます。DockerまたはKubernetesでは、キーを環境変数として保存し、スタートアップで読み込む。License.IsValidLicenseを使用して、トラフィックを受け入れる前にバリデーションを行う。
IronOCRはAzure Computer Visionと同じようにPDFを処理できますか?
IronOCRはネイティブPDFもスキャンしたPDFも読み取ります。IronTesseractをインスタンス化し、ocr.Read(input)を呼び出し、入力はPDFパスまたはOcrPdfInputであり、OcrResultページを反復します。別のPDFレンダリングパイプラインは必要ありません。
IronOCRはどのように大量処理のスレッディングを処理するのですか?
IronTesseractはスレッド毎にインスタンス化しても安全です。Parallel.ForEachまたはタスクプールでスレッドごとにインスタンスを1つスピンアップし、OCRを同時に実行し、完了したら各インスタンスを破棄します。グローバルステートやロックは必要ありません。
IronOCRはテキスト抽出後、どのような出力形式をサポートしていますか?
IronOCRはテキスト、単語座標、信頼度スコア、ページ構造を含む構造化された結果を返します。エクスポートオプションには、プレーンテキスト、検索可能なPDF、下流処理用の構造化結果オブジェクトが含まれます。
IronOCRの価格は、Azure Computer Vision OCRよりもワークロードのスケーリングが予測可能ですか?
IronOCRは、定額制の永久ライセンスを採用しており、ページ単位やボリューム単位の料金はかかりません。10,000ページでも1,000万ページでも、ライセンスコストは一定です。ボリュームライセンスとチームライセンスのオプションはIronOCRの価格ページをご覧ください。
Azure Computer Vision OCRからIronOCRに移行した後、既存のテストはどうなりますか?
抽出されたテキストコンテンツをアサートするテストは、移行後もパスし続ける必要があります。APIコールパターンやCOMオブジェクトのライフサイクルを検証するテストは、IronOCRのシンプルな初期化と結果モデルを反映するように更新する必要があります。

