跳至页脚内容
视频

如何用 C# 阅读多帧页面 GIF 和 TIFF

此指南带领.NET开发人员完成从PaddleSharp OCR (Sdcb.PaddleOCR)到IronOCR的完整迁移。 它涵盖了替换推理会话管理、消除对 OpenCV 预处理的依赖性、移除 CPU、GPU 和 OpenVINO 的后端选择逻辑以及迁移表格识别工作流程。 每个部分都提供了从 PaddleSharp 特有的模式中提取的前后对比代码,这些模式不会出现在通用的 OCR 比较中。

为什么要从PaddleSharp OCR迁移?

PaddleSharp 在应用层公开了一个深度学习推理管道。 该架构使您能够获得 PaddlePaddle 模型的性能,但要求您的应用程序管理原本属于基础设施方面的问题。 以下这些痛点促使大多数.NET团队寻找替代方案。

推理后端配置是应用程序代码。在PaddleSharp中选择CPU、GPU和OpenVINO后端需要构建和配置PaddleConfig对象,选择适合部署目标的本机运行时NuGet包,并根据运行时的硬件条件分支初始化代码。此逻辑存在于您的应用程序中,而不是库中,当目标环境改变时会被破坏。

OpenCV 是图像输入的必要依赖项。PaddleSharp无法直接接受文件路径或数据流。 每个图像在到达OCR引擎之前都会经过OpenCV的OpenCvSharp4.runtime.*包强制进入您的依赖关系图中。 只更新一个平台运行时而不更新另一个平台运行时会导致运行时故障,而且这些故障很难在不同环境中重现。

推理会话生命周期需要显式设计。PaddleOcrAll在构建时从磁盘加载三个模型二进制文件。这个开销以数百毫秒计,意味着对象不能每次请求实例化。团队必须设计生命周期策略:单例、池化或范围。 在ASP.NET Core中,这通常意味着一个注册服务,需进行仔细的线程安全分析,因为PaddleOcrAll共享底层的本机状态。

表格识别需要单独下载模型。PaddleSharp中的结构化文档提取除了标准的三阶段检测/分类/识别流程外,还需要一个专用的表格识别模型。该模型是需要下载、版本控制和配置的第四个文件。 没有统一的 API 接口——表格识别使用不同的代码路径和自己的结果类型。

不支持可搜索的 PDF 输出。PaddleSharp生成的是文本字符串。 它无法生成可搜索的PDF文件。 需要将扫描文档归档为可搜索文本的 PDF 的团队必须集成一个单独的 PDF 库,管理该额外依赖项,并编写转换层。 输出格式完全缺失:没有 hOCR,没有结构化可搜索 PDF,没有文本层叠加。

上游依赖链并非由.NET社区所有。PaddleSharp封装了百度的 PaddlePaddle 推理框架。 PaddleOCR 版本之间的模型格式变更过去曾导致.NET绑定层出现问题。目前大部分问题跟踪、文档编写和版本发布讨论都使用中文。 对于一个没有懂普通话的人员监控上游项目的.NET团队来说,重大变更会在毫无预警的情况下发生。

基本问题

在 PaddleSharp 中选择和初始化后端需要配置代码,这些代码属于基础设施范畴,而不是 OCR 逻辑范畴:

// PaddleSharp: Backend selection sprawls into application startup
// Simplified — see Sdcb.PaddleInference documentation for full API
using Sdcb.PaddleInference;
using Sdcb.PaddleOCR;

// CPU-only deployment
var config = PaddleConfig.FromModelDir("models/det");
config.SetCpuMathLibraryNumThreads(4);

// GPU deployment — different package, different init path
// var config = PaddleConfig.FromModelDir("models/det");
// config.EnableGpu(500, 0);  // memoryMB, deviceId

// OpenVINO deployment — third conditional branch
// config.EnableMkldnn();

// Application code now owns the hardware topology decision
// PaddleSharp: Backend selection sprawls into application startup
// Simplified — see Sdcb.PaddleInference documentation for full API
using Sdcb.PaddleInference;
using Sdcb.PaddleOCR;

// CPU-only deployment
var config = PaddleConfig.FromModelDir("models/det");
config.SetCpuMathLibraryNumThreads(4);

// GPU deployment — different package, different init path
// var config = PaddleConfig.FromModelDir("models/det");
// config.EnableGpu(500, 0);  // memoryMB, deviceId

// OpenVINO deployment — third conditional branch
// config.EnableMkldnn();

// Application code now owns the hardware topology decision
Imports Sdcb.PaddleInference
Imports Sdcb.PaddleOCR

' PaddleSharp: Backend selection sprawls into application startup
' Simplified — see Sdcb.PaddleInference documentation for full API

' CPU-only deployment
Dim config = PaddleConfig.FromModelDir("models/det")
config.SetCpuMathLibraryNumThreads(4)

' GPU deployment — different package, different init path
' Dim config = PaddleConfig.FromModelDir("models/det")
' config.EnableGpu(500, 0)  ' memoryMB, deviceId

' OpenVINO deployment — third conditional branch
' config.EnableMkldnn()

' Application code now owns the hardware topology decision
$vbLabelText   $csharpLabel
// IronOCR: 否 backend selection. 否 config objects. Zero hardware decisions.
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

var result = new IronTesseract().Read("document.jpg");
Console.WriteLine(result.Text);
// Runs on CPU, Linux, Docker, or ARM without a code change
// IronOCR: 否 backend selection. 否 config objects. Zero hardware decisions.
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

var result = new IronTesseract().Read("document.jpg");
Console.WriteLine(result.Text);
// Runs on CPU, Linux, Docker, or ARM without a code change
Imports IronOcr

' IronOCR: 否 backend selection. 否 config objects. Zero hardware decisions.
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"

Dim result = New IronTesseract().Read("document.jpg")
Console.WriteLine(result.Text)
' Runs on CPU, Linux, Docker, or ARM without a code change
$vbLabelText   $csharpLabel

IronOCR与 PaddleSharp OCR:功能对比

以下是迁移过程中最重要的几个维度上的直接能力对比:

特征 PaddleSharp OCR IronOCR
需要NuGet包 至少 3-4 个 1
图像输入方法 OpenCV Cv2.ImRead() 直接路径、流或字节数组
PDF 输入(原生)
受密码保护的PDF
多页 TIFF 文件 通过 OpenCV 本地
可搜索的 PDF 输出 是 (result.SaveAsSearchablePdf())
hOCR导出
后端选择(CPU/GPU/OpenVINO) 手动 PaddleConfig 自动翻译
预处理流程 手动 OpenCV 操作 内置 (Contrast等)
推理会话生命周期管理 手工(成本高昂) 轻量级 IronTesseract
表格识别模型 单独的下载和代码路径 input.LoadImage() + 结构化结果
支持的语言 约10-20 125+
语言安装 模型文件下载 NuGet 软件包
多语言同步 有限的 是 (OcrLanguage.French + OcrLanguage.German)
基于区域的OCR 没有内置 CropRectangle
OCR过程中的条形码读取 是 (ocr.Configuration.ReadBarCodes = true)
置信度评分 按地区 按字、按行、按页
结构化输出层次结构 平坦地区列表 页数 → 段落 → 行数 → 单词数 → 字符数
跨平台部署 复杂(平台运行时包) 单个NuGet,适用于所有平台
Docker部署 多层运行时包 单层
商业支持 GitHub问题(主要为中文) 电子邮件支持
许可模式 阿帕奇 2.0 永久 ($999 Lite, $1,499 Pro, $2,999 Enterprise)

快速入门:PaddleSharp OCR 到IronOCR 的迁移

步骤 1:替换 NuGet 软件包

移除 PaddleSharp 及其 OpenCV 依赖项:

dotnet remove package Sdcb.PaddleOCR
dotnet remove package Sdcb.PaddleInference
dotnet remove package OpenCvSharp4
dotnet remove package OpenCvSharp4.runtime.win
dotnet remove package Sdcb.PaddleOCR
dotnet remove package Sdcb.PaddleInference
dotnet remove package OpenCvSharp4
dotnet remove package OpenCvSharp4.runtime.win
SHELL

NuGet安装IronOCR :

dotnet add package IronOcr

步骤 2:更新命名空间

将 PaddleSharp 命名空间替换为单个IronOCR命名空间:

// Before (PaddleSharp)
using Sdcb.PaddleOCR;
using Sdcb.PaddleOCR.Models;
using Sdcb.PaddleInference;
using OpenCvSharp;

// After (IronOCR)
using IronOcr;
// Before (PaddleSharp)
using Sdcb.PaddleOCR;
using Sdcb.PaddleOCR.Models;
using Sdcb.PaddleInference;
using OpenCvSharp;

// After (IronOCR)
using IronOcr;
Imports IronOcr
$vbLabelText   $csharpLabel

步骤 3:初始化许可证

在应用程序启动时添加一次许可证初始化 —— 在Startup.cs或您的合成根中:

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

代码迁移示例

推理会话生命周期替换

PaddleSharp的PaddleOcrAll构建成本昂贵,因为它在实例化时同步加载三个模型二进制文件。 生产应用必须将其视为长期存在的对象,这驱动着一种特定的依赖注入模式。 处置链也需要关注,因为底层原生资源必须按正确的顺序释放。

PaddleSharp OCR 方法:

// Simplified — see Sdcb.PaddleOCR documentation for full API
using Sdcb.PaddleOCR;
using Sdcb.PaddleOCR.Models;
using OpenCvSharp;
using Microsoft.Extensions.DependencyInjection;

// Expensive: loads 3 model files from disk on construction (~300–800ms)
public class PaddleOcrEngine : IDisposable
{
    private readonly PaddleOcrAll _ocr;
    private bool _disposed;

    public PaddleOcrEngine()
    {
        var detModel = LocalFullModels.ChineseV3.DetectionModel;
        var clsModel = LocalFullModels.ChineseV3.ClassifierModel;
        var recModel = LocalFullModels.ChineseV3.RecognitionModel;

        // Must be singleton — cannot afford per-request construction
        _ocr = new PaddleOcrAll(detModel, clsModel, recModel);
    }

    public string Read(string imagePath)
    {
        using var mat = Cv2.ImRead(imagePath); // OpenCV required even for a file path
        var result = _ocr.Run(mat);
        return string.Join(" ", result.Regions.Select(r => r.Text));
    }

    public void Dispose()
    {
        if (!_disposed)
        {
            _ocr?.Dispose();
            _disposed = true;
        }
    }
}

// Startup.cs — forced singleton because of construction cost
services.AddSingleton<PaddleOcrEngine>();
// Simplified — see Sdcb.PaddleOCR documentation for full API
using Sdcb.PaddleOCR;
using Sdcb.PaddleOCR.Models;
using OpenCvSharp;
using Microsoft.Extensions.DependencyInjection;

// Expensive: loads 3 model files from disk on construction (~300–800ms)
public class PaddleOcrEngine : IDisposable
{
    private readonly PaddleOcrAll _ocr;
    private bool _disposed;

    public PaddleOcrEngine()
    {
        var detModel = LocalFullModels.ChineseV3.DetectionModel;
        var clsModel = LocalFullModels.ChineseV3.ClassifierModel;
        var recModel = LocalFullModels.ChineseV3.RecognitionModel;

        // Must be singleton — cannot afford per-request construction
        _ocr = new PaddleOcrAll(detModel, clsModel, recModel);
    }

    public string Read(string imagePath)
    {
        using var mat = Cv2.ImRead(imagePath); // OpenCV required even for a file path
        var result = _ocr.Run(mat);
        return string.Join(" ", result.Regions.Select(r => r.Text));
    }

    public void Dispose()
    {
        if (!_disposed)
        {
            _ocr?.Dispose();
            _disposed = true;
        }
    }
}

// Startup.cs — forced singleton because of construction cost
services.AddSingleton<PaddleOcrEngine>();
Imports Sdcb.PaddleOCR
Imports Sdcb.PaddleOCR.Models
Imports OpenCvSharp
Imports Microsoft.Extensions.DependencyInjection

' Expensive: loads 3 model files from disk on construction (~300–800ms)
Public Class PaddleOcrEngine
    Implements IDisposable

    Private ReadOnly _ocr As PaddleOcrAll
    Private _disposed As Boolean

    Public Sub New()
        Dim detModel = LocalFullModels.ChineseV3.DetectionModel
        Dim clsModel = LocalFullModels.ChineseV3.ClassifierModel
        Dim recModel = LocalFullModels.ChineseV3.RecognitionModel

        ' Must be singleton — cannot afford per-request construction
        _ocr = New PaddleOcrAll(detModel, clsModel, recModel)
    End Sub

    Public Function Read(imagePath As String) As String
        Using mat = Cv2.ImRead(imagePath) ' OpenCV required even for a file path
            Dim result = _ocr.Run(mat)
            Return String.Join(" ", result.Regions.Select(Function(r) r.Text))
        End Using
    End Function

    Public Sub Dispose() Implements IDisposable.Dispose
        If Not _disposed Then
            _ocr?.Dispose()
            _disposed = True
        End If
    End Sub
End Class

' Startup.vb — forced singleton because of construction cost
services.AddSingleton(Of PaddleOcrEngine)()
$vbLabelText   $csharpLabel

IronOCR方法:

using IronOcr;
using Microsoft.Extensions.DependencyInjection;

// IronTesseract has lightweight initialization — no model loading on construction
public class OcrEngine
{
    public string Read(string imagePath)
    {
        return new IronTesseract().Read(imagePath).Text;
    }
}

// Flexible registration — singleton, scoped, or transient all work
services.AddTransient<OcrEngine>();

// Or skip the wrapper entirely and inject IronTesseract directly
services.AddTransient<IronTesseract>();
using IronOcr;
using Microsoft.Extensions.DependencyInjection;

// IronTesseract has lightweight initialization — no model loading on construction
public class OcrEngine
{
    public string Read(string imagePath)
    {
        return new IronTesseract().Read(imagePath).Text;
    }
}

// Flexible registration — singleton, scoped, or transient all work
services.AddTransient<OcrEngine>();

// Or skip the wrapper entirely and inject IronTesseract directly
services.AddTransient<IronTesseract>();
Imports IronOcr
Imports Microsoft.Extensions.DependencyInjection

' IronTesseract has lightweight initialization — no model loading on construction
Public Class OcrEngine
    Public Function Read(imagePath As String) As String
        Return New IronTesseract().Read(imagePath).Text
    End Function
End Class

' Flexible registration — singleton, scoped, or transient all work
services.AddTransient(Of OcrEngine)()

' Or skip the wrapper entirely and inject IronTesseract directly
services.AddTransient(Of IronTesseract)()
$vbLabelText   $csharpLabel

从强制单例寿命到灵活寿命的Shift意义重大。 PaddleSharp 的建设成本决定了您的服务生命周期决策; IronOCR允许您根据应用程序的线程和请求隔离需求进行选择。 IronTesseract 设置指南涵盖了适用于实例级别的配置选项。

OpenCV 预处理流程迁移

对于低质量扫描的 PaddleSharp 团队,通常会在调用 OCR 引擎之前构建一个 OpenCV 预处理流程。该流程需要了解 OpenCV 的 API 接口,而 OpenCV 的 API 接口远比任何 OCR 预处理任务实际需要的要大得多。 常见操作——矫正、去噪、对比度拉伸——需要多个using块仔细管理内存以防止本机内存泄漏。

PaddleSharp OCR 方法:

// Simplified — see OpenCvSharp documentation for full API
using OpenCvSharp;
using Sdcb.PaddleOCR;

public string ReadWithPreprocessing(string imagePath, PaddleOcrAll ocr)
{
    using var original = Cv2.ImRead(imagePath);

    // Step 1: Grayscale conversion
    using var gray = new Mat();
    Cv2.CvtColor(original, gray, ColorConversionCodes.BGR2GRAY);

    // Step 2: Denoise (Gaussian blur to reduce noise)
    using var denoised = new Mat();
    Cv2.GaussianBlur(gray, denoised, new Size(3, 3), 0);

    // Step 3: Adaptive threshold for binarization
    using var binary = new Mat();
    Cv2.AdaptiveThreshold(denoised, binary, 255,
        AdaptiveThresholdTypes.GaussianC, ThresholdTypes.Binary, 11, 2);

    // Step 4: Deskew — requires custom rotation detection logic (not shown)
    // Several dozen lines of custom Mat operations

    var result = ocr.Run(binary);
    return string.Join(" ", result.Regions.Select(r => r.Text));
    // Each Mat must be disposed; missing a using block leaks native memory
}
// Simplified — see OpenCvSharp documentation for full API
using OpenCvSharp;
using Sdcb.PaddleOCR;

public string ReadWithPreprocessing(string imagePath, PaddleOcrAll ocr)
{
    using var original = Cv2.ImRead(imagePath);

    // Step 1: Grayscale conversion
    using var gray = new Mat();
    Cv2.CvtColor(original, gray, ColorConversionCodes.BGR2GRAY);

    // Step 2: Denoise (Gaussian blur to reduce noise)
    using var denoised = new Mat();
    Cv2.GaussianBlur(gray, denoised, new Size(3, 3), 0);

    // Step 3: Adaptive threshold for binarization
    using var binary = new Mat();
    Cv2.AdaptiveThreshold(denoised, binary, 255,
        AdaptiveThresholdTypes.GaussianC, ThresholdTypes.Binary, 11, 2);

    // Step 4: Deskew — requires custom rotation detection logic (not shown)
    // Several dozen lines of custom Mat operations

    var result = ocr.Run(binary);
    return string.Join(" ", result.Regions.Select(r => r.Text));
    // Each Mat must be disposed; missing a using block leaks native memory
}
Imports OpenCvSharp
Imports Sdcb.PaddleOCR

Public Function ReadWithPreprocessing(imagePath As String, ocr As PaddleOcrAll) As String
    Using original As Mat = Cv2.ImRead(imagePath)

        ' Step 1: Grayscale conversion
        Using gray As New Mat()
            Cv2.CvtColor(original, gray, ColorConversionCodes.BGR2GRAY)

            ' Step 2: Denoise (Gaussian blur to reduce noise)
            Using denoised As New Mat()
                Cv2.GaussianBlur(gray, denoised, New Size(3, 3), 0)

                ' Step 3: Adaptive threshold for binarization
                Using binary As New Mat()
                    Cv2.AdaptiveThreshold(denoised, binary, 255, AdaptiveThresholdTypes.GaussianC, ThresholdTypes.Binary, 11, 2)

                    ' Step 4: Deskew — requires custom rotation detection logic (not shown)
                    ' Several dozen lines of custom Mat operations

                    Dim result = ocr.Run(binary)
                    Return String.Join(" ", result.Regions.Select(Function(r) r.Text))
                End Using
            End Using
        End Using
    End Using
End Function
$vbLabelText   $csharpLabel

IronOCR方法:

using IronOcr;

public string ReadWithPreprocessing(string imagePath)
{
    using var input = new OcrInput();
    input.LoadImage(imagePath);

    // Named operations replace OpenCV knowledge requirements
    input.Deskew();
    input.DeNoise();
    input.Contrast();
    input.Binarize();

    var result = new IronTesseract().Read(input);
    return result.Text;
    // OcrInput implements IDisposable; using block handles cleanup
}
using IronOcr;

public string ReadWithPreprocessing(string imagePath)
{
    using var input = new OcrInput();
    input.LoadImage(imagePath);

    // Named operations replace OpenCV knowledge requirements
    input.Deskew();
    input.DeNoise();
    input.Contrast();
    input.Binarize();

    var result = new IronTesseract().Read(input);
    return result.Text;
    // OcrInput implements IDisposable; using block handles cleanup
}
Imports IronOcr

Public Function ReadWithPreprocessing(imagePath As String) As String
    Using input As New OcrInput()
        input.LoadImage(imagePath)

        ' Named operations replace OpenCV knowledge requirements
        input.Deskew()
        input.DeNoise()
        input.Contrast()
        input.Binarize()

        Dim result = New IronTesseract().Read(input)
        Return result.Text
        ' OcrInput implements IDisposable; using block handles cleanup
    End Using
End Function
$vbLabelText   $csharpLabel

Mat分配。 不了解自适应阈值参数。 没有自定义的偏斜旋转计算。 原本需要 30-50 行 OpenCV 代码的预处理流程,现在只需 4 次方法调用即可完成。 图像质量校正指南详细记录了每个可用的滤镜,并附有校正前后的示例。 对于背景噪音严重的文档,DeNoise()走得更远,而不需要任何额外参数。

对于预处理要求非标准的团队,筛选向导提供了一个交互式工具,用于在提交代码之前评估特定文档类型的筛选器组合。

后端选择消除

PaddleSharp 将推理后端作为应用程序级别的关注点公开出来。 需要在仅使用 CPU 的云虚拟机上运行的部署,其初始化代码与面向 GPU 工作站或支持 Intel OpenVINO 的边缘设备的部署所使用的初始化代码不同。这种条件逻辑通常出现在应用程序启动代码、环境变量检查或功能标志中——这些基础架构工作与从图像中读取文本无关。

PaddleSharp OCR 方法:

// Simplified — see Sdcb.PaddleInference documentation for full API
using Sdcb.PaddleInference;
using Sdcb.PaddleOCR;

public PaddleOcrAll CreateOcrEngine(string backendMode)
{
    // Each backend requires a different NuGet runtime package installed
    switch (backendMode)
    {
        case "gpu":
            // Requires: Sdcb.PaddleInference.runtime.win64.cuda
            // Requires: CUDA toolkit + cuDNN installed on host
            var gpuConfig = PaddleConfig.FromModelDir("models/");
            gpuConfig.EnableGpu(500, deviceId: 0); // Simplified
            break;

        case "openvino":
            // Requires: Sdcb.PaddleInference.runtime.win64.mkl
            var oviConfig = PaddleConfig.FromModelDir("models/");
            oviConfig.EnableMkldnn(); // Simplified
            break;

        default:
            // CPU-only — still requires platform-specific runtime package
            var cpuConfig = PaddleConfig.FromModelDir("models/");
            cpuConfig.SetCpuMathLibraryNumThreads(Environment.ProcessorCount);
            break;
    }

    // Backend-specific config passed to model constructors — Simplified
    var detModel = LocalFullModels.ChineseV3.DetectionModel;
    var clsModel = LocalFullModels.ChineseV3.ClassifierModel;
    var recModel = LocalFullModels.ChineseV3.RecognitionModel;
    return new PaddleOcrAll(detModel, clsModel, recModel);
}
// Simplified — see Sdcb.PaddleInference documentation for full API
using Sdcb.PaddleInference;
using Sdcb.PaddleOCR;

public PaddleOcrAll CreateOcrEngine(string backendMode)
{
    // Each backend requires a different NuGet runtime package installed
    switch (backendMode)
    {
        case "gpu":
            // Requires: Sdcb.PaddleInference.runtime.win64.cuda
            // Requires: CUDA toolkit + cuDNN installed on host
            var gpuConfig = PaddleConfig.FromModelDir("models/");
            gpuConfig.EnableGpu(500, deviceId: 0); // Simplified
            break;

        case "openvino":
            // Requires: Sdcb.PaddleInference.runtime.win64.mkl
            var oviConfig = PaddleConfig.FromModelDir("models/");
            oviConfig.EnableMkldnn(); // Simplified
            break;

        default:
            // CPU-only — still requires platform-specific runtime package
            var cpuConfig = PaddleConfig.FromModelDir("models/");
            cpuConfig.SetCpuMathLibraryNumThreads(Environment.ProcessorCount);
            break;
    }

    // Backend-specific config passed to model constructors — Simplified
    var detModel = LocalFullModels.ChineseV3.DetectionModel;
    var clsModel = LocalFullModels.ChineseV3.ClassifierModel;
    var recModel = LocalFullModels.ChineseV3.RecognitionModel;
    return new PaddleOcrAll(detModel, clsModel, recModel);
}
Imports Sdcb.PaddleInference
Imports Sdcb.PaddleOCR

Public Function CreateOcrEngine(ByVal backendMode As String) As PaddleOcrAll
    ' Each backend requires a different NuGet runtime package installed
    Select Case backendMode
        Case "gpu"
            ' Requires: Sdcb.PaddleInference.runtime.win64.cuda
            ' Requires: CUDA toolkit + cuDNN installed on host
            Dim gpuConfig As PaddleConfig = PaddleConfig.FromModelDir("models/")
            gpuConfig.EnableGpu(500, deviceId:=0) ' Simplified

        Case "openvino"
            ' Requires: Sdcb.PaddleInference.runtime.win64.mkl
            Dim oviConfig As PaddleConfig = PaddleConfig.FromModelDir("models/")
            oviConfig.EnableMkldnn() ' Simplified

        Case Else
            ' CPU-only — still requires platform-specific runtime package
            Dim cpuConfig As PaddleConfig = PaddleConfig.FromModelDir("models/")
            cpuConfig.SetCpuMathLibraryNumThreads(Environment.ProcessorCount)
    End Select

    ' Backend-specific config passed to model constructors — Simplified
    Dim detModel = LocalFullModels.ChineseV3.DetectionModel
    Dim clsModel = LocalFullModels.ChineseV3.ClassifierModel
    Dim recModel = LocalFullModels.ChineseV3.RecognitionModel
    Return New PaddleOcrAll(detModel, clsModel, recModel)
End Function
$vbLabelText   $csharpLabel

IronOCR方法:

using IronOcr;

// 否 backend selection. 否 switch statement. 否 environment variable check.
// The same code runs on CPU-only VMs, GPU workstations, and ARM devices.
public IronTesseract CreateOcrEngine()
{
    return new IronTesseract();
}

// Parallel processing across CPU cores — no GPU configuration required
public IEnumerable<string> ReadBatch(IEnumerable<string> imagePaths)
{
    var results = new System.Collections.Concurrent.ConcurrentBag<string>();
    Parallel.ForEach(imagePaths, path =>
    {
        var result = new IronTesseract().Read(path);
        results.Add(result.Text);
    });
    return results;
}
using IronOcr;

// 否 backend selection. 否 switch statement. 否 environment variable check.
// The same code runs on CPU-only VMs, GPU workstations, and ARM devices.
public IronTesseract CreateOcrEngine()
{
    return new IronTesseract();
}

// Parallel processing across CPU cores — no GPU configuration required
public IEnumerable<string> ReadBatch(IEnumerable<string> imagePaths)
{
    var results = new System.Collections.Concurrent.ConcurrentBag<string>();
    Parallel.ForEach(imagePaths, path =>
    {
        var result = new IronTesseract().Read(path);
        results.Add(result.Text);
    });
    return results;
}
Imports IronOcr
Imports System.Collections.Concurrent
Imports System.Threading.Tasks

Public Class OcrProcessor

    ' 否 backend selection. 否 switch statement. 否 environment variable check.
    ' The same code runs on CPU-only VMs, GPU workstations, and ARM devices.
    Public Function CreateOcrEngine() As IronTesseract
        Return New IronTesseract()
    End Function

    ' Parallel processing across CPU cores — no GPU configuration required
    Public Function ReadBatch(imagePaths As IEnumerable(Of String)) As IEnumerable(Of String)
        Dim results As New ConcurrentBag(Of String)()
        Parallel.ForEach(imagePaths, Sub(path)
                                         Dim result = New IronTesseract().Read(path)
                                         results.Add(result.Text)
                                     End Sub)
        Return results
    End Function

End Class
$vbLabelText   $csharpLabel

这里的Parallel.ForEach模式具有线程安全的特性。 每个IronTesseract实例都是独立的,没有共享的本机状态。 对于那些在 PaddleSharp 部署中花费大量时间管理后端条件的团队来说,这种简化也提高了部署的可靠性——同一个构建产物可以在任何地方运行,而无需硬件检测代码。 速度优化指南涵盖了对吞吐量要求较高的场景下的配置选项。

表格识别迁移

PaddleSharp 中的表格提取需要一个专门的表格识别模型——除了标准的检测、分类和识别集之外,还需要第四个模型文件。 表格模型使用单独的 API 调用,并返回自己的结果结构。 构建发票、表单或电子表格处理管道的团队维护两条并行初始化路径和两种结果解析策略。

PaddleSharp OCR 方法:

// Simplified — see Sdcb.PaddleOCR documentation for full API
using Sdcb.PaddleOCR;
using Sdcb.PaddleOCR.Models;
using OpenCvSharp;

public class TableRecognitionService
{
    // Standard OCR engine — 3 models
    private readonly PaddleOcrAll _textOcr;

    // Table engine — 4th model, separate initialization
    // private readonly PaddleOcrTable _tableOcr; // Simplified

    public TableRecognitionService()
    {
        var detModel = LocalFullModels.ChineseV3.DetectionModel;
        var clsModel = LocalFullModels.ChineseV3.ClassifierModel;
        var recModel = LocalFullModels.ChineseV3.RecognitionModel;
        _textOcr = new PaddleOcrAll(detModel, clsModel, recModel);

        // Table model: separate download, separate version tracking
        // var tableModel = LocalFullModels.TableEnV2.Model; // Simplified
        // _tableOcr = new PaddleOcrTable(tableModel); // Simplified
    }

    public void ProcessDocument(string imagePath)
    {
        using var image = Cv2.ImRead(imagePath);

        // Text extraction path
        var textResult = _textOcr.Run(image);
        var text = string.Join(" ", textResult.Regions.Select(r => r.Text));

        // Table extraction path — different API, different result structure
        // var tableResult = _tableOcr.Run(image); // Simplified
        // foreach (var cell in tableResult.Cells) { ... } // Simplified
    }
}
// Simplified — see Sdcb.PaddleOCR documentation for full API
using Sdcb.PaddleOCR;
using Sdcb.PaddleOCR.Models;
using OpenCvSharp;

public class TableRecognitionService
{
    // Standard OCR engine — 3 models
    private readonly PaddleOcrAll _textOcr;

    // Table engine — 4th model, separate initialization
    // private readonly PaddleOcrTable _tableOcr; // Simplified

    public TableRecognitionService()
    {
        var detModel = LocalFullModels.ChineseV3.DetectionModel;
        var clsModel = LocalFullModels.ChineseV3.ClassifierModel;
        var recModel = LocalFullModels.ChineseV3.RecognitionModel;
        _textOcr = new PaddleOcrAll(detModel, clsModel, recModel);

        // Table model: separate download, separate version tracking
        // var tableModel = LocalFullModels.TableEnV2.Model; // Simplified
        // _tableOcr = new PaddleOcrTable(tableModel); // Simplified
    }

    public void ProcessDocument(string imagePath)
    {
        using var image = Cv2.ImRead(imagePath);

        // Text extraction path
        var textResult = _textOcr.Run(image);
        var text = string.Join(" ", textResult.Regions.Select(r => r.Text));

        // Table extraction path — different API, different result structure
        // var tableResult = _tableOcr.Run(image); // Simplified
        // foreach (var cell in tableResult.Cells) { ... } // Simplified
    }
}
Imports Sdcb.PaddleOCR
Imports Sdcb.PaddleOCR.Models
Imports OpenCvSharp

Public Class TableRecognitionService
    ' Standard OCR engine — 3 models
    Private ReadOnly _textOcr As PaddleOcrAll

    ' Table engine — 4th model, separate initialization
    ' Private ReadOnly _tableOcr As PaddleOcrTable ' Simplified

    Public Sub New()
        Dim detModel = LocalFullModels.ChineseV3.DetectionModel
        Dim clsModel = LocalFullModels.ChineseV3.ClassifierModel
        Dim recModel = LocalFullModels.ChineseV3.RecognitionModel
        _textOcr = New PaddleOcrAll(detModel, clsModel, recModel)

        ' Table model: separate download, separate version tracking
        ' Dim tableModel = LocalFullModels.TableEnV2.Model ' Simplified
        ' _tableOcr = New PaddleOcrTable(tableModel) ' Simplified
    End Sub

    Public Sub ProcessDocument(imagePath As String)
        Using image = Cv2.ImRead(imagePath)
            ' Text extraction path
            Dim textResult = _textOcr.Run(image)
            Dim text = String.Join(" ", textResult.Regions.Select(Function(r) r.Text))

            ' Table extraction path — different API, different result structure
            ' Dim tableResult = _tableOcr.Run(image) ' Simplified
            ' For Each cell In tableResult.Cells ' Simplified
            ' ... 
            ' Next
        End Using
    End Sub
End Class
$vbLabelText   $csharpLabel

IronOCR方法:

using IronOcr;

public class TableRecognitionService
{
    // One engine handles both text and table regions
    public void ProcessDocument(string imagePath)
    {
        var ocr = new IronTesseract();
        var result = ocr.Read(imagePath);

        // Structured hierarchy: pages → paragraphs → lines → words
        foreach (var page in result.Pages)
        {
            foreach (var paragraph in page.Paragraphs)
            {
                Console.WriteLine($"Block at ({paragraph.X},{paragraph.Y}): {paragraph.Text}");
            }
        }

        Console.WriteLine($"Full document text: {result.Text}");
    }
}
using IronOcr;

public class TableRecognitionService
{
    // One engine handles both text and table regions
    public void ProcessDocument(string imagePath)
    {
        var ocr = new IronTesseract();
        var result = ocr.Read(imagePath);

        // Structured hierarchy: pages → paragraphs → lines → words
        foreach (var page in result.Pages)
        {
            foreach (var paragraph in page.Paragraphs)
            {
                Console.WriteLine($"Block at ({paragraph.X},{paragraph.Y}): {paragraph.Text}");
            }
        }

        Console.WriteLine($"Full document text: {result.Text}");
    }
}
Imports IronOcr

Public Class TableRecognitionService
    ' One engine handles both text and table regions
    Public Sub ProcessDocument(imagePath As String)
        Dim ocr As New IronTesseract()
        Dim result = ocr.Read(imagePath)

        ' Structured hierarchy: pages → paragraphs → lines → words
        For Each page In result.Pages
            For Each paragraph In page.Paragraphs
                Console.WriteLine($"Block at ({paragraph.X},{paragraph.Y}): {paragraph.Text}")
            Next
        Next

        Console.WriteLine($"Full document text: {result.Text}")
    End Sub
End Class
$vbLabelText   $csharpLabel

对于必须将表格结构本身提取为行和列的文档, IronOCR提供了专门的表格提取功能:

using IronOcr;

var ocr = new IronTesseract();
using var input = new OcrInput();
input.LoadImage("invoice-with-table.jpg");

var result = ocr.Read(input);

// Access structured page layout for table region extraction
foreach (var page in result.Pages)
{
    foreach (var line in page.Lines)
    {
        // Lines within a table region preserve spatial ordering
        Console.WriteLine($"Row text: {line.Text} | Y position: {line.Y}");
        foreach (var word in line.Words)
        {
            Console.WriteLine($"  Cell: '{word.Text}' at X={word.X}");
        }
    }
}
using IronOcr;

var ocr = new IronTesseract();
using var input = new OcrInput();
input.LoadImage("invoice-with-table.jpg");

var result = ocr.Read(input);

// Access structured page layout for table region extraction
foreach (var page in result.Pages)
{
    foreach (var line in page.Lines)
    {
        // Lines within a table region preserve spatial ordering
        Console.WriteLine($"Row text: {line.Text} | Y position: {line.Y}");
        foreach (var word in line.Words)
        {
            Console.WriteLine($"  Cell: '{word.Text}' at X={word.X}");
        }
    }
}
Imports IronOcr

Dim ocr As New IronTesseract()
Using input As New OcrInput()
    input.LoadImage("invoice-with-table.jpg")

    Dim result = ocr.Read(input)

    ' Access structured page layout for table region extraction
    For Each page In result.Pages
        For Each line In page.Lines
            ' Lines within a table region preserve spatial ordering
            Console.WriteLine($"Row text: {line.Text} | Y position: {line.Y}")
            For Each word In line.Words
                Console.WriteLine($"  Cell: '{word.Text}' at X={word.X}")
            Next
        Next
    Next
End Using
$vbLabelText   $csharpLabel

已删除一个模型下载。 一条初始化路径被移除。 IronOCR中的结构化结果层次结构(带有词级 X/Y 坐标)提供了重建表格行和列所需的定位数据,而无需单独的识别模型。 表格读取指南读取结果指南涵盖了完整的结构化输出 API。

从扫描文档输出可搜索的PDF文件

PaddleSharp 只生成文本字符串,不生成其他任何内容。 构建一个能够对扫描的 PDF 进行文本搜索的文档库,需要集成一个单独的 PDF 库,编写一个文本叠加层,并协同管理这两个库。 接受这一限制的团队通常发现,这是迁移的触发因素——两个图书馆集成的工作量超过了切换 OCR 提供商的工作量。

PaddleSharp OCR 方法:

// Simplified — PaddleSharp has no PDF output. Requires a separate PDF library.
// Example of what teams typically build:

// using Sdcb.PaddleOCR;
// using SomePdfLibrary; // Third dependency to produce searchable PDF

public void ArchiveScannedDocument(string imagePath, string outputPdfPath)
{
    // Step 1: OCR via PaddleSharp — produces text only
    // var text = _ocr.Run(Cv2.ImRead(imagePath));

    // Step 2: Build a PDF with text overlay using a separate PDF library
    // Requires: text positions mapped to PDF coordinate space
    // Requires: image embedded as background
    // Requires: invisible text layer positioned over image
    // ~50–100 lines of PDF construction code
    throw new NotImplementedException("Requires a separate PDF library");
}
// Simplified — PaddleSharp has no PDF output. Requires a separate PDF library.
// Example of what teams typically build:

// using Sdcb.PaddleOCR;
// using SomePdfLibrary; // Third dependency to produce searchable PDF

public void ArchiveScannedDocument(string imagePath, string outputPdfPath)
{
    // Step 1: OCR via PaddleSharp — produces text only
    // var text = _ocr.Run(Cv2.ImRead(imagePath));

    // Step 2: Build a PDF with text overlay using a separate PDF library
    // Requires: text positions mapped to PDF coordinate space
    // Requires: image embedded as background
    // Requires: invisible text layer positioned over image
    // ~50–100 lines of PDF construction code
    throw new NotImplementedException("Requires a separate PDF library");
}
Public Sub ArchiveScannedDocument(imagePath As String, outputPdfPath As String)
    ' Step 1: OCR via PaddleSharp — produces text only
    ' Dim text = _ocr.Run(Cv2.ImRead(imagePath))

    ' Step 2: Build a PDF with text overlay using a separate PDF library
    ' Requires: text positions mapped to PDF coordinate space
    ' Requires: image embedded as background
    ' Requires: invisible text layer positioned over image
    ' ~50–100 lines of PDF construction code
    Throw New NotImplementedException("Requires a separate PDF library")
End Sub
$vbLabelText   $csharpLabel

IronOCR方法:

using IronOcr;

public void ArchiveScannedDocument(string imagePath, string outputPdfPath)
{
    using var input = new OcrInput();
    input.LoadImage(imagePath);
    input.Deskew();   // Straighten scan before archiving
    input.DeNoise();  // Clean up scan artifacts

    var ocr = new IronTesseract();
    var result = ocr.Read(input);

    // One call: OCR + searchable PDF with text layer + image background
    result.SaveAsSearchablePdf(outputPdfPath);
}

// Multi-page document — same pattern
public void ArchiveMultiPageDocument(string[] imageFiles, string outputPdfPath)
{
    using var input = new OcrInput();
    foreach (var file in imageFiles)
        input.LoadImage(file);

    var result = new IronTesseract().Read(input);
    result.SaveAsSearchablePdf(outputPdfPath);
}
using IronOcr;

public void ArchiveScannedDocument(string imagePath, string outputPdfPath)
{
    using var input = new OcrInput();
    input.LoadImage(imagePath);
    input.Deskew();   // Straighten scan before archiving
    input.DeNoise();  // Clean up scan artifacts

    var ocr = new IronTesseract();
    var result = ocr.Read(input);

    // One call: OCR + searchable PDF with text layer + image background
    result.SaveAsSearchablePdf(outputPdfPath);
}

// Multi-page document — same pattern
public void ArchiveMultiPageDocument(string[] imageFiles, string outputPdfPath)
{
    using var input = new OcrInput();
    foreach (var file in imageFiles)
        input.LoadImage(file);

    var result = new IronTesseract().Read(input);
    result.SaveAsSearchablePdf(outputPdfPath);
}
Imports IronOcr

Public Sub ArchiveScannedDocument(imagePath As String, outputPdfPath As String)
    Using input As New OcrInput()
        input.LoadImage(imagePath)
        input.Deskew()   ' Straighten scan before archiving
        input.DeNoise()  ' Clean up scan artifacts

        Dim ocr As New IronTesseract()
        Dim result = ocr.Read(input)

        ' One call: OCR + searchable PDF with text layer + image background
        result.SaveAsSearchablePdf(outputPdfPath)
    End Using
End Sub

' Multi-page document — same pattern
Public Sub ArchiveMultiPageDocument(imageFiles As String(), outputPdfPath As String)
    Using input As New OcrInput()
        For Each file In imageFiles
            input.LoadImage(file)
        Next

        Dim result = New IronTesseract().Read(input)
        result.SaveAsSearchablePdf(outputPdfPath)
    End Using
End Sub
$vbLabelText   $csharpLabel

没有PDF库。 没有坐标映射。 无需进行文本图层定位。 IronOCR的可搜索 PDF 输出格式在原始图像上嵌入了一个不可见的文本层,生成的文件在视觉上忠实于扫描文档,并且完全可进行文本搜索。 这份可搜索的 PDF 使用指南涵盖了页面选择、质量选项和元数据控制。

PaddleSharp OCRAPI 到IronOCR映射参考

PaddleSharp OCR IronOCR
Sdcb.PaddleOCR (namespace) IronOcr (namespace)
Sdcb.PaddleInference (namespace) 无需自动配置
PaddleOcrAll IronTesseract
new PaddleOcrAll(det, cls, rec) new IronTesseract()
LocalFullModels.ChineseV3.DetectionModel 没有等效选项——没有型号选择
LocalFullModels.ChineseV3.ClassifierModel 没有等效选项——没有型号选择
LocalFullModels.ChineseV3.RecognitionModel 没有等效选项——没有型号选择
PaddleConfig.FromModelDir() 没有等效项——没有配置对象
config.EnableGpu(memMB, deviceId) 没有等效方案——后端是自动的
config.EnableMkldnn() 没有等效方案——后端是自动的
config.SetCpuMathLibraryNumThreads(n) 没有同等服务——内部管理
Cv2.ImRead(path) (OpenCV加载) input.LoadImage(path)
ocr.Run(mat) ocr.Read(input)ocr.Read("file.jpg")
result.Regions result.Pages[0].Wordsresult.Pages[0].Lines
region.Text word.Text, line.Text, paragraph.Text
region.Rect.Center.X/.Y word.X, word.Y
region.Score (confidence) word.Confidence, result.Confidence
模型级语言交换 ocr.Language = OcrLanguage.French
表格模型(单独下载) 内置结构化结果层次结构
Cv2.CvtColor(..., GRAY) input.Binarize()input.Contrast()
Cv2.GaussianBlur(...) input.DeNoise()
没有可搜索的 PDF 输出 result.SaveAsSearchablePdf("output.pdf")

常见迁移问题和解决方案

问题 1:OpenCV 依赖项卸载失败

PaddleSharp OCR:OpenCvSharp4.runtime.win和类似的平台特定运行时包安装非托管的本机DLL。 这些 DLL 文件在某些​​托管场景(尤其是 IIS 应用程序池回收)中会妨碍正确的清理工作,并且在构建时引用了错误的平台运行时包时会导致程序集加载失败。要移除它们,需要同时移除NuGet包并清除输出目录中所有缓存的本机二进制文件。

解决方案:在删除OpenCvSharp4.runtime.*包后,清理构建输出目录然后重新构建:

dotnet remove package OpenCvSharp4
dotnet remove package OpenCvSharp4.runtime.win
dotnet clean
dotnet build
dotnet remove package OpenCvSharp4
dotnet remove package OpenCvSharp4.runtime.win
dotnet clean
dotnet build
SHELL

IronOCR将其原生依赖项打包在内部,并处理非托管生命周期。 无需选择特定于平台的运行时包。 IronTesseract 设置指南记录了IronOCR自动处理的平台要求。

问题二:迁移后磁盘上残留的模型文件

PaddleSharp OCR:由PaddleSharp下载的模型文件(检测、分类、识别和任何表格模型)通常存储在相对于应用程序的models/目录或配置路径中。 卸载NuGet包时,这些文件不会被删除。 在 Docker 镜像中,它们会增加不必要的层大小。在部署流水线中,如果任何残留的初始化代码引用了旧路径上的过时模型文件,则可能导致启动失败。

解决方案:在迁移过程中显式删除模型目录。 检查启动配置中是否存在任何路径引用:

# Locate model directory references in application code
grep -r "LocalFullModels\|ModelPath\|models/" --include="*.cs" .
grep -r "DetectionModel\|RecognitionModel\|ClassifierModel" --include="*.cs" .
# Locate model directory references in application code
grep -r "LocalFullModels\|ModelPath\|models/" --include="*.cs" .
grep -r "DetectionModel\|RecognitionModel\|ClassifierModel" --include="*.cs" .
SHELL

移除模型引用并初始化IronOCR后,从存储库和 Docker 构建上下文中删除模型目录。

问题 3:迁移后单例生命周期假设不再成立

PaddleSharp OCR:由于其构建成本使每次请求实例化不切实际,PaddleOcrAll被注册为单例。 将IronOCR提升到同一个单例注册中的迁移代码会在请求之间引入不必要的状态共享。 虽然IronTesseract在并发使用时是线程安全的,但没有必要共享单个实例——每个实例是独立的。

解决方案:评估单例注册除了性能之外是否还有其他用途。 对于大多数ASP.NET Core应用程序,使用IronOCR时,瞬态注册是更简洁的选择:

// PaddleSharp — forced singleton due to construction cost
services.AddSingleton<PaddleOcrAll>(sp =>
{
    var det = LocalFullModels.ChineseV3.DetectionModel;  // Simplified
    var cls = LocalFullModels.ChineseV3.ClassifierModel; // Simplified
    var rec = LocalFullModels.ChineseV3.RecognitionModel; // Simplified
    return new PaddleOcrAll(det, cls, rec);
});

//IronOCR— transient works; no expensive construction
services.AddTransient<IronTesseract>();
// PaddleSharp — forced singleton due to construction cost
services.AddSingleton<PaddleOcrAll>(sp =>
{
    var det = LocalFullModels.ChineseV3.DetectionModel;  // Simplified
    var cls = LocalFullModels.ChineseV3.ClassifierModel; // Simplified
    var rec = LocalFullModels.ChineseV3.RecognitionModel; // Simplified
    return new PaddleOcrAll(det, cls, rec);
});

//IronOCR— transient works; no expensive construction
services.AddTransient<IronTesseract>();
Imports Microsoft.Extensions.DependencyInjection

' PaddleSharp — forced singleton due to construction cost
services.AddSingleton(Of PaddleOcrAll)(Function(sp)
    Dim det = LocalFullModels.ChineseV3.DetectionModel ' Simplified
    Dim cls = LocalFullModels.ChineseV3.ClassifierModel ' Simplified
    Dim rec = LocalFullModels.ChineseV3.RecognitionModel ' Simplified
    Return New PaddleOcrAll(det, cls, rec)
End Function)

' IronOCR— transient works; no expensive construction
services.AddTransient(Of IronTesseract)()
$vbLabelText   $csharpLabel

对于需要显式实例重用的高吞吐量批处理场景,单例或池化模式仍然有效——但这是一种性能选择,而不是正确性要求。

问题 4:不再需要按结果区域排序

PaddleSharp OCR:result.Regions按检测顺序返回检测到的文本区域,这不一定与阅读顺序(从左到右,从上到下)匹配。 团队通常先按.Rect.Center.X排序,然后再连接区域文本——这种模式出现在几乎每个PaddleSharp文本提取实现中。 将此模式直接迁移到IronOCR会产生冗余代码。

解决方案: IronOCR默认按阅读顺序返回结果。 取消排序:

// PaddleSharp — manual reading-order sort required
var text = string.Join("\n", result.Regions
    .OrderBy(r => r.Rect.Center.Y)
    .ThenBy(r => r.Rect.Center.X)
    .Select(r => r.Text));

//IronOCR— result.Text is already in reading order; no sort needed
var text = result.Text;

// For word-level access with position, use the structured hierarchy directly
foreach (var word in result.Pages[0].Words)
{
    Console.WriteLine($"{word.Text} at ({word.X},{word.Y})");
}
// PaddleSharp — manual reading-order sort required
var text = string.Join("\n", result.Regions
    .OrderBy(r => r.Rect.Center.Y)
    .ThenBy(r => r.Rect.Center.X)
    .Select(r => r.Text));

//IronOCR— result.Text is already in reading order; no sort needed
var text = result.Text;

// For word-level access with position, use the structured hierarchy directly
foreach (var word in result.Pages[0].Words)
{
    Console.WriteLine($"{word.Text} at ({word.X},{word.Y})");
}
Imports System
Imports System.Linq

' PaddleSharp — manual reading-order sort required
Dim text = String.Join(vbLf, result.Regions _
    .OrderBy(Function(r) r.Rect.Center.Y) _
    .ThenBy(Function(r) r.Rect.Center.X) _
    .Select(Function(r) r.Text))

' IronOCR— result.Text is already in reading order; no sort needed
text = result.Text

' For word-level access with position, use the structured hierarchy directly
For Each word In result.Pages(0).Words
    Console.WriteLine($"{word.Text} at ({word.X},{word.Y})")
Next
$vbLabelText   $csharpLabel

问题 5:后端条件包导致恢复失败

PaddleSharp OCR:一些PaddleSharp设置基于目标环境有条件地引用不同的Sdcb.PaddleInference.runtime.*包(CUDA用于GPU,MKL用于OpenVINO,仅CPU)。 这有时会以.csproj条件或每个部署目标的独立项目文件的形式出现。 当恢复错误的软件包集时,生成的构建矩阵会破坏 CI 流水线。

解决方案:在删除PaddleSharp包之后,检查OpenCvSharp</em>包并完全移除它们:

grep -n "Sdcb\|OpenCvSharp\|PaddleInference" *.csproj
grep -n "Sdcb\|OpenCvSharp\|PaddleInference" *.csproj
SHELL

IronOCR使用一个没有平台条件的IronOcr包引用。 同一个软件包在 Windows、Linux 和 macOS 上都能正确恢复。

问题 6:表格结果结构没有直接等效项

PaddleSharp OCR:PaddleOcrTable返回一个基于单元格的结构,每个识别的单元格具有行和列索引。 使用此结构的代码通常构建一个由(row, column)索引的二维数组。 IronOCR不提供完全相同的单元格索引结构——它提供的是单词和行坐标,需要进行空间分组才能重建单元格网格。

解决方案:根据IronOCR单词坐标重建表格结构,行按 Y 轴位置分组,列按 X 轴位置排序。 对于常见的表格格式,表格阅读方法提供了一种空间分组方法。 对于已知字段位置的结构化发票,使用基于区域的OCRCropRectangle是一种比全页面表格提取更简洁的模式:

using IronOcr;

// Target specific table cells by region instead of full-page table detection
var totalAmountRegion = new CropRectangle(400, 600, 200, 30); // x, y, width, height
using var input = new OcrInput();
input.LoadImage("invoice.jpg", totalAmountRegion);

var result = new IronTesseract().Read(input);
Console.WriteLine($"Total: {result.Text}");
using IronOcr;

// Target specific table cells by region instead of full-page table detection
var totalAmountRegion = new CropRectangle(400, 600, 200, 30); // x, y, width, height
using var input = new OcrInput();
input.LoadImage("invoice.jpg", totalAmountRegion);

var result = new IronTesseract().Read(input);
Console.WriteLine($"Total: {result.Text}");
Imports IronOcr

' Target specific table cells by region instead of full-page table detection
Dim totalAmountRegion As New CropRectangle(400, 600, 200, 30) ' x, y, width, height
Using input As New OcrInput()
    input.LoadImage("invoice.jpg", totalAmountRegion)

    Dim result = New IronTesseract().Read(input)
    Console.WriteLine($"Total: {result.Text}")
End Using
$vbLabelText   $csharpLabel

PaddleSharp OCR迁移检查清单

迁移前

在删除软件包之前,请审核代码库中所有对 PaddleSharp 的引用:

# Find all PaddleSharp and PaddleInference usages
grep -rn "Sdcb\.PaddleOCR\|Sdcb\.PaddleInference" --include="*.cs" .

# Find OpenCV usages that will need replacement
grep -rn "OpenCvSharp\|Cv2\.\|using var.*Mat\b" --include="*.cs" .

# Find model path references and configuration
grep -rn "LocalFullModels\|ModelDir\|DetectionModel\|RecognitionModel\|ClassifierModel" --include="*.cs" .

# Find backend selection logic
grep -rn "EnableGpu\|EnableMkldnn\|PaddleConfig\|SetCpuMath" --include="*.cs" .

# Find table recognition usages
grep -rn "PaddleOcrTable\|TableModel\|table.*ocr\|ocr.*table" --include="*.cs" .

# Find result region access patterns that need updating
grep -rn "\.Regions\b\|Rect\.Center\|region\.Text" --include="*.cs" .
# Find all PaddleSharp and PaddleInference usages
grep -rn "Sdcb\.PaddleOCR\|Sdcb\.PaddleInference" --include="*.cs" .

# Find OpenCV usages that will need replacement
grep -rn "OpenCvSharp\|Cv2\.\|using var.*Mat\b" --include="*.cs" .

# Find model path references and configuration
grep -rn "LocalFullModels\|ModelDir\|DetectionModel\|RecognitionModel\|ClassifierModel" --include="*.cs" .

# Find backend selection logic
grep -rn "EnableGpu\|EnableMkldnn\|PaddleConfig\|SetCpuMath" --include="*.cs" .

# Find table recognition usages
grep -rn "PaddleOcrTable\|TableModel\|table.*ocr\|ocr.*table" --include="*.cs" .

# Find result region access patterns that need updating
grep -rn "\.Regions\b\|Rect\.Center\|region\.Text" --include="*.cs" .
SHELL

清点磁盘上的模型文件并记录其路径。 清点所有部署目标,以及是否有任何GPU或OpenVINO特定的NuGet条件在.csproj中。 请注意,由于 PaddleSharp 的构建成本,任何注册为单例的服务都将被忽略。

代码迁移

  1. 从每个项目文件中删除所有OpenCvSharp4.runtime.* NuGet包。
  2. 安装IronOcr NuGet包。
  3. 为所需语言安装语言NuGet包(例如,IronOcr.Languages.ChineseSimplified)。
  4. IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";添加到应用程序启动中。
  5. using OpenCvSharp语句。
  6. PaddleOcrAll实例化和模型加载。
  7. 删除所有PaddleConfig后端选择块(CPU、GPU、OpenVINO条件)。
  8. input.LoadImage(path)
  9. Threshold等)。
  10. ocr.Run(mat)调用。
  11. result.Regions枚举。
  12. 移除.OrderBy(r => r.Rect.Center.Y).ThenBy(r => r.Rect.Center.X)排序链——读取顺序是自动的。
  13. PaddleOcrTable初始化和结果解析。
  14. 在需要可搜索的PDF存档的任何地方添加result.SaveAsSearchablePdf(path)
  15. 重新评估服务生命周期注册:受 PaddleSharp 构建成本驱动的单例注册通常会变成临时的或范围有限的。
  16. 从磁盘中删除模型文件,并从 Docker 构建上下文中删除模型目录。
  17. 删除任何PackageReference块用于平台特定的Paddle或OpenCV运行时包。

后迁移

  • 验证在管道中每种文档类型选取 20-30 个具有代表性的文档样本,文本提取输出是否与 PaddleSharp 的输出相匹配或超过其输出。
  • 确认在应用启动日志中没有OpenCvSharp相关的程序集加载异常。
  • 使用相同的构建产物在每个目标平台(Windows、Linux、Docker)上进行测试部署——无需选择特定于平台的软件包。
  • 确认之前需要手动结果排序的文档通过result.Text生成正确排序的文本。
  • 确认可搜索的 PDF 输出文件在 Adob​​e Acrobat Reader 或您选择的 PDF 查看器中可以进行文本搜索。
  • 在负载下运行应用程序以确认每请求创建的PaddleOcrAll 构建相当的内存压力。
  • 验证以NuGet包形式安装的语言包是否能在 CI 中正确还原,而无需额外的文件部署步骤。
  • 使用基于区域或坐标分组的方法,针对预期的行/列结构测试任何表提取场景。
  • 确认在从启动路径中消除PaddleOcrAll单例构建后应用程序启动时间减少。

迁移到IronOCR的主要优势

一个包替换了四个包的堆栈。在迁移后,OCR依赖关系的足迹是一个IronOcr NuGet引用。 四包堆栈——OpenCvSharp4和一个平台特定的运行时——变成项目文件中的一个条目。依赖关系审核、许可证扫描和漏洞监控现在只覆盖一个面而不是四个。

部署工件在不同环境下保持一致。后端选择条件(CPU、GPU 或 OpenVINO)已不再需要。 同一个构建产物可以部署到开发人员笔记本电脑、CI 运行器、Linux 容器和云虚拟机,而无需任何特定于环境的软件包选择或初始化分支。 Docker镜像缩小,因为没有模型文件需要COPY,也没有平台运行时包需要安装。

文档归档管道不再需要第二个库。result.SaveAsSearchablePdf()消除了大多数PaddleSharp团队为生成可搜索档案而添加的PDF库依赖。 OCR识别和可搜索PDF写入只需一次API调用。 对于每天处理数千份扫描文档的团队来说,这种简化消除了一整类馆际版本冲突。 这篇关于可搜索 PDF 的博客文章涵盖了生产规模方面的考虑因素。

服务生命周期决策反映应用程序需求,而非库约束。IronTesseract具有轻量级构建。 由 PaddleSharp 昂贵的模型加载所导致的强制单例模式已不再必要。 在ASP.NET Core中,服务可以按请求进行范围限定,从而在并发用户之间创建更清晰的隔离,并消除共享状态线程问题。 有关部署选项的更多信息,请参阅ASP.NET OCR 用例页面

语言扩展是一个软件包安装项目,而非研究项目。语言目录包含 125 多种语言,涵盖欧洲、亚洲、中东以及其他专业语言,并以NuGet包的形式提供。 在最初只有中文的管道中添加法语、德语、阿拉伯语或日语是dotnet add package IronOcr.Languages.French且只需一行配置。无需模型文件来源,无需上游可用性研究,无需手动文件部署。

预处理是 OCR API 的一部分。PaddleSharp预处理所需的OpenCV知识——理解滤波器模板,管理Mat处置,选择自适应阈值参数——不再是OCR工作的先决条件。 OcrInput 提供了具有合理默认值的命名操作。 并非 OpenCV 专家但维护 OpenCV 预处理代码的团队可以删除该代码而无需替换它。 预处理功能页面列出了所有可用的过滤器,并附有关于何时应用每个过滤器的文档。

请注意Adobe Acrobat, PaddleOCR和Tesseract是其各自所有者的注册商标。 本网站与Adobe Inc.、百度、谷歌或PaddlePaddle无关,不受其认可或赞助。 所有产品名称、徽标和品牌均为各自所有者的财产。 比较仅供参考,反映撰写时公开可用的信息。

常见问题解答

我为什么要从 PaddleSharp OCR 迁移到 IronOCR?

常见的驱动因素包括消除 COM 互操作的复杂性、取代基于文件的许可证管理、避免按页计费、启用 Docker/容器部署以及采用与标准 .NET 工具集成的 NuGet 原生工作流程。

从 PaddleSharp OCR 迁移到 IronOCR 时,主要的代码变更有哪些?

将 PaddleSharp 初始化序列替换为 IronTesseract 实例化,移除 COM 生命周期管理(显式的创建/加载/关闭模式),并更新结果属性名称。这样可以显著减少样板代码行数。

我该如何安装 IronOCR 以开始迁移?

在软件包管理器控制台中运行“Install-Package IronOcr”,或在命令行界面中运行“dotnet add package IronOcr”。语言包是单独的软件包:例如,法语语言包需要运行“dotnet add package IronOcr.Languages.French”。

IronOCR 在标准商业文档的 OCR 准确率方面是否能与 PaddleSharp OCR 相媲美?

IronOCR 对标准商业内容(包括发票、合同、收据和打印表格)的识别准确率很高。图像预处理滤镜(例如去倾斜、去噪、对比度增强)可进一步提高对质量较差输入文件的识别率。

IronOCR 如何处理 PaddleSharp OCR 单独安装的语言数据?

IronOCR 中的语言数据以 NuGet 包的形式分发。“dotnet add package IronOcr.Languages.German”命令用于安装德语支持。无需手动放置文件或指定目录路径。

从 PaddleSharp OCR 迁移到 IronOCR 是否需要对部署基础架构进行更改?

IronOCR 比 PaddleSharp OCR 需要的底层架构变更更少。它无需 SDK 二进制文件路径、许可证文件放置位置或许可证服务器配置。NuGet 包包含完整的 OCR 引擎,许可证密钥是应用程序代码中设置的字符串。

迁移后如何配置 IronOCR 许可?

在应用程序启动代码中,将 IronOcr.License.LicenseKey 赋值为 "YOUR-KEY"。在 Docker 或 Kubernetes 中,将此密钥存储为环境变量,并在启动时读取。使用 License.IsValidLicense 在接受流量之前进行验证。

IronOCR 能否像 PaddleSharp 一样处理 PDF 文件?

是的。IronOCR 可以读取原生 PDF 和扫描版 PDF。实例化 IronTesseract,调用 ocr.Read(input) 方法(其中 input 可以是 PDF 路径或 OcrPdfInput),然后遍历 OcrResult 页面。无需单独的 PDF 渲染流程。

IronOCR 如何处理大批量处理中的线程问题?

IronTesseract 可以安全地按线程实例化。在 Parallel.ForEach 或 Task 池中为每个线程启动一个实例,并发运行 OCR,并在完成后释放每个实例。无需全局状态或锁定。

IronOCR在提取文本后支持哪些输出格式?

IronOCR 返回结构化结果,包括文本、词坐标、置信度评分和页面结构。导出选项包括纯文本、可搜索 PDF 和用于下游处理的结构化结果对象。

对于可扩展的工作负载,IronOCR 的定价是否比 PaddleSharp OCR 更可预测?

IronOCR采用永久统一费率许可模式,不收取任何页数或数量费用。无论您处理1万页还是1000万页,许可费用都保持不变。批量许可和团队许可选项请参见IronOCR定价页面。

从 PaddleSharp OCR 迁移到 IronOCR 后,我现有的测试会发生什么变化?

迁移后,对提取的文本内容进行断言的测试应该继续通过。验证 API 调用模式或 COM 对象生命周期的测试需要更新,以反映 IronOCR 更简化的初始化和结果模型。

Kannaopat Udonpant
软件工程师
在成为软件工程师之前,Kannapat 在日本北海道大学完成了环境资源博士学位。在攻读学位期间,Kannapat 还成为了车辆机器人实验室的成员,隶属于生物生产工程系。2022 年,他利用自己的 C# 技能加入 Iron Software 的工程团队,专注于 IronPDF。Kannapat 珍视他的工作,因为他可以直接从编写大多数 IronPDF 代码的开发者那里学习。除了同行学习外,Kannapat 还喜欢在 Iron Software 工作的社交方面。不撰写代码或文档时,Kannapat 通常可以在他的 PS5 上玩游戏或重温《最后生还者》。

钢铁支援团队

我们每周 5 天,每天 24 小时在线。
聊天
电子邮件
打电话给我