跳至頁尾內容
影片

從 Azure Computer Vision OCR 遷移到

本指南帶領.NET開發人員用IronOCR替換Azure Computer Vision OCR,這是一個在地OCR程式庫,能在不需要雲端基礎設施的情況下本地處理文件。 它涵蓋了交換NuGet程式包和命名空間、將Azure的非同步輪詢模型翻譯為同步本地呼叫,以及處理特定模式——依賴注入連接、Form Recognizer輪詢迴圈、多頁TIFF處理和批量吞吐量——在遷移期間需要最多注意的機械步驟。

為什麼要從Azure Computer Vision OCR進行遷移

遷移的理由不是抽象的。 通常團隊在生產環境中遇到一個或多個具體的運營問題後,才做出決策。

端點和API密鑰管理永不結束。 每個部署環境——開發、測試、運行、災難恢復——都需要配置的Azure Cognitive Services資源、一個端點URL和至少一個API密鑰。 密鑰必須輪替。 當資源更換地區時,端點會更改。 每個環境都需要出站防火牆規則來達到cognitiveservices.azure.com。 每個環境和團隊中的每個開發人員都會擴大操作表面。 IronOCR用一個在應用啟動時設置的一次性字串授權密鑰替換了所有這些,不需要輪換計劃,也沒有出站網路要求。

按頁計費懲罰了多頁文件。 Azure Computer Vision將每個PDF頁面計為一次分開的事務。 20頁的合約就是20次計費呼叫。 以每1,000次事務1.00美元來計算,每月處理50,000個多頁文件的團隊平均每份4頁會產生200,000次事務量——在免費層之後每月195美元,每年2,340美元。 這是在不到四個月內達到IronOCR Lite ($999)的損益平衡點,之後每增加一頁都沒有成本。

非同步傳播傳遍整個呼叫堆棧。 Azure Computer Vision無法同步返回——雲端I/O具有網路延遲。 在await要求迫使每個呼叫方法都必須是非同步的,從服務層向上傳播模式通過控制器、背景工作者和任何必須重新調整的同步程式碼,以適應它。 Form Recognizer的基於輪詢的操作使這更複雜:UpdateStatusAsync輪詢迴圈。

每次呼叫文件都離開網路。 對於處理受到HIPAA保護的健康資訊、ITAR控制的國防文件、律師-客戶保密通信或任何遵循資料駐留規則的文件類別的團隊來說,強制的雲端傳輸是一種架構上的不相容,而不是權衡。 沒有Azure Computer Vision模式可以避免將檔案傳送到Microsoft資料中心。

速率限制建立了吞吐量上限。 Azure Computer Vision的S1層限制為每秒10次事務。 每小時處理3,600張圖片的批量作業正好達到上限。 超過它會返回HTTP 429響應,要求在每個呼叫路徑中使用指數反彈重試邏輯。 IronOCR的吞吐量上限是寄存硬體——沒有服務施加的上限,也不需要重試結構。

圖像OCR和PDF OCR需要兩個單獨的服務。 標準圖像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
$vbLabelText   $csharpLabel

IronOCR與Azure Computer Vision OCR:特徵比較

下表涵蓋了團隊評估此遷移時最相關的功能。

功能 Azure Computer Vision OCR IronOCR
處理地點 微軟Azure雲 本地,企業內
需要網際網路 是的,每次請求 不是
需要Azure訂閱 不是
定價模型 每次事務(1,000次1.00美元) 永久授權(從$999開始)
按頁計費於多頁的PDF 是 — 每頁 = 1個交易 無按頁成本
免費層級 每月5,000次事務 試用模式(水印)
圖像OCR API AnalyzeAsync(僅限非同步) Read()(同步)
PDF OCR 單獨的Form Recognizer服務 內建 – 同一Read()呼叫
受密碼保護的 PDF 由Form Recognizer提供 input.LoadPdf(path, Password: "x")
可搜尋的 PDF 輸出 手動構造 result.SaveAsSearchablePdf()
多頁TIFF 不支持 input.LoadImageFrames()
自動圖像預處理 不透明的伺服器端,不可配置 Deskew,DeNoise,Contrast,Binarize,Sharpen,Scale
深度噪音去除 不是 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
SHELL

如果專案也使用Form Recognizer進行PDF處理,則也移除該程式包:

dotnet remove package Azure.AI.FormRecognizer
dotnet remove package Azure.AI.FormRecognizer
SHELL

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)
$vbLabelText   $csharpLabel

步驟3:初始化許可證

在應用啟動時新增一次授權密鑰,在任何OCR呼叫之前:

IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
$vbLabelText   $csharpLabel

將密鑰儲存在生產環境的環境變數中:

IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE");
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE");
Imports System

IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE")
$vbLabelText   $csharpLabel

程式碼遷移範例

替換依賴注入的Azure客戶端配置

遵循建議的Azure SDK模式的團隊使用IOptions<AzureComputerVisionOptions>。 此接線從appsettings.json中拉取端點URL和API密鑰,並在每個部署環境中需要有出站網路配置。

Azure Computer Vision 方法:

// 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)()
$vbLabelText   $csharpLabel
// 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
$vbLabelText   $csharpLabel

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)()
$vbLabelText   $csharpLabel
// 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
$vbLabelText   $csharpLabel

從兩個配置類別(選項+客戶端工廠)到單個AddSingleton<IronTesseract>()呼叫的DI接線下降。 cognitiveservices.azure.com的出口防火牆規則全部刪除。 請參考IronTesseract安裝指南為單例實例提供的配置選項。

消除Form Recognizer輪詢迴圈

Form Recognizer的LongRunningOperationWaitUntil.Completed阻塞呼叫執行緒直到雲端作業完成——通常每份文件2–10秒。 為了實現非阻塞行為,團隊編寫UpdateStatusAsync輪詢迴圈,並在輪詢之間提供延遲,增加30–50行沒有OCR邏輯的基礎程式碼。

Azure Computer Vision 方法:

// 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
$vbLabelText   $csharpLabel

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
$vbLabelText   $csharpLabel

輪詢迴圈及其延遲邏輯完全消失。 LoadPdfPages處理頁面範圍選擇——無需單獨的按頁呼叫,也無需事務計數。 PDF輸入指南詳細介紹了流輸入、字節陣列載入和頁面範圍參數。

將Azure字級別結果映射到IronOCR結構化輸出

Azure Computer Vision以多邊形的形式返回字節邊界框,並具有可變頂點數量——通常為四,但不一定。 結果層次結構是float。 讀取單詞位置的程式碼必須處理多邊形頂點陣列,並對信度縮放進行標準化以供任何閾值比較。

Azure Computer Vision 方法:

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
$vbLabelText   $csharpLabel

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
$vbLabelText   $csharpLabel

多邊形到矩形的轉換消失了。 一旦Azure 0.0–1.0的數值乘以100,信度值就能直接匹配——任何現有的閾值邏輯只需這一項調整。 結構化資料輸出指南記錄了完整的層次和座標屬性。 有關信度評分模型的詳細說明,請參見信度分數指南

無需雲端上傳的多頁TIFF處理

Azure Computer Vision的ImageAnalysisClient接受單個圖像。 多幀TIFF文件——常見於文件掃描工作流、傳真存檔及醫學成像管道——要求在上傳前將TIFF分割為單個圖像(每幀一個事務),或者切換到具有單獨配置的Form Recognizer。 這兩種路徑都不乾淨。

Azure Computer Vision 方法:

// 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
$vbLabelText   $csharpLabel

IronOCR方法:

//IronOCRhandles 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;
}
//IronOCRhandles 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 = 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
$vbLabelText   $csharpLabel

每幀事務成本降至零。 System.Drawing幀提取迴圈和其臨時的PNG序列化步驟完全消失。 用於傳真存檔工作流中文件質量不同的情況,TIFF和GIF輸入指南涵蓋了幀選擇選項,圖像質量校正指南記錄了完整的預處理過濾器集。

無速率限制排隊的并行批處理

Azure Computer Vision的S1等級限制吞吐量每秒10次事務。 超過此速率的批處理作業會收到HTTP 429響應。 生產實現需要一個速率限制包裝,一個信號量,或一個排隊層來保持在標準內。IronOCR API是設計上執行緒安全的——每個執行緒建立一個Parallel.ForEach

Azure Computer Vision 方法:

// 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
$vbLabelText   $csharpLabel

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
$vbLabelText   $csharpLabel

信號量、100ms延遲和HTTP 429捕獲塊全被移除。 並行性僅限於CPU核心和可用記憶體,而不是由服務等級限制。 多執行緒範例展示了完整的模式及其時間比較,速度優化指南覆蓋了批量工作負載的引擎配置微調。

對Azure拒絕的低質量掃描進行預處理

Azure Computer Vision執行伺服器端圖片增強,但這是不可察知的,也不可配置。 過於傾斜、過於吵雜或對比度過低的文件返回低置信度結果或空白文字,而無法干預。 IronOCR直接在OcrInput上公開預處理管道。

Azure Computer Vision 方法:

//不是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 YourClassName
    '不是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
$vbLabelText   $csharpLabel

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
$vbLabelText   $csharpLabel

外部預處理依賴——System.Drawing、SkiaSharp或ImageMagick——被移除。 重新上傳和第二次事務成本消失了。 預註釋管道是OcrInput生命週期的一部分,因此在OCR引擎看到圖像之前應用。 圖像過濾器教程逐步展示了每個過濾器及其前/後精度比較。

Azure Computer Vision OCR API到IronOCR映射參考

Azure Computer Vision 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浮點) word.Confidence(0–100雙精度)
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:服務接口通常聲明Task<string>返回型別,因為Azure要求非同步。 調用程式碼、控制器和背景工作者都作為非同步方法編寫。 切換到IronOCR消除了OCR層的非同步需求,但在大程式碼庫中更改每個介面簽名並不總是可行的。

解決方案:將同步的IronOCR調用包裹在Task.Run中以滿足現有介面而不連鎖重構:

// Existing interface — do not change it
public interface IOcrService
{
    Task<string> ReadAsync(string imagePath);
}

// NewIronOCRimplementation — 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);
}

// NewIronOCRimplementation — 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
    Function ReadAsync(imagePath As String) As Task(Of String)
End Interface

' NewIronOCRimplementation — 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
$vbLabelText   $csharpLabel

這是一個有效的中間步驟。非同步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:IronOCRthreshold (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:IronOCRthreshold (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
$vbLabelText   $csharpLabel

問題3:Form Recognizer預建模範字段提取無法直接對應IronOCR

Azure Computer Vision:Form Recognizer的預建發票和收據模型會自動提取命名字段——InvoiceTotal, VendorName, 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
$vbLabelText   $csharpLabel

基於區域的OCR指南涵蓋坐標系統的詳細資訊和多區域批處理。

問題4:缺少hOCR和結構化導出

Azure Computer Vision:Azure不提供hOCR輸出。 需要標準化佈局資料以供下游文件分析工具使用的團隊需要從Azure回應中手動提取邊界框並打造自己的輸出格式。

解決方案:IronOCR一趟調用即可生產符合標準的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")
$vbLabelText   $csharpLabel

問題5:Azure SDK與其他Azure包的版本衝突

Azure Computer Vision:使用多個Azure SDK包(Azure.Core瞬態依賴之間的版本衝突。 Azure SDK的版本控制策略有助於,但並未消除所有衝突,特別是在混合GA和預覽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
SHELL

問題6:掃描文件質量低於Azure接受門檻

Azure Computer Vision:非常低解析度圖片(~150 DPI以下)或嚴重傾斜的掃描返回從Azure伺服器端管道獲得非常少的文字或空文字,沒有任何有關增強的反饋。 呼叫者無法在無外部預處理的情況下改善結果。

解決方案:使用IronOCR的預處理管道在光學字元識別之前準備圖片:

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
$vbLabelText   $csharpLabel

圖像方向校正指南專注於去偏和旋轉檢測。

Azure Computer Vision 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" .
SHELL

識別包含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" .
SHELL

清查項目前確保編碼開始:

  • 實現Azure OCR服務模式的類數量
  • 從OCR呼叫傳播的非同步方法鏈的數量
  • 使用0.0–1.0 Azure比例的任何單詞置信度門檻
  • 識别需要基於區域替換的Form Recognizer預構建模型使用情況(發票、收据、身份)
  • 確認需要按幀上傳的多幀TIFF輸入
  • 檢查Docker及CI/CD配置以備不再需要的Azure出站網路規則

程式碼遷移

  1. 從所有使用的項目中移除Azure.AI.Vision.ImageAnalysis NuGet包
  2. 從所有使用的項目中移除Azure.AI.FormRecognizer NuGet包
  3. 在每個受影響的項目中運行dotnet add package IronOcr
  4. 在應用啟動時新增IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE")
  5. 替換using Azure; using Azure.AI.Vision.ImageAnalysis; with using IronOcr;
  6. 在DI中將services.AddSingleton<IronTesseract>()
  7. 移除AzureComputerVisionOptions或同等配置類別; 刪除appsettings.json Azure OCR部分
  8. 在所有服務類中用ImageAnalysisClient建構注入
  9. OcrInput
  10. 移除所有WaitUntil.Completed模式; 用IronTesseract + DocumentAnalysisClient
  11. 更新單詞信度門檻比較:將所有Azure 0.0–1.0的值乘以100
  12. 將多邊形界限盒存取(word.Height屬性
  13. input.LoadImageFrames(tiffPath)替換按幀上傳的多幀TIFF迴圈
  14. 將Form Recognizer預建模範字段提取轉換為已知文件模板的CropRectangle基於區域OCR
  15. 移除RequestFailedException捕獲塊以應對HTTP 429和5xx; 簡化錯誤處理到僅文件系統和輸入驗證例外

遷移後

  • 驗證純文字提取輸出的匹配或改進20個以上文件的代表性樣本中的Azure結果
  • 確認多頁PDF處理為所有頁面生成文字而無需在監控中逐頁計費計數器
  • 測試多幀TIFF處理返回與源幀數相同的頁面數
  • 驗證單詞置信度值落在0–100的範圍內並且閾值比較行為正確
  • 驗證單詞界限框座標系統與源圖像座標系統正確對齊
  • 運行批量處理路徑並確認其無速率限制錯誤或信號量爭用地完成
  • 在無出站Internet連接的環境中進行測試以確認無Azure端點呼叫
  • 擔保Docker和容器部署成功啟動而無需cognitiveservices.azure.com的出站防火牆規則
  • 檢查DI容器構建是否成功,並且IronTesseract單例被正確解析
  • 驗證IRONOCR_LICENSE環境變數已設置在所有部署環境中,且OCR生成授權的(無水印)輸出

遷移至IronOCR的主要好處

成本變得可預測。 Azure Computer Vision的每次事務計數器不斷運行。 單月高文件量可能超過IronOCR授權的年度成本。 遷移後,OCR預算是一個固定的項目。 處理量可以增長——由於業務擴展、批量重新處理或臨時高峰,而不會產生更大的賬單。IronOCR授權頁面顯示從$999單開發人員授權到2,999美元不限開發者的層選項。

應用棧變得簡化。 移除Azure.AI.FormRecognizer消除了兩個NuGet包、兩個Azure資源配置、兩套憑證以及整個異步輪詢結構。 因為雲端I/O,需要async Task<string>簽名的服務類變成同步方法。 錯誤處理從網路故障、速率限制和服務可用性縮小到文件系統和輸入驗證。 將來的每位開發人員在這個程式碼庫中必要理解的程式碼更少。

文件資料留在組織控制范围內。 遷移之后,OCR處理是一個本地操作。 文件不穿過組織邊界,不顯現在Azure日志中,亦不受限於Microsoft的資料保留或處理政策。 受HIPAA監管的實體、受ITAR管理的承包商、受GDPR監管的組織以及任何具有資料駐留要求的團隊可以在不進行雲端第三方合規審查的情況下處理文件。 Linux部署指南Docker部署指南展示了如何在受限環境中部署IronOCR。

批量吞吐量隨硬體擴展。 Azure S1等級的10 TPS天花板是個硬限,需要排隊、節流或等級升級以繞過。 遷移后,同步的OCR作業充分利用可用CPU核而無需任何服務施加的上限。四核伺服器可同時運行四個IronTesseract實例。 多執行緒範例演示了模式並顯示了與核心數比例擴展的吞吐量。

預處理缺陷可在程式碼中解決。Azure Computer Vision的伺服器端圖像增強是個黑盒。 當掃描返回空白或低置信度輸出時,唯一的選擇是接受或者在重新上傳之前自行制定預處理,而這意味着額外的成本。 IronOCR的OcrInput管道提供Deskew、降低噪音、對比、二值化、縮放、銳化及深度噪音去除作為一級方法。 問題掃描型別變為可調整參數。 預處理特徵頁面列出了完整的過濾器集,並提供了針對每個掃描缺陷適合的過濾器指導。

請注意Azure Computer Vision 和 Tesseract是其各自所有者的註冊商標。 此站點與Google或Microsoft無任何聯繫、認可或贊助。 所有產品名稱、標誌和品牌均為其各自所有者的財產。 比較僅供資訊用途,並反映撰寫時獲得的公開資訊。

常見問題

為什麼我要從Azure Computer Vision OCR遷移到IronOCR?

常見的驅動因素包括消除COM互操作複雜性、更換基於文件的許可證管理、避免按頁收費、啟用Docker/容器部署,以及採取與標準.NET工具整合的NuGet本地工作流。

從Azure Computer Vision OCR遷移到IronOCR時主要的程式碼變更是什麼?

用IronTesseract初始化替代Azure Computer Vision初始化序列,移除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。實例化IronTesseract,調用ocr.Read(input),其中input是PDF路徑或OcrPdfInput,並迭代OcrResult頁面。不需要單獨的PDF渲染管道。

IronOCR如何處理大批量處理中的多執行緒?

IronTesseract可以安全地在每個執行緒中實例化。在Parallel.ForEach或Task池中為每個執行緒建立一個實例,並發併運行OCR,當完成時處置每個實例。不需要全域狀態或鎖定。

IronOCR在文字提取後支持哪些輸出格式?

IronOCR返回結構化結果,包括文字、單詞坐標、信心分數和頁面結構。導出選項包括純文字、可搜索PDF以及下游處理的結構化結果物件。

在擴展工作負載時,IronOCR的價格是否比Azure Computer Vision OCR更具可預測性?

IronOCR使用固定費率的永久性許可證,沒有按頁或按量收費。無論您處理10000頁還是1000萬頁,許可證費用保持不變。團隊和批量許可證選項可在IronOCR定價頁面上找到。

在從Azure Computer Vision OCR遷移到IronOCR之後,我現有的測試會怎麼樣?

遷移後,斷言提取的文字內容的測試應繼續通過。需要更新的測試是驗證API調用模式或COM物件生命週期,這需要反映IronOCR較簡單的初始化和結果模型。

Kannaopat Udonpant
軟體工程師
在成為軟體工程師之前,Kannapat在日本北海道大學完成了環境資源博士學位。在攻讀學位期間,Kannapat還成為車輛機器人實驗室的一員,該實驗室隸屬於生產工程系。在2022年,他憑藉C#技能加入了Iron Software的工程團隊,專注於IronPDF。Kannapat珍視他的工作,因為他能直接向撰寫大部分IronPDF程式碼的開發者學習。除了同儕學習,Kannapat還喜歡在Iron Software工作的社交方面。不寫程式碼或文件時,Kannapat通常在他的PS5上玩遊戲或重看The Last of Us。

Iron 支援團隊

我們線上24小時,每週5天。
聊天
電子郵件
給我打電話