Passer au contenu du pied de page
VIDéOS

Migrer d'Azure Computer Vision OCR vers

Ce guide accompagne les développeurs .NET dans le remplacement d'Azure Computer Vision OCR par IronOCR , une bibliothèque OCR sur site qui traite les documents localement sans infrastructure cloud. Il décrit les étapes mécaniques du remplacement du package NuGet et des espaces de noms, de la traduction du modèle d'interrogation asynchrone d'Azure en appels locaux synchrones et de la gestion des modèles spécifiques — câblage d'injection de dépendances, boucles d'interrogation de Form Recognizer, traitement TIFF multipage et débit par lots — qui requièrent le plus d'attention lors de la migration.

Pourquoi migrer depuis Azure Computer VisionOCR

La question de la migration n'est pas abstraite. Les équipes prennent généralement cette décision après avoir rencontré un ou plusieurs problèmes opérationnels concrets avec Azure Computer Visionen production.

La gestion des points de terminaison et des clés API est un processus continu. Chaque environnement de déploiement (développement, préproduction, production, reprise après sinistre) nécessite une ressource Azure Cognitive Services provisionnée, une URL de point de terminaison et au moins une clé API. Les touches doivent être tournées. Les points de terminaison changent lorsque les ressources sont déplacées de région. Chaque environnement a besoin de règles de pare-feu sortant pour atteindre cognitiveservices.azure.com. La surface opérationnelle s'accroît avec chaque environnement et chaque développeur au sein de l'équipe. IronOCR remplace tout cela par une simple clé de licence, définie une seule fois au démarrage de l'application, sans planification de rotation ni exigence de réseau sortant.

La facturation à la page pénalise les documents de plusieurs pages. Azure Computer Visioncomptabilise chaque page PDF comme une transaction distincte. Un contrat de 20 pages correspond à 20 appels facturables. À 1,00 $ pour 1 000 transactions, une équipe traitant 50 000 documents de plusieurs pages par mois, à raison de 4 pages en moyenne par document, génère 200 000 transactions, soit 195 $ par mois après la période gratuite, ou 2 340 $ par an. C'est le point d'équilibre par rapport à IronOCR Lite ($999) en moins de quatre mois, après quoi chaque page supplémentaire ne coûte rien.

La propagation asynchrone se propage à travers toute la pile d'appels. Azure Computer Visionne peut pas renvoyer de réponse de manière synchrone : les E/S cloud subissent une latence réseau. La nécessité de await sur AnalyzeAsync oblige chaque méthode appelante à être asynchrone, propageant le modèle de la couche service aux contrôleurs, travailleurs en arrière-plan, et tout code synchrone qui doit être refactorisé pour l'accommoder. Les opérations basées sur le sondage de Form Recognizer aggravent cela : WaitUntil.Completed bloque le thread, et un véritable comportement non-bloquant nécessite de gérer manuellement des boucles de sondage UpdateStatusAsync.

À chaque appel, des documents quittent le réseau. Pour les équipes traitant des informations de santé couvertes par la loi HIPAA, des documents de défense soumis à la réglementation ITAR, des communications confidentielles entre un avocat et son client, ou toute autre catégorie de documents soumise à des règles de résidence des données, la transmission obligatoire vers le cloud constitue une incompatibilité architecturale, et non un compromis. Il n'existe aucun mode Azure Computer Visionpermettant d'éviter la transmission du contenu des documents aux centres de données Microsoft.

Les limitations de débit imposent des plafonds de performance. Le niveau S1 d'Azure Computer Vision est limité à 10 transactions par seconde. Un traitement par lots de 3 600 images par heure atteint exactement sa limite. Le dépassement de cette limite renvoie des réponses HTTP 429, nécessitant une logique de nouvelle tentative avec un délai exponentiel dans chaque chemin d'appel. Le débit maximal d'IronOCR est limité par le matériel hôte ; aucun plafond imposé par le service, aucune infrastructure de nouvelle tentative requise.

Image OCR et OCR PDF nécessitent deux services séparés. Le standard image OCR utilise ImageAnalysisClient de Azure.AI.Vision.ImageAnalysis. Le traitement complet des PDF nécessite DocumentAnalysisClient de Azure.AI.FormRecognizer.DocumentAnalysis — un package NuGet différent, une ressource Azure différente, un point de terminaison différent, et un schéma de résultat différent. Chaque application qui traite à la fois des images et des PDF engendre cette surcharge de configuration doublée. IronOCR gère les deux avec IronTesseract.Read() et un chargeur OcrInput unique.

Le problème fondamental

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

Comparaison des fonctionnalités IronOCR et d'Azure Computer Vision OCR

Le tableau ci-dessous récapitule les fonctionnalités les plus pertinentes pour les équipes évaluant cette migration.

Fonction Azure Computer VisionOCR IronOCR
Lieu de traitement Microsoft Azure Cloud Local, sur place
Internet requis Oui, chaque demande Non
Abonnement Azure requis Oui Non
Modèle de tarification Par transaction (1,00 $ par 1 000) Licence perpétuelle (de $999)
Facturation à la page pour les PDF multipages Oui — chaque page = 1 transaction Aucun coût par page
Niveau gratuit 5 000 transactions par mois Mode d'essai (avec filigrane)
API OCR d'images AnalyzeAsync (asynchrone uniquement) Read() (synchrone)
OCR PDF Service de reconnaissance de formulaires séparés Intégré, même appel Read()
PDF protégé par mot de passe Via Form Recognizer input.LoadPdf(path, Password: "x")
Sortie PDF consultable construction manuelle result.SaveAsSearchablePdf()
TIFF multipage Non pris en charge input.LoadImageFrames()
prétraitement automatique des images Côté serveur opaque, non configurable Redresser, réduire le bruit, contraster, binariser, accentuer, mettre à l'échelle
suppression profonde du bruit Non input.DeepCleanBackgroundNoise()
Lecture de codes-barres lors de la reconnaissance optique de caractères (OCR) Fonction d'analyse d'images séparée ocr.Configuration.ReadBarCodes = true
OCR basé sur la région Pas directement (recadrage manuel avant téléchargement) CropRectangle sur OcrInput
Limites de débit 10 TPS au niveau S1 Limité au matériel uniquement
Logique de nouvelle tentative requise Oui (HTTP 429, 5xx) Non
Déploiement en mode air-gapped Impossible Entièrement pris en charge
Langues prises en charge 164+ (géré par serveur) Plus de 125 (packs de langue NuGet )
Multilingue simultané Oui Oui (OcrLanguage.French + OcrLanguage.German)
Cadres de délimitation des mots Polygone (nombre de sommets variable) Rectangle (x, y, largeur, hauteur)
Score de confiance Flottant par mot (0,0–1,0) Par mot et globalement (échelle de 0 à 100)
Exportation hOCR Non result.SaveAsHocrFile()
Hiérarchie de sortie structurée Blocs / Lignes / Mots Pages / Paragraphes / Lignes / Mots / Caractères
Compatibilité .NET .NET Standard 2.0+ .NET Framework 4.6.2+, .NET Core, .NET 5-9
Multiplateforme Windows, Linux, macOS (via le cloud) Windows, Linux, macOS, Docker, ARM64
Soutien commercial Plans de support Azure Prise en charge IronOCR incluse avec la licence

Démarrage rapide : Migration d'Azure Computer Vision OCR vers IronOCR

Étape 1 : Remplacer le package NuGet

Supprimez le package Azure Computer Vision :

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

Si le projet utilise également Form Recognizer pour le traitement des PDF, supprimez également ce package :

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

Installez IronOCR depuis NuGet :

dotnet add package IronOcr

Étape 2 : Mise à jour des espaces de noms

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

Étape 3 : initialisation de la licence

Ajoutez la clé de licence une seule fois au démarrage de l'application, avant tout appel OCR :

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

Stockez la clé dans une variable d'environnement en production :

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

Exemples de migration de code

Remplacement de la configuration du client Azure injectée par dépendance

Les équipes qui suivent le modèle recommandé du SDK Azure enregistrent ImageAnalysisClient dans le conteneur DI en utilisant IOptions<AzureComputerVisionOptions> ou une liaison directe IConfiguration. Ce câblage tire les URL de point de terminaison et les clés d'API de appsettings.json et nécessite une configuration réseau sortante dans chaque environnement de déploiement.

Approche 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

Approche 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

Le câblage DI passe de deux classes de configuration (options + usine de clients) à un seul appel AddSingleton<IronTesseract>(). La section Azure appsettings.json, les références du coffre-fort de clés, et les règles de pare-feu sortant pour cognitiveservices.azure.com sont toutes supprimées. Consultez le guide d'installation d'IronTesseract pour connaître les options de configuration disponibles sur l'instance singleton.

Suppression de la boucle d'interrogation du reconnaisseur de formulaire

Le AnalyzeDocumentAsync Form Recognizer retourne un LongRunningOperation. WaitUntil.Completed bloque le thread appelant jusqu'à ce que le travail cloud se termine — généralement 2–10 secondes par document. Pour un comportement non-bloquant, les équipes écrivent une boucle de sondage UpdateStatusAsync avec un délai entre les sondages, ajoutant 30–50 lignes de code d'infrastructure qui ne contient aucune logique OCR.

Approche 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

Approche 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

La boucle d'interrogation et sa logique de délai disparaissent complètement. LoadPdfPages gère la sélection de la plage de pages — aucun appel page par page séparé, aucun comptage de transactions. Le guide d'entrée PDF couvre en détail l'entrée de flux, le chargement de tableaux d'octets et les paramètres de plage de pages.

Conversion des résultats Azure au niveau des mots en sortie structurée IronOCR

Azure Computer Vision renvoie les boîtes englobantes des mots sous forme de polygones avec un nombre variable de sommets — généralement quatre, mais ce n'est pas garanti. La hiérarchie des résultats est Blocks → Lines → Words, et la confiance est un float sur une échelle de 0,0 à 1,0. Le code qui lit les positions des mots doit gérer le tableau des sommets du polygone et normaliser l'échelle de confiance pour toute comparaison de seuil.

Approche 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

Approche 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

La conversion polygone-rectangle disparaît. Les valeurs de confiance correspondent directement une fois que les valeurs Azure 0.0–1.0 sont multipliées par 100 — toute logique de seuil existante nécessite ce seul ajustement. Le guide de sortie des données structurées documente la hiérarchie complète et les propriétés des coordonnées. Pour plus de détails sur le modèle de notation de confiance, consultez le guide des scores de confiance .

Traitement TIFF multipage sans téléchargement sur le cloud

Le ImageAnalysisClient Azure Computer Visionaccepte des images individuelles. Les fichiers TIFF multi-images — courants dans les flux de travail de numérisation de documents, les archives de télécopies et les pipelines d'imagerie médicale — nécessitent soit de diviser le TIFF en images individuelles avant le téléchargement (une transaction par image), soit de passer à Form Recognizer avec sa configuration distincte. Aucun des deux chemins n'est sans risque.

Approche 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

Approche 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

Le coût de transaction par image tombe à zéro. La boucle d'extraction de trame System.Drawing et son étape de sérialisation PNG temporaire sont complètement éliminées. Pour les flux de travail d'archivage de télécopies où la qualité des documents varie, le guide d'entrée TIFF et GIF couvre les options de sélection d'images, et le guide de correction de la qualité d'image documente l'ensemble complet des filtres de prétraitement.

Traitement par lots parallèle sans file d'attente à débit limité

Le niveau S1 d'Azure Computer Vision limite le débit à 10 transactions par seconde. Les traitements par lots qui dépassent ce taux reçoivent des réponses HTTP 429. Les mises en œuvre en production nécessitent un wrapper de limitation de débit, un sémaphore, ou une couche de mise en file d'attente pour rester dans la limite. L'API IronOCR est conçue pour être thread-safe — créez une instance IronTesseract par thread et exécutez avec Parallel.ForEach.

Approche 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

Approche IronOCR :

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

' Non 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

Le sémaphore, le délai de 100 ms et le bloc de gestion des erreurs HTTP 429 sont tous supprimés. Le parallélisme est limité uniquement par le nombre de cœurs du processeur et la mémoire disponible, et non par un niveau de service. L' exemple multithread présente le schéma complet avec des comparaisons de temps, et le guide d'optimisation de la vitesse couvre le réglage de la configuration du moteur pour les charges de travail par lots.

Prétraitement des numérisations de faible qualité rejetées par Azure

Azure Computer Vision effectue une amélioration d'image côté serveur, mais elle est opaque et non configurable. Les documents trop déformés, trop bruités ou présentant un contraste trop faible renvoient des résultats peu fiables ou un texte vide sans possibilité d'intervention. IronOCR expose le pipeline de prétraitement directement sur OcrInput.

Approche Azure Computer Vision :

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

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

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

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

Approche 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

La dépendance externe de prétraitement — System.Drawing, SkiaSharp, ou ImageMagick — est supprimée. Les frais de relivraison et de seconde transaction disparaissent. Le pipeline de prétraitement fait partie du cycle de vie OcrInput, de sorte qu'il est appliqué avant que le moteur OCR ne voie l'image. Le tutoriel sur les filtres d'image présente chaque filtre en comparant la précision avant/après.

Référence de mappage de l'API OCR Azure Computer Visionvers IronOCR

Azure Computer Vision Équivalent d'IronOCR
ImageAnalysisClient IronTesseract
new AzureKeyCredential(apiKey) IronOcr.License.LicenseKey = key
new Uri(endpoint) Non requis
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 (flottant 0,0–1,0) word.Confidence (double 0–100)
word.BoundingPolygon word.X, word.Y, word.Width, word.Height
DocumentAnalysisClient IronTesseract + OcrInput
AnalyzeDocumentAsync(WaitUntil.Completed, "prebuilt-read", stream) ocr.Read(input) avec input.LoadPdf(path)
operation.Value.Pages result.Pages
page.Lines[i].Content result.Lines[i].Text
Boucle de sondage UpdateStatusAsync() Non requis — résultat synchrone
RequestFailedException (statut 429) Sans objet — aucune limite de débit
RequestFailedException (statut 5xx) Sans objet — aucune erreur de service
Drapeau d'énumération VisualFeatures.Read Implicite — Read() extrait toujours le texte
Form Recognizer modèle prebuilt-read Moteur OCR intégré (pas de sélection de modèle)
URL du point de terminaison Azure dans appsettings.json Non requis
procédures de rotation des clés API Non requis

Problèmes de migration courants et solutions

Problème 1 : Contrats d'interface asynchrones qui ne peuvent pas être modifiés

Azure Computer Vision : Les interfaces de service déclarent souvent des types de retour Task<string> parce qu'Azure impose l'asynchrone. Le code appelant, les contrôleurs et les processus en arrière-plan sont tous écrits sous forme de méthodes asynchrones. Passer à IronOCR élimine le besoin d'asynchrone dans la couche OCR, mais modifier chaque signature d'interface n'est pas toujours faisable dans une base de code importante.

Solution : Enveloppez l'appel synchrone IronOCR dans Task.Run pour satisfaire l'interface existante sans cascader de refactors :

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

Il s'agit d'une étape intermédiaire valable. Le guide sur la reconnaissance optique de caractères asynchrone (OCR) décrit la prise en charge asynchrone intégrée d'IronOCR pour les scénarios où une intégration asynchrone complète est préférable.

Problème n° 2 : La logique du seuil de confiance produit des résultats erronés

Azure Computer Vision : Azure retourne la confiance des mots en tant que float entre 0,0 et 1,0. Le code de filtrage existant utilise des seuils comme word.Confidence > 0.85f. Après la migration, ces comparaisons sont toujours évaluées à faux car la confiance IronOCR est de 0 à 100, et non de 0 à 1.

Solution : multipliez les seuils Azure existants par 100 lors de la mise à jour de la logique de filtrage :

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

Problème 3 : L'extraction de champs du modèle prédéfini de Form Recognizer n'a pas d'équivalent direct à IronOCR.

Azure Computer Vision : Les modèles prédéfinis de factures et de reçus de Form Recognizer extraient automatiquement les champs nommés — InvoiceTotal, VendorName, InvoiceDate — sans préciser où ces champs apparaissent sur la page. La logique d'extraction est intégrée au modèle Azure.

Solution : Remplacez l'extraction de champ basée sur le modèle par un OCR basé sur la région en utilisant CropRectangle. Cela nécessite de connaître la mise en page du document, mais la plupart des déploiements réels utilisent déjà des modèles fixes :

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

Le guide OCR basé sur les régions couvre les détails du système de coordonnées et le traitement par lots multirégional.

Problème n° 4 : Absence de hOCR et d'exportation structurée

Azure Computer Vision : Azure ne fournit pas de sortie hOCR. Les équipes qui ont besoin de données de mise en page standardisées pour les outils d'analyse de documents en aval extraient manuellement les cadres de délimitation de la réponse Azure et construisent leur propre format de sortie.

Solution : IronOCR produit un hOCR conforme aux normes en un seul appel :

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

Problème n° 5 : La version du SDK Azure entre en conflit avec d'autres packages Azure

Azure Computer Vision : Les projets qui utilisent plusieurs paquets SDK Azure (Azure.Storage.Blobs, Azure.Identity, Azure.KeyVault.Secrets) peuvent rencontrer des conflits de versions entre les dépendances transitives Azure.Core. La politique de versionnage monorepo du SDK Azure est utile, mais n'élimine pas tous les conflits, en particulier lors du mélange de versions GA et de versions préliminaires du SDK.

Solution : La suppression de Azure.AI.Vision.ImageAnalysis et de Azure.AI.FormRecognizer élimine ces packages SDK de l'arborescence de dépendance. Si le projet utilise Azure uniquement pour OCR, l'ensemble de dépendances Azure.* est supprimé. Si d'autres services Azure sont conservés, la réduction du nombre de paquets diminue la surface d'exposition aux conflits :

# 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

Problème n° 6 : Qualité des documents numérisés inférieure au seuil d'acceptation d'Azure

Azure Computer Vision : les images à très basse résolution (inférieure à ~150 DPI) ou les numérisations fortement déformées renvoient un texte minimal ou vide du pipeline côté serveur d'Azure sans aucun retour d'information sur l'amélioration tentée. L'appelant n'a aucun moyen d'améliorer le résultat sans prétraitement externe.

Solution : Utilisez le pipeline de prétraitement d'IronOCR pour préparer l'image avant l'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

Le guide de correction de l'orientation de l'image traite spécifiquement de la détection des déformations et des rotations.

Liste de contrôle de migration OCR Azure Computer Vision

Pré-migration

Repérez toutes les utilisations d'Azure Computer Vision et de Form Recognizer dans le code source :

# 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

Identifiez les fichiers de configuration contenant les points de terminaison et les clés 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

Éléments à inventorier avant le début du codage :

  • Nombre de classes implémentant les modèles de service Azure OCR
  • Nombre de chaînes de méthodes asynchrones propagées par les appels OCR
  • Identifier les seuils de confiance des mots à l'aide de l'échelle Azure de 0,0 à 1,0.
  • Identifier l'utilisation du modèle prédéfini de Form Recognizer (facture, reçu, identité) nécessitant un remplacement basé sur la région
  • Identifier les fichiers TIFF multi-images actuellement divisés pour le chargement image par image
  • Vérifiez les configurations Docker et CI/CD pour les règles de réseau Azure sortantes qui ne seront plus nécessaires.

Migration de code

  1. Supprimez le package NuGet Azure.AI.Vision.ImageAnalysis de tous les projets qui l'utilisent
  2. Supprimez le package NuGet Azure.AI.FormRecognizer de tous les projets qui l'utilisent
  3. Exécutez dotnet add package IronOcr dans chaque projet affecté
  4. Ajoutez IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE") au démarrage de l'application
  5. Remplacez using Azure; using Azure.AI.Vision.ImageAnalysis; with using IronOcr;
  6. Enregistrez IronTesseract en tant que singleton dans DI (services.AddSingleton<IronTesseract>())
  7. Supprimez AzureComputerVisionOptions ou les classes de configuration équivalentes; supprimez les sections OCR Azure appsettings.json
  8. Remplacez l'injection du constructeur ImageAnalysisClient par l'injection IronTesseract dans toutes les classes de service
  9. Remplacez les appels AnalyzeAsync(BinaryData.FromStream(stream), VisualFeatures.Read) par ocr.Read(imagePath) ou ocr.Read(input) avec OcrInput selon le cas
  10. Supprimez toutes les boucles de sondage UpdateStatusAsync et les motifs WaitUntil.Completed; remplacez DocumentAnalysisClient par IronTesseract + OcrInput.LoadPdf()
  11. Mise à jour des comparaisons de seuil de confiance des mots : multiplier toutes les valeurs Azure 0,0–1,0 par 100
  12. Remplacez l'accès à la boîte de délimitation de polygone (word.BoundingPolygon.Min/Max) par les propriétés directes word.X, word.Y, word.Width, word.Height
  13. Remplacez les boucles de téléversement par-cadre TIFF multi-trame par input.LoadImageFrames(tiffPath)
  14. Convertissez l'extraction de champ du modèle préconstruit de Form Recognizer en OCR basé sur la région CropRectangle pour les modèles de documents connus
  15. Supprimez les blocs try-catch RequestFailedException pour HTTP 429 et 5xx; simplifier la gestion des erreurs en se limitant aux exceptions liées au système de fichiers et à la validation des entrées

Après la migration

  • Vérifier que les résultats de l'extraction de texte brut correspondent ou améliorent ceux d'Azure pour un échantillon représentatif de plus de 20 documents.
  • Vérifier que le traitement des PDF multipages génère bien du texte pour toutes les pages, sans compteur de facturation par page dans la surveillance.
  • Le test de traitement TIFF multi-images renvoie le même nombre de pages que le nombre d'images source.
  • Vérifier que les valeurs de confiance des mots se situent dans la plage 0-100 et que les comparaisons de seuil fonctionnent correctement.
  • Vérifier que les coordonnées du cadre de délimitation des mots sont correctement alignées avec le système de coordonnées de l'image source.
  • Exécutez le traitement par lots et vérifiez qu'il se termine sans erreurs de limitation de débit ni contention de sémaphore.
  • Effectuer un test dans un environnement sans accès Internet sortant pour confirmer qu'aucun appel au point de terminaison Azure n'est effectué.
  • Confirmez que les déploiements Docker et de conteneurs démarrent avec succès sans règles de pare-feu sortant pour cognitiveservices.azure.com
  • Vérifiez que la construction du conteneur DI réussit et que le singleton IronTesseract est correctement résolu
  • Vérifiez que la variable d'environnement IRONOCR_LICENSE est définie dans tous les environnements de déploiement et que l'OCR produit une sortie sous licence (sans filigrane)

Principaux avantages de la migration vers IronOCR

Les coûts deviennent prévisibles. Le compteur par transaction d'Azure Computer Vision fonctionne en continu. Un seul mois de forte activité documentaire peut dépasser le coût annuel d'une licence IronOCR . Après la migration, le budget OCR est un poste budgétaire fixe. Le volume de traitement peut croître — en raison de l'expansion de l'entreprise, du retraitement en masse ou des pics temporaires — sans générer de facture plus importante. La page de licence IronOCR montre les options de niveau allant de $999 pour une licence développeur unique à 2 999 $ pour un nombre illimité de développeurs.

La pile d'applications devient plus simple. La suppression de Azure.AI.Vision.ImageAnalysis et Azure.AI.FormRecognizer élimine deux packages NuGet, deux configurations de ressources Azure, deux ensembles d'identifiants, et l'ensemble de l'infrastructure de sondage asynchrone. Les classes de service qui nécessitaient auparavant une signature async Task<string> en raison de l'I/O cloud deviennent des méthodes synchrones. La gestion des erreurs se limite désormais aux pannes réseau, aux limites de débit et à la disponibilité du service, ainsi qu'à la validation du système de fichiers et des entrées. Chaque futur développeur travaillant sur cette base de code aura moins de code à comprendre.

Les données des documents restent sous le contrôle de l'organisation. Après la migration, le traitement OCR est effectué localement. Les documents ne franchissent pas les frontières organisationnelles, n'apparaissent pas dans la télémétrie Azure et ne sont pas soumis aux politiques de conservation ou de traitement des données de Microsoft. Les entités couvertes par la loi HIPAA, les sous-traitants ITAR, les organisations soumises au RGPD et toute équipe ayant des exigences de résidence des données peuvent traiter des documents sans examen de conformité par un tiers cloud. Le guide de déploiement Linux et le guide de déploiement Docker expliquent comment déployer IronOCR dans des environnements restreints.

Le débit des traitements par lots dépend du matériel. La limite de 10 TPS du niveau S1 d'Azure est une contrainte stricte qui nécessite la mise en file d'attente, la limitation du débit ou une mise à niveau du niveau pour être contournée. Après la migration, les travaux d'OCR simultanés saturent les cœurs CPU disponibles sans aucune restriction imposée par le service. Un serveur à quatre cœurs peut exécuter quatre instances IronTesseract en parallèle simultanément. L' exemple multithread illustre ce modèle et montre l'évolution du débit en fonction du nombre de cœurs.

Les défauts de prétraitement sont corrigibles par le code. L'amélioration d'image côté serveur d'Azure Computer Vision reste une boîte noire. Lorsqu'un scan retourne une sortie vide ou de faible confiance, les seules options sont de l'accepter ou de prétraiter à l'extérieur avant de relancer à un coût supplémentaire. Le pipeline OcrInput d'IronOCR expose le désalignement, la réduction de bruit, le contraste, la binarisation, la mise à l'échelle, l'affûtage, et la suppression de bruit profond comme des méthodes de première classe. Les types de balayage problématiques deviennent des paramètres réglables. La page relative aux fonctionnalités de prétraitement répertorie l'ensemble des filtres et fournit des indications sur les filtres qui permettent de corriger quels défauts d'analyse.

Veuillez noterAzure Computer Vision et Tesseract sont des marques déposées de leurs propriétaires respectifs. Ce site n'est pas affilié, approuvé, ou sponsorisé par Google ou Microsoft. Tous les noms de produits, logos et marques sont la propriété de leurs propriétaires respectifs. Les comparaisons sont à titre informatif uniquement et reflètent les informations publiquement disponibles au moment de l'écriture.

Questions Fréquemment Posées

Pourquoi devrais-je migrer d'Azure Computer Vision OCR vers IronOCR ?

Parmi les motivations communes, citons l'élimination de la complexité de l'interopérabilité COM, le remplacement de la gestion des licences basée sur les fichiers, l'absence de facturation à la page, l'activation du déploiement Docker/conteneur et l'adoption d'un flux de travail NuGet-natif qui s'intègre à l'outillage .NET standard.

Quels sont les principaux changements de code lors de la migration d'Azure Computer Vision OCR vers IronOCR ?

Remplacer les séquences d'initialisation d'Azure Computer Vision par l'instanciation d'IronTesseract, supprimer la gestion du cycle de vie de COM (modèles explicites Create/Load/Close) et mettre à jour les noms des propriétés des résultats. Le résultat est une réduction significative du nombre de lignes de code.

Comment installer IronOCR pour commencer la migration ?

Exécutez "Install-Package IronOcr" dans la console du Package Manager ou "dotnet add package IronOcr" dans le CLI. Les packs de langues sont des paquets distincts : 'dotnet add package IronOcr.Languages.French' pour le français, par exemple.

IronOCR atteint-il la précision de l'OCR d'Azure Computer Vision pour les documents commerciaux standard ?

IronOcr atteint un niveau de précision élevé pour les contenus commerciaux standard, notamment les factures, les contrats, les reçus et les formulaires dactylographiés. Les filtres de prétraitement d'image (désalignement, suppression du bruit, amélioration du contraste) améliorent encore la reconnaissance sur des données dégradées.

Comment IronOCR gère-t-il les données linguistiques qu'Azure Computer Vision OCR installe séparément ?

Les données linguistiques de l'IronOcr sont distribuées sous forme de packages NuGet. 'dotnet add package IronOcr.Languages.German' installe la prise en charge de l'allemand. Il n'y a pas de placement manuel de fichiers ou de chemins d'accès aux répertoires.

La migration d'Azure Computer Vision OCR vers IronOCR nécessite-t-elle des changements au niveau de l'infrastructure de déploiement ?

IronOcr nécessite moins de changements d'infrastructure qu'Azure Computer Vision OCR. Il n'y a pas de chemins binaires SDK, de placements de fichiers de licence ou de configurations de serveurs de licence. Le package NuGet contient le moteur d'OCR complet, et la clé de licence est une chaîne définie dans le code de l'application.

Comment configurer les licences IronOCR après la migration ?

Attribuer IronOcr.License.LicenseKey = "YOUR-KEY" dans le code de démarrage de l'application. Dans Docker ou Kubernetes, stockez la clé dans une variable d'environnement et lisez-la au démarrage. Utilisez License.IsValidLicense pour valider avant d'accepter le trafic.

IronOcr peut-il traiter les PDF de la même manière qu'Azure Computer Vision ?

Oui, IronOCR lit aussi bien les PDF natifs que les PDF numérisés. Instanciez IronTesseract, appelez ocr.Read(input) où l'input est un chemin PDF ou OcrPdfInput, et itérez les pages OcrResult. Aucun pipeline de rendu PDF séparé n'est nécessaire.

Comment IronOcr gère-t-il le threading dans le cadre d'un traitement à haut volume ?

IronTesseract est sûr pour l'instanciation par thread. Créez une instance par thread dans un Parallel.ForEach ou un Task pool, exécutez l'OCR simultanément et disposez de chaque instance lorsque vous avez terminé. Aucun état global ou verrouillage n'est nécessaire.

Quels formats de sortie IronOCR prend-il en charge après l'extraction du texte ?

IronOCR renvoie des résultats structurés comprenant le texte, les coordonnées des mots, les scores de confiance et la structure des pages. Les options d'exportation comprennent le texte brut, le PDF interrogeable et les objets de résultats structurés pour le traitement en aval.

La tarification d'IronOCR est-elle plus prévisible que celle d'Azure Computer Vision OCR pour la mise à l'échelle des charges de travail ?

IronOCR utilise des licences perpétuelles forfaitaires sans frais par page ou par volume. Que vous traitiez 10 000 ou 10 millions de pages, le coût de la licence reste constant. Les options de licence en volume et en équipe sont disponibles sur la page de tarification d'IronOcr.

Qu'advient-il de mes tests existants après la migration d'Azure Computer Vision OCR vers IronOCR ?

Les tests qui vérifient le contenu du texte extrait doivent continuer à passer après la migration. Les tests qui valident les modèles d'appel d'API ou le cycle de vie des objets COM devront être mis à jour pour refléter le modèle d'initialisation et de résultat plus simple d'IronOcr.

Kannaopat Udonpant
Ingénieur logiciel
Avant de devenir ingénieur logiciel, Kannapat a obtenu un doctorat en ressources environnementales à l'université d'Hokkaido au Japon. Pendant qu'il poursuivait son diplôme, Kannapat est également devenu membre du laboratoire de robotique de véhicules, qui fait partie du département de bioproduction. En 2022, il a utilisé ses compé...
Lire la suite

Équipe de soutien Iron

Nous sommes en ligne 24 heures sur 24, 5 jours sur 7.
Chat
Email
Appelez-moi