Migración de Azure Computer Vision OCR a
Esta guía explica a los desarrolladores de .NET cómo reemplazar OCR de Visión por computadora de Azure por IronOCR , una biblioteca OCR local que procesa documentos localmente sin infraestructura en la nube. Abarca los pasos mecánicos para intercambiar el paquete NuGet y los espacios de nombres, traducir el modelo de sondeo asíncrono de Azure a llamadas locales síncronas y gestionar los patrones específicos (configuración de la inyección de dependencias, bucles de sondeo del Form Recognizer, procesamiento de TIFF de varias páginas y rendimiento por lotes) que requieren mayor atención durante la migración.
¿Por qué migrar desde Visión por computadora de Azure OCR?
La justificación de la migración no es abstracta. Los equipos suelen tomar esta decisión tras encontrar uno o más problemas operativos concretos con Visión por computadora de Azure en producción.
La administración de puntos de conexión y claves de API nunca termina. Cada entorno de implementación (desarrollo, pruebas, producción, recuperación ante desastres) requiere un recurso de Azure Cognitive Services aprovisionado, una URL de punto de conexión y al menos una clave de API. Las llaves deben rotarse. Los puntos finales cambian cuando los recursos se mueven entre regiones. Cada entorno necesita reglas de firewall de salida para alcanzar cognitiveservices.azure.com. La superficie operativa aumenta con cada entorno y desarrollador del equipo.IronOCR sustituye todo eso por una única clave de licencia que se establece una sola vez al iniciar la aplicación, sin necesidad de rotación ni de conexión a la red saliente.
La facturación por página penaliza los documentos de varias páginas. Visión por computadora de Azure contabiliza cada página de un PDF como una transacción independiente. Un contrato de 20 páginas equivale a 20 llamadas facturables. A un coste de 1,00 $ por cada 1.000 transacciones, un equipo que procese 50.000 documentos de varias páginas al mes, con un promedio de 4 páginas cada uno, genera 200.000 transacciones: 195 $ al mes después del nivel gratuito, o 2.340 $ al año. Ese es el punto de equilibrio contra IronOCR Lite ($999) en menos de cuatro meses, después de lo cual cada página adicional no cuesta nada.
La propagación asíncrona se extiende por toda la pila de llamadas. Visión por computadora de Azure no puede devolver resultados de forma síncrona, ya que la E/S en la nube presenta latencia de red. El requisito de await en AnalyzeAsync obliga a que cada método llamante sea asíncrono, propagando el patrón desde la capa de servicio hasta los controladores, trabajadores en segundo plano y cualquier código sincrónico que deba refactorizarse para acomodarlo. Las operaciones basadas en sondeo de Form Recognizer complican esto: WaitUntil.Completed bloquea el hilo, y un verdadero comportamiento no bloqueante requiere gestionar manualmente los bucles de sondeo de UpdateStatusAsync.
Los documentos salen de la red con cada llamada. Para los equipos que procesan información sanitaria protegida por la HIPAA, documentos de defensa controlados por la ITAR, comunicaciones privilegiadas entre abogado y cliente, o cualquier categoría de documento sujeta a las normas de residencia de datos, la transmisión obligatoria a la nube es una incompatibilidad arquitectónica, no una ventaja. No existe ningún modo de Visión por computadora de Azure que evite la transmisión del contenido de los documentos a los centros de datos de Microsoft.
Los límites de velocidad crean topes de rendimiento. El nivel S1 de Visión por computadora de Azure tiene un límite de 10 transacciones por segundo. Un proceso por lotes que procesa 3.600 imágenes por hora alcanza exactamente el límite máximo. Si se supera este límite, se devuelven respuestas HTTP 429, lo que requiere una lógica de reintento con retroceso exponencial en cada ruta de llamada. El límite de rendimiento de IronOCR lo impone el hardware de alojamiento; no hay límites impuestos por el servicio ni se requiere infraestructura de reintentos.
El OCR de imágenes y el OCR de PDF requieren dos servicios separados. El OCR estándar de imágenes utiliza ImageAnalysisClient de Azure.AI.Vision.ImageAnalysis. El procesamiento completo de PDF requiere DocumentAnalysisClient de Azure.AI.FormRecognizer.DocumentAnalysis: un paquete NuGet diferente, un recurso de Azure diferente, un punto final diferente y un esquema de resultado diferente. Todas las aplicaciones que procesan tanto imágenes como archivos PDF conllevan esta doble sobrecarga de configuración.IronOCR maneja ambos con IronTesseract.Read() y un solo cargador OcrInput.
El problema fundamental
// 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
Comparación de características entre IronOCR y Visión por computadora de Azure OCR
La tabla que aparece a continuación resume las funcionalidades más relevantes para los equipos que evalúan esta migración.
| Característica | OCR de Visión por computadora de Azure | IronOCR |
|---|---|---|
| Ubicación de procesamiento | Nube Microsoft Azure | Local, en el establecimiento |
| Se requiere conexión a Internet | Sí, cada solicitud | No |
| Se requiere una suscripción a Azure | Sí | No |
| Modelo de precios | Por transacción (1,00 $ por cada 1.000) | Licencia perpetua (de $999) |
| Facturación por página en archivos PDF de varias páginas. | Sí — cada página = 1 transacción | Sin coste por página |
| Nivel gratuito | 5.000 transacciones al mes | Modo de prueba (con marca de agua) |
| API de OCR de imágenes | AnalyzeAsync (solo asincrónico) |
Read() (sincrónico) |
| OCR de PDF | Servicio independiente de reconocimiento de formularios | Incorporado, misma llamada Read() |
| PDF protegido con contraseña | A través de Form Recognizer | input.LoadPdf(path, Password: "x") |
| Salida en PDF con capacidad de búsqueda | Elaboración del manual | result.SaveAsSearchablePdf() |
| TIFF de varias páginas | No soportado | input.LoadImageFrames() |
| preprocesamiento automático de imágenes | Opaco en el lado del servidor, no configurable. | Corregir inclinación, Reducir ruido, Contraste, Binarizar, Enfocar, Escalar |
| Eliminación profunda de ruido | No | input.DeepCleanBackgroundNoise() |
| Lectura de BarCodes durante el OCR | Función de análisis de imágenes independiente | ocr.Configuration.ReadBarCodes = true |
| OCR basado en la región | No directamente (recorte manual antes de subirlo) | CropRectangle en OcrInput |
| límites de velocidad | 10 TPS en el nivel S1 | Solo para hardware |
| Se requiere lógica de reintento | Sí (HTTP 429, 5xx) | No |
| Implementación en entorno aislado | Imposible | Totalmente compatible |
| Idiomas compatibles | 164+ (administrado por el servidor) | Más de 125 paquetes de idiomas NuGet |
| Multilingüe simultáneo | Sí | Sí (OcrLanguage.French + OcrLanguage.German) |
| cuadros delimitadores de palabras | Polígono (número de vértices variable) | Rectángulo (x, y, ancho, alto) |
| Puntuación de confianza | Valor flotante por palabra (0,0–1,0) | Por palabra y en general (escala de 0 a 100) |
| Exportación hOCR | No | result.SaveAsHocrFile() |
| Jerarquía de salida estructurada | Bloques / Líneas / WORDs | Páginas / Párrafos / Líneas / Palabras / Caracteres |
| Compatibilidad con .NET | .NET Standard 2.0 o superior | .NET Framework 4.6.2+, .NET Core, .NET 5-9 |
| Plataforma cruzada | Windows, Linux, macOS (a través de la nube) | Windows, Linux, macOS, Docker, ARM64 |
| Apoyo comercial | Planes de soporte de Azure | Soporte IronOCR incluido con la licencia. |
Inicio rápido: Migración de OCR de Visión por computadora de Azure a IronOCR
Paso 1: Sustituir el paquete NuGet
Elimine el paquete Azure Computer Vision:
dotnet remove package Azure.AI.Vision.ImageAnalysis
dotnet remove package Azure.AI.Vision.ImageAnalysis
Si el proyecto también utiliza Form Recognizer para el procesamiento de PDF, elimine también ese paquete:
dotnet remove package Azure.AI.FormRecognizer
dotnet remove package Azure.AI.FormRecognizer
Instala IronOCR desde NuGet :
dotnet add package IronOcr
Paso 2: Actualizar los espacios de nombres
// 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)
Paso 3: Inicializar licencia
Agregue la clave de licencia una sola vez al iniciar la aplicación, antes de cualquier llamada de OCR:
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Almacene la clave en una variable de entorno en producción:
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE");
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE");
Imports System
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE")
Ejemplos de migración de código
Reemplazo de la configuración del cliente de Azure con inyección de dependencias
Los equipos que siguen el patrón recomendado del SDK de Azure registran ImageAnalysisClient en el contenedor DI usando IOptions<AzureComputerVisionOptions> o enlace directo de IConfiguration. Este cableado extrae URLs de punto final y claves API de appsettings.json y requiere configuración de red de salida en cada entorno de implementación.
Enfoque de visión artificial de Azure:
// appsettings.json binds to this class
public class AzureComputerVisionOptions
{
public string Endpoint { get; set; } // "https://your-resource.cognitiveservices.azure.com/"
public string ApiKey { get; set; } // rotated periodically
}
// Program.cs / Startup.cs
services.Configure<AzureComputerVisionOptions>(
configuration.GetSection("AzureComputerVision"));
services.AddSingleton<ImageAnalysisClient>(sp =>
{
var opts = sp.GetRequiredService<IOptions<AzureComputerVisionOptions>>().Value;
return new ImageAnalysisClient(
new Uri(opts.Endpoint),
new AzureKeyCredential(opts.ApiKey));
});
services.AddScoped<IOcrService, AzureOcrService>();
// appsettings.json binds to this class
public class AzureComputerVisionOptions
{
public string Endpoint { get; set; } // "https://your-resource.cognitiveservices.azure.com/"
public string ApiKey { get; set; } // rotated periodically
}
// Program.cs / Startup.cs
services.Configure<AzureComputerVisionOptions>(
configuration.GetSection("AzureComputerVision"));
services.AddSingleton<ImageAnalysisClient>(sp =>
{
var opts = sp.GetRequiredService<IOptions<AzureComputerVisionOptions>>().Value;
return new ImageAnalysisClient(
new Uri(opts.Endpoint),
new AzureKeyCredential(opts.ApiKey));
});
services.AddScoped<IOcrService, AzureOcrService>();
' appsettings.json binds to this class
Public Class AzureComputerVisionOptions
Public Property Endpoint As String ' "https://your-resource.cognitiveservices.azure.com/"
Public Property ApiKey As String ' rotated periodically
End Class
' Program.vb / Startup.vb
services.Configure(Of AzureComputerVisionOptions)(
configuration.GetSection("AzureComputerVision"))
services.AddSingleton(Of ImageAnalysisClient)(Function(sp)
Dim opts = sp.GetRequiredService(Of IOptions(Of AzureComputerVisionOptions))().Value
Return New ImageAnalysisClient(
New Uri(opts.Endpoint),
New AzureKeyCredential(opts.ApiKey))
End Function)
services.AddScoped(Of IOcrService, AzureOcrService)()
// AzureOcrService.cs
public class AzureOcrService : IOcrService
{
private readonly ImageAnalysisClient _client;
public AzureOcrService(ImageAnalysisClient client)
{
_client = client;
}
public async Task<string> ReadAsync(string imagePath)
{
using var stream = File.OpenRead(imagePath);
var data = BinaryData.FromStream(stream);
var result = await _client.AnalyzeAsync(data, VisualFeatures.Read);
return string.Join("\n",
result.Value.Read.Blocks
.SelectMany(b => b.Lines)
.Select(l => l.Text));
}
}
// AzureOcrService.cs
public class AzureOcrService : IOcrService
{
private readonly ImageAnalysisClient _client;
public AzureOcrService(ImageAnalysisClient client)
{
_client = client;
}
public async Task<string> ReadAsync(string imagePath)
{
using var stream = File.OpenRead(imagePath);
var data = BinaryData.FromStream(stream);
var result = await _client.AnalyzeAsync(data, VisualFeatures.Read);
return string.Join("\n",
result.Value.Read.Blocks
.SelectMany(b => b.Lines)
.Select(l => l.Text));
}
}
Imports System.IO
Imports System.Threading.Tasks
Public Class AzureOcrService
Implements IOcrService
Private ReadOnly _client As ImageAnalysisClient
Public Sub New(client As ImageAnalysisClient)
_client = client
End Sub
Public Async Function ReadAsync(imagePath As String) As Task(Of String) Implements IOcrService.ReadAsync
Using stream = File.OpenRead(imagePath)
Dim data = BinaryData.FromStream(stream)
Dim result = Await _client.AnalyzeAsync(data, VisualFeatures.Read)
Return String.Join(vbLf, result.Value.Read.Blocks _
.SelectMany(Function(b) b.Lines) _
.Select(Function(l) l.Text))
End Using
End Function
End Class
Enfoque IronOCR:
// Program.cs / Startup.cs
// One-time license key — no endpoint, no credential class, no options binding
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE");
// Register IronTesseract as a singleton — it is thread-safe
services.AddSingleton<IronTesseract>();
services.AddScoped<IOcrService, IronOcrService>();
// Program.cs / Startup.cs
// One-time license key — no endpoint, no credential class, no options binding
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE");
// Register IronTesseract as a singleton — it is thread-safe
services.AddSingleton<IronTesseract>();
services.AddScoped<IOcrService, IronOcrService>();
' Program.vb / Startup.vb
' One-time license key — no endpoint, no credential class, no options binding
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE")
' Register IronTesseract as a singleton — it is thread-safe
services.AddSingleton(Of IronTesseract)()
services.AddScoped(Of IOcrService, IronOcrService)()
// IronOcrService.cs
public class IronOcrService : IOcrService
{
private readonly IronTesseract _ocr;
public IronOcrService(IronTesseract ocr)
{
_ocr = ocr;
}
public string Read(string imagePath)
{
return _ocr.Read(imagePath).Text;
}
}
// IronOcrService.cs
public class IronOcrService : IOcrService
{
private readonly IronTesseract _ocr;
public IronOcrService(IronTesseract ocr)
{
_ocr = ocr;
}
public string Read(string imagePath)
{
return _ocr.Read(imagePath).Text;
}
}
' IronOcrService.vb
Public Class IronOcrService
Implements IOcrService
Private ReadOnly _ocr As IronTesseract
Public Sub New(ocr As IronTesseract)
_ocr = ocr
End Sub
Public Function Read(imagePath As String) As String Implements IOcrService.Read
Return _ocr.Read(imagePath).Text
End Function
End Class
El cableado DI se reduce de dos clases de configuración (opciones + fábrica de clientes) a una sola llamada AddSingleton<IronTesseract>(). La sección Azure appsettings.json, las referencias del almacén de claves y las reglas de firewall de salida para cognitiveservices.azure.com son eliminadas. Consulte la guía de configuración de IronTesseract para conocer las opciones de configuración disponibles en la instancia singleton.
Eliminación del bucle de sondeo del reconocedor de formularios
El AnalyzeDocumentAsync de Form Recognizer devuelve un LongRunningOperation. WaitUntil.Completed bloquea el hilo llamante hasta que el trabajo en la nube termina; típicamente 2-10 segundos por documento. Para un comportamiento no bloqueante, los equipos escriben un bucle de sondeo UpdateStatusAsync con un retraso entre sondeos, añadiendo 30-50 líneas de código de infraestructura que no tiene lógica de OCR.
Enfoque de visión artificial de Azure:
// DocumentAnalysisClient — separate from ImageAnalysisClient, separate resource
public class FormRecognizerPdfService
{
private readonly DocumentAnalysisClient _client;
public FormRecognizerPdfService(string endpoint, string apiKey)
{
_client = new DocumentAnalysisClient(
new Uri(endpoint),
new AzureKeyCredential(apiKey));
}
// Blocking wait — thread is held for the duration of cloud processing
public async Task<string> ExtractPdfTextBlocking(string pdfPath)
{
using var stream = File.OpenRead(pdfPath);
var operation = await _client.AnalyzeDocumentAsync(
WaitUntil.Completed, // blocks until Azure finishes
"prebuilt-read",
stream);
var docResult = operation.Value;
var sb = new StringBuilder();
foreach (var page in docResult.Pages)
{
foreach (var line in page.Lines)
{
sb.AppendLine(line.Content); // .Content, not .Text
}
}
return sb.ToString();
}
// True async — manual polling loop required
public async Task<string> ExtractPdfTextNonBlocking(string pdfPath)
{
using var stream = File.OpenRead(pdfPath);
var operation = await _client.AnalyzeDocumentAsync(
WaitUntil.Started, // returns immediately, not complete yet
"prebuilt-read",
stream);
// Poll every 500ms until the operation finishes
while (!operation.HasCompleted)
{
await Task.Delay(500);
await operation.UpdateStatusAsync();
}
var docResult = operation.Value;
var sb = new StringBuilder();
foreach (var page in docResult.Pages)
{
foreach (var line in page.Lines)
{
sb.AppendLine(line.Content);
}
}
return sb.ToString();
}
}
// DocumentAnalysisClient — separate from ImageAnalysisClient, separate resource
public class FormRecognizerPdfService
{
private readonly DocumentAnalysisClient _client;
public FormRecognizerPdfService(string endpoint, string apiKey)
{
_client = new DocumentAnalysisClient(
new Uri(endpoint),
new AzureKeyCredential(apiKey));
}
// Blocking wait — thread is held for the duration of cloud processing
public async Task<string> ExtractPdfTextBlocking(string pdfPath)
{
using var stream = File.OpenRead(pdfPath);
var operation = await _client.AnalyzeDocumentAsync(
WaitUntil.Completed, // blocks until Azure finishes
"prebuilt-read",
stream);
var docResult = operation.Value;
var sb = new StringBuilder();
foreach (var page in docResult.Pages)
{
foreach (var line in page.Lines)
{
sb.AppendLine(line.Content); // .Content, not .Text
}
}
return sb.ToString();
}
// True async — manual polling loop required
public async Task<string> ExtractPdfTextNonBlocking(string pdfPath)
{
using var stream = File.OpenRead(pdfPath);
var operation = await _client.AnalyzeDocumentAsync(
WaitUntil.Started, // returns immediately, not complete yet
"prebuilt-read",
stream);
// Poll every 500ms until the operation finishes
while (!operation.HasCompleted)
{
await Task.Delay(500);
await operation.UpdateStatusAsync();
}
var docResult = operation.Value;
var sb = new StringBuilder();
foreach (var page in docResult.Pages)
{
foreach (var line in page.Lines)
{
sb.AppendLine(line.Content);
}
}
return sb.ToString();
}
}
Imports System
Imports System.IO
Imports System.Text
Imports System.Threading.Tasks
Imports Azure
Imports Azure.AI.FormRecognizer.DocumentAnalysis
' DocumentAnalysisClient — separate from ImageAnalysisClient, separate resource
Public Class FormRecognizerPdfService
Private ReadOnly _client As DocumentAnalysisClient
Public Sub New(endpoint As String, apiKey As String)
_client = New DocumentAnalysisClient(
New Uri(endpoint),
New AzureKeyCredential(apiKey))
End Sub
' Blocking wait — thread is held for the duration of cloud processing
Public Async Function ExtractPdfTextBlocking(pdfPath As String) As Task(Of String)
Using stream = File.OpenRead(pdfPath)
Dim operation = Await _client.AnalyzeDocumentAsync(
WaitUntil.Completed, ' blocks until Azure finishes
"prebuilt-read",
stream)
Dim docResult = operation.Value
Dim sb = New StringBuilder()
For Each page In docResult.Pages
For Each line In page.Lines
sb.AppendLine(line.Content) ' .Content, not .Text
Next
Next
Return sb.ToString()
End Using
End Function
' True async — manual polling loop required
Public Async Function ExtractPdfTextNonBlocking(pdfPath As String) As Task(Of String)
Using stream = File.OpenRead(pdfPath)
Dim operation = Await _client.AnalyzeDocumentAsync(
WaitUntil.Started, ' returns immediately, not complete yet
"prebuilt-read",
stream)
' Poll every 500ms until the operation finishes
While Not operation.HasCompleted
Await Task.Delay(500)
Await operation.UpdateStatusAsync()
End While
Dim docResult = operation.Value
Dim sb = New StringBuilder()
For Each page In docResult.Pages
For Each line In page.Lines
sb.AppendLine(line.Content)
Next
Next
Return sb.ToString()
End Using
End Function
End Class
Enfoque 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
El bucle de sondeo y su lógica de retardo desaparecen por completo. LoadPdfPages maneja la selección de rango de página: no hay llamada separada por página, sin conteo de transacciones. La guía de entrada de PDF cubre en detalle la entrada de flujo, la carga de matrices de bytes y los parámetros de rango de páginas.
Asignación de resultados a nivel de palabra de Azure a la salida estructurada de IronOCR
Azure Computer Vision devuelve los cuadros delimitadores de las palabras como polígonos con un número variable de vértices; normalmente cuatro, pero no está garantizado. La jerarquía de resultados es Blocks → Lines → Words, y la confianza es un float en una escala de 0.0 a 1.0. El código que lee las posiciones de las palabras debe manejar la matriz de vértices del polígono y normalizar la escala de confianza para cualquier comparación de umbrales.
Enfoque de visión artificial de Azure:
public async Task<List<WordResult>> ExtractWordPositionsAsync(string imagePath)
{
using var stream = File.OpenRead(imagePath);
var imageData = BinaryData.FromStream(stream);
var response = await _client.AnalyzeAsync(imageData, VisualFeatures.Read);
var words = new List<WordResult>();
foreach (var block in response.Value.Read.Blocks)
{
foreach (var line in block.Lines)
{
foreach (var word in line.Words)
{
// BoundingPolygon is a list of ImagePoint — variable vertex count
var polygon = word.BoundingPolygon;
int minX = polygon.Min(p => p.X);
int minY = polygon.Min(p => p.Y);
int maxX = polygon.Max(p => p.X);
int maxY = polygon.Max(p => p.Y);
words.Add(new WordResult
{
Text = word.Text,
// Azure confidence: 0.0 to 1.0 — multiply by 100 for comparison
Confidence = (double)word.Confidence * 100.0,
X = minX,
Y = minY,
Width = maxX - minX,
Height = maxY - minY
});
}
}
}
return words;
}
public record WordResult(string Text, double Confidence, int X, int Y, int Width, int Height);
public async Task<List<WordResult>> ExtractWordPositionsAsync(string imagePath)
{
using var stream = File.OpenRead(imagePath);
var imageData = BinaryData.FromStream(stream);
var response = await _client.AnalyzeAsync(imageData, VisualFeatures.Read);
var words = new List<WordResult>();
foreach (var block in response.Value.Read.Blocks)
{
foreach (var line in block.Lines)
{
foreach (var word in line.Words)
{
// BoundingPolygon is a list of ImagePoint — variable vertex count
var polygon = word.BoundingPolygon;
int minX = polygon.Min(p => p.X);
int minY = polygon.Min(p => p.Y);
int maxX = polygon.Max(p => p.X);
int maxY = polygon.Max(p => p.Y);
words.Add(new WordResult
{
Text = word.Text,
// Azure confidence: 0.0 to 1.0 — multiply by 100 for comparison
Confidence = (double)word.Confidence * 100.0,
X = minX,
Y = minY,
Width = maxX - minX,
Height = maxY - minY
});
}
}
}
return words;
}
public record WordResult(string Text, double Confidence, int X, int Y, int Width, int Height);
Imports System.IO
Imports System.Threading.Tasks
Imports System.Linq
Public Class ImageAnalyzer
Private _client As SomeClientType ' Replace with the actual type of _client
Public Async Function ExtractWordPositionsAsync(imagePath As String) As Task(Of List(Of WordResult))
Using stream = File.OpenRead(imagePath)
Dim imageData = BinaryData.FromStream(stream)
Dim response = Await _client.AnalyzeAsync(imageData, VisualFeatures.Read)
Dim words = New List(Of WordResult)()
For Each block In response.Value.Read.Blocks
For Each line In block.Lines
For Each word In line.Words
' BoundingPolygon is a list of ImagePoint — variable vertex count
Dim polygon = word.BoundingPolygon
Dim minX = polygon.Min(Function(p) p.X)
Dim minY = polygon.Min(Function(p) p.Y)
Dim maxX = polygon.Max(Function(p) p.X)
Dim maxY = polygon.Max(Function(p) p.Y)
words.Add(New WordResult With {
.Text = word.Text,
' Azure confidence: 0.0 to 1.0 — multiply by 100 for comparison
.Confidence = CDbl(word.Confidence) * 100.0,
.X = minX,
.Y = minY,
.Width = maxX - minX,
.Height = maxY - minY
})
Next
Next
Next
Return words
End Using
End Function
End Class
Public Class WordResult
Public Property Text As String
Public Property Confidence As Double
Public Property X As Integer
Public Property Y As Integer
Public Property Width As Integer
Public Property Height As Integer
End Class
Enfoque 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
La conversión de polígono a rectángulo desaparece. Los valores de confianza coinciden directamente una vez que los valores de Azure de 0,0 a 1,0 se multiplican por 100; cualquier lógica de umbral existente necesita ese único ajuste. La guía de salida de datos estructurados documenta la jerarquía completa y las propiedades de las coordenadas. Para obtener información específica sobre el modelo de puntuación de confianza, consulte la guía de puntuaciones de confianza .
Procesamiento de archivos TIFF de varias páginas sin carga en la nube
El ImageAnalysisClient de Visión por computadora de Azure acepta imágenes individuales. Los archivos TIFF multifotograma, habituales en los flujos de trabajo de escaneo de documentos, los archivos de fax y los sistemas de procesamiento de imágenes médicas, requieren dividir el archivo TIFF en imágenes individuales antes de cargarlo (una transacción por fotograma) o cambiar a Form Recognizer con su configuración independiente. Ninguno de los dos caminos está limpio.
Enfoque de visión artificial de Azure:
// Azure does not support multi-frame TIFF directly via ImageAnalysisClient
// Must split frames manually and upload each as a separate transaction
public async Task<string> ExtractMultiFrameTiffAsync(string tiffPath)
{
// Load TIFF using System.Drawing or a third-party library
using var bitmap = new System.Drawing.Bitmap(tiffPath);
int frameCount = bitmap.GetFrameCount(
System.Drawing.Imaging.FrameDimension.Page);
var allText = new StringBuilder();
for (int i = 0; i < frameCount; i++)
{
// Select frame, save to temporary PNG, upload to Azure
bitmap.SelectActiveFrame(System.Drawing.Imaging.FrameDimension.Page, i);
using var ms = new MemoryStream();
bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
ms.Position = 0;
// Each frame = 1 Azure transaction = $0.001
var imageData = BinaryData.FromStream(ms);
var result = await _client.AnalyzeAsync(imageData, VisualFeatures.Read);
foreach (var block in result.Value.Read.Blocks)
foreach (var line in block.Lines)
allText.AppendLine(line.Text);
}
return allText.ToString();
}
// Azure does not support multi-frame TIFF directly via ImageAnalysisClient
// Must split frames manually and upload each as a separate transaction
public async Task<string> ExtractMultiFrameTiffAsync(string tiffPath)
{
// Load TIFF using System.Drawing or a third-party library
using var bitmap = new System.Drawing.Bitmap(tiffPath);
int frameCount = bitmap.GetFrameCount(
System.Drawing.Imaging.FrameDimension.Page);
var allText = new StringBuilder();
for (int i = 0; i < frameCount; i++)
{
// Select frame, save to temporary PNG, upload to Azure
bitmap.SelectActiveFrame(System.Drawing.Imaging.FrameDimension.Page, i);
using var ms = new MemoryStream();
bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
ms.Position = 0;
// Each frame = 1 Azure transaction = $0.001
var imageData = BinaryData.FromStream(ms);
var result = await _client.AnalyzeAsync(imageData, VisualFeatures.Read);
foreach (var block in result.Value.Read.Blocks)
foreach (var line in block.Lines)
allText.AppendLine(line.Text);
}
return allText.ToString();
}
Imports System.Drawing
Imports System.Drawing.Imaging
Imports System.IO
Imports System.Text
Imports System.Threading.Tasks
Public Class TiffProcessor
Private _client As ImageAnalysisClient
Public Async Function ExtractMultiFrameTiffAsync(tiffPath As String) As Task(Of String)
' Load TIFF using System.Drawing or a third-party library
Using bitmap As New Bitmap(tiffPath)
Dim frameCount As Integer = bitmap.GetFrameCount(FrameDimension.Page)
Dim allText As New StringBuilder()
For i As Integer = 0 To frameCount - 1
' Select frame, save to temporary PNG, upload to Azure
bitmap.SelectActiveFrame(FrameDimension.Page, i)
Using ms As New MemoryStream()
bitmap.Save(ms, Imaging.ImageFormat.Png)
ms.Position = 0
' Each frame = 1 Azure transaction = $0.001
Dim imageData As BinaryData = BinaryData.FromStream(ms)
Dim result = Await _client.AnalyzeAsync(imageData, VisualFeatures.Read)
For Each block In result.Value.Read.Blocks
For Each line In block.Lines
allText.AppendLine(line.Text)
Next
Next
End Using
Next
Return allText.ToString()
End Using
End Function
End Class
Enfoque 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
El coste de transacción por fotograma se reduce a cero. El bucle de extracción de fotogramas System.Drawing y su paso de serialización temporal a PNG se eliminan por completo. Para los flujos de trabajo de archivo de fax donde la calidad de los documentos varía, la guía de entrada TIFF y GIF cubre las opciones de selección de fotogramas, y la guía de corrección de la calidad de la imagen documenta el conjunto completo de filtros de preprocesamiento.
Procesamiento por lotes en paralelo sin colas con límite de velocidad
El nivel S1 de Visión por computadora de Azure limita el rendimiento a 10 transacciones por segundo. Los trabajos por lotes que superen esta tasa recibirán respuestas HTTP 429. Las implementaciones de producción requieren un envoltorio de limitación de velocidad, un semáforo o una capa de encolado para mantenerse dentro del límite. La API de IronOCR es segura para hilos por diseño: cree una instancia IronTesseract por hilo y ejecútela con Parallel.ForEach.
Enfoque de visión artificial de Azure:
// Must throttle to 10 TPS to avoid 429 errors on S1 tier
public class ThrottledAzureBatchProcessor
{
private readonly ImageAnalysisClient _client;
// Semaphore limits concurrent Azure calls to stay under 10 TPS
private readonly SemaphoreSlim _throttle = new SemaphoreSlim(10, 10);
public async Task<Dictionary<string, string>> ProcessBatchAsync(
IEnumerable<string> imagePaths)
{
var results = new ConcurrentDictionary<string, string>();
var tasks = imagePaths.Select(async path =>
{
await _throttle.WaitAsync();
try
{
using var stream = File.OpenRead(path);
var data = BinaryData.FromStream(stream);
var response = await _client.AnalyzeAsync(data, VisualFeatures.Read);
var text = string.Join("\n",
response.Value.Read.Blocks
.SelectMany(b => b.Lines)
.Select(l => l.Text));
results[path] = text;
// Respect 1-second window for the 10 TPS ceiling
await Task.Delay(100);
}
catch (RequestFailedException ex) when (ex.Status == 429)
{
// Rate limited despite throttling — back off and retry
await Task.Delay(2000);
// Re-queue or log failure — simplified here
results[path] = string.Empty;
}
finally
{
_throttle.Release();
}
});
await Task.WhenAll(tasks);
return new Dictionary<string, string>(results);
}
}
// Must throttle to 10 TPS to avoid 429 errors on S1 tier
public class ThrottledAzureBatchProcessor
{
private readonly ImageAnalysisClient _client;
// Semaphore limits concurrent Azure calls to stay under 10 TPS
private readonly SemaphoreSlim _throttle = new SemaphoreSlim(10, 10);
public async Task<Dictionary<string, string>> ProcessBatchAsync(
IEnumerable<string> imagePaths)
{
var results = new ConcurrentDictionary<string, string>();
var tasks = imagePaths.Select(async path =>
{
await _throttle.WaitAsync();
try
{
using var stream = File.OpenRead(path);
var data = BinaryData.FromStream(stream);
var response = await _client.AnalyzeAsync(data, VisualFeatures.Read);
var text = string.Join("\n",
response.Value.Read.Blocks
.SelectMany(b => b.Lines)
.Select(l => l.Text));
results[path] = text;
// Respect 1-second window for the 10 TPS ceiling
await Task.Delay(100);
}
catch (RequestFailedException ex) when (ex.Status == 429)
{
// Rate limited despite throttling — back off and retry
await Task.Delay(2000);
// Re-queue or log failure — simplified here
results[path] = string.Empty;
}
finally
{
_throttle.Release();
}
});
await Task.WhenAll(tasks);
return new Dictionary<string, string>(results);
}
}
Imports System.Collections.Concurrent
Imports System.IO
Imports System.Threading
Imports System.Threading.Tasks
' Must throttle to 10 TPS to avoid 429 errors on S1 tier
Public Class ThrottledAzureBatchProcessor
Private ReadOnly _client As ImageAnalysisClient
' Semaphore limits concurrent Azure calls to stay under 10 TPS
Private ReadOnly _throttle As New SemaphoreSlim(10, 10)
Public Async Function ProcessBatchAsync(imagePaths As IEnumerable(Of String)) As Task(Of Dictionary(Of String, String))
Dim results As New ConcurrentDictionary(Of String, String)()
Dim tasks = imagePaths.Select(Async Function(path)
Await _throttle.WaitAsync()
Try
Using stream = File.OpenRead(path)
Dim data = BinaryData.FromStream(stream)
Dim response = Await _client.AnalyzeAsync(data, VisualFeatures.Read)
Dim text = String.Join(vbLf,
response.Value.Read.Blocks _
.SelectMany(Function(b) b.Lines) _
.Select(Function(l) l.Text))
results(path) = text
' Respect 1-second window for the 10 TPS ceiling
Await Task.Delay(100)
End Using
Catch ex As RequestFailedException When ex.Status = 429
' Rate limited despite throttling — back off and retry
Await Task.Delay(2000)
' Re-queue or log failure — simplified here
results(path) = String.Empty
Finally
_throttle.Release()
End Try
End Function)
Await Task.WhenAll(tasks)
Return New Dictionary(Of String, String)(results)
End Function
End Class
Enfoque IronOCR:
// No 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);
}
// No 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
' No 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
Se eliminan el semáforo, el retardo de 100 ms y el bloqueo de captura HTTP 429. El paralelismo solo está limitado por los núcleos de la CPU y la memoria disponible, no por una capa de servicio. El ejemplo de multihilo muestra el patrón completo con comparaciones de tiempos, y la guía de optimización de velocidad abarca el ajuste de la configuración del motor para cargas de trabajo por lotes.
Preprocesamiento de escaneos de baja calidad que Azure rechaza
Azure Computer Vision realiza mejoras de imagen en el servidor, pero es opaco y no configurable. Los documentos que están demasiado sesgados, tienen demasiado ruido o un contraste demasiado bajo arrojan resultados poco fiables o texto vacío sin posibilidad de intervención.IronOCR expone la línea preprocesadora directamente en OcrInput.
Enfoque de visión artificial de Azure:
// No 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);
// No way to know if the server applied enhancement
// No 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;
}
// No 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);
// No way to know if the server applied enhancement
// No confidence on the overall result — only per-word
var text = string.Join("\n",
result.Value.Read.Blocks
.SelectMany(b => b.Lines)
.Select(l => l.Text));
if (string.IsNullOrWhiteSpace(text))
{
// Option 2: Apply external preprocessing using System.Drawing, SkiaSharp,
// or ImageMagick, re-serialize to stream, re-upload — second billable transaction
throw new Exception("Azure returned empty result; manual preprocessing needed");
}
return text;
}
Imports System.IO
Imports System.Threading.Tasks
Public Class Example
Public Async Function ExtractFromLowQualityScanAsync(imagePath As String) As Task(Of String)
' Option 1: Accept whatever Azure returns (may be empty or low-quality)
Using stream = File.OpenRead(imagePath)
Dim imageData = BinaryData.FromStream(stream)
Dim result = Await _client.AnalyzeAsync(imageData, VisualFeatures.Read)
' No way to know if the server applied enhancement
' No 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
Enfoque 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
La dependencia externa del preprocesamiento: System.Drawing, SkiaSharp o ImageMagick, se elimina. El coste de la nueva carga y de la segunda transacción desaparece. La línea preprocesadora es parte del ciclo de vida OcrInput, por lo que se aplica antes de que el motor OCR vea la imagen. El tutorial sobre filtros de imagen explica cada filtro con comparaciones de precisión antes y después de su aplicación.
Referencia de mapeo de la API OCR de Visión por computadora de Azure a IronOCR
| Visión por computadora de Azure | Equivalente a IronOCR |
|---|---|
ImageAnalysisClient |
IronTesseract |
new AzureKeyCredential(apiKey) |
IronOcr.License.LicenseKey = key |
new Uri(endpoint) |
No es necesario |
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 (flotante de 0.0 a 1.0) |
word.Confidence (doble de 0–100) |
word.BoundingPolygon |
word.X, word.Y, word.Width, word.Height |
DocumentAnalysisClient |
IronTesseract + OcrInput |
AnalyzeDocumentAsync(WaitUntil.Completed, "prebuilt-read", stream) |
ocr.Read(input) con input.LoadPdf(path) |
operation.Value.Pages |
result.Pages |
page.Lines[i].Content |
result.Lines[i].Text |
Bucle de sondeo UpdateStatusAsync() |
No es necesario: resultado síncrono |
RequestFailedException (estado 429) |
No aplicable — sin límites de tarifa |
RequestFailedException (estado 5xx) |
No aplicable — no hay errores de servicio |
Bandera de enumeración VisualFeatures.Read |
Implícito: Read() siempre extrae texto |
Modelo prebuilt-read de Form Recognizer |
Motor OCR integrado (sin selección de modelo) |
URL de punto final Azure en appsettings.json |
No es necesario |
| Procedimientos de rotación de claves API | No es necesario |
Problemas comunes de migración y soluciones
Problema 1: Contratos de interfaz asíncronos que no pueden cambiar
Azure Computer Vision: Las interfaces de servicio a menudo declaran tipos de retorno Task<string> porque Azure exige asincronía. El código que realiza las llamadas, los controladores y los procesos en segundo plano están escritos como métodos asíncronos. El cambio a IronOCR elimina la necesidad de usar operaciones asíncronas en la capa OCR, pero cambiar todas las firmas de interfaz no siempre es factible en una base de código grande.
Solución: Envuelva la llamada sincrónica de IronOCR en Task.Run para satisfacer la interfaz existente sin refactorizaciones en cascada:
// 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
Este es un paso intermedio válido. La guía de OCR asíncrono cubre la compatibilidad asíncrona integrada de IronOCR para escenarios donde se prefiere la integración asíncrona completa.
Problema 2: La lógica del umbral de confianza produce resultados erróneos.
Azure Computer Vision: Azure devuelve confianza de palabra como un float entre 0.0 y 1.0. El código de filtrado existente utiliza umbrales como word.Confidence > 0.85f. Tras la migración, estas comparaciones siempre dan como resultado falso porque el nivel de confianza de IronOCR es de 0 a 100, no de 0 a 1.
Solución: Multiplique los umbrales de Azure existentes por 100 al actualizar la lógica de filtrado:
// 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
Problema 3: La extracción de campos de modelos predefinidos del reconocedor de formularios no tiene un equivalente directo en IronOCR.
Azure Computer Vision: Los modelos preconstruidos de factura y recibo de Form Recognizer extraen automáticamente campos nombrados — InvoiceTotal, VendorName, InvoiceDate— sin especificar dónde aparecen esos campos en la página. La lógica de extracción está integrada en el modelo de Azure.
Solución: Reemplace la extracción de campos basada en modelos con OCR basado en regiones usando CropRectangle. Esto requiere conocer el diseño del documento, pero la mayoría de las implementaciones reales ya cuentan con plantillas fijas:
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
La guía de OCR basada en regiones abarca detalles del sistema de coordenadas y el procesamiento por lotes en múltiples regiones.
Problema 4: Falta hOCR y exportación estructurada.
Azure Computer Vision: Azure no proporciona resultados de hOCR. Los equipos que necesitan datos de diseño estandarizados para las herramientas de análisis de documentos posteriores extraen manualmente los cuadros delimitadores de la respuesta de Azure y construyen su propio formato de salida.
Solución:IronOCR produce hOCR que cumple con los estándares en una sola llamada:
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")
Problema 5: La versión del SDK de Azure entra en conflicto con otros paquetes de Azure.
Azure Computer Vision: Los proyectos que usan múltiples paquetes SDK de Azure (Azure.Storage.Blobs, Azure.Identity, Azure.KeyVault.Secrets) pueden encontrar conflictos de versión entre las dependencias transitivas de Azure.Core. La política de control de versiones del monorepositorio del SDK de Azure ayuda, pero no elimina todos los conflictos, especialmente cuando se mezclan versiones de disponibilidad general (GA) y versiones preliminares del SDK.
Solución: Eliminar Azure.AI.Vision.ImageAnalysis y Azure.AI.FormRecognizer elimina esos paquetes SDK del árbol de dependencias. Si el proyecto usa Azure solo para OCR, se elimina todo el conjunto de dependencias de Azure.*. Si se mantienen otros servicios de Azure, la reducción en el número de paquetes disminuye la superficie de conflicto:
# 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
Problema 6: La calidad de los documentos escaneados está por debajo del umbral de aceptación de Azure.
Azure Computer Vision: Las imágenes de muy baja resolución (inferiores a ~150 ppp) o los escaneos con una distorsión severa devuelven un texto mínimo o vacío desde la canalización del lado del servidor de Azure, sin ninguna información sobre la mejora que se intentó realizar. Quien realiza la llamada no tiene forma de mejorar el resultado sin un preprocesamiento externo.
Solución: Utilice el proceso de preprocesamiento de IronOCR para preparar la imagen antes del 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
La guía de corrección de orientación de imagen cubre específicamente la detección de inclinación y rotación.
Lista de verificación para la migración de OCR de Azure Computer Vision
Pre-Migración
Localiza todos los usos de Visión por computadora de Azure y Form Recognizer en el código fuente:
# 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" .
Identifique los archivos de configuración que contienen puntos finales y claves de 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" .
Artículos del inventario antes de que comience la codificación:
- Número de clases que implementan patrones de servicio de Azure OCR
- Número de cadenas de métodos asíncronos que se propagan desde las llamadas de OCR
- Identifique cualquier umbral de confianza de palabras utilizando la escala de Azure de 0,0 a 1,0.
- Identificar el uso del modelo predefinido de Form Recognizer (factura, recibo, identificación) que requiere reemplazo basado en la región.
- Identificar las entradas TIFF multifotograma que actualmente se dividen para la carga por fotograma.
- Compruebe las configuraciones de Docker y CI/CD para las reglas de red salientes de Azure que ya no serán necesarias.
Migración de código
- Eliminar el paquete NuGet
Azure.AI.Vision.ImageAnalysisde todos los proyectos que lo usan - Eliminar el paquete NuGet
Azure.AI.FormRecognizerde todos los proyectos que lo usan - Ejecutar
dotnet add package IronOcren cada proyecto afectado - Añadir
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE")al iniciar la aplicación - Reemplazar
usando Azure; using Azure.AI.Vision.ImageAnalysis;withusing IronOcr; - Registrar
IronTesseractcomo un singleton en DI (services.AddSingleton<IronTesseract>()) - Eliminar
AzureComputerVisionOptionso clases de configuración equivalentes; eliminar las secciones de OCR de Azureappsettings.json - Reemplace inyecciones de constructor
ImageAnalysisClientcon inyecciones deIronTesseracten todas las clases de servicio - Reemplace las llamadas
AnalyzeAsync(BinaryData.FromStream(stream), VisualFeatures.Read)conocr.Read(imagePath)oocr.Read(input)conOcrInputsegún corresponda - Elimine todos los bucles de sondeo
UpdateStatusAsyncy patronesWaitUntil.Completed; reemplaceDocumentAnalysisClientconIronTesseract+OcrInput.LoadPdf() - Actualizar las comparaciones del umbral de confianza de las palabras: multiplicar todos los valores de Azure de 0,0 a 1,0 por 100.
- Sustituir el acceso al cuadro delimitador de polígonos (
word.BoundingPolygon.Min/Max) con propiedades directasword.X,word.Y,word.Width,word.Height - Sustituir bucles de subida por cuadro por carga para cada cuadro en imágenes TIFF multimarco con
input.LoadImageFrames(tiffPath) - Convertir la extracción de campos de modelos preconstruidos de Form Recognizer a OCR basado en regiones
CropRectanglepara plantillas de documentos conocidas - Eliminar bloques de captura
RequestFailedExceptionpara HTTP 429 y 5xx; simplificar la gestión de errores únicamente a las excepciones del sistema de archivos y de validación de entradas
Posmigración
- Verificar que la salida de la extracción de texto sin formato coincida o mejore los resultados de Azure para una muestra representativa de más de 20 documentos.
- Confirmar que el procesamiento de PDF de varias páginas genera texto para todas las páginas sin contadores de facturación por página en el monitoreo.
- La prueba de procesamiento de TIFF multifotograma devuelve el mismo número de páginas que el número de fotogramas de origen.
- Validar que los valores de confianza de las palabras se encuentren en el rango de 0 a 100 y que las comparaciones de umbral se comporten correctamente.
- Verificar que las coordenadas del cuadro delimitador de palabras se alineen correctamente con el sistema de coordenadas de la imagen de origen.
- Ejecute la ruta de procesamiento por lotes y confirme que se completa sin errores de límite de velocidad ni conflictos de semáforos.
- Realice una prueba en un entorno sin acceso a Internet saliente para confirmar que no se producen llamadas a los puntos finales de Azure.
- Confirmar que los despliegues de Docker y contenedores se inician con éxito sin reglas de firewall de salida para
cognitiveservices.azure.com - Verifique que la construcción del contenedor DI tenga éxito y que el singleton de
IronTesseractse resuelva correctamente - Verificar que la variable de entorno
IRONOCR_LICENSEesté configurada en todos los entornos de implementación y que el OCR produzca salidas licenciadas (sin marcas de agua)
Principales ventajas de migrar a IronOCR
El coste se vuelve predecible. El contador por transacción de Visión por computadora de Azure funciona de forma continua. Un solo mes con un alto volumen de documentos puede superar el coste anual de una licencia de IronOCR. Tras la migración, el presupuesto de OCR se convierte en una partida fija. El volumen de procesamiento puede crecer, debido a la expansión del negocio, reprocesamiento masivo o picos temporales, sin generar una factura mayor. La página de licencias de IronOCR muestra opciones de nivel desde $999 para una licencia de un solo desarrollador hasta $2,999 para desarrolladores ilimitados.
La pila de aplicaciones se simplifica. Eliminar Azure.AI.Vision.ImageAnalysis y Azure.AI.FormRecognizer elimina dos paquetes NuGet, dos configuraciones de recursos de Azure, dos conjuntos de credenciales y toda la infraestructura de sondeo asincrónico. Las clases de servicio que anteriormente requerían una firma async Task<string> debido a la E/S de la nube se convierten en métodos sincrónicos. El manejo de errores se reduce desde fallas de red, límites de velocidad y disponibilidad del servicio hasta la validación del sistema de archivos y de las entradas. Cada futuro desarrollador que trabaje con este código fuente encontrará menos código que comprender.
Los datos de los documentos permanecen bajo el control de la organización. Tras la migración, el procesamiento OCR se realiza localmente. Los documentos no traspasan los límites organizativos, no aparecen en la telemetría de Azure y no están sujetos a las políticas de retención o procesamiento de datos de Microsoft. Las entidades sujetas a la HIPAA, los contratistas de la ITAR, las organizaciones reguladas por el RGPD y cualquier equipo con requisitos de residencia de datos pueden procesar documentos sin la revisión de cumplimiento de un tercero en la nube. La guía de implementación para Linux y la guía de implementación para Docker muestran cómo implementar IronOCR en entornos restringidos.
El rendimiento de las operaciones por lotes se adapta al hardware. El límite de 10 transacciones por segundo (TPS) del nivel S1 de Azure es un límite estricto que requiere el uso de colas, la limitación de velocidad o actualizaciones de nivel para sortearlo. Después de la migración, los trabajos de OCR concurrentes saturan los núcleos de CPU disponibles sin ningún límite impuesto por el servicio. Un servidor de cuatro núcleos puede ejecutar cuatro instancias IronTesseract en paralelo simultáneamente. El ejemplo de multihilo demuestra el patrón y muestra cómo el rendimiento aumenta con el número de núcleos.
Los defectos de preprocesamiento se pueden solucionar mediante código. La mejora de imágenes del lado del servidor de Visión por computadora de Azure es una caja negra. Cuando un escaneo devuelve salida vacía o de baja confianza, las únicas opciones son aceptarlo o preprocesarlo externamente antes de volver a cargar, lo cual implica un costo adicional. El pipeline OcrInput de IronOCR expone métodos de primer nivel como deskew, denoise, contraste, binarización, escala, afilar y eliminación de ruido profundo. Los tipos de escaneo problemáticos se convierten en parámetros ajustables. La página de funciones de preprocesamiento enumera el conjunto completo de filtros, con indicaciones sobre qué filtros corrigen qué defectos de escaneo.
Preguntas Frecuentes
¿Por qué debería migrar de Azure Computer Vision OCR a IronOCR?
Los impulsores comunes incluyen la eliminación de la complejidad de la interoperabilidad COM, la sustitución de la gestión de licencias basada en archivos, la evitación de la facturación por página, la habilitación de la implementación de Docker/contenedores y la adopción de un flujo de trabajo nativo de NuGet que se integre con las herramientas .NET estándar.
¿Cuáles son los principales cambios de código al migrar de Azure Computer Vision OCR a IronOCR?
Sustituya las secuencias de inicialización de Azure Computer Vision por la instanciación de IronTesseract, elimine la gestión del ciclo de vida de COM (patrones explícitos Create/Load/Close) y actualice los nombres de las propiedades de los resultados. El resultado es un número significativamente menor de líneas repetitivas.
¿Cómo instalo IronOCR para comenzar la migración?
Ejecute 'Install-Package IronOcr' en la consola del gestor de paquetes o 'dotnet add package IronOcr' en la CLI. Los paquetes de idiomas son paquetes independientes: 'dotnet add package IronOcr.Languages.French' para el francés, por ejemplo.
¿IronOCR alcanza la precisión de OCR de Azure Computer Vision OCR para documentos comerciales estándar?
IronOCR consigue una gran precisión para contenido empresarial estándar, como facturas, contratos, recibos y formularios mecanografiados. Los filtros de preprocesamiento de imágenes (eliminación de distorsiones, eliminación de ruido, mejora del contraste) mejoran aún más el reconocimiento en entradas degradadas.
¿Cómo gestiona IronOCR los datos de idioma que Azure Computer Vision OCR instala por separado?
Los datos de idiomas en IronOCR se distribuyen como paquetes NuGet. dotnet add package IronOcr.Languages.German' instala la compatibilidad con el alemán. No es necesario colocar manualmente los archivos ni las rutas de los directorios.
¿La migración de Azure Computer Vision OCR a IronOCR requiere cambios en la infraestructura de despliegue?
IronOCR requiere menos cambios de infraestructura que Azure Computer Vision OCR. No hay rutas binarias del SDK, ubicaciones de archivos de licencia ni configuraciones del servidor de licencias. El paquete NuGet contiene el motor OCR completo, y la clave de licencia es una cadena establecida en el código de la aplicación.
¿Cómo configuro las licencias de IronOCR después de la migración?
Asigne IronOcr.License.LicenseKey = "YOUR-KEY" en el código de inicio de la aplicación. En Docker o Kubernetes, almacene la clave como una variable de entorno y léala en el inicio. Utilice License.IsValidLicense para validar antes de aceptar tráfico.
¿Puede IronOCR procesar archivos PDF del mismo modo que Azure Computer Vision?
Sí, IronOCR lee PDF nativos y escaneados. Instancie IronTesseract, llame a ocr.Read(input) donde input es una ruta PDF u OcrPdfInput, e itere las páginas OcrResult. No es necesario un proceso de renderizado de PDF independiente.
¿Cómo gestiona IronOCR los hilos en el procesamiento de grandes volúmenes?
IronTesseract puede instanciarse de forma segura por subproceso. Gire una instancia por hilo en un Parallel.ForEach o Task pool, ejecute OCR concurrentemente, y disponga de cada instancia cuando termine. No se requiere estado global o bloqueo.
¿Qué formatos de salida admite IronOCR tras la extracción de texto?
IronOCR devuelve resultados estructurados que incluyen texto, coordenadas de palabras, puntuaciones de confianza y estructura de páginas. Las opciones de exportación incluyen texto sin formato, PDF con opción de búsqueda y objetos de resultados estructurados para su procesamiento posterior.
¿Es el precio de IronOCR más predecible que el de Azure Computer Vision OCR para escalar cargas de trabajo?
IronOCR utiliza licencias perpetuas de tarifa plana sin cargos por página o volumen. Tanto si procesa 10.000 como 10 millones de páginas, el coste de la licencia permanece constante. Las opciones de licencias por volumen y por equipo se encuentran en la página de precios de IronOCR.
¿Qué ocurre con mis pruebas existentes después de migrar de Azure Computer Vision OCR a IronOCR?
Las pruebas que validan el contenido de texto extraído deben seguir superándose tras la migración. Las pruebas que validan patrones de llamada a API o el ciclo de vida de objetos COM deberán actualizarse para reflejar el modelo de inicialización y resultados más sencillo de IronOCR.

