Cómo mejorar la calidad de la imagen para obtener mejores resultados de OCR con C#
Esta guía guía a los desarrolladores de .NET a través de una migración completa desde la familia de paquetes Sdcb.PaddleOCR a IronOCR. Cubre todo el proceso de reemplazo: eliminar la pila de múltiples paquetes PaddlePaddle, eliminar la gestión de archivos de modelo y la configuración de la GPU, y reemplazar la canalización de inferencia dependiente de OpenCV con una única instalación de NuGet . Cada sección es independiente; no es necesario leer previamente el artículo comparativo.
¿Por qué migrar desde PaddleOCR?
El contenedor Sdcb.PaddleOCR es un puente mantenido por la comunidad entre el ecosistema de aprendizaje profundo en Python de PaddlePaddle y .NET. Cumple su función, pero conlleva todo el peso de ese puente: archivos de modelo, binarios de inferencia nativos, OpenCV para la carga de imágenes e infraestructura CUDA opcional. Para la mayoría de las cargas de trabajo de OCR en .NET , esa es una infraestructura que el proyecto nunca necesitó.
Tres directorios de modelos antes de leer un solo carácter. El proceso de inferencia de PaddleOCR encadena tres redes neuronales: un modelo de detección, un modelo de clasificación de dirección y un modelo de reconocimiento. Cada red es un directorio separado de archivos .pdmodel y .pdiparams que deben existir en disco antes de que new PaddleOcrAll(models) compile a un motor funcional. Ya sea que esos archivos lleguen mediante una llamada de descarga asíncrona, que conecta al almacenamiento de bj.bcebos.com de Baidu en China, o mediante un árbol de directorio mantenido manualmente models/, el desarrollador es permanentemente responsable de la versionado de modelos. Cuando Sdcb.PaddleOCR se actualiza, los modelos pre-descargados de la versión anterior pueden requerir una re-descarga.IronOCR no tiene archivos de modelo, ni directorios de modelo, ni problemas de sincronización de versiones. El motor viene incluido en el paquete NuGet .
OpenCV no es opcional. No hay un camino desde una ruta de archivo a la inferencia de PaddleOCR que pase por alto OpenCvSharp. Cada imagen, independientemente del formato, debe pasar a través de Cv2.ImRead(path) para convertirse en un objeto Mat antes de que ocr.Run(mat) pueda aceptarlo. Eso significa dos paquetes NuGet adicionales (OpenCvSharp4 y OpenCvSharp4.runtime.win), DLLs nativas específicas de plataforma en el resultado de implementación y una línea apt-get install libopencv-dev en el Dockerfile.IronOCR acepta rutas de archivo, flujos, matrices de bytes y System.Drawing.Bitmap directamente. No existe el intermediario Mat.
La configuración de la GPU es un proyecto que lleva varios días. Las cifras de rendimiento de la GPU que anuncia PaddleOCR (50-100 ms por imagen frente a 300-500 ms en la CPU) son reales. Para ello se requieren controladores NVIDIA de una versión específica, CUDA Toolkit 11.8 (no 12.x), cuDNN 8.6+ ubicado en las rutas PATH correctas y un paquete NuGet de tiempo de ejecución de GPU independiente. En Docker, la imagen base debe ser nvidia/cuda:11.8.0-cudnn8-runtime-ubuntu22.04 y el host debe tener instalado nvidia-container-toolkit. Los equipos que no disponen de infraestructura de GPU invierten entre 2 y 8 horas en la configuración de CUDA por entorno.IronOCR está diseñado para la inferencia por CPU, ofrece un tiempo de procesamiento de 150 a 300 ms por imagen en hardware estándar y no requiere ninguna configuración de GPU.
Los artefactos de implementación son 4-6x más grandes. El resultado de implementación de PaddleOCR incluye paddle_inference.dll (~200MB), paddle2onnx.dll (~5MB), archivos opencv_world*.dll (~50MB combinados) y los directorios de modelo (~21MB). Una imagen de Docker ocupa aproximadamente 1,5 GB. El tamaño total de una instalación de IronOCR es de aproximadamente 80 MB. La imagen de Docker ocupa aproximadamente 400 MB. La diferencia se agrava en CI/CD: cada ejecución del pipeline que restaura paquetes NuGet debe descargar los modelos de Baidu o extraerlos de una capa de caché mantenida por separado.
No genera archivos PDF con capacidad de búsqueda. PaddleOCR extrae regiones de texto de las imágenes, pero no dispone de ningún mecanismo para incrustar el texto reconocido en un PDF como una capa con capacidad de búsqueda. Para crear un PDF con capacidad de búsqueda a partir de la salida de PaddleOCR, se requiere una biblioteca de PDF de terceros, la inyección de capas de texto página por página y la reasignación de coordenadas.IronOCR produce un PDF completamente buscable con una línea: result.SaveAsSearchablePdf("output.pdf").
El problema fundamental
PaddleOCR requiere que se configuren tres directorios de modelos antes de que pueda comenzar la inferencia:
// PaddleOCR: three model directories, all must exist and match the wrapper version
FullOcrModel models = new FullOcrModel(
LocalDetectionModel.FromDirectory("models/ch_PP-OCRv4_det_infer"), // ~5MB
LocalClassificationModel.FromDirectory("models/ch_ppocr_mobile_v2.0_cls_infer"), // ~2MB
LocalRecognitionModel.FromDirectory("models/ch_PP-OCRv4_rec_infer") // ~15MB
);
using PaddleOcrAll ocr = new PaddleOcrAll(models);
using Mat mat = Cv2.ImRead("document.png"); // OpenCvSharp required for every image
PaddleOcrResult result = ocr.Run(mat);
// PaddleOCR: three model directories, all must exist and match the wrapper version
FullOcrModel models = new FullOcrModel(
LocalDetectionModel.FromDirectory("models/ch_PP-OCRv4_det_infer"), // ~5MB
LocalClassificationModel.FromDirectory("models/ch_ppocr_mobile_v2.0_cls_infer"), // ~2MB
LocalRecognitionModel.FromDirectory("models/ch_PP-OCRv4_rec_infer") // ~15MB
);
using PaddleOcrAll ocr = new PaddleOcrAll(models);
using Mat mat = Cv2.ImRead("document.png"); // OpenCvSharp required for every image
PaddleOcrResult result = ocr.Run(mat);
Imports OpenCvSharp
Imports PaddleOCR
' PaddleOCR: three model directories, all must exist and match the wrapper version
Dim models As New FullOcrModel(
LocalDetectionModel.FromDirectory("models/ch_PP-OCRv4_det_infer"), ' ~5MB
LocalClassificationModel.FromDirectory("models/ch_ppocr_mobile_v2.0_cls_infer"), ' ~2MB
LocalRecognitionModel.FromDirectory("models/ch_PP-OCRv4_rec_infer") ' ~15MB
)
Using ocr As New PaddleOcrAll(models)
Using mat As Mat = Cv2.ImRead("document.png") ' OpenCvSharp required for every image
Dim result As PaddleOcrResult = ocr.Run(mat)
End Using
End Using
IronOCR no tiene archivos de modelo, ni directorios de modelo, ni dependencia de OpenCV:
// IronOCR: one package, zero model management
var ocr = new IronTesseract();
using var input = new OcrInput();
input.LoadImage("document.png");
var result = ocr.Read(input);
Console.WriteLine(result.Text);
// IronOCR: one package, zero model management
var ocr = new IronTesseract();
using var input = new OcrInput();
input.LoadImage("document.png");
var result = ocr.Read(input);
Console.WriteLine(result.Text);
Imports IronOcr
Dim ocr As New IronTesseract()
Using input As New OcrInput()
input.LoadImage("document.png")
Dim result = ocr.Read(input)
Console.WriteLine(result.Text)
End Using
IronOCR vs PaddleOCR (.NET): Comparación de características
La tabla que aparece a continuación recoge las dimensiones más importantes a la hora de planificar la migración.
| Característica | PaddleOCR (Sdcb) | IronOCR |
|---|---|---|
| Se requieren paquetes NuGet | 4-5 | 1 |
| Se requieren archivos de modelo | Sí (3 directorios, ~21 MB) | No (incluido en el paquete) |
| Fuente de descarga del modelo | Servidores de Baidu (bj.bcebos.com) | Restauración de NuGet (Iron Software) |
| dependencia de OpenCV | Requerido (OpenCvSharp4) | None |
| Entrada de imagen | A través de Mat mat = Cv2.ImRead() |
Ruta de archivo directa, flujo, matriz de bytes |
| Entrada nativa de PDF | No | Sí (input.LoadPdf()) |
| Salida en PDF con capacidad de búsqueda | No | Sí (result.SaveAsSearchablePdf()) |
| Entrada TIFF multifotograma | Bucle manual por fotograma | input.LoadImageFrames() |
| Compatibilidad con GPU | Sí (se requiere CUDA 11.8 + cuDNN) | Optimizado para CPU (no requiere GPU) |
| Preprocesamiento integrado | No (la red neuronal maneja la asimetría/el ruido) | Sí (Desinclinar, Reducir ruido, Contraste, Binarizar, Nitidez) |
| Idiomas compatibles | 14 | 125+ |
| Método de instalación del idioma | DownloadAsync() por modelo de idioma |
dotnet add package IronOcr.Languages.* |
| Multilingüe simultáneo | No (modelo independiente por idioma) | Sí (OcrLanguage.English + OcrLanguage.French) |
| Salida estructurada | result.Regions (espacial, desordenado) |
Páginas, párrafos, líneas, palabras, caracteres |
| Puntuación de confianza | Valor flotante por región (0-1) | Porcentaje por palabra (0-100) |
| Lectura de códigos de barras | No | Sí (ocr.Configuration.ReadBarCodes = true) |
| Exportación hOCR | No | Sí |
| Tamaño de despliegue | 300-500 MB | ~80MB |
| Tamaño de la imagen de Docker | ~1,5 GB (con base CUDA) | ~400 MB |
| Tiempo de arranque en frío | 3-5 segundos (carga del modelo) | Menos de 1 segundo |
| Plataforma cruzada | Windows, Linux (parcial) | Windows, Linux, macOS, Docker, Azure, AWS |
| Compatibilidad con .NET | .NET 6+ (envoltorio comunitario) | .NET Framework 4.6.2+, .NET 5/6/7/8/9 |
| Apoyo comercial | Problemas de la comunidad/GitHub | Sí (Iron Software, con SLA) |
| Licencia | Apache 2.0 (gratuito) | Perpetual ($999 Lite / $1,499 Pro / $2,999 Enterprise) |
Inicio rápido: Migración de PaddleOCR (.NET) a IronOCR
Paso 1: Sustituir paquetes NuGet
Elimine los cinco paquetes relacionados con PaddleOCR:
dotnet remove package Sdcb.PaddleOCR
dotnet remove package Sdcb.PaddleOCR.Models.Online
dotnet remove package Sdcb.PaddleInference.runtime.win64.mkl
dotnet remove package OpenCvSharp4
dotnet remove package OpenCvSharp4.runtime.win
dotnet remove package Sdcb.PaddleOCR
dotnet remove package Sdcb.PaddleOCR.Models.Online
dotnet remove package Sdcb.PaddleInference.runtime.win64.mkl
dotnet remove package OpenCvSharp4
dotnet remove package OpenCvSharp4.runtime.win
Si el entorno de ejecución de la GPU estaba instalado, elimínelo también:
dotnet remove package Sdcb.PaddleInference.runtime.win64.cuda118
dotnet remove package Sdcb.PaddleInference.runtime.win64.cuda118
Instale IronOCR desde la página del paquete NuGet :
dotnet add package IronOcr
Paso 2: Actualizar los espacios de nombres
Reemplace todas las importaciones de espacios de nombres de PaddleOCR y OpenCvSharp:
// Before (PaddleOCR)
using Sdcb.PaddleOCR;
using Sdcb.PaddleOCR.Models;
using Sdcb.PaddleOCR.Models.Online;
using Sdcb.PaddleInference;
using OpenCvSharp;
// After (IronOCR)
using IronOcr;
// Before (PaddleOCR)
using Sdcb.PaddleOCR;
using Sdcb.PaddleOCR.Models;
using Sdcb.PaddleOCR.Models.Online;
using Sdcb.PaddleInference;
using OpenCvSharp;
// After (IronOCR)
using IronOcr;
Imports IronOcr
Imports Sdcb.PaddleOCR
Imports Sdcb.PaddleOCR.Models
Imports Sdcb.PaddleOCR.Models.Online
Imports Sdcb.PaddleInference
Imports OpenCvSharp
Paso 3: Inicializar licencia
Agrega la inicialización de la licencia una vez al inicio de la aplicación, antes de que se cree cualquier instancia de IronTesseract:
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Puede obtener una clave de prueba gratuita en la página de licencias de IronOCR . La versión de prueba genera un resultado con marca de agua y permite probar todas las funciones antes de la compra.
Ejemplos de migración de código
Eliminación de la configuración de la ruta del modelo local
Los proyectos que descargan previamente los archivos del modelo PaddleOCR para evitar conexiones con el servidor Baidu durante la ejecución deben configurar tres rutas de directorio separadas. Esta configuración debe actualizarse cada vez que cambie la versión del envoltorio.
Enfoque PaddleOCR:
// Local model configuration — developer owns the directory structure
// Each wrapper update may require re-downloading model files
string modelsRoot = Path.Combine(AppContext.BaseDirectory, "models");
FullOcrModel models = new FullOcrModel(
LocalDetectionModel.FromDirectory(
Path.Combine(modelsRoot, "ch_PP-OCRv4_det_infer")),
LocalClassificationModel.FromDirectory(
Path.Combine(modelsRoot, "ch_ppocr_mobile_v2.0_cls_infer")),
LocalRecognitionModel.FromDirectory(
Path.Combine(modelsRoot, "ch_PP-OCRv4_rec_infer"))
);
// Fails at runtime if any of the three directories is missing or stale
using PaddleOcrAll ocr = new PaddleOcrAll(models)
{
AllowRotateDetection = true,
Enable180Classification = true
};
using Mat mat = Cv2.ImRead("document.png");
PaddleOcrResult result = ocr.Run(mat);
Console.WriteLine(result.Text);
// Local model configuration — developer owns the directory structure
// Each wrapper update may require re-downloading model files
string modelsRoot = Path.Combine(AppContext.BaseDirectory, "models");
FullOcrModel models = new FullOcrModel(
LocalDetectionModel.FromDirectory(
Path.Combine(modelsRoot, "ch_PP-OCRv4_det_infer")),
LocalClassificationModel.FromDirectory(
Path.Combine(modelsRoot, "ch_ppocr_mobile_v2.0_cls_infer")),
LocalRecognitionModel.FromDirectory(
Path.Combine(modelsRoot, "ch_PP-OCRv4_rec_infer"))
);
// Fails at runtime if any of the three directories is missing or stale
using PaddleOcrAll ocr = new PaddleOcrAll(models)
{
AllowRotateDetection = true,
Enable180Classification = true
};
using Mat mat = Cv2.ImRead("document.png");
PaddleOcrResult result = ocr.Run(mat);
Console.WriteLine(result.Text);
Imports System
Imports System.IO
' Local model configuration — developer owns the directory structure
' Each wrapper update may require re-downloading model files
Dim modelsRoot As String = Path.Combine(AppContext.BaseDirectory, "models")
Dim models As New FullOcrModel(
LocalDetectionModel.FromDirectory(
Path.Combine(modelsRoot, "ch_PP-OCRv4_det_infer")),
LocalClassificationModel.FromDirectory(
Path.Combine(modelsRoot, "ch_ppocr_mobile_v2.0_cls_infer")),
LocalRecognitionModel.FromDirectory(
Path.Combine(modelsRoot, "ch_PP-OCRv4_rec_infer"))
)
' Fails at runtime if any of the three directories is missing or stale
Using ocr As New PaddleOcrAll(models) With {
.AllowRotateDetection = True,
.Enable180Classification = True
}
Using mat As Mat = Cv2.ImRead("document.png")
Dim result As PaddleOcrResult = ocr.Run(mat)
Console.WriteLine(result.Text)
End Using
End Using
Enfoque IronOCR:
// No model directories, no path configuration, no version matching
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var ocr = new IronTesseract();
using var input = new OcrInput();
input.LoadImage("document.png");
var result = ocr.Read(input);
Console.WriteLine(result.Text);
// No model directories, no path configuration, no version matching
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var ocr = new IronTesseract();
using var input = new OcrInput();
input.LoadImage("document.png");
var result = ocr.Read(input);
Console.WriteLine(result.Text);
Imports IronOcr
' No model directories, no path configuration, no version matching
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Dim ocr = New IronTesseract()
Using input As New OcrInput()
input.LoadImage("document.png")
Dim result = ocr.Read(input)
Console.WriteLine(result.Text)
End Using
El árbol de directorio models/, las tres llamadas FromDirectory() y la preocupación de sincronización de versiones desaparecen. El motor IronOCR viene incluido en el paquete NuGet en el momento de la restauración y no requiere resolución de ruta en tiempo de ejecución. Consulta la guía de configuración de IronTesseract para opciones de inicialización, incluyendo la colocación de la llave de licencia en appsettings.json.
Consolidación del sistema de detección y reconocimiento en dos etapas
La canalización de rotación y orientación de PaddleOCR se configura a través de propiedades en PaddleOcrAll. Replicar este comportamiento en IronOCR utiliza los métodos de preprocesamiento de OcrInput, que manejan los mismos problemas del documento con una superficie de llamada más simple.
Enfoque PaddleOCR:
using Sdcb.PaddleOCR;
using Sdcb.PaddleOCR.Models.Online;
using OpenCvSharp;
// Separate async initialization step — blocks startup for 3-5 seconds on cold run
FullOcrModel models = await OnlineFullModels.EnglishV4.DownloadAsync();
using PaddleOcrAll ocr = new PaddleOcrAll(models)
{
AllowRotateDetection = true, // Enables 0/90/180/270 degree rotation detection
Enable180Classification = true // Additional pass for upside-down text
};
// OpenCV Mat required — no direct file path support
using Mat mat = Cv2.ImRead("rotated-scan.png");
if (mat.Empty())
{
throw new FileNotFoundException("Image could not be loaded by OpenCvSharp");
}
// Three neural network passes: detection → classification → recognition
PaddleOcrResult result = ocr.Run(mat);
// Regions arrive in spatial order, not reading order
// Manual sort required for top-to-bottom, left-to-right output
var orderedRegions = result.Regions
.OrderBy(r => r.Rect.Center.Y)
.ThenBy(r => r.Rect.Center.X);
foreach (var region in orderedRegions)
{
Console.WriteLine($"{region.Text} (confidence: {region.Score:P1})");
}
using Sdcb.PaddleOCR;
using Sdcb.PaddleOCR.Models.Online;
using OpenCvSharp;
// Separate async initialization step — blocks startup for 3-5 seconds on cold run
FullOcrModel models = await OnlineFullModels.EnglishV4.DownloadAsync();
using PaddleOcrAll ocr = new PaddleOcrAll(models)
{
AllowRotateDetection = true, // Enables 0/90/180/270 degree rotation detection
Enable180Classification = true // Additional pass for upside-down text
};
// OpenCV Mat required — no direct file path support
using Mat mat = Cv2.ImRead("rotated-scan.png");
if (mat.Empty())
{
throw new FileNotFoundException("Image could not be loaded by OpenCvSharp");
}
// Three neural network passes: detection → classification → recognition
PaddleOcrResult result = ocr.Run(mat);
// Regions arrive in spatial order, not reading order
// Manual sort required for top-to-bottom, left-to-right output
var orderedRegions = result.Regions
.OrderBy(r => r.Rect.Center.Y)
.ThenBy(r => r.Rect.Center.X);
foreach (var region in orderedRegions)
{
Console.WriteLine($"{region.Text} (confidence: {region.Score:P1})");
}
Imports Sdcb.PaddleOCR
Imports Sdcb.PaddleOCR.Models.Online
Imports OpenCvSharp
' Separate async initialization step — blocks startup for 3-5 seconds on cold run
Dim models As FullOcrModel = Await OnlineFullModels.EnglishV4.DownloadAsync()
Using ocr As New PaddleOcrAll(models) With {
.AllowRotateDetection = True, ' Enables 0/90/180/270 degree rotation detection
.Enable180Classification = True ' Additional pass for upside-down text
}
' OpenCV Mat required — no direct file path support
Using mat As Mat = Cv2.ImRead("rotated-scan.png")
If mat.Empty() Then
Throw New FileNotFoundException("Image could not be loaded by OpenCvSharp")
End If
' Three neural network passes: detection → classification → recognition
Dim result As PaddleOcrResult = ocr.Run(mat)
' Regions arrive in spatial order, not reading order
' Manual sort required for top-to-bottom, left-to-right output
Dim orderedRegions = result.Regions _
.OrderBy(Function(r) r.Rect.Center.Y) _
.ThenBy(Function(r) r.Rect.Center.X)
For Each region In orderedRegions
Console.WriteLine($"{region.Text} (confidence: {region.Score:P1})")
Next
End Using
End Using
Enfoque IronOCR:
using IronOcr;
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var ocr = new IronTesseract();
using var input = new OcrInput();
input.LoadImage("rotated-scan.png");
input.Deskew(); // Corrects rotation and skew automatically
var result = ocr.Read(input);
// Output is already in reading order — no sort needed
foreach (var page in result.Pages)
{
foreach (var line in page.Lines)
{
Console.WriteLine($"{line.Text} (confidence: {line.Confidence}%)");
}
}
using IronOcr;
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var ocr = new IronTesseract();
using var input = new OcrInput();
input.LoadImage("rotated-scan.png");
input.Deskew(); // Corrects rotation and skew automatically
var result = ocr.Read(input);
// Output is already in reading order — no sort needed
foreach (var page in result.Pages)
{
foreach (var line in page.Lines)
{
Console.WriteLine($"{line.Text} (confidence: {line.Confidence}%)");
}
}
Imports IronOcr
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Dim ocr As New IronTesseract()
Using input As New OcrInput()
input.LoadImage("rotated-scan.png")
input.Deskew() ' Corrects rotation and skew automatically
Dim result = ocr.Read(input)
' Output is already in reading order — no sort needed
For Each page In result.Pages
For Each line In page.Lines
Console.WriteLine($"{line.Text} (confidence: {line.Confidence}%)")
Next
Next
End Using
El método Deskew() de IronOCR maneja la detección de rotación como parte de la canalización de preprocesamiento. La colección de resultados Lines se entrega en orden de lectura por el motor de diseño Tesseract, eliminando el patrón de ordenamiento manual. La guía de corrección de la orientación de la imagen documenta toda la gama de opciones de rotación y corrección de la inclinación.
Eliminación de la selección de dispositivos GPU y CPU
Las aplicaciones de PaddleOCR que ejecutan inferencia en GPU tienen la mayor superficie de migración: el paquete NuGet de tiempo de ejecución de GPU, los requisitos previos del entorno CUDA/cuDNN y la llamada de configuración PaddleDevice.Gpu(). Todo esto se elimina durante la migración.
Enfoque PaddleOCR:
using Sdcb.PaddleOCR;
using Sdcb.PaddleOCR.Models.Online;
using Sdcb.PaddleInference; // GPU configuration namespace
using OpenCvSharp;
// Prerequisites must exist on every deployment environment:
// - NVIDIA Driver 452.39+ (Windows) / 450.80.02+ (Linux)
// - CUDA Toolkit 11.8 (not 12.x — version must match exactly)
// - cuDNN 8.6.0+ placed in CUDA bin directory
// - dotnet add package Sdcb.PaddleInference.runtime.win64.cuda118
FullOcrModel models = await OnlineFullModels.ChineseV4.DownloadAsync();
// GPU device 0, 1000MB initial memory pool
// Throws native load exception if CUDA_PATH not set or cuDNN DLL missing
using PaddleOcrAll ocr = new PaddleOcrAll(models, PaddleDevice.Gpu(deviceId: 0))
{
AllowRotateDetection = true,
Enable180Classification = true
};
using Mat mat = Cv2.ImRead("scanned-batch.png");
PaddleOcrResult result = ocr.Run(mat);
Console.WriteLine($"Text regions: {result.Regions.Length}");
Console.WriteLine(result.Text);
using Sdcb.PaddleOCR;
using Sdcb.PaddleOCR.Models.Online;
using Sdcb.PaddleInference; // GPU configuration namespace
using OpenCvSharp;
// Prerequisites must exist on every deployment environment:
// - NVIDIA Driver 452.39+ (Windows) / 450.80.02+ (Linux)
// - CUDA Toolkit 11.8 (not 12.x — version must match exactly)
// - cuDNN 8.6.0+ placed in CUDA bin directory
// - dotnet add package Sdcb.PaddleInference.runtime.win64.cuda118
FullOcrModel models = await OnlineFullModels.ChineseV4.DownloadAsync();
// GPU device 0, 1000MB initial memory pool
// Throws native load exception if CUDA_PATH not set or cuDNN DLL missing
using PaddleOcrAll ocr = new PaddleOcrAll(models, PaddleDevice.Gpu(deviceId: 0))
{
AllowRotateDetection = true,
Enable180Classification = true
};
using Mat mat = Cv2.ImRead("scanned-batch.png");
PaddleOcrResult result = ocr.Run(mat);
Console.WriteLine($"Text regions: {result.Regions.Length}");
Console.WriteLine(result.Text);
Imports Sdcb.PaddleOCR
Imports Sdcb.PaddleOCR.Models.Online
Imports Sdcb.PaddleInference ' GPU configuration namespace
Imports OpenCvSharp
' Prerequisites must exist on every deployment environment:
' - NVIDIA Driver 452.39+ (Windows) / 450.80.02+ (Linux)
' - CUDA Toolkit 11.8 (not 12.x — version must match exactly)
' - cuDNN 8.6.0+ placed in CUDA bin directory
' - dotnet add package Sdcb.PaddleInference.runtime.win64.cuda118
Dim models As FullOcrModel = Await OnlineFullModels.ChineseV4.DownloadAsync()
' GPU device 0, 1000MB initial memory pool
' Throws native load exception if CUDA_PATH not set or cuDNN DLL missing
Using ocr As New PaddleOcrAll(models, PaddleDevice.Gpu(deviceId:=0)) With {
.AllowRotateDetection = True,
.Enable180Classification = True
}
Using mat As Mat = Cv2.ImRead("scanned-batch.png")
Dim result As PaddleOcrResult = ocr.Run(mat)
Console.WriteLine($"Text regions: {result.Regions.Length}")
Console.WriteLine(result.Text)
End Using
End Using
Enfoque IronOCR:
El enfoque de IronOCR es idéntico al ejemplo anterior: IronTesseract maneja este escenario con la misma llamada API. No se requieren paquetes de GPU, ni requisitos previos de CUDA, ni selección de dispositivo. Reemplace new PaddleOcrAll(models, PaddleDevice.Gpu(deviceId: 0)) con new IronTesseract() y elimine toda la configuración relacionada con GPU.
IronOCR ofrece un tiempo de procesamiento de 150 a 300 ms por imagen en la CPU, más rápido que PaddleOCR en la CPU (300-500 ms) y suficiente para la mayoría de las cargas de trabajo de API web y procesamiento de documentos sin necesidad de infraestructura de GPU. Para escenarios de alto rendimiento, la guía de optimización de velocidad abarca opciones de configuración que incluyen la gestión de subprocesos y el ajuste del modo de segmentación de páginas.
Extracción de datos de documentos estructurados
PaddleOCR devuelve una matriz plana de objetos PaddleOcrResultRegion ordenados espacialmente, no por flujo de lectura. La extracción de la estructura a nivel de párrafo o de línea requiere una lógica de agrupación manual basada en la proximidad de los cuadros delimitadores.IronOCR proporciona un árbol de resultados jerárquico con el orden de lectura garantizado.
Enfoque PaddleOCR:
using Sdcb.PaddleOCR;
using Sdcb.PaddleOCR.Models.Online;
using OpenCvSharp;
using System.Collections.Generic;
FullOcrModel models = await OnlineFullModels.EnglishV4.DownloadAsync();
using PaddleOcrAll ocr = new PaddleOcrAll(models);
using Mat mat = Cv2.ImRead("invoice.png");
PaddleOcrResult result = ocr.Run(mat);
// No paragraph or line grouping — must implement manually
// Group regions into lines by proximity on the Y axis
var lineGroups = new Dictionary<int, List<PaddleOcrResultRegion>>();
foreach (var region in result.Regions)
{
// Round Y center to nearest 15 pixels to approximate line grouping
int lineKey = (int)(region.Rect.Center.Y / 15) * 15;
if (!lineGroups.ContainsKey(lineKey))
lineGroups[lineKey] = new List<PaddleOcrResultRegion>();
lineGroups[lineKey].Add(region);
}
// Sort lines top to bottom, then regions left to right within each line
foreach (var line in lineGroups.OrderBy(kv => kv.Key))
{
var lineText = string.Join(" ", line.Value
.OrderBy(r => r.Rect.Center.X)
.Select(r => r.Text));
Console.WriteLine(lineText);
}
using Sdcb.PaddleOCR;
using Sdcb.PaddleOCR.Models.Online;
using OpenCvSharp;
using System.Collections.Generic;
FullOcrModel models = await OnlineFullModels.EnglishV4.DownloadAsync();
using PaddleOcrAll ocr = new PaddleOcrAll(models);
using Mat mat = Cv2.ImRead("invoice.png");
PaddleOcrResult result = ocr.Run(mat);
// No paragraph or line grouping — must implement manually
// Group regions into lines by proximity on the Y axis
var lineGroups = new Dictionary<int, List<PaddleOcrResultRegion>>();
foreach (var region in result.Regions)
{
// Round Y center to nearest 15 pixels to approximate line grouping
int lineKey = (int)(region.Rect.Center.Y / 15) * 15;
if (!lineGroups.ContainsKey(lineKey))
lineGroups[lineKey] = new List<PaddleOcrResultRegion>();
lineGroups[lineKey].Add(region);
}
// Sort lines top to bottom, then regions left to right within each line
foreach (var line in lineGroups.OrderBy(kv => kv.Key))
{
var lineText = string.Join(" ", line.Value
.OrderBy(r => r.Rect.Center.X)
.Select(r => r.Text));
Console.WriteLine(lineText);
}
Imports Sdcb.PaddleOCR
Imports Sdcb.PaddleOCR.Models.Online
Imports OpenCvSharp
Imports System.Collections.Generic
Dim models As FullOcrModel = Await OnlineFullModels.EnglishV4.DownloadAsync()
Using ocr As New PaddleOcrAll(models)
Using mat As Mat = Cv2.ImRead("invoice.png")
Dim result As PaddleOcrResult = ocr.Run(mat)
' No paragraph or line grouping — must implement manually
' Group regions into lines by proximity on the Y axis
Dim lineGroups As New Dictionary(Of Integer, List(Of PaddleOcrResultRegion))()
For Each region In result.Regions
' Round Y center to nearest 15 pixels to approximate line grouping
Dim lineKey As Integer = CInt(region.Rect.Center.Y / 15) * 15
If Not lineGroups.ContainsKey(lineKey) Then
lineGroups(lineKey) = New List(Of PaddleOcrResultRegion)()
End If
lineGroups(lineKey).Add(region)
Next
' Sort lines top to bottom, then regions left to right within each line
For Each line In lineGroups.OrderBy(Function(kv) kv.Key)
Dim lineText As String = String.Join(" ", line.Value _
.OrderBy(Function(r) r.Rect.Center.X) _
.Select(Function(r) r.Text))
Console.WriteLine(lineText)
Next
End Using
End Using
Enfoque IronOCR:
using IronOcr;
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var ocr = new IronTesseract();
using var input = new OcrInput();
input.LoadImage("invoice.png");
var result = ocr.Read(input);
// Hierarchical structure: Pages → Paragraphs → Lines → Words → Characters
// All delivered in reading order by the layout engine
foreach (var page in result.Pages)
{
Console.WriteLine($"Page {page.PageNumber} — {page.Words.Count} words");
foreach (var paragraph in page.Paragraphs)
{
Console.WriteLine($" Paragraph at ({paragraph.X}, {paragraph.Y}):");
Console.WriteLine($" {paragraph.Text}");
}
}
using IronOcr;
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var ocr = new IronTesseract();
using var input = new OcrInput();
input.LoadImage("invoice.png");
var result = ocr.Read(input);
// Hierarchical structure: Pages → Paragraphs → Lines → Words → Characters
// All delivered in reading order by the layout engine
foreach (var page in result.Pages)
{
Console.WriteLine($"Page {page.PageNumber} — {page.Words.Count} words");
foreach (var paragraph in page.Paragraphs)
{
Console.WriteLine($" Paragraph at ({paragraph.X}, {paragraph.Y}):");
Console.WriteLine($" {paragraph.Text}");
}
}
Imports IronOcr
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Dim ocr As New IronTesseract()
Using input As New OcrInput()
input.LoadImage("invoice.png")
Dim result = ocr.Read(input)
' Hierarchical structure: Pages → Paragraphs → Lines → Words → Characters
' All delivered in reading order by the layout engine
For Each page In result.Pages
Console.WriteLine($"Page {page.PageNumber} — {page.Words.Count} words")
For Each paragraph In page.Paragraphs
Console.WriteLine($" Paragraph at ({paragraph.X}, {paragraph.Y}):")
Console.WriteLine($" {paragraph.Text}")
Next
Next
End Using
La aproximación manual de agrupación de líneas —que consiste en redondear las coordenadas Y al tamaño de un cubo de píxeles— se sustituye por la segmentación de párrafos integrada del motor de maquetación Tesseract. Las coordenadas de cuadro delimitador están disponibles en cada nivel de la jerarquía a través de paragraph.X, paragraph.Y, paragraph.Width y paragraph.Height. Consulte la guía de resultados estructurados y el tutorial sobre cómo leer texto de imágenes para obtener una cobertura completa del árbol de resultados.
Generación de PDF con capacidad de búsqueda
PaddleOCR no genera ningún archivo PDF. Generar un PDF buscable a partir de los resultados de PaddleOCR requiere una biblioteca PDF separada, mapeo manual de coordenadas de region.Rect a unidades de página PDF y una inyección de capa de texto invisible.IronOCR genera un PDF con capacidad de búsqueda directamente a partir del resultado del OCR.
Enfoque PaddleOCR:
using Sdcb.PaddleOCR;
using Sdcb.PaddleOCR.Models.Online;
using OpenCvSharp;
// Requires additional package: PdfSharp, iTextSharp, or similar
// Manual coordinate remapping from OpenCV pixel space to PDF point space
FullOcrModel models = await OnlineFullModels.EnglishV4.DownloadAsync();
using PaddleOcrAll ocr = new PaddleOcrAll(models);
using Mat mat = Cv2.ImRead("scanned-page.png");
PaddleOcrResult paddleResult = ocr.Run(mat);
// No built-in searchable PDF output — must build with external library
// region.Rect coordinates are in pixel space, PDF uses points (1 point = 1/72 inch)
// DPI conversion required for coordinate mapping
float dpiScale = 72.0f / 96.0f; // Assuming 96 DPI source image
// ... hundreds of lines of PDF construction code using external library ...
// This is permanent maintenance, not a one-time cost
Console.WriteLine("Searchable PDF output requires external PDF library and coordinate mapping.");
using Sdcb.PaddleOCR;
using Sdcb.PaddleOCR.Models.Online;
using OpenCvSharp;
// Requires additional package: PdfSharp, iTextSharp, or similar
// Manual coordinate remapping from OpenCV pixel space to PDF point space
FullOcrModel models = await OnlineFullModels.EnglishV4.DownloadAsync();
using PaddleOcrAll ocr = new PaddleOcrAll(models);
using Mat mat = Cv2.ImRead("scanned-page.png");
PaddleOcrResult paddleResult = ocr.Run(mat);
// No built-in searchable PDF output — must build with external library
// region.Rect coordinates are in pixel space, PDF uses points (1 point = 1/72 inch)
// DPI conversion required for coordinate mapping
float dpiScale = 72.0f / 96.0f; // Assuming 96 DPI source image
// ... hundreds of lines of PDF construction code using external library ...
// This is permanent maintenance, not a one-time cost
Console.WriteLine("Searchable PDF output requires external PDF library and coordinate mapping.");
Imports Sdcb.PaddleOCR
Imports Sdcb.PaddleOCR.Models.Online
Imports OpenCvSharp
' Requires additional package: PdfSharp, iTextSharp, or similar
' Manual coordinate remapping from OpenCV pixel space to PDF point space
Dim models As FullOcrModel = Await OnlineFullModels.EnglishV4.DownloadAsync()
Using ocr As New PaddleOcrAll(models)
Using mat As Mat = Cv2.ImRead("scanned-page.png")
Dim paddleResult As PaddleOcrResult = ocr.Run(mat)
End Using
End Using
' No built-in searchable PDF output — must build with external library
' region.Rect coordinates are in pixel space, PDF uses points (1 point = 1/72 inch)
' DPI conversion required for coordinate mapping
Dim dpiScale As Single = 72.0F / 96.0F ' Assuming 96 DPI source image
' ... hundreds of lines of PDF construction code using external library ...
' This is permanent maintenance, not a one-time cost
Console.WriteLine("Searchable PDF output requires external PDF library and coordinate mapping.")
Enfoque IronOCR:
using IronOcr;
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var ocr = new IronTesseract();
using var input = new OcrInput();
input.LoadImage("scanned-page.png");
input.Deskew();
input.DeNoise();
var result = ocr.Read(input);
// Searchable PDF in one line — no external PDF library, no coordinate mapping
result.SaveAsSearchablePdf("searchable-output.pdf");
Console.WriteLine($"Searchable PDF created. Confidence: {result.Confidence}%");
using IronOcr;
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var ocr = new IronTesseract();
using var input = new OcrInput();
input.LoadImage("scanned-page.png");
input.Deskew();
input.DeNoise();
var result = ocr.Read(input);
// Searchable PDF in one line — no external PDF library, no coordinate mapping
result.SaveAsSearchablePdf("searchable-output.pdf");
Console.WriteLine($"Searchable PDF created. Confidence: {result.Confidence}%");
Imports IronOcr
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Dim ocr As New IronTesseract()
Using input As New OcrInput()
input.LoadImage("scanned-page.png")
input.Deskew()
input.DeNoise()
Dim result = ocr.Read(input)
' Searchable PDF in one line — no external PDF library, no coordinate mapping
result.SaveAsSearchablePdf("searchable-output.pdf")
Console.WriteLine($"Searchable PDF created. Confidence: {result.Confidence}%")
End Using
El problema del mapeo de coordenadas —convertir las coordenadas de píxeles de OpenCV al espacio de puntos PDF con la resolución DPI correcta— no existe en IronOCR. La guía en formato PDF, que permite realizar búsquedas, abarca la impresión de documentos de varias páginas, los archivos PDF protegidos con contraseña y la configuración de la calidad de impresión. Para los equipos que digitalizan archivos escaneados o que crean sistemas de conversión de fax a PDF con capacidad de búsqueda, esta única llamada a un método sustituye lo que de otro modo sería un proyecto de integración sustancial.
Procesamiento por lotes de TIFF multifotograma
Los archivos TIFF de varias páginas aparecen con frecuencia en los flujos de trabajo de escaneo de documentos. PaddleOCR no tiene soporte directo para múltiples marcos TIFF — cada marco debe extraerse individualmente usando una biblioteca de imágenes externa y cargarse como un Mat separado.IronOCR maneja archivos TIFF multifotograma de forma nativa.
Enfoque PaddleOCR:
using Sdcb.PaddleOCR;
using Sdcb.PaddleOCR.Models.Online;
using OpenCvSharp;
using System.Drawing; // For multi-frame TIFF extraction
using System.Drawing.Imaging;
using System.Text;
FullOcrModel models = await OnlineFullModels.EnglishV4.DownloadAsync();
using PaddleOcrAll ocr = new PaddleOcrAll(models);
var fullText = new StringBuilder();
// Must use System.Drawing to extract individual TIFF frames
// OpenCvSharp cannot enumerate TIFF frames directly
using var tiff = Image.FromFile("multipage-scan.tiff");
FrameDimension dimension = new FrameDimension(tiff.FrameDimensionsList[0]);
int frameCount = tiff.GetFrameCount(dimension);
for (int i = 0; i < frameCount; i++)
{
tiff.SelectActiveFrame(dimension, i);
// Save frame to temp file — OpenCvSharp needs a file path
string tempPath = Path.GetTempFileName() + ".png";
tiff.Save(tempPath, ImageFormat.Png);
try
{
using Mat mat = Cv2.ImRead(tempPath);
PaddleOcrResult result = ocr.Run(mat);
fullText.AppendLine($"=== Frame {i + 1} ===");
fullText.AppendLine(result.Text);
}
finally
{
File.Delete(tempPath); // Must clean up temp files
}
}
Console.WriteLine(fullText.ToString());
using Sdcb.PaddleOCR;
using Sdcb.PaddleOCR.Models.Online;
using OpenCvSharp;
using System.Drawing; // For multi-frame TIFF extraction
using System.Drawing.Imaging;
using System.Text;
FullOcrModel models = await OnlineFullModels.EnglishV4.DownloadAsync();
using PaddleOcrAll ocr = new PaddleOcrAll(models);
var fullText = new StringBuilder();
// Must use System.Drawing to extract individual TIFF frames
// OpenCvSharp cannot enumerate TIFF frames directly
using var tiff = Image.FromFile("multipage-scan.tiff");
FrameDimension dimension = new FrameDimension(tiff.FrameDimensionsList[0]);
int frameCount = tiff.GetFrameCount(dimension);
for (int i = 0; i < frameCount; i++)
{
tiff.SelectActiveFrame(dimension, i);
// Save frame to temp file — OpenCvSharp needs a file path
string tempPath = Path.GetTempFileName() + ".png";
tiff.Save(tempPath, ImageFormat.Png);
try
{
using Mat mat = Cv2.ImRead(tempPath);
PaddleOcrResult result = ocr.Run(mat);
fullText.AppendLine($"=== Frame {i + 1} ===");
fullText.AppendLine(result.Text);
}
finally
{
File.Delete(tempPath); // Must clean up temp files
}
}
Console.WriteLine(fullText.ToString());
Imports Sdcb.PaddleOCR
Imports Sdcb.PaddleOCR.Models.Online
Imports OpenCvSharp
Imports System.Drawing ' For multi-frame TIFF extraction
Imports System.Drawing.Imaging
Imports System.Text
Dim models As FullOcrModel = Await OnlineFullModels.EnglishV4.DownloadAsync()
Using ocr As New PaddleOcrAll(models)
Dim fullText As New StringBuilder()
' Must use System.Drawing to extract individual TIFF frames
' OpenCvSharp cannot enumerate TIFF frames directly
Using tiff As Image = Image.FromFile("multipage-scan.tiff")
Dim dimension As New FrameDimension(tiff.FrameDimensionsList(0))
Dim frameCount As Integer = tiff.GetFrameCount(dimension)
For i As Integer = 0 To frameCount - 1
tiff.SelectActiveFrame(dimension, i)
' Save frame to temp file — OpenCvSharp needs a file path
Dim tempPath As String = Path.GetTempFileName() & ".png"
tiff.Save(tempPath, ImageFormat.Png)
Try
Using mat As Mat = Cv2.ImRead(tempPath)
Dim result As PaddleOcrResult = ocr.Run(mat)
fullText.AppendLine($"=== Frame {i + 1} ===")
fullText.AppendLine(result.Text)
End Using
Finally
File.Delete(tempPath) ' Must clean up temp files
End Try
Next
End Using
Console.WriteLine(fullText.ToString())
End Using
Enfoque IronOCR:
using IronOcr;
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var ocr = new IronTesseract();
using var input = new OcrInput();
input.LoadImageFrames("multipage-scan.tiff"); // All frames in one call
var result = ocr.Read(input);
foreach (var page in result.Pages)
{
Console.WriteLine($"=== Frame {page.PageNumber} ===");
Console.WriteLine(page.Text);
}
// Optionally save the entire multi-frame result as searchable PDF
result.SaveAsSearchablePdf("multipage-searchable.pdf");
using IronOcr;
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var ocr = new IronTesseract();
using var input = new OcrInput();
input.LoadImageFrames("multipage-scan.tiff"); // All frames in one call
var result = ocr.Read(input);
foreach (var page in result.Pages)
{
Console.WriteLine($"=== Frame {page.PageNumber} ===");
Console.WriteLine(page.Text);
}
// Optionally save the entire multi-frame result as searchable PDF
result.SaveAsSearchablePdf("multipage-searchable.pdf");
Imports IronOcr
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Dim ocr As New IronTesseract()
Using input As New OcrInput()
input.LoadImageFrames("multipage-scan.tiff") ' All frames in one call
Dim result = ocr.Read(input)
For Each page In result.Pages
Console.WriteLine($"=== Frame {page.PageNumber} ===")
Console.WriteLine(page.Text)
Next
' Optionally save the entire multi-frame result as searchable PDF
result.SaveAsSearchablePdf("multipage-searchable.pdf")
End Using
El bucle de extracción de cuadros, la dependencia System.Drawing, la creación de archivos temporales y la lógica de limpieza son eliminados.IronOCR carga todos los marcos en una sola llamada LoadImageFrames() y expone cada marco como Page en el resultado. La guía de entrada de archivos TIFF y GIF abarca las opciones de carga de múltiples fotogramas, los rangos de fotogramas selectivos y las consideraciones de memoria para archivos TIFF grandes.
Referencia de mapeo de la API de PaddleOCR (.NET) a IronOCR
| PaddleOCR (Sdcb) | IronOCR | Notas |
|---|---|---|
Sdcb.PaddleOCR |
IronOcr |
Espacio de nombres |
Sdcb.PaddleOCR.Models.Online |
N/A | No se necesita espacio de nombres para la adquisición del modelo. |
Sdcb.PaddleInference |
N/A | No se necesita espacio de nombres de backend de inferencia |
FullOcrModel |
N/A | No existe un equivalente: los modelos se ofrecen en paquetes. |
OnlineFullModels.ChineseV4.DownloadAsync() |
dotnet add package IronOcr.Languages.ChineseSimplified |
La adquisición de modelos se reemplaza por NuGet. |
LocalDetectionModel.FromDirectory(path) |
N/A | Sin gestión de rutas de modelos |
LocalClassificationModel.FromDirectory(path) |
N/A | Sin gestión de rutas de modelos |
LocalRecognitionModel.FromDirectory(path) |
N/A | Sin gestión de rutas de modelos |
new PaddleOcrAll(models) |
new IronTesseract() |
Instanciación del motor |
new PaddleOcrAll(models, PaddleDevice.Gpu(0)) |
N/A | Se eliminó por completo la selección del dispositivo GPU. |
PaddleDevice.Cpu() |
N/A | La CPU es el único modo; no es necesario seleccionar |
ocr.AllowRotateDetection = true |
input.Deskew() |
Corrección de rotación |
ocr.Enable180Classification = true |
Automático | La detección de posición invertida está integrada. |
Cv2.ImRead(path) |
input.LoadImage(path) |
Carga de imágenes: no se requiere OpenCV. |
ocr.Run(mat) |
ocr.Read(input) |
Ejecutar OCR |
result.Text |
result.Text |
Cadena de texto completa del documento |
result.Regions |
result.Pages[0].Lines o .Words |
Regiones de texto estructurado |
region.Text |
word.Text / line.Text |
Contenido de texto de una región |
region.Score (flotar 0-1) |
word.Confidence (entero 0-100) |
Valor de confianza: la escala difiere. |
region.Rect.Center.X |
word.X |
Posición horizontal |
region.Rect.Center.Y |
word.Y |
Posición vertical |
region.Rect.Size.Width |
word.Width |
Ancho del cuadro delimitador |
region.Rect.Size.Height |
word.Height |
Altura del cuadro delimitador |
| N/A | input.LoadPdf(path) |
Entrada de PDF nativa (sin equivalente en PaddleOCR) |
| N/A | input.LoadImageFrames(path) |
TIFF multifotograma (sin equivalente en PaddleOCR) |
| N/A | result.SaveAsSearchablePdf(path) |
Salida PDF con capacidad de búsqueda (sin equivalente en PaddleOCR) |
Problemas comunes de migración y soluciones
Problema 1: Discrepancia en la escala de confianza
PaddleOCR: La confianza de la región es un float de 0.0 a 1.0. Un umbral común es region.Score >= 0.8 para filtrar detecciones de baja calidad.
Solución: La confianza de IronOCR es un porcentaje de int de 0 a 100. Multiplique el umbral de PaddleOCR por 100:
// PaddleOCR: filter at 0.8
var highConfidence = result.Regions.Where(r => r.Score >= 0.8);
//IronOCR equivalent: filter at 80
var highConfidence = result.Pages
.SelectMany(p => p.Words)
.Where(w => w.Confidence >= 80);
// PaddleOCR: filter at 0.8
var highConfidence = result.Regions.Where(r => r.Score >= 0.8);
//IronOCR equivalent: filter at 80
var highConfidence = result.Pages
.SelectMany(p => p.Words)
.Where(w => w.Confidence >= 80);
' PaddleOCR: filter at 0.8
Dim highConfidence = result.Regions.Where(Function(r) r.Score >= 0.8)
' IronOCR equivalent: filter at 80
Dim highConfidence = result.Pages _
.SelectMany(Function(p) p.Words) _
.Where(Function(w) w.Confidence >= 80)
La confianza a nivel de documento está disponible como result.Confidence para un filtrado rápido de calidad. La guía de puntuaciones de confianza abarca umbrales por palabra y a nivel de documento.
Número 2: Supuestos sobre el orden de lectura
PaddleOCR: result.Regions se ordena por secuencia de detección, no por orden de lectura. Cualquier código que consuma result.Text esperando salida de arriba hacia abajo, de izquierda a derecha, depende del patrón de ordenamiento manual utilizado en los ejemplos de PaddleOCR.
Solución: El de IronOCRresult.Text ya está en orden de lectura. Eliminar la clasificación manual. Para los casos donde el ordenamiento se utilizó para construir una salida línea por línea, utilice result.Pages[0].Lines directamente:
// PaddleOCR: manual sort required for reading order
var lines = result.Regions
.OrderBy(r => r.Rect.Center.Y)
.ThenBy(r => r.Rect.Center.X)
.Select(r => r.Text);
// IronOCR: reading order is the default
var lines = result.Pages[0].Lines.Select(l => l.Text);
// PaddleOCR: manual sort required for reading order
var lines = result.Regions
.OrderBy(r => r.Rect.Center.Y)
.ThenBy(r => r.Rect.Center.X)
.Select(r => r.Text);
// IronOCR: reading order is the default
var lines = result.Pages[0].Lines.Select(l => l.Text);
Imports System.Linq
' PaddleOCR: manual sort required for reading order
Dim lines = result.Regions _
.OrderBy(Function(r) r.Rect.Center.Y) _
.ThenBy(Function(r) r.Rect.Center.X) _
.Select(Function(r) r.Text)
' IronOCR: reading order is the default
Dim lines = result.Pages(0).Lines.Select(Function(l) l.Text)
Problema 3: Código de conversión de matriz de OpenCV
PaddleOCR: Algunas bases de código contienen métodos de ayuda que cargan imágenes desde flujos o matrices de bytes primero escribiendo en un archivo temporal y luego llamando a Cv2.ImRead(). Estos patrones existen porque Cv2.ImRead() solo acepta rutas de archivo.
Solución: OcrInput de IronOCR acepta flujos y matrices de bytes directamente. Elimine el archivo intermedio temporal:
// PaddleOCR: stream → temp file → Mat → OCR
string tempPath = Path.GetTempFileName() + ".png";
using (var fs = File.Create(tempPath))
await imageStream.CopyToAsync(fs);
using Mat mat = Cv2.ImRead(tempPath);
PaddleOcrResult result = ocr.Run(mat);
File.Delete(tempPath);
// IronOCR: stream → OCR (no temp file)
using var input = new OcrInput();
input.LoadImage(imageStream);
var result = ocr.Read(input);
// PaddleOCR: stream → temp file → Mat → OCR
string tempPath = Path.GetTempFileName() + ".png";
using (var fs = File.Create(tempPath))
await imageStream.CopyToAsync(fs);
using Mat mat = Cv2.ImRead(tempPath);
PaddleOcrResult result = ocr.Run(mat);
File.Delete(tempPath);
// IronOCR: stream → OCR (no temp file)
using var input = new OcrInput();
input.LoadImage(imageStream);
var result = ocr.Read(input);
Imports System.IO
Imports OpenCvSharp
Imports IronOcr
' PaddleOCR: stream → temp file → Mat → OCR
Dim tempPath As String = Path.GetTempFileName() & ".png"
Using fs = File.Create(tempPath)
Await imageStream.CopyToAsync(fs)
End Using
Using mat As Mat = Cv2.ImRead(tempPath)
Dim result As PaddleOcrResult = ocr.Run(mat)
End Using
File.Delete(tempPath)
' IronOCR: stream → OCR (no temp file)
Using input As New OcrInput()
input.LoadImage(imageStream)
Dim result = ocr.Read(input)
End Using
La guía de entrada de flujos abarca la carga de flujos desde respuestas HTTP, blobs de bases de datos y flujos en memoria.
Problema 4: Eliminación del patrón de inicialización asíncrona
PaddleOCR: La inicialización del motor es asíncrona porque la descarga del modelo implica E/S de red. Esto fuerza la asincronía en toda la cadena de llamadas, lo que puede resultar problemático en contextos síncronos como constructores o controladores de eventos no asíncronos.
Solución: La inicialización de IronOCR es síncrona. new IronTesseract() no realiza E/S. Elimine el await y el modificador async de cualquier método cuya única operación asíncrona fuera la descarga del modelo:
// PaddleOCR: async forced by model download
public async Task<string> ExtractTextAsync(string imagePath)
{
FullOcrModel models = await OnlineFullModels.EnglishV4.DownloadAsync();
using PaddleOcrAll ocr = new PaddleOcrAll(models);
using Mat mat = Cv2.ImRead(imagePath);
return ocr.Run(mat).Text;
}
// IronOCR: synchronous — no async required unless the caller needs it
public string ExtractText(string imagePath)
{
var ocr = new IronTesseract();
using var input = new OcrInput();
input.LoadImage(imagePath);
return ocr.Read(input).Text;
}
// PaddleOCR: async forced by model download
public async Task<string> ExtractTextAsync(string imagePath)
{
FullOcrModel models = await OnlineFullModels.EnglishV4.DownloadAsync();
using PaddleOcrAll ocr = new PaddleOcrAll(models);
using Mat mat = Cv2.ImRead(imagePath);
return ocr.Run(mat).Text;
}
// IronOCR: synchronous — no async required unless the caller needs it
public string ExtractText(string imagePath)
{
var ocr = new IronTesseract();
using var input = new OcrInput();
input.LoadImage(imagePath);
return ocr.Read(input).Text;
}
Imports System.Threading.Tasks
' PaddleOCR: async forced by model download
Public Async Function ExtractTextAsync(imagePath As String) As Task(Of String)
Dim models As FullOcrModel = Await OnlineFullModels.EnglishV4.DownloadAsync()
Using ocr As New PaddleOcrAll(models)
Using mat As Mat = Cv2.ImRead(imagePath)
Return ocr.Run(mat).Text
End Using
End Using
End Function
' IronOCR: synchronous — no async required unless the caller needs it
Public Function ExtractText(imagePath As String) As String
Dim ocr As New IronTesseract()
Using input As New OcrInput()
input.LoadImage(imagePath)
Return ocr.Read(input).Text
End Using
End Function
IronOCR también proporciona soporte asíncrono nativo a través de ocr.ReadAsync(input) cuando se necesita ejecución no bloqueante en un contexto asíncrono.
Problema 5: Limpieza del paso de compilación de Docker
PaddleOCR: El Dockerfile contiene apt-get install libopencv-dev, una instrucción COPY models/ /app/models/ y a menudo un paso de pre-descarga del modelo RUN. La imagen base es con frecuencia la imagen NVIDIA CUDA para implementaciones GPU.
Solución: Eliminar todas las instrucciones del Dockerfile específicas de PaddleOCR. La imagen Docker de IronOCR no requiere ninguna imagen base especial ni ningún paso de copia de modelo:
# PaddleOCR Dockerfile (remove all of this)
FROM nvidia/cuda:11.8.0-cudnn8-runtime-ubuntu22.04
RUN apt-get update && apt-get install -y libopencv-dev libgdiplus
COPY models/ /app/models/
COPY . /app
#IronOCR Dockerfile (clean)
FROM mcr.microsoft.com/dotnet/aspnet:8.0
COPY . /app
WORKDIR /app
ENTRYPOINT ["dotnet", "YourApp.dll"]
La imagen resultante reduce su tamaño de aproximadamente 1,5 GB a aproximadamente 400 MB. La guía de implementación de Docker cubre los requisitos de las bibliotecas de Linux y las compilaciones multiarquitectura.
Problema 6: Invalidación de la caché del modelo CI/CD
PaddleOCR: Las canalizaciones de CI/CD que almacenan en caché el paso de restauración de NuGet deben gestionar por separado el almacenamiento en caché de los archivos del modelo. Un patrón común es almacenar en caché una carpeta models/ entre ejecuciones. Cuando se actualiza la versión del envoltorio, la clave de caché cambia y los modelos deben volver a descargarse de los servidores de Baidu, lo que añade entre 30 y 60 segundos al proceso.
Solución:IronOCR no tiene directorio de caché de modelos. La única caché necesaria es la caché estándar de paquetes NuGet . Sin paso de caché separado, sin invalidación de caché en actualizaciones de envoltura, sin descarga desde servidores de terceros durante la CI:
# Remove from CI/CD pipeline:
# - name: Cache PaddleOCR models
# uses: actions/cache@v3
# with:
# path: models/
# key: paddleocr-models-${{env.PADDLEOCR_VERSION}}
#IronOCR only needs standard NuGet caching:
- name: Cache NuGet packages
uses: actions/cache@v3
with:
path: ~/.nuget/packages
key: nuget-${{hashFiles('**/*.csproj')}}
# Remove from CI/CD pipeline:
# - name: Cache PaddleOCR models
# uses: actions/cache@v3
# with:
# path: models/
# key: paddleocr-models-${{env.PADDLEOCR_VERSION}}
#IronOCR only needs standard NuGet caching:
- name: Cache NuGet packages
uses: actions/cache@v3
with:
path: ~/.nuget/packages
key: nuget-${{hashFiles('**/*.csproj')}}
Lista de verificación para la migración de PaddleOCR (.NET)
Pre-Migración
Antes de realizar cualquier cambio, revise el código fuente para identificar todos los usos de PaddleOCR:
# Find all PaddleOCR namespace imports
grep -rn "using Sdcb.PaddleOCR" --include="*.cs" .
# Find all OpenCvSharp imports (added as PaddleOCR dependency)
grep -rn "using OpenCvSharp" --include="*.cs" .
# Find all Mat usage patterns
grep -rn "Cv2\.ImRead\|new Mat\|Mat mat" --include="*.cs" .
# Find all async model download calls
grep -rn "DownloadAsync\|OnlineFullModels\|LocalDetectionModel" --include="*.cs" .
# Find all GPU device configuration
grep -rn "PaddleDevice\|EnableUseGpu\|cuda" --include="*.cs" .
# Find all result region access patterns
grep -rn "result\.Regions\|region\.Score\|region\.Rect" --include="*.cs" .
# Locate model directory references in configuration files
grep -rn "PP-OCRv4\|cls_infer\|det_infer\|rec_infer" --include="*.cs" --include="*.json" --include="*.yaml" .
# Find all PaddleOCR namespace imports
grep -rn "using Sdcb.PaddleOCR" --include="*.cs" .
# Find all OpenCvSharp imports (added as PaddleOCR dependency)
grep -rn "using OpenCvSharp" --include="*.cs" .
# Find all Mat usage patterns
grep -rn "Cv2\.ImRead\|new Mat\|Mat mat" --include="*.cs" .
# Find all async model download calls
grep -rn "DownloadAsync\|OnlineFullModels\|LocalDetectionModel" --include="*.cs" .
# Find all GPU device configuration
grep -rn "PaddleDevice\|EnableUseGpu\|cuda" --include="*.cs" .
# Find all result region access patterns
grep -rn "result\.Regions\|region\.Score\|region\.Rect" --include="*.cs" .
# Locate model directory references in configuration files
grep -rn "PP-OCRv4\|cls_infer\|det_infer\|rec_infer" --include="*.cs" --include="*.json" --include="*.yaml" .
Inventarie los directorios del modelo y anote el tamaño total. Identifique qué modelos de idioma están en uso (chino, inglés, japonés, etc.) para determinar qué paquetes IronOcr.Languages.* agregar. Tenga en cuenta si la configuración de la GPU está presente; esos archivos son los que tienen mayor superficie que limpiar.
Migración de código
- Elimine
Sdcb.PaddleOCR,Sdcb.PaddleOCR.Models.Online,Sdcb.PaddleInference.runtime.*,OpenCvSharp4yOpenCvSharp4.runtime.*del archivo.csproj - Agregue
IronOcral archivo.csproj - Agregue paquetes
IronOcr.Languages.*para cada idioma no inglés descargado previamente como modelo PaddleOCR - Reemplace todas las directivas
using Sdcb.PaddleOCR*yusing OpenCvSharpporusing IronOcr - Agregue
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";al inicio de la aplicación - Reemplace
FullOcrModel models = await OnlineFullModels.*.DownloadAsync()con nada — elimine completamente la línea - Reemplace
new PaddleOcrAll(models)connew IronTesseract() - Reemplace
new PaddleOcrAll(models, PaddleDevice.Gpu(deviceId: 0))connew IronTesseract() - Replace
Mat mat = Cv2.ImRead(path)withvar input = new OcrInput(); input.LoadImage(ruta); - Reemplace
ocr.Run(mat)conocr.Read(input) - Reemplace el acceso
result.Regionsconresult.Pages[0].Linesoresult.Pages[0].Words - Reemplace
region.Score >= thresholdconword.Confidence >= threshold * 100 - Reemplace
region.Rect.Center.X / .Center.Yconword.X / word.Y - Eliminar la lógica de ordenación manual: la salida de IronOCR ya está en orden de lectura.
- Elimine el directorio
models/y todos los archivos de modelo del repositorio y los scripts de implementación
Posmigración
- Verifique que
dotnet buildtenga éxito con cero referencias aSdcb.*,OpenCvSharpoPaddleInferenceen el resultado de la compilación - Ejecutar el OCR en el mismo conjunto de documentos representativos utilizado para validar la salida de PaddleOCR y comparar la precisión del texto.
- Confirme que los valores de confianza se estén leyendo como números enteros de 0 a 100 (no como números de coma flotante de 0 a 1) en todos los puntos de filtrado.
- Verifique que el orden de lectura sea correcto sin ordenar manualmente; revise específicamente los diseños de varias columnas y de facturas.
- Pruebe que la compilación de la imagen muy Docker se complete sin la imagen base de CUDA o
apt-get install libopencv-dev - Confirma que el tamaño de la imagen de Docker sea inferior a 500 MB.
- Ejecuta la canalización de CI/CD de principio a fin y verifica que no se produzcan descargas externas durante la compilación.
- Prueba de implementación aislada: verifica que la aplicación se inicie y procese documentos sin conexiones de red salientes.
- Para cualquier entrada TIFF de múltiples fotogramas, verifique que todos los fotogramas se procesen y que el número de fotogramas coincida con el archivo de origen.
- Para cualquier entrada de PDF, verifique que
input.LoadPdf()produzca la misma cantidad de páginas y contenido de texto que la conversión anterior basada en PdfiumViewer
Principales ventajas de migrar a IronOCR
Los artefactos de implementación se reducen en un 80 por ciento. La huella de implementación de PaddleOCR — paddle_inference.dll, DLLs de OpenCV, y tres directorios de modelo — añade 300-500 MB a cada destino de implementación. Tras la migración, el despliegue de IronOCR ocupa aproximadamente 80 MB. Las imágenes de Docker se reducen de aproximadamente 1,5 GB a aproximadamente 400 MB. El inicio de los contenedores es más rápido, los costes de almacenamiento son menores y las canalizaciones de despliegue que antes transferían 500 MB de artefactos ahora transfieren 80 MB.
El tiempo de arranque en frío se reduce de segundos a milisegundos. PaddleOCR carga tres archivos de modelo de red neuronal desde el disco en la primera inferencia, añadiendo una pausa de 3 a 5 segundos antes de que finalice la primera llamada. En funciones sin servidor, escenarios de escalado automático o cualquier contexto donde se inicien nuevas instancias bajo demanda, ese arranque en frío se paga repetidamente. El motor de IronOCR viene integrado y se inicializa en menos de un segundo. El ejemplo básico de OCR demuestra el patrón de inicialización.
La cobertura lingüística se amplía de 14 a 125 sin necesidad de infraestructura adicional. PaddleOCR admite 14 idiomas. Agregar cualquiera de los 111 idiomas que admite IronOCR más allá del límite de PaddleOCR requiere la adición de un único paquete NuGet por idioma; no se requiere la descarga del modelo, la gestión de directorios ni la sincronización de versiones. Los equipos cuyo volumen de documentos se expande a nuevos mercados no tienen que reescribir el texto ni iniciar un nuevo proyecto de infraestructura para añadir compatibilidad con OCR en polaco, vietnamita, griego o hebreo. El catálogo completo de idiomas muestra los más de 125 paquetes disponibles.
La salida PDF con capacidad de búsqueda requiere una línea. PaddleOCR devuelve regiones de texto. Convertir esas regiones en una capa PDF con capacidad de búsqueda requiere una biblioteca PDF independiente, una conversión de coordenadas de píxeles a puntos y un código de inyección de texto invisible que implica un mantenimiento permanente. Después de la migración, result.SaveAsSearchablePdf("output.pdf") reemplaza todo ese subsistema. Los flujos de trabajo de archivo de documentos escaneados, las canalizaciones de fax a PDF y las integraciones de gestión documental se benefician directamente. El tutorial en formato PDF con función de búsqueda y la entrada del blog sobre la extracción de datos en PDF cubren todas las opciones de salida.
No hay conexiones de red externas en ninguna etapa. PaddleOCR se conecta al almacenamiento de bj.bcebos.com de Baidu para descargas de modelos. En entornos donde las conexiones salientes están restringidas (redes gubernamentales, sistemas aislados, infraestructura de servicios financieros), esa conexión requiere una excepción en el firewall o un flujo de trabajo previo a la descarga que añade complejidad a la integración y entrega continuas (CI/CD).IronOCR no realiza conexiones externas en tiempo de ejecución. Los modelos se restauran como parte de dotnet restore desde NuGet y están presentes en el resultado de implementación. La guía de implementación de AWS y la guía de implementación de Azure cubren la configuración específica de la nube para entornos con restricciones de red.
Un único contacto de soporte comercial para toda la pila OCR. Los problemas de PaddleOCR abarcan el contenedor Sdcb.PaddleOCR (GitHub comunitario), el framework PaddlePaddle (Baidu), OpenCvSharp (comunitario), y CUDA/cuDNN (NVIDIA). Cada capa tiene un canal de soporte diferente, sin garantía de tiempo de respuesta.IronOCR es un producto único de Iron Software con soporte comercial por correo electrónico y niveles de respuesta prioritaria. El centro de documentación de IronOCR consolida toda la documentación de la API, las guías prácticas y los recursos para la resolución de problemas en un solo lugar.
Preguntas Frecuentes
¿Por qué debería migrar de PaddleOCR 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 en el código al migrar de PaddleOCR a IronOCR?
Sustituya las secuencias de inicialización de PaddleOCR por la instanciación de IronTesseract, elimine la gestión del ciclo de vida 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 tiene la misma precisión de OCR que PaddleOCR 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 PaddleOCR 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.
¿Requiere la migración de PaddleOCR a IronOCR cambios en la infraestructura de despliegue?
IronOCR requiere menos cambios de infraestructura que PaddleOCR. 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 PaddleOCR?
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 PaddleOCR 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é pasa con mis pruebas existentes después de migrar de PaddleOCR 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.

