跳至页脚内容
与其他组件比较

MODI OCR C# 与 IronOCR:在 C# 中选择合适的 OCR 光学字符识别库

TesseractOCR(Sicos1977 的分支)是一个真正活跃的、现代的.NET封装器——而这正是它的局限性值得仔细研究的原因。 与已归档的 charlesw/tesseract 项目不同,此分支面向.NET 6+,并封装了 Tesseract 5.4.1。但是,较新的封装程序并没有修复其底层 Tesseract 引擎的问题。 为了实现框架兼容性,从 charlesw 升级到TesseractOCR的团队发现所有难题依然存在:tessdata 文件夹管理、零内置预处理、没有原生 PDF 支持,以及非线程安全的引擎,在并发场景中强制每个线程运行一个实例。

了解 TesseractOCR

TesseractOCR 是一个 Apache 2.0 许可的.NET封装程序,由 Kees van Spelde (Sicos1977) 维护,是原始 charlesw/tesseract 项目的社区分支。 此次分叉的主要动机是实际的:charlesw 在 2023 年后活动放缓,导致.NET 6/7/8 开发人员没有当前框架的 Tesseract 绑定。TesseractOCR通过面向.NET 6.0、7.0 和 8.0 以及捆绑适用于 Windows x64、Linux x64 和 macOS 的 Tesseract 5.x 本机库来填补这一空白。

该架构是一个 P/Invoke 封装器:托管的.NET代码通过互操作调用 Tesseract 的原生 C API。NuGet 包NuGet了适用于常用平台的原生二进制文件,从而消除了旧版封装器中存在的一些原生库部署方面的繁琐问题。 然而,其基本设计仍然只是与 Tesseract 引擎的简单绑定——没有预处理逻辑,没有 PDF 管道,也没有线程抽象。

主要架构特征:

-由一名志愿者开发者进行积极维护——会发布更新,但没有服务级别协议 (SLA),没有商业支持,而且维护成本为 1。 -封装了 Tesseract 5.5.0 — 最新的 LSTM 引擎改进可供使用,这是相对于 charlesw 的 5.2.0 版本的优势。 -目标框架为.NET 6.0+ — 面向现代框架是该分支存在的主要原因

  • 需要手动管理tessdata — 语言.traineddata文件必须单独下载并与应用程序一起部署
  • 没有内置的预处理 — 包装器直接调用engine.Process(image); 图像质量的提升完全是开发者的责任。
  • 非线程安全引擎Engine实例不能跨线程共享; 每个并行工作进程都需要自己的实例,这会增加内存消耗。 -不支持原生 PDF ——PDF 输入需要单独的库(Docnet.Core、PdfiumViewer)将页面渲染成图像,然后 Tesseract 才能处理它们。
  • NuGet下载量约为 20 万次,而 charlesw 的下载量约为 800 万次——较小的社区意味着 Stack Overflow 上的答案较少,教程较少,并且需要更多地对现有的 Tesseract 资源进行改编。

引擎初始化和tessdata依赖关系

每个TesseractOCR操作都从.traineddata文件的tessdata文件夹:

// tessdata/eng.traineddata must exist before this line runs
// Downloaded separately: curl -L -o tessdata/eng.traineddata
//   https://github.com/tesseract-ocr/tessdata_best/raw/main/eng.traineddata
using var engine = new Engine(@"./tessdata", Language.English, EngineMode.Default);
using var image = TesseractOCR.Pix.Image.LoadFromFile("document.png");
using var page = engine.Process(image);

string text = page.Text;
float confidence = page.MeanConfidence; // Returns 0.0-1.0 float
// tessdata/eng.traineddata must exist before this line runs
// Downloaded separately: curl -L -o tessdata/eng.traineddata
//   https://github.com/tesseract-ocr/tessdata_best/raw/main/eng.traineddata
using var engine = new Engine(@"./tessdata", Language.English, EngineMode.Default);
using var image = TesseractOCR.Pix.Image.LoadFromFile("document.png");
using var page = engine.Process(image);

string text = page.Text;
float confidence = page.MeanConfidence; // Returns 0.0-1.0 float
Imports TesseractOCR

' tessdata/eng.traineddata must exist before this line runs
' Downloaded separately: curl -L -o tessdata/eng.traineddata
'   https://github.com/tesseract-ocr/tessdata_best/raw/main/eng.traineddata
Using engine As New Engine("./tessdata", Language.English, EngineMode.Default)
    Using image As Pix.Image = TesseractOCR.Pix.Image.LoadFromFile("document.png")
        Using page As Page = engine.Process(image)
            Dim text As String = page.Text
            Dim confidence As Single = page.MeanConfidence ' Returns 0.0-1.0 float
        End Using
    End Using
End Using
$vbLabelText   $csharpLabel

Language枚举值。 如果目录不存在,如果.traineddata文件丢失,或者文件版本与Tesseract引擎版本不匹配,初始化将引发异常。 这是任何 Tesseract 封装器最常见的三种生产失败情况,TesseractOCR 也继承了所有这些失败情况。 该项目的 README 文件包含防御性验证代码,该代码会在尝试构建引擎之前检查 tessdata 文件夹和各个语言文件——这说明开发者遇到此问题的频率有多高。

了解IronOCR

IronOCR是一个商业的.NET OCR 库,它封装了一个优化的 Tesseract 5 引擎,具有自动预处理、原生 PDF 输入/输出和线程安全架构。 整个库以单个NuGet包的形式发布,没有外部依赖项,没有 tessdata 文件夹管理,也没有本地库配置。

主要特点:

  • 单个NuGet安装dotnet add package IronOcr生成一个可工作的OCR管道;不需要tessdata,不需要本机二进制设置,不需要核心工作流程的附加包 -自动预处理— 引擎自动应用去斜、去噪、对比度增强、二值化和分辨率缩放; explicit filter methods are available when fine-grained control is needed
  • 原生PDF输入和输出 — PDF通过OcrInput.LoadPdf()直接加载; 扫描的PDF通过result.SaveAsSearchablePdf()生成可搜索的PDF输出
  • 线程安全IronTesseract — 单个实例处理并发请求,无需每线程复制
  • 125 多种语言以NuGet包的形式提供——无需下载外部文件; 语言包通过dotnet add package IronOcr.Languages.French安装,并在不需要路径配置的情况下引用
  • 永久许可证 — $999 Lite / $1,499 Plus / $2,999 Professional; 无需按文档付费,也无需订阅 -跨平台且行为一致——Windows、Linux、macOS、Docker、Azure 和 AWS 均可使用同一软件包,无需针对特定平台进行配置。

功能对比

特征 TesseractOCR IronOCR
面向.NET 的目标平台 .NET 6.0、7.0、8.0 .NET 6.0、7.0、8.0、 .NET Framework 4.6.2+
许可证 Apache 2.0(免费) 商业($999+永久)
tessdata management 需要(手动下载) 非必需(已捆绑)
内置预处理 None 自动+显式过滤器
原生 PDF 输入
可搜索的 PDF 输出
螺纹安全 否(每个线程的引擎) 是的(单个共享实例)

详细功能对比

特征 TesseractOCR IronOCR
设置和部署
NuGet安装 TesseractOCR IronOcr
需要 tessdata 文件夹。
语言文件下载 手册(GitHub) NuGet 软件包
原生二进制捆绑 部分(通用平台) 满的
单包部署 否(tessdata 单独)
气隙环境 需要预先准备好的 tessdata 语言NuGet包可离线使用
OCR功能
Tesseract 引擎版本 5.5.0 5.x(已优化)
自动校正斜角
自动降噪
自动对比度
分辨率增强 是(EnhanceResolution(300)
二值化
PDF 支持
PDF 输入 否(需要外部库) 是的(母语)
受密码保护的PDF 否(需要解密和重新处理) 是的(单参数)
可搜索的 PDF 输出
特定页面范围 手动(逐页渲染循环) 是(LoadPdfPages
语言支持
支持的语言 任何 tessdata 文件 通过NuGet获取 125+
多语言语法 Language.English | Language.French OcrLanguage.English + OcrLanguage.French
自定义语言数据 是的(将文件复制到tessdata) 是的(自定义语言包)
线程和批量
线程安全引擎
并行处理模式 每个线程的引擎(内存密集型) 单实例并行输入
每个线程的内存 每个引擎实例约 40-100MB 共享实例
产出和结果
置信度得分 page.MeanConfidence(0.0-1.0) result.Confidence(0-100%)
词级定位 有限的 是的(每个单词的 X、Y、宽度、高度)
结构化结果层级 页数、段落、行数、字数
OCR过程中的条形码读取
hOCR导出
支持与维护
维护模式 单人志愿者开发者 商业团队
商业支持 是的(电子邮件、SLA选项)
GitHub问题响应 志愿者日程安排 商业日程

Tessdata 管理:始终存在的部署难题

Sicos1977 分支更新了 Tesseract 引擎,并对目标框架进行了现代化改造。 它并没有改变语言数据的工作方式。 运行TesseractOCR的每个环境在第一次.traineddata文件的tessdata文件夹。

TesseractOCR方法

此存储库中的basic-ocr.cs文件包含一个ValidateTessData()方法,建议项目在任何OCR操作之前运行。 这种防御性模式存在的原因是—一个管道中途抛出的TesseractException—这种故障模式足够常见,以至于库本身的示例都进行防护:

// From BasicOcrService in tesseractocr-basic-ocr.cs
private void ValidateTessData()
{
    if (!Directory.Exists(_tessDataPath))
    {
        throw new DirectoryNotFoundException(
            $"tessdata folder not found at: {_tessDataPath}\n" +
            "Download traineddata files from: https://github.com/tesseract-ocr/tessdata_best");
    }

    string engTrainedData = Path.Combine(_tessDataPath, "eng.traineddata");
    if (!File.Exists(engTrainedData))
    {
        throw new FileNotFoundException(
            $"eng.traineddata not found in {_tessDataPath}\n" +
            "Download from: https://github.com/tesseract-ocr/tessdata_best/raw/main/eng.traineddata");
    }
}
// From BasicOcrService in tesseractocr-basic-ocr.cs
private void ValidateTessData()
{
    if (!Directory.Exists(_tessDataPath))
    {
        throw new DirectoryNotFoundException(
            $"tessdata folder not found at: {_tessDataPath}\n" +
            "Download traineddata files from: https://github.com/tesseract-ocr/tessdata_best");
    }

    string engTrainedData = Path.Combine(_tessDataPath, "eng.traineddata");
    if (!File.Exists(engTrainedData))
    {
        throw new FileNotFoundException(
            $"eng.traineddata not found in {_tessDataPath}\n" +
            "Download from: https://github.com/tesseract-ocr/tessdata_best/raw/main/eng.traineddata");
    }
}
Private Sub ValidateTessData()
    If Not Directory.Exists(_tessDataPath) Then
        Throw New DirectoryNotFoundException(
            $"tessdata folder not found at: {_tessDataPath}" & vbCrLf &
            "Download traineddata files from: https://github.com/tesseract-ocr/tessdata_best")
    End If

    Dim engTrainedData As String = Path.Combine(_tessDataPath, "eng.traineddata")
    If Not File.Exists(engTrainedData) Then
        Throw New FileNotFoundException(
            $"eng.traineddata not found in {_tessDataPath}" & vbCrLf &
            "Download from: https://github.com/tesseract-ocr/tessdata_best/raw/main/eng.traineddata")
    End If
End Sub
$vbLabelText   $csharpLabel

多语言OCR技术加剧了这个问题。 每种语言需要自己的.traineddata文件—每种语言占用15到50 MB—而这些文件必须来自正确的存储库版本。 tessdata_best 存储库提供更高的准确度,但处理速度较慢; tessdata_fast 以牺牲准确性为代价来换取速度。 混合使用不同版本,或者将为 Tesseract 4.x 构建的 tessdata 文件与 Tesseract 5.x 引擎一起使用,会导致精度悄无声息地下降,而不会发出任何错误信号。

对于 Docker 部署,tessdata 文件必须嵌入到镜像中或挂载到已知路径。 对于 CI/CD 流水线,下载步骤必须编写脚本并进行缓存。 对于物理隔离环境,文件必须预先准备好。 每增加一种部署配置,就多了一个可能出错的地方。

// Multi-language requires each .traineddata file pre-downloaded
// eng.traineddata + fra.traineddata + deu.traineddata all required
using var engine = new Engine(@"./tessdata",
    Language.English | Language.French | Language.German,
    EngineMode.Default);

// If any traineddata file is missing, this throws at construction time
using var image = TesseractOCR.Pix.Image.LoadFromFile(imagePath);
using var page = engine.Process(image);

return page.Text;
// Multi-language requires each .traineddata file pre-downloaded
// eng.traineddata + fra.traineddata + deu.traineddata all required
using var engine = new Engine(@"./tessdata",
    Language.English | Language.French | Language.German,
    EngineMode.Default);

// If any traineddata file is missing, this throws at construction time
using var image = TesseractOCR.Pix.Image.LoadFromFile(imagePath);
using var page = engine.Process(image);

return page.Text;
Imports TesseractOCR

' Multi-language requires each .traineddata file pre-downloaded
' eng.traineddata + fra.traineddata + deu.traineddata all required
Using engine As New Engine("./tessdata", Language.English Or Language.French Or Language.German, EngineMode.Default)

    ' If any traineddata file is missing, this throws at construction time
    Using image As TesseractOCR.Pix.Image = TesseractOCR.Pix.Image.LoadFromFile(imagePath)
        Using page As Page = engine.Process(image)
            Return page.Text
        End Using
    End Using
End Using
$vbLabelText   $csharpLabel

IronOCR方法

IronOCR以NuGet包的形式提供语言支持。 英语已包含在核心软件包中。 只需一条命令即可安装其他语言,无需路径配置:

// dotnet add package IronOcr.Languages.French
// dotnet add package IronOcr.Languages.German
// 否 tessdata folder, no download scripts, no path validation

var ocr = new IronTesseract();
ocr.Language = OcrLanguage.French;
ocr.AddSecondaryLanguage(OcrLanguage.German);

using var input = new OcrInput();
input.LoadImage(imagePath);
var result = ocr.Read(input);

return result.Text;
// dotnet add package IronOcr.Languages.French
// dotnet add package IronOcr.Languages.German
// 否 tessdata folder, no download scripts, no path validation

var ocr = new IronTesseract();
ocr.Language = OcrLanguage.French;
ocr.AddSecondaryLanguage(OcrLanguage.German);

using var input = new OcrInput();
input.LoadImage(imagePath);
var result = ocr.Read(input);

return result.Text;
Imports IronOcr

Dim ocr As New IronTesseract()
ocr.Language = OcrLanguage.French
ocr.AddSecondaryLanguage(OcrLanguage.German)

Using input As New OcrInput()
    input.LoadImage(imagePath)
    Dim result = ocr.Read(input)
    Return result.Text
End Using
$vbLabelText   $csharpLabel

语言包是NuGet依赖项,具有版本控制,可自动还原,并与应用程序二进制文件一起部署。 没有外部GitHub仓库,没有 curl 脚本,也没有构建系统配置来将文件复制到输出目录。 对于物理隔离部署, NuGet包可以像其他任何包一样,从私有源离线恢复。 多语言指南涵盖了所有 125 种以上受支持语言的设置。

预处理:现代 Fork 仍然无法做到的事情

TesseractOCR 的 Sicos1977 分支比 charlesw 的分支更新,面向当前的.NET,并捆绑了更新的 Tesseract 二进制文件。 这一切都不会改变当开发者将一个倾斜的、低对比度或电话摄像头质量的图像传递给engine.Process(image)时所发生的情况。 引擎获取原始像素。 Tesseract 产生劣化的输出。 然后,开发人员将外部图像库添加到依赖关系图中,并编写预处理代码。

TesseractOCR方法

此存储库中的 migration-comparison.cs 文件显示了TesseractOCR所需的预处理模式。 必须添加外部图像库(在此情况下为SixLabors.ImageSharp),必须调整手动滤波器参数,并且必须将预处理过的图像写入临时文件,然后TesseractOCR才能读取它—因为TesseractOCR.Pix.ImageAPI期望一个文件路径:

// Requires: dotnet add package SixLabors.ImageSharp
// Manual preprocessing — each parameter requires tuning per document type

using var image = Image.Load(imagePath);

image.Mutate(x => x.Grayscale());
image.Mutate(x => x.Contrast(1.5f));         // 1.5 is a guess; tune per use case
image.Mutate(x => x.GaussianBlur(0.5f));     // Denoise with blur
image.Mutate(x => x.BinaryThreshold(0.5f)); // Threshold requires manual tuning

// Deskew is NOT in ImageSharp — requires separate Hough transform implementation
// (~50-100 additional lines)

string tempPath = Path.GetTempFileName() + ".png";
try
{
    image.Save(tempPath);

    using var engine = new Engine(@"./tessdata", Language.English);
    using var pixImage = TesseractOCR.Pix.Image.LoadFromFile(tempPath);
    using var page = engine.Process(pixImage);

    return page.Text;
}
finally
{
    File.Delete(tempPath); // Clean up temp file
}
// Requires: dotnet add package SixLabors.ImageSharp
// Manual preprocessing — each parameter requires tuning per document type

using var image = Image.Load(imagePath);

image.Mutate(x => x.Grayscale());
image.Mutate(x => x.Contrast(1.5f));         // 1.5 is a guess; tune per use case
image.Mutate(x => x.GaussianBlur(0.5f));     // Denoise with blur
image.Mutate(x => x.BinaryThreshold(0.5f)); // Threshold requires manual tuning

// Deskew is NOT in ImageSharp — requires separate Hough transform implementation
// (~50-100 additional lines)

string tempPath = Path.GetTempFileName() + ".png";
try
{
    image.Save(tempPath);

    using var engine = new Engine(@"./tessdata", Language.English);
    using var pixImage = TesseractOCR.Pix.Image.LoadFromFile(tempPath);
    using var page = engine.Process(pixImage);

    return page.Text;
}
finally
{
    File.Delete(tempPath); // Clean up temp file
}
Imports SixLabors.ImageSharp
Imports SixLabors.ImageSharp.Processing
Imports TesseractOCR
Imports System.IO

' Requires: dotnet add package SixLabors.ImageSharp
' Manual preprocessing — each parameter requires tuning per document type

Dim image As Image = Image.Load(imagePath)

image.Mutate(Sub(x) x.Grayscale())
image.Mutate(Sub(x) x.Contrast(1.5F))         ' 1.5 is a guess; tune per use case
image.Mutate(Sub(x) x.GaussianBlur(0.5F))     ' Denoise with blur
image.Mutate(Sub(x) x.BinaryThreshold(0.5F))  ' Threshold requires manual tuning

' Deskew is NOT in ImageSharp — requires separate Hough transform implementation
' (~50-100 additional lines)

Dim tempPath As String = Path.GetTempFileName() & ".png"
Try
    image.Save(tempPath)

    Using engine As New Engine("./tessdata", Language.English)
        Using pixImage As TesseractOCR.Pix.Image = TesseractOCR.Pix.Image.LoadFromFile(tempPath)
            Using page As Page = engine.Process(pixImage)
                Return page.Text
            End Using
        End Using
    End Using
Finally
    File.Delete(tempPath) ' Clean up temp file
End Try
$vbLabelText   $csharpLabel

TesseractOCR 的 README 文件列出了输入不完美时准确率的下降情况:5 度倾斜会使准确率从 97% 下降到 65-75%; 手机摄像头拍摄率下降到 30-50%。 这些并非生产环境中的极端情况——它们是扫描文档、白板照片和传真的默认状态。 要恢复这种精度,需要进行偏斜校正、降噪和对比度归一化。 Deskew 本身在常见的.NET图像处理库中不可用,需要实现霍夫变换角度检测算法。

IronOCR方法

IronOCR的预处理管道内置于OcrInput。 调用EnhanceResolution()应用对应的算法,无需外部库,无需临时文件,也无需为常见的文档类型调整参数:

// 否 external imaging library needed
// 否 temp files, no manual parameter tuning

using var input = new OcrInput();
input.LoadImage(imagePath);
input.Deskew();           // Automatic angle detection and correction
input.DeNoise();          // Intelligent noise removal
input.Contrast();         // 自动对比度 enhancement
input.EnhanceResolution(300); // Upscale if below 300 DPI

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

return result.Text;
// 否 external imaging library needed
// 否 temp files, no manual parameter tuning

using var input = new OcrInput();
input.LoadImage(imagePath);
input.Deskew();           // Automatic angle detection and correction
input.DeNoise();          // Intelligent noise removal
input.Contrast();         // 自动对比度 enhancement
input.EnhanceResolution(300); // Upscale if below 300 DPI

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

return result.Text;
$vbLabelText   $csharpLabel

对于事先未知质量问题的文档,引擎会自动应用基线校正,而无需任何显式的过滤器调用。 图像质量校正指南涵盖了每个滤镜的参数选项,以便在需要调整自动行为时使用。 图像方向校正指南专门涵盖了倾斜和旋转检测——这些操作需要使用TesseractOCR进行自定义实现。 低质量扫描示例展示了在处理复杂文档时准确度的差异。

PDF 处理:一项外部图书馆税

TesseractOCR 处理图像。 它无法处理PDF文件。 使用TesseractOCR的每个 PDF 工作流程都需要第二个库来将 PDF 页面渲染成图像文件,而每个 PDF 渲染图像工作流程都需要临时文件管理、字节格式转换和清理逻辑。

TesseractOCR方法

本仓库中的 tesseractocr-pdf-processing.cs 文件实现了一个完整的 PDF OCR 服务。它需要 Docnet.Core 作为额外的依赖项,并且大约需要 100 行代码才能完成IronOCR只需三行代码就能完成的工作。 核心提取循环涉及使用Docnet加载PDF,将每个页面渲染为BGRA字节数组,将每个页面写入临时文件(因为finally块中删除临时文件:

// Requires: dotnet add package TesseractOCR
//           dotnet add package Docnet.Core
// Note: Docnet is MIT-licensed; iTextSharp would be AGPL

using var library = DocLib.Instance;
using var docReader = library.GetDocReader(pdfPath, new PageDimensions(dpi, dpi));

int pageCount = docReader.GetPageCount();
var allText = new StringBuilder();
var tempFiles = new List<string>();

try
{
    using var engine = new Engine(_tessDataPath, Language.English, EngineMode.Default);

    for (int pageIndex = 0; pageIndex < pageCount; pageIndex++)
    {
        using var pageReader = docReader.GetPageReader(pageIndex);
        var width = pageReader.GetPageWidth();
        var height = pageReader.GetPageHeight();
        var imageBytes = pageReader.GetImage(); // BGRA bytes

        // TesseractOCR.Pix.Image requires a file path — write to temp
        string tempPath = Path.Combine(_tempDirectory, $"page_{pageIndex}_{Guid.NewGuid()}.png");
        tempFiles.Add(tempPath);
        SaveBgraAsPng(imageBytes, width, height, tempPath); // ~30 lines

        using var image = TesseractOCR.Pix.Image.LoadFromFile(tempPath);
        using var page = engine.Process(image);

        allText.AppendLine($"--- Page {pageIndex + 1} ---");
        allText.AppendLine(page.Text);
    }
}
finally
{
    foreach (var tempFile in tempFiles)
    {
        try { File.Delete(tempFile); } catch { }
    }
}
// Requires: dotnet add package TesseractOCR
//           dotnet add package Docnet.Core
// Note: Docnet is MIT-licensed; iTextSharp would be AGPL

using var library = DocLib.Instance;
using var docReader = library.GetDocReader(pdfPath, new PageDimensions(dpi, dpi));

int pageCount = docReader.GetPageCount();
var allText = new StringBuilder();
var tempFiles = new List<string>();

try
{
    using var engine = new Engine(_tessDataPath, Language.English, EngineMode.Default);

    for (int pageIndex = 0; pageIndex < pageCount; pageIndex++)
    {
        using var pageReader = docReader.GetPageReader(pageIndex);
        var width = pageReader.GetPageWidth();
        var height = pageReader.GetPageHeight();
        var imageBytes = pageReader.GetImage(); // BGRA bytes

        // TesseractOCR.Pix.Image requires a file path — write to temp
        string tempPath = Path.Combine(_tempDirectory, $"page_{pageIndex}_{Guid.NewGuid()}.png");
        tempFiles.Add(tempPath);
        SaveBgraAsPng(imageBytes, width, height, tempPath); // ~30 lines

        using var image = TesseractOCR.Pix.Image.LoadFromFile(tempPath);
        using var page = engine.Process(image);

        allText.AppendLine($"--- Page {pageIndex + 1} ---");
        allText.AppendLine(page.Text);
    }
}
finally
{
    foreach (var tempFile in tempFiles)
    {
        try { File.Delete(tempFile); } catch { }
    }
}
Imports Docnet.Core
Imports TesseractOCR
Imports System.IO
Imports System.Text

' Requires: dotnet add package TesseractOCR
'           dotnet add package Docnet.Core
' Note: Docnet is MIT-licensed; iTextSharp would be AGPL

Dim library = DocLib.Instance
Dim docReader = library.GetDocReader(pdfPath, New PageDimensions(dpi, dpi))

Dim pageCount As Integer = docReader.GetPageCount()
Dim allText As New StringBuilder()
Dim tempFiles As New List(Of String)()

Try
    Using engine As New Engine(_tessDataPath, Language.English, EngineMode.Default)
        For pageIndex As Integer = 0 To pageCount - 1
            Using pageReader = docReader.GetPageReader(pageIndex)
                Dim width = pageReader.GetPageWidth()
                Dim height = pageReader.GetPageHeight()
                Dim imageBytes = pageReader.GetImage() ' BGRA bytes

                ' TesseractOCR.Pix.Image requires a file path — write to temp
                Dim tempPath As String = Path.Combine(_tempDirectory, $"page_{pageIndex}_{Guid.NewGuid()}.png")
                tempFiles.Add(tempPath)
                SaveBgraAsPng(imageBytes, width, height, tempPath) ' ~30 lines

                Using image = TesseractOCR.Pix.Image.LoadFromFile(tempPath)
                    Using page = engine.Process(image)
                        allText.AppendLine($"--- Page {pageIndex + 1} ---")
                        allText.AppendLine(page.Text)
                    End Using
                End Using
            End Using
        Next
    End Using
Finally
    For Each tempFile In tempFiles
        Try
            File.Delete(tempFile)
        Catch
        End Try
    Next
End Try
$vbLabelText   $csharpLabel

密码保护的 PDF 需要第三个库(iText 与 AGPL 许可证,或 PDFSharp)来先解密文档,增加了另一个依赖和需要评估的进一步许可证问题。 tesseractocr-pdf-processing.cs 文件对此的注释很直接:"TesseractOCR + Docnet 无法直接处理受密码保护的 PDF 文件。 你需要:1. 使用支持解密的 PDF 库…… 2. 首先解密/移除密码…… 3. 保存解密后的PDF文件…… 4. 然后使用上面的代码进行处理。

IronOCR方法

IronOCR对PDF的支持是原生支持的。无需外部库,无需临时文件,也无需字节格式转换。 PDF 输入指南涵盖了所有 PDF 场景——完整文档、页面范围和受密码保护的文件:

// 满的 PDF — native, no external library
var ocr = new IronTesseract();
using var input = new OcrInput();
input.LoadPdf(pdfPath);
var result = ocr.Read(input);
string text = result.Text;

// 受密码保护的PDF — built-in, one parameter
using var encryptedInput = new OcrInput();
encryptedInput.LoadPdf("encrypted.pdf", Password: "secret");
var encryptedResult = ocr.Read(encryptedInput);

// Specific page range — no manual loop required
using var pageInput = new OcrInput();
pageInput.LoadPdfPages(pdfPath, startPage: 1, endPage: 5);
var pageResult = ocr.Read(pageInput);
// 满的 PDF — native, no external library
var ocr = new IronTesseract();
using var input = new OcrInput();
input.LoadPdf(pdfPath);
var result = ocr.Read(input);
string text = result.Text;

// 受密码保护的PDF — built-in, one parameter
using var encryptedInput = new OcrInput();
encryptedInput.LoadPdf("encrypted.pdf", Password: "secret");
var encryptedResult = ocr.Read(encryptedInput);

// Specific page range — no manual loop required
using var pageInput = new OcrInput();
pageInput.LoadPdfPages(pdfPath, startPage: 1, endPage: 5);
var pageResult = ocr.Read(pageInput);
Imports IronOcr

Dim ocr As New IronTesseract()

' 满的 PDF — native, no external library
Using input As New OcrInput()
    input.LoadPdf(pdfPath)
    Dim result = ocr.Read(input)
    Dim text As String = result.Text
End Using

' 受密码保护的PDF — built-in, one parameter
Using encryptedInput As New OcrInput()
    encryptedInput.LoadPdf("encrypted.pdf", Password:="secret")
    Dim encryptedResult = ocr.Read(encryptedInput)
End Using

' Specific page range — no manual loop required
Using pageInput As New OcrInput()
    pageInput.LoadPdfPages(pdfPath, startPage:=1, endPage:=5)
    Dim pageResult = ocr.Read(pageInput)
End Using
$vbLabelText   $csharpLabel

扫描的PDF——即TesseractOCR的Docnet+预处理+OCR组合最为痛苦的场景——也是IronOCR的预处理管道最重要的场景。扫描的PDF通过LoadPdf(),自动预处理,OCR,和可选的可搜索PDF输出,无需临时文件管理,进行线性链处理。 PDF OCR示例可搜索PDF指南涵盖了包括result.SaveAsSearchablePdf()在内的完整工作流程,TesseractOCR中无此对应物。

线程:非线程安全引擎的内存开销

TesseractOCR的Engine不是线程安全的。 basic-ocr.cs文件包含一个ThreadSafeOcrService类,带有明确警告:"内存开销:4个线程 x 50MB = 200MB+仅用于引擎"。并发TesseractOCR的成本是每个线程一个引擎实例,每个持有约40-100MB的原生Tesseract内存,每个需要约500ms的初始化时间。

TesseractOCR方法

使用TesseractOCR进行并行处理需要在每个工作lambda中创建一个新的Engine

// WARNING: Engine is NOT thread-safe — must create per thread
// Memory: _maxDegreeOfParallelism * engine footprint (~40-100MB each)

var results = new ConcurrentDictionary<string, string>();

Parallel.ForEach(
    imagePaths,
    new ParallelOptions { MaxDegreeOfParallelism = 4 },
    imagePath =>
    {
        // Per-thread engine — required, expensive (~500ms init, ~50MB memory)
        using var engine = new Engine(@"./tessdata", Language.English, EngineMode.Default);
        using var image = TesseractOCR.Pix.Image.LoadFromFile(imagePath);
        using var page = engine.Process(image);

        results[imagePath] = page.Text;
    });
// WARNING: Engine is NOT thread-safe — must create per thread
// Memory: _maxDegreeOfParallelism * engine footprint (~40-100MB each)

var results = new ConcurrentDictionary<string, string>();

Parallel.ForEach(
    imagePaths,
    new ParallelOptions { MaxDegreeOfParallelism = 4 },
    imagePath =>
    {
        // Per-thread engine — required, expensive (~500ms init, ~50MB memory)
        using var engine = new Engine(@"./tessdata", Language.English, EngineMode.Default);
        using var image = TesseractOCR.Pix.Image.LoadFromFile(imagePath);
        using var page = engine.Process(image);

        results[imagePath] = page.Text;
    });
Imports System.Collections.Concurrent
Imports System.Threading.Tasks

' WARNING: Engine is NOT thread-safe — must create per thread
' Memory: _maxDegreeOfParallelism * engine footprint (~40-100MB each)

Dim results As New ConcurrentDictionary(Of String, String)()

Parallel.ForEach(
    imagePaths,
    New ParallelOptions With {.MaxDegreeOfParallelism = 4},
    Sub(imagePath)
        ' Per-thread engine — required, expensive (~500ms init, ~50MB memory)
        Using engine As New Engine("./tessdata", Language.English, EngineMode.Default)
            Using image As TesseractOCR.Pix.Image = TesseractOCR.Pix.Image.LoadFromFile(imagePath)
                Using page As Page = engine.Process(image)
                    results(imagePath) = page.Text
                End Using
            End Using
        End Using
    End Sub)
$vbLabelText   $csharpLabel

单引擎重用模式(在循环外创建一个引擎并按顺序重用它)适用于串行处理,但如果任何其他线程访问该实例,则会出错。 因此,在高负载下进行批量处理要么需要接受每个线程引擎的内存成本,要么需要实现一个线程本地引擎池并进行仔细的生命周期管理。

IronOCR方法

IronTesseract是线程安全的。 一个实例可以处理来自任意数量并发线程的请求:

// Single instance — thread-safe, no per-thread duplication
var ocr = new IronTesseract();

var results = new ConcurrentDictionary<string, string>();

Parallel.ForEach(imagePaths, imagePath =>
{
    using var input = new OcrInput(imagePath);
    results[imagePath] = ocr.Read(input).Text;
});
// Single instance — thread-safe, no per-thread duplication
var ocr = new IronTesseract();

var results = new ConcurrentDictionary<string, string>();

Parallel.ForEach(imagePaths, imagePath =>
{
    using var input = new OcrInput(imagePath);
    results[imagePath] = ocr.Read(input).Text;
});
Imports System.Collections.Concurrent
Imports System.Threading.Tasks

' Single instance — thread-safe, no per-thread duplication
Dim ocr As New IronTesseract()

Dim results As New ConcurrentDictionary(Of String, String)()

Parallel.ForEach(imagePaths, Sub(imagePath)
    Using input As New OcrInput(imagePath)
        results(imagePath) = ocr.Read(input).Text
    End Using
End Sub)
$vbLabelText   $csharpLabel

多线程示例展示了这种模式。 4 个并行工作进程的内存占用量相当于一个引擎实例,而不是四个。 对于吞吐量至关重要的批量文档处理流程而言,这是一个实质性的差异。

API 映射参考

TesseractOCR IronOCR当量 备注
Engine(tessDataPath, Language.English, EngineMode.Default) new IronTesseract() 无需 tessdata 路径
TesseractOCR.Pix.Image.LoadFromFile(path) new OcrInput(path) 支持更多格式
engine.Process(image) ocr.Read(input) 核心OCR调用
page.Text result.Text 完整提取文本
page.MeanConfidence(0.0-1.0) result.Confidence(0-100) 规模不同
Language.English | Language.French OcrLanguage.English + OcrLanguage.French 操作者不同
EngineMode.Default 不适用 自动选择
TesseractOCR.Exceptions.TesseractException IronOcr.Exceptions.OcrException 需要处理的异常类型更少。
手动预处理(ImageSharp) input.Deskew(), input.DeNoise(), input.Contrast() 内置,无需外部库
Docnet GetPageReader().GetImage() + 临时文件 input.LoadPdf(path) 原生 PDF,无临时文件
不适用 input.LoadPdf(path, Password: "secret") 没有额外的库,就没有等效的方案。
不适用 result.SaveAsSearchablePdf(path) TesseractOCR中没有等效项
不适用 result.Pages, result.Lines, result.Words 结构化输出
不适用 ocr.Configuration.ReadBarCodes = true 条形码协同读取
每线程Engine实例 单个IronTesseract实例 内置螺纹安全装置

当团队考虑从TesseractOCR迁移到IronOCR时

文件质量参差不齐

TesseractOCR 集成可以完美处理高质量的 300 DPI 扫描件。 一旦文件质量下降——平板打印机打印出的歪斜页面、对比度低的传真件、手机拍摄的收据照片——准确性差距就会出现。 README 文件中的基准测试表明,未经预处理的手机摄像头拍摄准确率会下降到 30-50%。 在 ImageSharp 或 SkiaSharp 中构建和调整预处理流程以恢复该精度需要 8-20 小时的开发时间,并且会引入额外的依赖项。 团队在最初集成六个月后发现他们"高质量扫描"的假设是错误的,这是典型的TesseractOCR迁移案例。 预处理差距不是一个可以一次性解决的设置问题——每当有新的文档类型或捕获方法进入流程时,它就会出现。

PDF 文档是输入工作流程的一部分

Docnet.Core +TesseractOCR组合用于 PDF OCR 可以工作,但需要大约 100 行代码才能替换 3 行代码。更实际的是,它需要评估 Docnet 的许可证(MIT)、其跨平台行为、其对格式错误的 PDF 的处理以及其与现有 tessdata 和预处理代码的交互。 构建文档管理系统、发票处理器或任何以 PDF 作为主要输入的工作流程的团队会发现,随着时间的推移,外部库 PDF 方法会积累摩擦:页面尺寸处理、渲染的 DPI 选择、临时文件清理逻辑以及完全没有可搜索的 PDF 输出。 对于需要从扫描输入生成可搜索 PDF 的团队来说,仅靠TesseractOCR是无法实现的。

线程架构达到内存限制

TesseractOCR 中的四个并发 OCR 工作进程在处理单个图像之前会消耗 200-400MB 的引擎内存。 对于低吞吐量的后台作业来说,这不是问题。 对于处理多个并发文档上传的ASP.NET Core端点或推动吞吐量的批处理处理器来说,这是一个问题。 每个线程的引擎模式也意味着每个新线程在处理第一个文档之前都要支付约 500 毫秒的初始化成本。 选择TesseractOCR作为后台服务,然后需要扩展吞吐量的团队会遇到这种瓶颈。 改用线程安全引擎可以完全消除每个线程的开销。

初始开发后部署环境的变化

TesseractOCR 需要与应用程序一起部署 tessdata 文件。 在开发者的本地环境中,这是可以管理的。 在 Docker 容器中,这意味着要么将 tessdata 文件打包到镜像中(每种语言会增加镜像大小 15-50MB),要么将卷挂载到已知路径(增加操作复杂性)。 在 CI/CD 流水线中,这意味着编写脚本并缓存下载内容。 在 Azure 应用服务或 AWS Lambda 中,tessdata 路径配置是另一个特定于环境的设置,可能与开发环境有所不同。 那些从本地概念验证开始,然后转向容器化或云部署的团队会发现,tessdata 需求在每个环境中表现都不同。IronOCR基于 NuGet 的语言包在软件包还原的任何位置都以相同的方式部署。

社区支持触及分岔口

TesseractOCR 的NuGet下载量约为 20 万次。 charlesw/tesseract 大约有 800 万。 关于Tesseract .NET包装器的问题,在Stack Overflow上的问题、博客文章和GitHub问题中,大量引用的是charlesw的API—EngineTesseractOCR.Pix.Image.LoadFromFile。 适用于 charlesw 的解决方案需要根据TesseractOCR的 API 差异进行调整。 对于主要依靠社区资源提供支持的团队来说,这会大大增加摩擦。

常见迁移注意事项

命名空间和类替换

核心替换是OcrInput。 命名空间交换(using IronOcr)捕获了大多数引用。TesseractOCR使用 Language.English |Language.French(bitwise OR on a flags enum),IronOCRusesOcrLanguage.English + OcrLanguage.French(加法运算符)。 信心尺度也不同:TesseractOCR返回page.MeanConfidence作为0.0-1.0浮点值; IronOCR返回result.Confidence`作为0-100双精度值。 任何比较置信度值的阈值逻辑都需要更新。

// Before (TesseractOCR)
using var engine = new Engine(@"./tessdata", Language.English, EngineMode.Default);
using var image = TesseractOCR.Pix.Image.LoadFromFile("document.png");
using var page = engine.Process(image);
float confidence = page.MeanConfidence; // 0.0 to 1.0

// After (IronOCR)
var ocr = new IronTesseract();
using var input = new OcrInput("document.png");
var result = ocr.Read(input);
double confidence = result.Confidence; // 0 to 100
// Before (TesseractOCR)
using var engine = new Engine(@"./tessdata", Language.English, EngineMode.Default);
using var image = TesseractOCR.Pix.Image.LoadFromFile("document.png");
using var page = engine.Process(image);
float confidence = page.MeanConfidence; // 0.0 to 1.0

// After (IronOCR)
var ocr = new IronTesseract();
using var input = new OcrInput("document.png");
var result = ocr.Read(input);
double confidence = result.Confidence; // 0 to 100
Imports TesseractOCR
Imports IronOcr

' Before (TesseractOCR)
Using engine As New Engine("./tessdata", Language.English, EngineMode.Default)
    Using image As TesseractOCR.Pix.Image = TesseractOCR.Pix.Image.LoadFromFile("document.png")
        Using page As Page = engine.Process(image)
            Dim confidence As Single = page.MeanConfidence ' 0.0 to 1.0
        End Using
    End Using
End Using

' After (IronOCR)
Dim ocr As New IronTesseract()
Using input As New OcrInput("document.png")
    Dim result As OcrResult = ocr.Read(input)
    Dim confidence As Double = result.Confidence ' 0 to 100
End Using
$vbLabelText   $csharpLabel

移除预处理依赖项

如果现有的TesseractOCR集成已经包含 ImageSharp 或 SkiaSharp 预处理流程,则迁移后可以删除该代码。 IronOCR的内置EnhanceResolution()方法替换了外部过滤链。 围绕预处理过的图像的临时文件创建和清理代码也消失了—Bitmap,无需中间文件写入。图像过滤器示例涵盖了可用的过滤器及其等效项。

移除 PDF 外部库

using Docnet.Core 或 PdfiumViewer 进行 PDF 渲染的团队可以完全移除这些软件包。 替换整个PDF渲染循环—input.LoadPdf(pdfPath)PDF 输入指南PDF OCR 用例页面涵盖了IronOCR PDF API 的全部内容。 从项目中删除tessdata文件夹,移除tessdata文件的apt-get install tesseract-ocr步骤。

错误处理表面收缩

TesseractOCR需要捕捉引擎初始化失败的BadImageFormatException。 IronOCR将其原生依赖项打包并在内部管理初始化,因此这些异常类型不适用。 剩余的错误表面是标准的IronOcr.Exceptions.OcrException用于OCR特定故障。

其他IronOCR功能

除了本次对比涵盖的领域之外, IronOCR还包含TesseractOCR所没有的同类功能:

  • 可搜索PDF输出result.SaveAsSearchablePdf()将扫描文档转换为带有嵌入式可选择文本的PDF;TesseractOCR不会生成任何类型的 PDF 输出。
  • 基于区域的OCRinput.LoadImage("invoice.jpg", new CropRectangle(0, 0, 600, 100))限制处理到特定区域; 可用于表单字段提取和结构化文档解析
  • OCR期间的条形码读取ocr.Configuration.ReadBarCodes = true读取嵌入在文档中的条形码和QR码,与文本提取同时进行
  • 结构化结果数据result.Words提供文件结构与逐字坐标数据;TesseractOCR返回一个包含单个置信度值的扁平文本字符串。
  • hOCR导出result.SaveAsHocrFile()生成hOCR格式输出以供下游文档处理管道使用
  • 异步OCR — 本机异步/等待支持,用于ASP.NET Core集成,无需手动Task.Run包装器 -每个词的置信度得分——词级置信度可以过滤掉不确定的提取结果;TesseractOCR仅提供文档级平均置信度 -专业文档识别——护照、MICR支票和车牌识别,并针对特定领域进行优化,超越通用OCR技术。

.NET兼容性和未来准备情况

TesseractOCR 的目标平台是.NET 6.0、7.0 和 8.0,涵盖了当前活跃的 LTS 和 STS 版本。 IronOCR支持相同的现代.NET版本,并向后兼容.NET Framework 4.6.2+,以满足尚未完成框架迁移的团队的需求。 这两个库都可以在 Windows、Linux 和 macOS 上运行。 IronOCR在其NuGet包中提供了针对所有受支持平台的平台特定优化,无需进行平台特定配置;TesseractOCR为常见平台打包了原生二进制文件,但对于不常见的 Linux 发行版和自定义 Docker 基础镜像,需要额外的原生库配置。 IronOCR发布了DockerLinuxAzureAWS部署指南,其中包含针对生产环境的已验证配置。

结论

TesseractOCR 占据了一个真正的细分市场:当您需要一个积极维护的、现代框架的 Tesseract 绑定,用于一个需要 Apache 2.0 许可的项目,处理干净的高质量图像,并且拥有内部图像处理专业知识来构建管道所需的任何预处理时,它是正确的选择。 对于新的.NET 6+ 工作而言,Sicos1977 分支比使用已存档的 charlesw 项目要好得多——更新的引擎、积极的错误修复、真正的跨平台原生打包。 对于符合"干净输入,仅开源"要求的项目来说,这就足够了。

这种比较的论点更加具体:更新包装器并不能解决 Tesseract 本身未提供的功能。tessdata 的需求没有改变。 非线程安全引擎保持不变。 没有预处理的情况保持不变。 原生PDF支持缺失的问题依然存在。 选择TesseractOCR作为其现代.NET目标平台的团队仍然需要预算 26-56 小时用于初始设置、预处理实施和 PDF 集成——这与他们使用 charlesw 所需的预算相同。 现代前叉减少了方向盘摩擦; 它并不会减少集成工作量。

IronOCR直接解决了所有四个差距:语言作为NuGet包安装,$999。 对于大多数生产应用而言,这种权衡很快就能得到解决:仅第一周的设置工作,开发人员花费的时间(按任何有竞争力的费率计算)就超过了许可费用,这还不包括后续的维护费用。

对于任何评估TesseractOCR的团队来说,悬而未决的问题不是该分支是否活跃且维护良好——答案是肯定的。 问题在于 Tesseract 的基本架构是否符合生产要求。 如果解决方案涉及质量参差不齐的文档、PDF 输入、可扩展的吞吐量,或者 tessdata 管理存在摩擦的部署模型,IronOCR 的方法可以通过一次性许可费来消除这些问题。

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

常见问题解答

TesseractOCR.Net是什么?

TesseractOCR.Net 是一款 OCR 解决方案,开发者和企业使用它从图像和文档中提取文本。它是与 IronOCR 一起评估的几种适用于 .NET 应用程序开发的 OCR 方案之一。

对于 .NET 开发人员来说,IronOCR 与 TesseractOCR.Net 相比如何?

IronOCR 是一个基于 NuGet 的 .NET OCR 库,它使用 IronTesseract 作为其核心引擎。与 TesseractOCR.Net 相比,它提供了更简单的部署方式(无需 SDK 安装程序)、统一的定价模式以及简洁的 C# API,无需 COM 互操作或云依赖。

IronOCR 比 TesseractOCR.Net 更容易设置吗?

IronOCR 通过单个 NuGet 包进行安装。无需 SDK 安装程序、复制许可证文件、注册 COM 组件或管理单独的运行时二进制文件。整个 OCR 引擎都打包在包中。

TesseractOCR.Net 和 IronOCR 的准确率存在哪些差异?

IronOCR 对标准商务文档、发票、收据和扫描表格的识别准确率很高。对于严重损坏的文档或不常见的文字,识别准确率会因源文件质量而异。IronOCR 包含图像预处理滤镜,可提高低质量输入文件的识别率。

IronOCR是否支持PDF文本提取?

是的。IronOCR只需一次调用即可从原生PDF和扫描的PDF图像中提取文本。它还支持多页TIFF文件、图像和流。对于扫描的PDF,OCR逐页进行处理,并为每个页面生成一个结果对象。

TesseractOCR.Net 的许可方式与 IronOCR 相比如何?

IronOCR采用永久统一费率许可,不按页或扫描次数收费。处理大量文档的机构无论处理量多少,都只需支付相同的许可费用。详情及批量定价请访问IronOCR许可页面。

IronOCR支持哪些语言?

IronOCR 通过独立的 NuGet 语言包支持 127 种语言。添加语言只需一条命令“dotnet add package IronOcr.Languages.{Language}”。无需手动放置文件或配置路径。

如何在.NET项目中安装IronOCR ?

通过 NuGet 安装:在程序包管理器控制台中运行“Install-Package IronOcr”命令,或在命令行界面 (CLI) 中运行“dotnet add package IronOcr”命令。其他语言包的安装方式相同。无需使用原生 SDK 安装程序。

与 TesseractOCR.Net 不同,IronOCR 是否适合 Docker 和容器化部署?

是的。IronOCR 通过 NuGet 包在 Docker 容器中运行。许可证密钥通过环境变量设置。OCR 引擎本身不需要任何许可证文件、SDK 路径或卷挂载。

我可以在购买前试用 IronOCR,并将其与 TesseractOCR.Net 进行比较吗?

是的。IronOCR 试用模式可以处理文档,并在输出结果上添加水印,从而生成 OCR 结果。您可以在购买许可证之前,先在自己的文档上验证其准确性。

IronOCR是否支持条形码读取和文本提取?

IronOCR专注于文本提取和OCR识别。对于条形码读取,Iron Software提供了配套库IronBarcode。两者都可单独购买,也可作为Iron Suite套装的一部分购买。

从 TesseractOCR.Net 迁移到 IronOCR 容易吗?

从 TesseractOCR.Net 迁移到 IronOCR 通常涉及将初始化序列替换为 IronTesseract 实例化、移除 COM 生命周期管理以及更新 API 调用。大多数迁移都能显著降低代码复杂度。

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

钢铁支援团队

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