Przejdź do treści stopki
FILMY

Migracja z Azure Computer Vision OCR do

Ten przewodnik przeprowadza programistów .NET przez proces zastępowaniaOCR Azure Computer Visionbiblioteką IronOCR, lokalną biblioteką OCR, która przetwarza dokumenty lokalnie bez infrastruktury chmurowej. Obejmuje on mechaniczne etapy wymiany pakietu NuGet i przestrzeni nazw, tłumaczenie modelu asynchronicznego odpytywania Azure na synchroniczne wywołania lokalne oraz obsługę konkretnych wzorców — podłączanie wstrzykiwania zależności, pętle odpytywania Form Recognizer, przetwarzanie wielostronicowych plików TIFF oraz przepustowość partii — które wymagają największej uwagi podczas migracji.

Dlaczego warto przejść zAzure Computer VisionOCR

Argumenty przemawiające za migracją nie są abstrakcyjne. Zespoły zazwyczaj podejmują decyzję po napotkaniu jednego lub kilku konkretnych problemów operacyjnych związanych z usługąAzure Computer Visionw środowisku produkcyjnym.

Zarządzanie punktami końcowymi i kluczami API nigdy się nie kończy. Każde środowisko wdrożeniowe — programistyczne, testowe, produkcyjne, odzyskiwania po awarii — wymaga przydzielonego zasobu Azure Cognitive Services, adresu URL punktu końcowego i co najmniej jednego klucza API. Klucze muszą być rotowane. Punkty końcowe zmieniają się, gdy zasoby są przenoszone do innych regionów. Kazde srodowisko potrzebuje zasad zapory wychodzacej, aby dotrzec do cognitiveservices.azure.com. Obszar działania rozszerza się wraz z każdym środowiskiem i programistą w zespole.IronOCR zastępuje to wszystko jednym kluczem licencyjnym w postaci ciągu znaków, ustawianym jednorazowo podczas uruchamiania aplikacji, bez harmonogramu rotacji i bez wymogu połączenia z siecią zewnętrzną.

Rozliczenie za stronę jest niekorzystne w przypadku dokumentów wielostronicowych. UsługaAzure Computer Visiontraktuje każdą stronę pliku PDF jako oddzielną transakcję. 20-stronicowa umowa to 20 rozliczanych połączeń. Przy stawce 1,00 USD za 1000 transakcji zespół przetwarzający 50 000 wielostronicowych dokumentów miesięcznie, średnio po 4 strony każdy, generuje 200 000 transakcji — 195 USD miesięcznie po wyczerpaniu limitu bezpłatnego, 2340 USD rocznie. To jest punkt rentownosci przeciwko IronOCR Lite ($999) w mniej niz cztery miesiace, po czym kazda dodatkowa strona jest bezplatna.

Propagacja asynchroniczna rozprzestrzenia się na cały stos wywołań. UsługaAzure Computer Visionnie może zwracać wyników synchronicznie — operacje wejścia/wyjścia w chmurze wiążą się z opóźnieniami sieciowymi. Wymaganie await na AnalyzeAsync wymusza, aby kazda metoda wywolujaca byla asynchroniczna, propagujac wzorzec z warstwy uslug do kontrolerow, pracownikow w tle i kazdego synchronicznego kodu, ktory musi zostac przekonwertowany w tym celu. Operacje Form Recognizer oparte na cyklach sondowania to pogarszaja: WaitUntil.Completed blokuje watek, a prawdziwie nieblokujace dzialanie wymaga recznego zarzadzania cyklami UpdateStatusAsync.

Dokumenty opuszczają sieć przy każdym wywołaniu. Dla zespołów przetwarzających informacje zdrowotne objęte HIPAA, dokumenty obronne podlegające ITAR, komunikację objętą tajemnicą adwokacką lub jakąkolwiek kategorię dokumentów podlegającą zasadom dotyczącym lokalizacji danych, obowiązkowa transmisja do chmury jest niezgodnością architektoniczną, a nie kompromisem. Nie ma trybu Azure Computer Vision, który pozwalałby uniknąć przesyłania treści dokumentów do centrów danych Microsoftu.

Limity szybkości tworzą pułapy przepustowości. Poziom S1 usługiAzure Computer Visionma limit 10 transakcji na sekundę. Zadanie wsadowe przetwarzające 3600 obrazów na godzinę osiąga dokładnie limit. Przekroczenie tego limitu powoduje zwrot odpowiedzi HTTP 429, co wymaga logiki ponownej próby z wykładniczym opóźnieniem w każdej ścieżce wywołania. Ograniczeniem przepustowości IronOCR jest sprzęt hostingowy — nie ma ograniczeń narzuconych przez usługę, nie jest wymagana infrastruktura ponownych prób.

Rozpoznawanie obrazu OCR oraz OCR PDF wymaga dwoch oddzielnych uslug. Standardowy obraz OCR uzywa ImageAnalysisClient z Azure.AI.Vision.ImageAnalysis. Pelne przetwarzanie PDF wymaga DocumentAnalysisClient z Azure.AI.FormRecognizer.DocumentAnalysis - inny pakiet NuGet, inny zasob Azure, inny punkt koncowy i inna struktura wynikow. Każda aplikacja przetwarzająca zarówno obrazy, jak i pliki PDF ponosi ten podwójny koszt konfiguracji.IronOCR obsluguje oba za pomoca IronTesseract.Read() i jednego OcrInput ladowacza.

Podstawowy problem

// 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 aAzure Computer VisionOCR: porównanie funkcji

Poniższa tabela przedstawia funkcje najbardziej istotne dla zespołów rozważających tę migrację.

Funkcja OCR Azure Computer Vision IronOCR
Miejsce przetwarzania Chmura Microsoft Azure Lokalne, na miejscu
Wymagane połączenie z Internetem Tak, każde zlecenie Nie
Wymagana subskrypcja Azure Tak Nie
Model cenowy Za transakcję (1,00 USD za 1000) Licencja wieczysta (z $999)
Rozliczenie za stronę w przypadku wielostronicowych plików PDF Tak — każda strona = 1 transakcja Brak kosztów za stronę
Wersja bezpłatna 5 000 transakcji miesięcznie Tryb próbny (z znakiem wodnym)
API OCR AnalyzeAsync (tylko async) Read() (synchroniczne)
OCR PDF Oddzielna usługa rozpoznawania formularzy Wbudowane, to samo wywolanie Read()
Plik PDF chroniony hasłem Za pośrednictwem Form Recognizer input.LoadPdf(path, Password: "x")
Wynik w formacie PDF z możliwością wyszukiwania Ręczne tworzenie result.SaveAsSearchablePdf()
Wielostronicowy plik TIFF Nieobsługiwane input.LoadImageFrames()
Automatyczne przetwarzanie wstępne obrazów Nieprzejrzysta strona serwerowa, niekonfigurowalna Wyrównanie, usuwanie szumów, kontrast, binarizacja, wyostrzanie, skalowanie
Głębokie usuwanie szumów Nie input.DeepCleanBackgroundNoise()
Odczytywanie BarCode podczas OCR Oddzielna funkcja analizy obrazu ocr.Configuration.ReadBarCodes = true
OCR oparte na regionie Nie bezpośrednio (ręczne przycięcie przed przesłaniem) CropRectangle na OcrInput
Limity szybkości 10 TPS na poziomie S1 Tylko sprzęt
Wymagana logika ponownej próby Tak (HTTP 429, 5xx) Nie
Wdrożenie w środowisku izolowanym Niemożliwe W pełni obsługiwane
Obsługiwane języki 164+ (zarządzane przez serwer) 125+ (pakiety językowe NuGet)
Wielojęzyczne tłumaczenie symultaniczne Tak Tak (OcrLanguage.French + OcrLanguage.German)
Ramki wyznaczające słowa Wielokąt (zmienna liczba wierzchołków) Prostokąt (x, y, szerokość, wysokość)
Ocena pewności Stawka za słowo (0,0–1,0) Za słowo i ogólna (w skali 0–100)
eksport hOCR Nie result.SaveAsHocrFile()
Strukturalna hierarchia wyników Bloki / Wiersze / Słowa Strony / Akapity / Wiersze / Słowa / Znaki
Kompatybilność z platformą .NET .NET Standard 2.0+ .NET Framework 4.6.2+, .NET Core, .NET 5–9
Wielopłatformowe Windows, Linux, macOS (przez chmurę) Windows, Linux, macOS, Docker, ARM64
Wsparcie komercyjne Plany wsparcia Azure Wsparcie IronOCR wliczone w cenę licencji

Szybki start: Migracja zOCR Azure Computer Visiondo IronOCR

Krok 1: Zastąp pakiet NuGet

Usuń pakiet Azure Computer Vision:

dotnet remove package Azure.AI.Vision.ImageAnalysis
dotnet remove package Azure.AI.Vision.ImageAnalysis
SHELL

Jeśli w projekcie wykorzystywany jest również pakiet Form Recognizer do przetwarzania plików PDF, należy usunąć również ten pakiet:

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

Zainstaluj IronOCR z NuGet:

dotnet add package IronOcr

Krok 2: Aktualizacja przestrzeni nazw

// 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

Krok 3: Inicjalizacja licencji

Dodaj klucz licencyjny jednorazowo podczas uruchamiania aplikacji, przed jakimkolwiek wywołaniem funkcji OCR:

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

W środowisku produkcyjnym należy zapisać klucz w zmiennej środowiskowej:

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

Przykłady migracji kodu

Zastąpienie konfiguracji klienta Azure z wstrzykiwaniem zależności

Zespoly, ktore stosuja zalecany wzorzec Azure SDK, rejestruja ImageAnalysisClient w pojemniku DI za pomoca IOptions<AzureComputerVisionOptions> lub bezposredniego wiazania IConfiguration. To polaczenie pobiera adresy URL punktow koncowych i klucze API z appsettings.json oraz wymaga konfiguracji sieci wychodzacej w kazdym srodowisku wdrozenia.

Podejście 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

Podejście 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

Przewod DI spada z dwoch klas konfiguracyjnych (opcje + fabryka klientow) do jednego wywolania AddSingleton<IronTesseract>(). Sekcja Azure appsettings.json, odniesienia kluczy do skarbcow oraz zasady zapory wychodzacej dla cognitiveservices.azure.com zostaly usuniete. Opcje konfiguracyjne dostępne dla instancji singletonowej można znaleźć w przewodniku konfiguracji IronTesseract.

Eliminacja pętli odpytywania modułu rozpoznawania formularzy

Zmienne Azure Form Recognizer AnalyzeDocumentAsync zwraca LongRunningOperation. WaitUntil.Completed blokuje watek wywolujacy, dopoki zadanie w chmurze nie zakonczy sie - zazwyczaj 2–10 sekund na dokument. Aby uzyskac nieblokujace dzialanie, zespoly pisza petle sondowania UpdateStatusAsync z opoznieniem miedzy sondowaniami, dodajac 30–50 lini kodu infrastruktury nie zawierajacego logiki OCR.

Podejście 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

Podejście 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

Pętla sondowania i jej logika opóźnienia znikają całkowicie. LoadPdfPages obsluguje wybor zakresu stron - brak oddzielnego wywolania dla kazdej strony, brak zliczania transakcji. Przewodnik w formacie PDF zawiera szczegółowe informacje na temat danych wejściowych strumieniowych, ładowania tablic bajtów oraz parametrów zakresu stron.

Mapowanie wyników Azure na poziomie słów do ustrukturyzowanego wyniku IronOCR

Azure Computer Vision zwraca prostokąty ograniczające słowa jako wielokąty o zmiennej liczbie wierzchołków — zazwyczaj czterech, ale nie jest to gwarantowane. Hierarchia wynikow to Blocks → Lines → Words, a pewnosc to float na skali 0,0–1,0. Kod odczytujący pozycje słów musi obsługiwać tablicę wierzchołków wielokąta i normalizować skalę pewności dla wszelkich porównań progowych.

Podejście 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

Podejście 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

Konwersja wielokąta na prostokąt znika. Wartości pewności są bezpośrednio zgodne po pomnożeniu wartości Azure 0,0–1,0 przez 100 — wszelkie istniejące logiki progowe wymagają tej jednej korekty. Przewodnik dotyczący struktury danych wyjściowych dokumentuje pełną hierarchię i właściwości współrzędnych. W przypadku modelu oceny pewności, zapoznaj się z przewodnikiem po ocenach pewności.

Przetwarzanie wielostronicowych plików TIFF bez przesyłania do chmury

ZmienneAzure Computer VisionImageAnalysisClient akceptuje pojedyncze obrazy. Pliki TIFF zawierające wiele klatek — powszechnie stosowane w procesach skanowania dokumentów, archiwach faksów i systemach obrazówania medycznego — wymagają albo podzielenia pliku TIFF na pojedyncze obrazy przed przesłaniem (jedna transakcja na klatkę), albo przełączenia się na Form Recognizer z jego oddzielną konfiguracją. Żadna z dróg nie jest czysta.

Podejście 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

Podejście 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
$vbLabelText   $csharpLabel

Koszt transakcji na klatkę spada do zera. Petla ekstrakcji System.Drawing i jej tymczasowy etap serializacji PNG sa calkowicie wyeliminowane. W przypadku procesów archiwizacji faksów, w których jakość dokumentów jest zróżnicowana, przewodnik dotyczący plików wejściowych TIFF i GIF obejmuje opcje wyboru klatek, a przewodnik dotyczący korekcji jakości obrazu dokumentuje pełny zestaw filtrów przetwarzania wstępnego.

Równoległe przetwarzanie wsadowe bez kolejkowania z ograniczeniem szybkości

Poziom S1 usługiAzure Computer Visionogranicza przepustowość do 10 transakcji na sekundę. Zadania wsadowe, które przekraczają tę szybkość, otrzymują odpowiedzi HTTP 429. Implementacje produkcyjne wymagaja opakowania ograniczajacego tempo, semafora lub wywazenia, aby pozostac ponizej limitu. API IronOCR jest domyslnie bezpieczne dla watkow - utworz jeden IronTesseract instancje na watek i uruchom je za pomoca Parallel.ForEach.

Podejście 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

Podejście IronOCR:

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

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

Semafor, opóźnienie 100 ms oraz blok przechwytujący HTTP 429 zostały usunięte. Równoległość jest ograniczona jedynie przez rdzenie procesora i dostępną pamięć, a nie przez poziom usługi. Przykład wielowątkowości pokazuje pełny wzorzec wraz z porównaniami czasowymi, a przewodnik po optymalizacji prędkości obejmuje dostosowywanie konfiguracji silnika dla obciążeń wsadowych.

Przetwarzanie wstępne skanów niskiej jakości odrzucanych przez platformę Azure

Azure Computer Vision wykonuje poprawianie obrazów po stronie serwera, ale jest to proces nieprzejrzysty i niekonfigurowalny. Dokumenty, które są zbyt przekrzywione, zbyt zaszumione lub mają zbyt niski kontrast, zwracają wyniki o niskim poziomie pewności lub pusty tekst bez możliwości interwencji.IronOCR bezposrednio otwiera dostep do rurociagu wstepnego przetwarzania na OcrInput.

Podejście Azure Computer Vision:

// Nie 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);

    // Nie way to know if the server applied enhancement
    // Nie 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;
}
// Nie 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);

    // Nie way to know if the server applied enhancement
    // Nie 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
    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)

            ' Nie way to know if the server applied enhancement
            ' Nie 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

Podejście 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

Zewnetrzna zaleznosc od wstepnego przetwarzania - System.Drawing, SkiaSharp lub ImageMagick - zostala usunieta. Koszty ponownego przesłania i drugiej transakcji znikają. Przeplyw przetwarzania wstepnego to czesc OcrInput cyklu zycia, wiec jest stosowany przed tym jak silnik OCR zobaczy obraz. Samouczek dotyczący filtrów obrazu omawia każdy filtr wraz z porównaniem dokładności przed i po zastosowaniu.

Odnośnik do dokumentacji APIOCR Azure Computer Visiondo IronOCR

Azure Computer Vision Odpowiednik IronOCR
ImageAnalysisClient IronTesseract
new AzureKeyCredential(apiKey) IronOcr.License.LicenseKey = key
new Uri(endpoint) Nie jest wymagane
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 liczba zmiennoprzecinkowa) word.Confidence (0–100 liczba podwojna)
word.BoundingPolygon word.X, word.Y, word.Width, word.Height
DocumentAnalysisClient IronTesseract + OcrInput
AnalyzeDocumentAsync(WaitUntil.Completed, "prebuilt-read", stream) ocr.Read(input) z input.LoadPdf(path)
operation.Value.Pages result.Pages
page.Lines[i].Content result.Lines[i].Text
UpdateStatusAsync() petla sondowania Nie jest wymagane — wynik synchroniczny
RequestFailedException (status 429) Nie dotyczy — brak limitów stawek
RequestFailedException (status 5xx) Nie dotyczy — brak błędów usługi
VisualFeatures.Read flaga wyliczenia Domniemany — Read() zawsze wyodrebnia tekst
Form Recognizer prebuilt-read model Wbudowany silnik OCR (bez wyboru modelu)
Adres URL punktu koncowego Azure w appsettings.json Nie jest wymagane
Procedury rotacji kluczy API Nie jest wymagane

Typowe problemy związane z migracją i ich rozwiązania

Problem 1: Kontrakty interfejsu asynchronicznego, których nie można zmieniać

Azure Computer Vision: Interfejsy uslug czesto deklaruja typy zwrotne Task<string> poniewaz Azure wymusza async. Kod wywołujący, kontrolery i procesy działające w tle są napisane jako metody asynchroniczne. Przejście na IronOCR eliminuje potrzebę stosowania asynchroniczności w warstwie OCR, ale zmiana każdej sygnatury interfejsu nie zawsze jest wykonalna w dużym kodzie źródłowym.

Rozwiazanie: Owin wywolanie synchroniczne IronOCR w Task.Run aby zaspokoic istniejacy interfejs bez kaskadowych refactorow:

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

Jest to prawidłowy krok pośredni. Przewodnik po asynchronicznym OCR obejmuje wbudowaną obsługę asynchroniczności w IronOCR dla scenariuszy, w których preferowana jest pełna integracja asynchroniczna.

Problem 2: Logika progu ufności dająca błędne wyniki

Azure Computer Vision: Azure zwraca pewnosc slowa jako float miedzy 0,0 a 1,0. Istniejacy kod filtrowania uzywa progow takich jak word.Confidence > 0.85f. Po migracji te porównania zawsze dają wynik fałszywy, ponieważ poziom pewności IronOCR wynosi 0–100, a nie 0–1.

Rozwiązanie: Pomnóż istniejące progi Azure przez 100 podczas aktualizacji logiki filtrowania:

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

Problem 3: Wbudowany model rozpoznawania formularzy do ekstrakcji pól nie ma bezpośredniego odpowiednika w IronOCR

Azure Computer Vision: Wbudowane modele faktur i odbiorow Form Recognizer automatycznie wyciagaja nazwane pola - InvoiceTotal, VendorName, InvoiceDate - bez okreslenia, gdzie te pola pojawiaja sie na stronie. Logika ekstrakcji jest wbudowana w model Azure.

Rozwiazanie: Zamien ekstrakcje pol oparte na modelu na OCR oparty na regionach za pomoca CropRectangle. Wymaga to znajomości układu dokumentu, ale większość rzeczywistych wdrożeń ma już ustalone szablony:

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

Przewodnik po OCR opartym na regionach obejmuje szczegóły dotyczące układów współrzędnych oraz przetwarzanie wsadowe wielu regionów.

Problem 4: Brak funkcji hOCR i eksportu ustrukturyzowanego

Azure Computer Vision: Azure nie zapewnia danych wyjściowych hOCR. Zespoły, które potrzebują znormalizowanych danych dotyczących układu do narzędzi analizy dokumentów, ręcznie wyodrębniają ramki z odpowiedzi Azure i tworzą własny format wyjściowy.

Rozwiązanie:IronOCR generuje zgodny ze standardami hOCR za pomocą jednego wywołania:

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

Problem 5: Konflikt wersji Azure SDK z innymi pakietami Azure

Azure Computer Vision: Projekty korzystajace z wielu pakietow Azure SDK (Azure.Storage.Blobs, Azure.Identity, Azure.KeyVault.Secrets) moga napotkac konflikty wersji miedzy przejsciowymi zaleznosciami Azure.Core. Polityka wersjonowania monorepo w Azure SDK pomaga, ale nie eliminuje wszystkich konfliktów, szczególnie w przypadku mieszania wersji GA i wersji zapoznawczych SDK.

Rozwiazanie: Usuniecie Azure.AI.Vision.ImageAnalysis i Azure.AI.FormRecognizer eliminuje te pakiety SDK z drzewa zaleznosci. Jesli projekt uzywa Azure tylko do OCR, caly zbior zaleznosci Azure.* zostaje usuniety. Jeśli pozostałe usługi Azure zostaną zachowane, zmniejszona liczba pakietów ogranicza obszar potencjalnych konfliktów:

# 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

Problem 6: Jakość zeskanowanych dokumentów poniżej progu akceptacji platformy Azure

Azure Computer Vision: Obrazy o bardzo niskiej rozdzielczości (poniżej ~150 DPI) lub mocno przekrzywione skany zwracają minimalną ilość tekstu lub pozostawiają pole puste w potoku po stronie serwera Azure, bez informacji zwrotnej o tym, jakie ulepszenie zostało podjęte. Użytkownik nie ma możliwości poprawienia wyniku bez zewnętrznego przetwarzania wstępnego.

Rozwiązanie: Użyj potoku przetwarzania wstępnego IronOCR, aby przygotować obraz przed 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
$vbLabelText   $csharpLabel

Przewodnik dotyczący korekcji orientacji obrazu obejmuje w szczególności wykrywanie przekrzywienia i obrotu.

Lista kontrolna migracji funkcji OCR w usłudze Azure Computer Vision

Przed migracją

Zlokalizuj wszystkie miejsca użyciaAzure Computer Visioni Form Recognizer w kodzie źródłowym:

# 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

Zidentyfikuj pliki konfiguracyjne zawierające punkty końcowe i klucze 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

Elementy do sprawdzenia przed rozpoczęciem kodowania:

  • Liczba klas implementujących wzorce usług Azure OCR
  • Liczba łańcuchów metod asynchronicznych propagowanych z wywołań OCR
  • Określ progi pewności słów, korzystając ze skali Azure w zakresie 0,0–1,0
  • Zidentyfikuj użycie gotowych modeli rozpoznawania formularzy (faktura, paragon, dokument tożsamości), wymagających zastąpienia w zależności od regionu
  • Identyfikacja wieloklatkowych plików TIFF, które są obecnie dzielone w celu przesyłania poszczególnych klatek
  • Sprawdź konfiguracje Docker i CI/CD pod kątem reguł sieciowych Azure dla ruchu wychodzącego, które nie będą już potrzebne

Migracja kodu

  1. Usun pakiet NuGet Azure.AI.Vision.ImageAnalysis ze wszystkich projektow, ktore go uzywaja
  2. Usun pakiet NuGet Azure.AI.FormRecognizer ze wszystkich projektow, ktore go uzywaja
  3. Uruchom dotnet add package IronOcr w kazdym dotknietym projekcie
  4. Dodaj IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE") podczas uruchamiania aplikacji
  5. Zastąp using Azure; using Azure.AI.Vision.ImageAnalysis; with using IronOcr;
  6. Zarejestruj IronTesseract jako singleton w DI (services.AddSingleton<IronTesseract>())
  7. Usun klasy konfiguracji AzureComputerVisionOptions lub ich odpowiedniki; usun czesci Azure OCR appsettings.json
  8. Zamien wstrzykiwanie konstruktora ImageAnalysisClient na wstrzykiwanie IronTesseract we wszystkich klasach uslugowych
  9. Zamien wywolania AnalyzeAsync(BinaryData.FromStream(stream), VisualFeatures.Read) na ocr.Read(imagePath) lub ocr.Read(input) z OcrInput, jak to jest odpowiednie
  10. Usun wszystkie petle sondowania UpdateStatusAsync i wzorce WaitUntil.Completed; zamien DocumentAnalysisClient na IronTesseract + OcrInput.LoadPdf()
  11. Zaktualizuj porównania progów pewności WORD: pomnóż wszystkie wartości Azure z przedziału 0,0–1,0 przez 100
  12. Zamien dostep do ograniczajacego prostokata wielokatnego (word.BoundingPolygon.Min/Max) na bezposredni word.X, word.Y, word.Width, word.Height
  13. Zamien petle przesylania wieloklatkowego TIFF dla kazdej klatki na input.LoadImageFrames(tiffPath)
  14. Przekonwertuj ekstrakcje pol modelu wbudowanego Form Recognizer na OCR oparty na regionach bazowany na CropRectangle dla znanych szablonow dokumentow
  15. Usun bloki try-catch RequestFailedException dla HTTP 429 i 5xx; uproszczona obsługa błędów odnosząca się jedynie do wyjątków związanych z systemem plików i walidacją danych wejściowych

Po migracji

  • Sprawdź, czy wyniki ekstrakcji zwykłego tekstu są zgodne z wynikami Azure lub lepsze od nich dla reprezentatywnej próbki ponad 20 dokumentów
  • Sprawdź, czy przetwarzanie wielostronicowych plików PDF generuje tekst dla wszystkich stron bez liczników rozliczeniowych na stronę w monitorowaniu
  • Test przetwarzania plików TIFF zawierających wiele ramek zwraca taką samą liczbę stron, jak liczba ramek w pliku źródłowym
  • Sprawdź, czy wartości pewności słów mieszczą się w przedziale 0–100 oraz czy porównania progowe działają poprawnie
  • Sprawdź, czy współrzędne ramki wycinkowej WORD są prawidłowo dopasowane do układu współrzędnych obrazu źródłowego
  • Uruchom ścieżkę przetwarzania wsadowego i upewnij się, że zakończy się ona bez błędów związanych z ograniczeniami szybkości lub konfliktami semaforów
  • Przetestuj w środowisku bez dostępu do Internetu, aby upewnić się, że nie dochodzi do wywołań punktów końcowych platformy Azure
  • Potwierdz uruchamianie wdrozen Docker i kontenerow bez zasad zapory wychodzacej dla cognitiveservices.azure.com
  • Sprawdz, czy konstrukcja pojemnika DI przebiega poprawnie i singleton IronTesseract jest prawidlowo rozwiazany
  • Zweryfikuj, czy zmienna srodowiskowa IRONOCR_LICENSE jest ustawiona we wszystkich srodowiskach wdrozenia i czy OCR produkuje licencjonowane (bez znaku wodnego) wyniki

Kluczowe korzyści z migracji do IronOCR

Koszt staje się przewidywalny. Licznik transakcji w usłudzeAzure Computer Visiondziała w sposób ciągły. Wystarczy jeden miesiąc intensywnego przetwarzania dokumentów, aby przekroczyć roczny koszt licencji IronOCR. Po migracji budżet na OCR jest stałą pozycją. Wolumin przetwarzania moze rosnac - z powodu rozbudowy biznesu, przetwarzania zbiorczego lub tymczasowych skokow - bez generowania wiekszej faktury. Strona licencjonowania IronOCR pokazuje opcje licencji tier od $999 dla jednoprogramowej licencji do $2,999 dla nieograniczonej liczby deweloperow.

Stos aplikacji staje sie prostszy. Usuniecie Azure.AI.Vision.ImageAnalysis i Azure.AI.FormRecognizer eliminuje dwa pakiety NuGet, dwie konfiguracje zasobow Azure, dwa zestawy uwierzytelnien i cala infrastrukture asynchronicznego sondowania. Klasy uslugowe, ktore wczesniej wymagaly sygnatury async Task<string> z powodu I/O w chmurze, staja sie metodami synchronicznymi. Obsługa błędów obejmuje zarówno awarie sieciowe, ograniczenia szybkości i dostępność usług, jak i system plików oraz sprawdzanie poprawności danych wejściowych. Każdy przyszły programista pracujący w tym kodzie ma mniej kodu do zrozumienia.

Dane dokumentów pozostają pod kontrolą organizacji. Po migracji przetwarzanie OCR odbywa się lokalnie. Dokumenty nie przekraczają granic organizacyjnych, nie pojawiają się w telemetrii platformy Azure i nie podlegają zasadom firmy Microsoft dotyczącym przechowywania lub przetwarzania danych. Podmioty objęte ustawą HIPAA, wykonawcy ITAR, organizacje podlegające przepisom RODO oraz wszelkie zespoły, które muszą spełniać wymogi dotyczące lokalizacji danych, mogą przetwarzać dokumenty bez konieczności przeprowadzania kontroli zgodności przez zewnętrznego dostawcę usług w chmurze. Przewodnik wdrażania w systemie Linux oraz przewodnik wdrażania w Dockerze pokazują, jak wdrożyćIronOCR w środowiskach o ograniczonym dostępie.

Przepustowość partii skaluje się w zależności od sprzętu. Limit 10 TPS w warstwie S1 platformy Azure jest sztywnym ograniczeniem, które wymaga stosowania kolejek, ograniczania przepustowości lub aktualizacji warstwy w celu obejścia tego ograniczenia. Po migracji, jednoczesne zadania OCR nasycaja dostepne rdzenie CPU bez zadnego ograniczenia narzucanego przez usluge. Czteroordzeniowy serwer moze jednoczesnie uruchomic cztery instancje IronTesseract. Przykład wielowątkowości ilustruje ten wzorzec i pokazuje skalowanie przepustowości w zależności od liczby rdzeni.

Usterki związane z przetwarzaniem wstępnym można rozwiązać w kodzie. Ulepszanie obrazów po stronie serwera wAzure Computer Visionjest procesem typu "czarna skrzynka". Gdy skanowanie zwraca puste lub niskiej pewnosci wyniki, jedyne opcje to ich zaakceptowanie lub przetworzenie zewnetrzne przed ponownym przeslaniem, generujac dodatkowe koszty. Rurociag OcrInputIronOCR otwiera deskew, denoise, kontrast, binaryzacje, skale, ostrzenie oraz usuwanie szumow jako metody pierwszorzędne. Problematyczne typy skanowania stają się parametrami, które można dostosować. Strona poświęcona funkcjom przetwarzania wstępnego zawiera pełną listę filtrów wraz z wskazówkami, które filtry służą do usuwania poszczególnych błędów skanowania.

Zwróć uwagęAzure Computer Vision i Tesseract są zarejestrowanymi znakami towarowymi ich odpowiednich właścicieli. Ta strona nie jest powiązana, zatwierdzona ani sponsorowana przez Google ani Microsoft. Wszystkie nazwy produktów, logo i marki są własnością ich odpowiednich właścicieli. Porównania mają charakter wyłącznie informacyjny i odzwierciedlają informacje dostępne publicznie w momencie pisania.

Często Zadawane Pytania

Dlaczego warto przejść z Azure Computer Vision OCR na IronOCR?

Typowe czynniki motywujące to eliminacja złożoności interoperacyjności COM, zastąpienie zarządzania licencjami opartego na plikach, uniknięcie rozliczeń za stronę, umożliwienie wdrażania w Dockerze/kontenerach oraz przyjęcie natywnego dla NuGet przepływu pracy, który integruje się ze standardowymi narzędziami .NET.

Jakie są główne zmiany w kodzie podczas migracji z Azure Computer Vision OCR do IronOCR?

Zastąp sekwencje inicjalizacji Azure Computer Vision instancjonowaniem IronTesseract, usuń zarządzanie cyklem życia COM (jawne wzorce Create/Load/Close) i zaktualizuj nazwy właściwości wyników. W rezultacie znacznie zmniejsza się liczba powtarzających się linii kodu.

Jak zainstalować IronOCR, aby rozpocząć migrację?

Uruchom polecenie „Install-Package IronOcr” w konsoli menedżera pakietów lub „dotnet add package IronOcr” w interfejsie CLI. Pakiety językowe są oddzielnymi pakietami: na przykład „dotnet add package IronOcr.Languages.French” dla języka francuskiego.

Czy IronOCR dorównuje dokładnością OCR rozwiązaniu Azure Computer Vision OCR w przypadku standardowych dokumentów biznesowych?

IronOCR zapewnia wysoką dokładność w przypadku standardowych treści biznesowych, w tym faktur, umów, paragonów i formularzy wypełnionych na komputerze. Filtry przetwarzania wstępnego obrazu (prostowanie, usuwanie szumów, wzmacnianie kontrastu) dodatkowo poprawiają rozpoznawanie w przypadku pogorszonej jakości danych wejściowych.

W jaki sposób IronOCR obsługuje dane językowe, które Azure Computer Vision OCR instaluje oddzielnie?

Dane językowe w IronOCR są dystrybuowane jako pakiety NuGet. Polecenie „dotnet add package IronOcr.Languages.German” instaluje obsługę języka niemiećkiego. Nie wymaga to ręcznego umieszczania plików ani podawania ścieżek katalogów.

Czy migracja z Azure Computer Vision OCR do IronOCR wymaga zmian w infrastrukturze wdrożeniowej?

IronOCR wymaga mniej zmian w infrastrukturze niż Azure Computer Vision OCR. Nie ma ścieżek binarnych SDK, lokalizacji plików licencyjnych ani konfiguracji serwerów licencyjnych. Pakiet NuGet zawiera kompletny silnik OCR, a klucz licencyjny jest ciągiem znaków ustawionym w kodzie aplikacji.

Jak skonfigurować licencjonowanie IronOCR po migracji?

W kodzie uruchamiającym aplikację przypisz IronOcr.License.LicenseKey = „YOUR-KEY”. W Dockerze lub Kubernetesie zapisz klucz jako zmienną środowiskową i odczytaj go podczas uruchamiania. Użyj License.IsValidLicense do sprawdzenia ważności przed przyjęciem ruchu.

Czy IronOCR może przetwarzać pliki PDF w taki sam sposób jak Azure Computer Vision?

Tak. IronOCR odczytuje zarówno natywne, jak i zeskanowane pliki PDF. Należy utworzyć instancję IronTesseract, wywołać ocr.Read(input), gdzie input jest ścieżką do pliku PDF lub obiektem OcrPdfInput, a następnie iterować strony OcrResult. Nie jest wymagany oddzielny proces renderowania plików PDF.

W jaki sposób IronOCR radzi sobie z wątkami podczas przetwarzania dużych ilości danych?

IronTesseract można bezpiecznie instancjonować dla każdego wątku. Uruchom jedną instancję na wątek w Parallel.ForEach lub puli zadań, uruchom OCR równolegle i usuń każdą instancję po zakończeniu. Nie jest wymagany żaden stan globalny ani blokowanie.

Jakie formaty wyjściowe obsługuje IronOCR po wyodrębnieniu tekstu?

IronOCR zwraca ustrukturyzowane wyniki, w tym tekst, współrzędne słów, wyniki pewności i strukturę strony. Opcje eksportu obejmują zwykły tekst, PDF z możliwością wyszukiwania oraz obiekty wyników ustrukturyzowanych do dalszego przetwarzania.

Czy ceny IronOCR są bardziej przewidywalne niż ceny Azure Computer Vision OCR w przypadku skalowania obciążeń?

IronOCR stosuje licencję wieczystą z opłatą ryczałtową, bez opłat za stronę lub wolumen. Niezależnie od tego, czy przetwarzasz 10 000, czy 10 milionów stron, koszt licencji pozostaje stały. Opcje licencji wolumenowych i zespołowych znajdują się na stronie z cennikiem IronOCR.

Co stanie się z moimi istniejącymi testami po migracji z Azure Computer Vision OCR do IronOCR?

Testy sprawdzające wyodrębnioną treść tekstową powinny nadal przechodzić pomyślnie po migracji. Testy weryfikujące wzorce wywołań API lub cykl życia obiektów COM będą wymagały aktualizacji, aby odzwierciedlały prostszy model inicjalizacji i wyników IronOCR.

Kannaopat Udonpant
Inżynier oprogramowania
Zanim stał się inżynierem oprogramowania, Kannapat ukończył doktorat z zasobów środowiskowych na Uniwersytecie Hokkaido w Japonii. W czasie studiowania, Kannapat również został członkiem Laboratorium Robotyki Pojazdów, które jest częścią Wydziału Inżynierii Bioprodukcji. W 2022 roku wykorzystał swoje umiejętności w ...
Czytaj więcej

Zespół wsparcia Iron

Jesteśmy online 24 godziny, 5 dni w tygodniu.
Czat
E-mail
Zadzwoń do mnie