跳至页脚内容
视频

如何使用 C# 增强图像质量以获得更好的 OCR 结果

本指南引导.NET开发人员完成从Sdcb.PaddleOCR包系列到IronOCR的完整迁移。 它涵盖了完整的替换路径:移除多包 PaddlePaddle 堆栈,消除模型文件管理和 GPU 配置,并将依赖 OpenCV 的推理管道替换为单个NuGet安装。 每个章节都是独立的——无需事先阅读对比文章。

为什么要从 PaddleOCR 迁移?

Sdcb.PaddleOCR包装器是一个社区维护的桥梁,连接了PaddlePaddle的Python深度学习生态系统和.NET。 它能完成这项工作,但它也承担了整个桥梁的重量——模型文件、原生推理二进制文件、用于图像加载的 OpenCV 以及可选的 CUDA 基础架构。 对于大多数.NET OCR 工作负载而言,这是项目根本不需要的基础架构。

在读取单个字符之前,需要经过三个模型目录。PaddleOCR的推理流程串联了三个神经网络:检测模型、方向分类模型和识别模型。 每个网络都是models/目录树到达,开发人员始终负责模型版本管理。 当Sdcb.PaddleOCR更新时,先前版本的预下载模型可能需要重新下载。 IronOCR没有模型文件,没有模型目录,也没有版本同步问题。 引擎已打包在NuGet包中。

OpenCV不是可选项。 没有从文件路径到PaddleOCR推理的路径可以绕过OpenCvSharp。无论格式如何,每个图像必须通过ocr.Run(mat)才能接受它。 这意味着两个额外的NuGet包(System.Drawing.BitmapMat中间件不存在。

GPU配置是一个需要数天才能完成的项目。PaddleOCR宣传的GPU性能数据——每张图像50-100毫秒,而CPU则需要300-500毫秒——是真实的。 要实现这一点,需要特定版本的 NVIDIA 驱动程序、CUDA 工具包 11.8(而不是 12.x)、放置在正确 PATH 位置的 cuDNN 8.6+ 以及单独的 GPU 运行时NuGet包。 在Docker中,基础镜像必须是nvidia-container-toolkit。 没有现有 GPU 基础设施的团队,每个环境需要花费 2-8 小时进行 CUDA 配置。 IronOCR专为 CPU 推理而设计,在标准硬件上每张图像的处理时间为 150-300 毫秒,完全不需要 GPU 设置。

部署工件大4到6倍。 PaddleOCR部署输出包括opencv_world*.dll文件(合计约50MB)和模型目录(约21MB)。 Docker 镜像文件大小约为 1.5GB。 IronOCR部署总共大约占用 80MB 的空间; Docker 镜像大小约为 400MB。 在 CI/CD 中,这种差异会更加明显:每次运行恢复NuGet包时,都必须从百度下载模型,或者从单独维护的缓存层拉取模型。

不支持可搜索的PDF输出。PaddleOCR只能从图像中提取文本区域,没有将识别出的文本作为可搜索图层嵌入PDF的机制。 从 PaddleOCR 输出创建可搜索的 PDF 需要第三方 PDF 库、逐页文本层注入和坐标重映射。 IronOCR通过一行代码生成一个完全可搜索的PDF:result.SaveAsSearchablePdf("output.pdf")

基本问题

PaddleOCR 需要先配置三个模型目录才能开始推理:

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

IronOCR没有模型文件、模型目录,也不依赖 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
$vbLabelText   $csharpLabel

IronOCR与 PaddleOCR (.NET):功能对比

下表涵盖了迁移规划中最关键的几个方面。

特征 PaddleOCR(Sdcb) IronOCR
需要NuGet包 4-5 1
所需模型文件 是的(3 个目录,约 21MB) 否(包含在套餐内)
模型下载源 百度服务器(bj.bcebos.com) NuGet还原(Iron Software)
OpenCV依赖项 必需(OpenCvSharp4) None
图像输入 通过Mat mat = Cv2.ImRead() 直接文件路径、流、字节数组
原生 PDF 输入 是的(input.LoadPdf()
可搜索的 PDF 输出 是的(result.SaveAsSearchablePdf()
多帧 TIFF 输入 手动逐帧循环 input.LoadImageFrames()
GPU 支持 是的(需要 CUDA 11.8 + cuDNN) CPU优化(无需GPU)
内置预处理 否(神经网络可以处理偏差/噪声) 是的(桌面倾斜校正、降噪、对比度调整、二值化、锐化)
支持的语言 14 125+
语言安装方法 每种语言模型的DownloadAsync() dotnet add package IronOcr.Languages.*
多语言同步 否(每种语言单独建模) 是的(OcrLanguage.English + OcrLanguage.French
结构化输出 result.Regions(空间,无序) 页数、段落数、行数、单词数、字符数
置信度评分 每个区域的浮点数(0-1) 每字百分比(0-100)
条形码读取 是的(ocr.Configuration.ReadBarCodes = true
hOCR导出
部署规模 300-500MB 约80MB
Docker 镜像大小 约 1.5GB(基于 CUDA) 约400MB
冷启动时间 3-5秒(模型加载) 不到 1 秒
跨平台 Windows、Linux(部分) Windows、Linux、macOS、Docker、Azure、AWS
.NET兼容性 .NET 6+(社区封装版) .NET Framework 4.6.2+,. .NET 5/6/7/8/9
商业支持 社区/ GitHub问题 是的(Iron Software,提供服务级别协议)
许可证 Apache 2.0(免费) 永久($999 Lite / $1,499 Pro / $2,999 Enterprise)

快速入门:PaddleOCR (.NET) 到IronOCR 的迁移

步骤 1:替换 NuGet 软件包

移除所有五个与 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
SHELL

如果安装了GPU运行时库,也请将其卸载:

dotnet remove package Sdcb.PaddleInference.runtime.win64.cuda118
dotnet remove package Sdcb.PaddleInference.runtime.win64.cuda118
SHELL

NuGet包页面安装IronOCR :

dotnet add package IronOcr

步骤 2:更新命名空间

替换所有 PaddleOCR 和 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
$vbLabelText   $csharpLabel

步骤 3:初始化许可证

在创建任何IronTesseract实例之前,请在应用程序启动时添加许可证初始化:

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

您可以从IronOCR许可页面获取免费试用密钥。 试用版会生成带有水印的输出文件,并允许在购买前测试所有功能。

代码迁移示例

本地模型路径配置消除

为了避免在运行时连接百度服务器,预先下载 PaddleOCR 模型文件的项目必须配置三个不同的目录路径。 每当包装器版本发生变化时,都必须更新此配置。

桨式OCR方法:

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

IronOCR方法:

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

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

FromDirectory()调用和版本同步问题均消失。 IronOCR引擎在还原时捆绑在NuGet包中,无需运行时路径解析。 请参阅IronTesseract设置指南以获取初始化选项,包括将许可证密钥放在appsettings.json

两阶段检测与识别流程整合

PaddleOCR的旋转和方向管道是通过PaddleOcrAll上的属性配置的。 在IronOCR中复制此行为使用了OcrInput预处理方法,这些方法以更简单的调用界面处理相同的文档问题。

桨式OCR方法:

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

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

IronOCR的Lines集合由Tesseract布局引擎以阅读顺序交付,消除了手动排序模式。 图像方向校正指南记录了所有旋转和倾斜校正选项。

GPU 和 CPU 设备选择移除

运行GPU推理的PaddleOCR应用程序带有最大的迁移面:GPU运行时NuGet包、CUDA/cuDNN环境前提条件和PaddleDevice.Gpu()配置调用。 所有这些内容都会在迁移过程中被移除。

桨式OCR方法:

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

IronOCR方法:

*IronOCR的方法与上面的示例相同——IronTesseract使用相同的API调用处理这种情况。 无需 GPU 软件包,无需 CUDA 先决条件,也无需选择设备。 用new PaddleOcrAll(models, PaddleDevice.Gpu(deviceId: 0))并删除所有与GPU相关的配置。

IronOCR在 CPU 上处理每张图像仅需 150-300 毫秒,比 PaddleOCR 在 CPU 上处理图像的速度更快(300-500 毫秒),足以满足大多数 Web API 和文档管道工作负载的需求,而无需任何 GPU 基础设施。 对于高吞吐量场景,速度优化指南涵盖了线程管理和页面分段模式调整等配置选项。

结构化文档数据提取

PaddleOCR返回一个按空间顺序排列的PaddleOcrResultRegion对象的平面数组,而不是按读取流排序。 提取段落级或行级结构需要根据边界框邻近性进行手动分组逻辑。 IronOCR提供分层结果树,并保证读取顺序。

桨式OCR方法:

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

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

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

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

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

手动行分组近似值(将 Y 坐标四舍五入到像素桶大小)被 Tesseract 布局引擎的内置段落分割所取代。 通过paragraph.Height可在层次结构的每个级别提供边界框坐标。 有关完整的结果树覆盖范围,请参阅结构化结果指南从图像中读取文本教程

生成可搜索的 PDF 文件

PaddleOCR 不会生成 PDF 输出。 从PaddleOCR结果生成一个可搜索的PDF需要一个单独的PDF库、从region.Rect到PDF页面单位的手动坐标映射和一个不可见的文本层注入。 IronOCR可以直接根据 OCR 结果生成可搜索的 PDF 文件。

桨式OCR方法:

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

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

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

        ' 否 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.")
    End Using
End Using
$vbLabelText   $csharpLabel

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

IronOCR不存在坐标映射问题——将 OpenCV 像素坐标转换为正确 DPI 的 PDF 点空间。 该可搜索的 PDF 指南涵盖多页输出、密码保护的 PDF 和输出质量设置。 对于将扫描档案数字化或构建传真到可搜索 PDF 管道的团队来说,这种单一的方法调用取代了原本需要进行的大量集成项目。

多帧 TIFF 批量处理

在文档扫描工作流程中,经常会遇到多页 TIFF 文件。 PaddleOCR没有直接的TIFF多帧支持——每个帧必须使用外部成像库单独提取并加载为单独的Mat。 IronOCR可原生处理多帧 TIFF 文件。

桨式OCR方法:

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

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

帧提取循环、System.Drawing依赖关系、临时文件创建和清理逻辑都被移除。 IronOCR在单个PageTIFF 和 GIF 输入指南涵盖多帧加载选项、选择性帧范围以及大型 TIFF 存档的内存注意事项。

PaddleOCR (.NET) API 到IronOCR映射参考

PaddleOCR(Sdcb) IronOCR 备注
Sdcb.PaddleOCR IronOcr 名称空间
Sdcb.PaddleOCR.Models.Online 不适用 无需模型获取命名空间
Sdcb.PaddleInference 不适用 无需推理后端命名空间
FullOcrModel 不适用 没有等效模型——模型是捆绑在一起的。
OnlineFullModels.ChineseV4.DownloadAsync() dotnet add package IronOcr.Languages.ChineseSimplified 模型获取方式已由NuGet取代。
LocalDetectionModel.FromDirectory(path) 不适用 无模型路径管理
LocalClassificationModel.FromDirectory(path) 不适用 无模型路径管理
LocalRecognitionModel.FromDirectory(path) 不适用 无模型路径管理
new PaddleOcrAll(models) new IronTesseract() 引擎实例化
new PaddleOcrAll(models, PaddleDevice.Gpu(0)) 不适用 GPU 设备选择已完全移除
PaddleDevice.Cpu() 不适用 CPU是唯一模式; 无需选择
ocr.AllowRotateDetection = true input.Deskew() 旋转校正
ocr.Enable180Classification = true 自动翻译 内置倒置检测功能
Cv2.ImRead(path) input.LoadImage(path) 图片加载——无需 OpenCV
ocr.Run(mat) ocr.Read(input) 执行OCR
result.Text result.Text 完整文档文本字符串
result.Regions .Words 结构化文本区域
region.Text word.Text / line.Text 区域的文本内容
region.Score(浮动0-1) word.Confidence(整型0-100) 置信度值——尺度不同
region.Rect.Center.X word.X 水平位置
region.Rect.Center.Y word.Y 垂直位置
region.Rect.Size.Width word.Width 边界框宽度
region.Rect.Size.Height word.Height 边界框高度
不适用 input.LoadPdf(path) 原生PDF输入(无PaddleOCR等效项)
不适用 input.LoadImageFrames(path) 多帧 TIFF(无 PaddleOCR 等效格式)
不适用 result.SaveAsSearchablePdf(path) 可搜索的PDF输出(无PaddleOCR等效功能)

常见迁移问题和解决方案

问题 1:置信度尺度不匹配

PaddleOCR:区域置信度是一个region.Score >= 0.8以过滤低质量检测。

解决方案:IronOCR的置信度是一个int百分比,从0到100。将PaddleOCR阈值乘以100:

// PaddleOCR: filter at 0.8
var highConfidence = result.Regions.Where(r => r.Score >= 0.8);

//IronOCRequivalent: 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);

//IronOCRequivalent: 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)

' IronOCRequivalent: filter at 80
Dim highConfidence = result.Pages _
    .SelectMany(Function(p) p.Words) _
    .Where(Function(w) w.Confidence >= 80)
$vbLabelText   $csharpLabel

文档级别的置信度可以作为result.Confidence使用,以快速进行质量设置。 置信度评分指南涵盖了逐词和文档级别的阈值。

问题二:阅读顺序假设

PaddleOCR:result.Regions按检测顺序排列,而不是阅读顺序。 任何使用result.Text的代码需要自顶向下、从左到右输出的代码,都依赖于PaddleOCR示例中使用的手动排序模式。

解决方案:IronOCR的result.Text已经是读取顺序。 移除手动排序。 对于排序用于构建逐行输出的案例,可以直接使用result.Pages[0].Lines

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

问题 3:OpenCV Mat 转换代码

PaddleOCR:一些代码库包含辅助方法,从流或字节数组中加载图像,首先写入临时文件,然后调用Cv2.ImRead()。 这些模式存在是因为Cv2.ImRead()只接受文件路径。

解决方案:IronOCR的OcrInput直接接受流和字节数组。 删除临时文件中间体:

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

流输入指南涵盖从 HTTP 响应、数据库 blob 和内存流加载流。

问题 4:异步初始化模式移除

PaddleOCR:引擎初始化是异步的,因为模型下载涉及网络 I/O。 这强制整个调用链使用异步方式,这在构造函数或非异步事件处理程序等同步上下文中可能会出现问题。

解决方案: IronOCR初始化是同步的。 new IronTesseract()不执行I/O。 删除任何方法中的async修饰符,这些方法的唯一异步操作是模型下载:

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

IronOCR还通过ocr.ReadAsync(input)提供本地异步支持,当在异步上下文中真正需要非阻塞执行时。

问题 5:Docker 构建步骤清理

PaddleOCR:Dockerfile包含RUN步骤。对于GPU部署,基础镜像通常是NVIDIA CUDA镜像。

解决方法:删除所有与 PaddleOCR 相关的 Dockerfile 指令。IronOCRDocker镜像不需要特殊的镜像基础,也不需要模型复制步骤:

# 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

#IronOCRDockerfile (clean)
FROM mcr.microsoft.com/dotnet/aspnet:8.0
COPY . /app
WORKDIR /app
ENTRYPOINT ["dotnet", "YourApp.dll"]

生成的图像大小从大约 1.5GB 减少到大约 400MB。 Docker 部署指南涵盖了 Linux 库要求和多架构构建。

问题 6:CI/CD 模型缓存失效

PaddleOCR:缓存NuGet还原步骤的 CI/CD 管道必须单独管理模型文件缓存。 一个常见的模式是缓存models/文件夹以供多次运行。 当封装版本更新时,缓存键会发生变化,模型必须从百度服务器重新下载,这会给管道增加 30-60 秒的时间。

解决方法: IronOCR没有模型缓存目录。 唯一需要的缓存是标准的NuGet包缓存。 没有单独的缓存步骤,包装器更新时不会使缓存失效,CI 期间不会从第三方服务器下载:

# Remove from CI/CD pipeline:
# - name: Cache PaddleOCR models
#   uses: actions/cache@v3
#   with:
#     path: models/
#     key: paddleocr-models-${{env.PADDLEOCR_VERSION}}

#IronOCRonly 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}}

#IronOCRonly needs standard NuGet caching:
- name: Cache NuGet packages
  uses: actions/cache@v3
  with:
    path: ~/.nuget/packages
    key: nuget-${{hashFiles('**/*.csproj')}}
YAML

PaddleOCR (.NET) 迁移清单

迁移前

在进行任何更改之前,请审核代码库以识别所有 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" .
SHELL

清点模型目录并注意总大小。确定正在使用哪些语言模型(中文、英文、日文等)以确定需要添加哪些IronOcr.Languages.*包。 注意是否存在 GPU 配置——这些文件需要清理的表面积最大。

代码迁移

  1. OpenCvSharp4.runtime.</em>
  2. .csproj文件中
  3. 为之前作为PaddleOCR模型下载的每种非英语语言添加IronOcr.Languages.*
  4. using OpenCvSharp指令
  5. 在应用程序启动时添加IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
  6. 用空值替换FullOcrModel models = await OnlineFullModels.*.DownloadAsync()——完全移除该行
  7. new PaddleOcrAll(models)
  8. new PaddleOcrAll(models, PaddleDevice.Gpu(deviceId: 0))
  9. var input = new OcrInput();替换Mat mat = Cv2.ImRead(path) input.LoadImage(path);
  10. ocr.Run(mat)
  11. result.Regions访问
  12. region.Score >= threshold
  13. region.Rect.Center.X / .Center.Y
  14. 移除手动排序逻辑IronOCR输出已按阅读顺序排列。
  15. 删除models/目录及所有模型文件从仓库和部署脚本中

后迁移

  • 验证PaddleInference
  • 在用于验证 PaddleOCR 输出并比较文本准确性的同一代表性文档集上运行 OCR 程序
  • 确认所有滤波点的置信度值均被读取为 0-100 的整数(而非 0-1 的浮点数)。
  • 无需手动排序,即可确认阅读顺序是否正确——尤其要检查多列布局和发票布局。
  • 测试Docker镜像构建在没有CUDA基础镜像或apt-get install libopencv-dev的情况下完成
  • 确认 Docker 镜像大小小于 500MB
  • 完整运行 CI/CD 流水线,并验证构建过程中是否发生外部下载。
  • 测试物理隔离部署:验证应用程序能否在没有出站网络连接的情况下启动并处理文档
  • 对于任何多帧 TIFF 输入文件,请验证所有帧是否都已处理,并且帧数与源文件匹配。
  • 对于任何PDF输入,验证input.LoadPdf()生成的页数和文本内容与先前基于PdfiumViewer的转换相同

迁移到IronOCR的主要优势

部署工件缩小80%。 PaddleOCR部署足迹——paddle_inference.dll、OpenCV DLLs及三个模型目录——给每个部署目标增加了300-500MB。 迁移后, IronOCR部署大小约为 80MB。 Docker 镜像大小从约 1.5GB 降至约 400MB。 容器启动速度更快,存储成本更低,以前需要传输 500MB 工件的部署管道现在只需传输 80MB。

冷启动时间从几秒缩短到几毫秒。PaddleOCR在首次推理时会从磁盘加载三个神经网络模型文件,并在首次调用返回前增加 3-5 秒的暂停时间。 在无服务器函数、自动扩展场景或任何按需启动新实例的场景中,冷启动的成本会被反复消耗。 IronOCR引擎已打包,初始化时间不到一秒。 基本的 OCR 示例演示了初始化模式。

语言覆盖范围从 14 种扩展到 125 种,无需基础设施建设。PaddleOCR目前支持 14 种语言。 IronOCR支持的 111 种语言中,超出 PaddleOCR 的上限,只需为每种语言添加一个NuGet包即可——无需下载模型,无需目录管理,无需版本同步。 文档量扩展到新市场的团队无需重写文档或新建基础设施项目即可添加波兰语、越南语、希腊语或希伯来语 OCR 支持。 完整的语言目录显示了所有 125 多个可用的语言包。

可搜索的 PDF 输出只需一行代码。PaddleOCR返回文本区域。 将这些区域转换为可搜索的 PDF 图层需要单独的 PDF 库、像素到点坐标转换以及不可见的文本注入代码,这将成为永久维护工作。 迁移后,result.SaveAsSearchablePdf("output.pdf")取代了整个子系统。 扫描文档归档工作流程、传真转 PDF 流程和文档管理集成都将直接受益。 可搜索的 PDF 操作指南PDF 数据提取博客文章涵盖了所有输出选项。

任何阶段都没有外部网络连接。 PaddleOCR连接到百度的bj.bcebos.com存储进行模型下载。 在出站连接受限的环境中(例如政府网络、物理隔离系统、金融服务基础设施),这种连接需要防火墙例外或预下载工作流程,这会增加 CI/CD 的复杂性。 IronOCR在运行时不进行外部连接。模型作为NuGet的一部分通过dotnet restore恢复,并存在于部署输出中。 AWS 部署指南Azure 部署指南涵盖了针对具有网络限制的环境的云特定配置。

整个OCR堆栈只有一个商业支持联系。 PaddleOCR的问题涉及Sdcb.PaddleOCR包装器(社区GitHub)、PaddlePaddle框架(百度)、OpenCvSharp(社区)和CUDA/cuDNN(NVIDIA)。 每个层级都有不同的支持渠道,且无法保证响应时间。IronOCR 是Iron Software的一款产品,提供商业电子邮件支持和优先响应服务。 IronOCR文档中心将所有 API 文档、操作指南和故障排除资源集中在一个地方。

请注意PDFium, PaddleOCR, PDFSharp, Tesseract和iText是各自所有者的注册商标。 本网站与百度、Chromium项目、Google、PaddlePaddle、empira Software GmbH或iText Group无关,也未获其认可或资助。所有产品名称、标识和品牌均为其各自所有者的财产。 比较仅供参考,反映撰写时公开可用的信息。

常见问题解答

我为什么要从 PaddleOCR 迁移到 IronOCR?

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

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

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

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

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

IronOCR 对标准商业文档的 OCR 准确率是否能与 PaddleOCR 相媲美?

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

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

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

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

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

迁移后如何配置 IronOCR 许可?

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

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

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

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

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

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

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

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

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

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

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

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

钢铁支援团队

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