
从 Syncfusion OCR 迁移到 IronOCR
本指南将引导.NET开发人员完成从Syncfusion OCR Processor 到IronOCR的完整迁移,以便从扫描文档和 PDF 中提取文本。 它涵盖了用IronOcr NuGet包替换Syncfusion.PDF.OCR.Net.Core所需的特定配置更改、代码重写和部署清理,特别关注消除每个Syncfusion OCR部署都需要的tessdata文件管理和Tesseract二进制路径配置。
为什么要从Syncfusion OCR 迁移?
Syncfusion OCR 是一个嵌入在包含 1600 个组件的Suite中的 Tesseract 封装器。对于那些仅需提取文本的团队而言,这种架构在各个层面都造成了阻碍:设置、部署、维护和许可。
tessdata文件夹跟随每个环境。每个开发者工作站、CI运行器、预发布服务器和生产容器都需要一个包含应用程序使用的每种语言的.traineddata文件的tessdata目录。 标准模型的英文文件大小为 23 MB,而最佳 LSTM 模型的英文文件大小为 94 MB。 五种语言的应用程序会使每个部署工件增加 100–500 MB 的大小。 该文件夹必须位于OCRProcessor构造函数期望的确切路径,否则应用程序将在启动时立即抛出。这不是一次性设置成本,而是每当配置新环境时都会出现的经常性运行成本。
Tesseract二进制路径配置在各环境中失败。OCRProcessor构造函数需要tessdata目录的路径,该路径必须在每个目标平台上正确解析。 在Windows开发机上有效的路径(@"tessdata/")在Linux容器上会失败,除非部署管道显式复制该文件夹。 Docker镜像构建必须包含COPY tessdata/ /app/tessdata/层。 CI 流水线必须编写 tessdata 下载脚本。 与NuGet包还原分开的物理隔离环境必须单独管理二进制文件分发。 每个环境都会增加路径不匹配的可能性,从而导致静默的 OCR 失败或运行时异常。
以PDF为中心的架构对图像输入施加转换开销。Syncfusion的PdfLoadedDocument对象,而不是图像文件。 从JPG中提取文本需要创建一个PdfLoadedDocument,然后运行OCR——在文本识别步骤之前有九个操作。每个图像优先的OCR工作流中,这种往返增加了执行开销和代码复杂性。
SyncfusionSuite模式会因业务增长而触发合规事件。Syncfusion社区许可要求开发者人数少于 5 人、员工人数少于 10 人、年收入少于 100 万美元,且终身外部融资总额少于 300 万美元——所有这些要求必须同时满足。 任何超过阈值都会立即使许可证失效,并需要以每年每位开发者 995 至 1,595 美元的价格进行商业升级。 一个由五名开发人员组成的团队,如果商业上使用Syncfusion OCR 三年,需要支付 14,925 美元至 23,925 美元才能获得相同的文本提取功能,而IronOCR Professional只需一次性支付 2,999 美元即可获得该功能。
由于没有内置预处理功能,Tesseract 在处理质量较差的扫描图像时需要依赖外部预处理工具。未经预处理的图像,例如旋转图像、噪声图像或低对比度图像,Tesseract 的处理效果较差。 Syncfusion没有公开任何预处理 API。 需要进行倾斜校正、降噪或对比度校正的开发人员必须添加单独的图像库(System.Drawing、SkiaSharp、ImageSharp),实现滤波器,并将输出连接到 PDF 往返流程,然后才能开始 OCR。 这是一个第三方依赖项,需要额外编写 20-40 行代码才能实现IronOCR作为内置方法提供的功能。
仅需要OCR,但整个套件已获得许可。Syncfusion无论实际使用哪些功能,都会引入Syncfusion.Compression.Net.Core和其他传递性依赖项。 对于构建专注于文档处理服务的团队来说,依赖关系图具有重要的意义——对于与文本提取无关的组件,它会影响构建时间、容器镜像大小和许可成本。
基本问题
Syncfusion OCR 需要先配置 tessdata 文件系统路径,然后才能进行任何 OCR 调用:
// Syncfusion: tessdata path required — fails in any environment where this path is wrong
private const string TessDataPath = @"tessdata/";
using var document = new PdfLoadedDocument("scanned-invoice.pdf");
using var processor = new OCRProcessor(TessDataPath); // throws if path does not resolve
processor.Settings.Language = Languages.English;
processor.PerformOCR(document);
var text = new StringBuilder();
foreach (PdfLoadedPage page in document.Pages)
text.AppendLine(page.ExtractText());' Syncfusion: tessdata path required — fails in any environment where this path is wrong
Private Const TessDataPath As String = "tessdata/"
Using document As New PdfLoadedDocument("scanned-invoice.pdf")
Using processor As New OCRProcessor(TessDataPath) ' throws if path does not resolve
processor.Settings.Language = Languages.English
processor.PerformOCR(document)
Dim text As New StringBuilder()
For Each page As PdfLoadedPage In document.Pages
text.AppendLine(page.ExtractText())
Next
End Using
End Using' Syncfusion: tessdata path required — fails in any environment where this path is wrong
Private Const TessDataPath As String = "tessdata/"
Using document As New PdfLoadedDocument("scanned-invoice.pdf")
Using processor As New OCRProcessor(TessDataPath) ' throws if path does not resolve
processor.Settings.Language = Languages.English
processor.PerformOCR(document)
Dim text As New StringBuilder()
For Each page As PdfLoadedPage In document.Pages
text.AppendLine(page.ExtractText())
Next
End Using
End UsingIronOCR无需路径配置。 语言数据已包含在软件包中:
// IronOCR: no tessdata path, no path configuration, no folder to deploy
var text = new IronTesseract().Read("scanned-invoice.pdf").Text;// IronOCR: no tessdata path, no path configuration, no folder to deploy
var text = new IronTesseract().Read("scanned-invoice.pdf").Text;' IronOCR: no tessdata path, no path configuration, no folder to deploy
Dim text = New IronTesseract().Read("scanned-invoice.pdf").Text' IronOCR: no tessdata path, no path configuration, no folder to deploy
Dim text = New IronTesseract().Read("scanned-invoice.pdf").TextIronOCR与Syncfusion OCR:功能对比
下表列出了从Syncfusion OCR 迁移的团队最关心的功能。
| 特征 | Syncfusion OCR | IronOCR |
|---|---|---|
| NuGet软件包 | Syncfusion.PDF.OCR.Net.Core (suite) | IronOcr (standalone) |
| tessdata 必填 | 是的——手动下载和路径配置 | 不——内部捆绑 |
| 直接图像OCR | 不——需要进行PDF格式的往返转换。 | 是的-LoadImage()或直接路径 |
| 直接 PDF OCR | 是的——主要投入模型 | 是的——一流的支持 |
| 自动预处理 | 否——需要外部库 | 是的——校正倾斜、降噪、对比度、二值化 |
| 可搜索的 PDF 输出 | 是的-在PerformOCR()之后保存 | 是的-result.SaveAsSearchablePdf() |
| 支持的语言 | 60+ 通过手动 tessdata 下载 | 通过NuGet语言包提供 125 多个语言包 |
| 多语言同步 | 是的-Languages枚举的按位标志 | 是的-AddSecondaryLanguage() |
| 基于区域的OCR | 否 | 是的-CropRectangle |
| 条形码读取 | 否 | 是的-ocr.Configuration.ReadBarCodes = true |
| 结构化输出 | 只有通过page.ExtractText()的页面 | 页、段落、行、单词、带坐标的字符 |
| 置信度评分 | 否 | 是的-result.Confidence和每字分数 |
| hOCR出口 | 否 | 是 |
| 流输入 | 仅通过 PDF 流 | 直接输入图像和PDF文件 |
| 线程安全 | 未记录为线程安全。 | 完整的-每线程一个IronTesseract实例 |
| 跨平台 | 是的——但tessdata必须在每个平台上解析 | 是的——单个NuGet,无需路径配置 |
| Docker部署 | 图像中需要 tessdata 层 | 单层包装,无额外层 |
| 许可模式 | 年度Suite订阅(995-1595美元/设备/年) | 永久(Lite $999, 专业版 $1,499, 企业版 $2,999) |
| 社区许可证限制 | 收入、员工人数和资金上限,并享有审计权 | 免费试用无任何限制 |
| OCR引擎 | Tesseract 5(标准包装) | 优化后的 Tesseract 5,精度有所提升 |
快速入门:Syncfusion OCR到IronOCR 的迁移
步骤 1:替换 NuGet 软件包
移除Syncfusion OCR 以及任何其他仅为实现 OCR 功能而引入的Syncfusion软件包:
dotnet remove package Syncfusion.PDF.OCR.Net.Core
dotnet remove package Syncfusion.Pdf.Net.Core
dotnet remove package Syncfusion.Compression.Net.Core
从NuGet安装IronOCR :
步骤 2:更新命名空间
将Syncfusion命名空间导入替换为单个IronOCR命名空间:
// Before (Syncfusion)
using Syncfusion.OCRProcessor;
using Syncfusion.Pdf;
using Syncfusion.Pdf.Parsing;
// After (IronOCR)
using IronOcr;// Before (Syncfusion)
using Syncfusion.OCRProcessor;
using Syncfusion.Pdf;
using Syncfusion.Pdf.Parsing;
// After (IronOCR)
using IronOcr;Imports IronOcrImports IronOcr步骤 3:初始化许可证
在应用程序启动时,在任何 OCR 调用之前,添加一次许可证初始化:
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"无需办理Suite登记手续。 无需进行社区许可证资格审查。 密钥是一个分配给静态属性的普通字符串。
代码迁移示例
Tessdata路径消除和OCR初始化
Syncfusion代码库通常包括tessdata验证逻辑 - 在尝试OCR之前检查目录是否存在并包含所需的.traineddata文件。 这段保护代码的存在是因为缺少 tessdata 文件会导致运行时异常,而缺少语言文件导致的生产事故非常常见,因此团队会编写防御性检查。
SyncfusionOCR 方法:
using Syncfusion.OCRProcessor;
using Syncfusion.Pdf.Parsing;
public class DocumentOcrService
{
// Path hardcoded — different on every deployment target
private const string TessDataPath = @"tessdata/";
private bool ValidateTessdataBeforeUse(string languageCode)
{
// Guard required because missing files cause runtime exceptions
if (!Directory.Exists(TessDataPath))
throw new InvalidOperationException(
"tessdata directory not found. Download from github.com/tesseract-ocr/tessdata_best");
string filePath = Path.Combine(TessDataPath, $"{languageCode}.traineddata");
if (!File.Exists(filePath))
throw new InvalidOperationException(
$"{languageCode}.traineddata not found — file must be downloaded manually");
return true;
}
public string ExtractText(string pdfPath, string languageCode = "eng")
{
ValidateTessdataBeforeUse(languageCode); // defensive check before every call
using var document = new PdfLoadedDocument(pdfPath);
using var processor = new OCRProcessor(TessDataPath);
processor.Settings.Language = Languages.English;
processor.PerformOCR(document);
var sb = new StringBuilder();
foreach (PdfLoadedPage page in document.Pages)
sb.AppendLine(page.ExtractText());
return sb.ToString();
}
}using Syncfusion.OCRProcessor;
using Syncfusion.Pdf.Parsing;
public class DocumentOcrService
{
// Path hardcoded — different on every deployment target
private const string TessDataPath = @"tessdata/";
private bool ValidateTessdataBeforeUse(string languageCode)
{
// Guard required because missing files cause runtime exceptions
if (!Directory.Exists(TessDataPath))
throw new InvalidOperationException(
"tessdata directory not found. Download from github.com/tesseract-ocr/tessdata_best");
string filePath = Path.Combine(TessDataPath, $"{languageCode}.traineddata");
if (!File.Exists(filePath))
throw new InvalidOperationException(
$"{languageCode}.traineddata not found — file must be downloaded manually");
return true;
}
public string ExtractText(string pdfPath, string languageCode = "eng")
{
ValidateTessdataBeforeUse(languageCode); // defensive check before every call
using var document = new PdfLoadedDocument(pdfPath);
using var processor = new OCRProcessor(TessDataPath);
processor.Settings.Language = Languages.English;
processor.PerformOCR(document);
var sb = new StringBuilder();
foreach (PdfLoadedPage page in document.Pages)
sb.AppendLine(page.ExtractText());
return sb.ToString();
}
}Imports Syncfusion.OCRProcessor
Imports Syncfusion.Pdf.Parsing
Public Class DocumentOcrService
' Path hardcoded — different on every deployment target
Private Const TessDataPath As String = "tessdata/"
Private Function ValidateTessdataBeforeUse(languageCode As String) As Boolean
' Guard required because missing files cause runtime exceptions
If Not Directory.Exists(TessDataPath) Then
Throw New InvalidOperationException("tessdata directory not found. Download from github.com/tesseract-ocr/tessdata_best")
End If
Dim filePath As String = Path.Combine(TessDataPath, $"{languageCode}.traineddata")
If Not File.Exists(filePath) Then
Throw New InvalidOperationException($"{languageCode}.traineddata not found — file must be downloaded manually")
End If
Return True
End Function
Public Function ExtractText(pdfPath As String, Optional languageCode As String = "eng") As String
ValidateTessdataBeforeUse(languageCode) ' defensive check before every call
Using document = New PdfLoadedDocument(pdfPath)
Using processor = New OCRProcessor(TessDataPath)
processor.Settings.Language = Languages.English
processor.PerformOCR(document)
Dim sb = New StringBuilder()
For Each page As PdfLoadedPage In document.Pages
sb.AppendLine(page.ExtractText())
Next
Return sb.ToString()
End Using
End Using
End Function
End ClassImports Syncfusion.OCRProcessor
Imports Syncfusion.Pdf.Parsing
Public Class DocumentOcrService
' Path hardcoded — different on every deployment target
Private Const TessDataPath As String = "tessdata/"
Private Function ValidateTessdataBeforeUse(languageCode As String) As Boolean
' Guard required because missing files cause runtime exceptions
If Not Directory.Exists(TessDataPath) Then
Throw New InvalidOperationException("tessdata directory not found. Download from github.com/tesseract-ocr/tessdata_best")
End If
Dim filePath As String = Path.Combine(TessDataPath, $"{languageCode}.traineddata")
If Not File.Exists(filePath) Then
Throw New InvalidOperationException($"{languageCode}.traineddata not found — file must be downloaded manually")
End If
Return True
End Function
Public Function ExtractText(pdfPath As String, Optional languageCode As String = "eng") As String
ValidateTessdataBeforeUse(languageCode) ' defensive check before every call
Using document = New PdfLoadedDocument(pdfPath)
Using processor = New OCRProcessor(TessDataPath)
processor.Settings.Language = Languages.English
processor.PerformOCR(document)
Dim sb = New StringBuilder()
For Each page As PdfLoadedPage In document.Pages
sb.AppendLine(page.ExtractText())
Next
Return sb.ToString()
End Using
End Using
End Function
End ClassIronOCR方法:
using IronOcr;
public class DocumentOcrService
{
// 否 tessdata path — no validation logic — no defensive checks
public string ExtractText(string pdfPath)
{
return new IronTesseract().Read(pdfPath).Text;
}
}using IronOcr;
public class DocumentOcrService
{
// 否 tessdata path — no validation logic — no defensive checks
public string ExtractText(string pdfPath)
{
return new IronTesseract().Read(pdfPath).Text;
}
}Imports IronOcr
Public Class DocumentOcrService
' 否 tessdata path — no validation logic — no defensive checks
Public Function ExtractText(pdfPath As String) As String
Return New IronTesseract().Read(pdfPath).Text
End Function
End ClassImports IronOcr
Public Class DocumentOcrService
' 否 tessdata path — no validation logic — no defensive checks
Public Function ExtractText(pdfPath As String) As String
Return New IronTesseract().Read(pdfPath).Text
End Function
End Class整个TessDataPath常数已删除。 已移除复制 tessdata 文件夹的部署管道步骤。 下载.traineddata文件的CI脚本已被移除。 将 tessdata 复制到容器镜像中的 Dockerfile 层被移除。 这些代码都不需要替换——它们已经不再必要了。 IronTesseract 设置指南涵盖了所有可用的初始化选项,以便在需要进行默认设置以外的配置时使用。
可搜索PDF生成流程
Syncfusion的可搜索PDF输出通过在已加载文档上调用PerformOCR()工作,这会替换一个不可见的文本层,然后将修改后的文档保存到流中。 该模式需要管理两个流——输入和输出——并且 OCR 和保存步骤是对同一个可变文档对象执行的独立操作。
SyncfusionOCR 方法:
using Syncfusion.OCRProcessor;
using Syncfusion.Pdf.Parsing;
public class SearchablePdfService
{
private const string TessDataPath = @"tessdata/";
public void ConvertToSearchable(string inputPdfPath, string outputPdfPath)
{
// Load document — mutable: PerformOCR modifies it in place
using var document = new PdfLoadedDocument(inputPdfPath);
using var processor = new OCRProcessor(TessDataPath);
processor.Settings.Language = Languages.English;
// Step 1: OCR modifies the document object
processor.PerformOCR(document);
// Step 2: Save the modified document to a separate output file
using var outputStream = new FileStream(outputPdfPath, FileMode.Create, FileAccess.Write);
document.Save(outputStream);
}
public byte[] ConvertToSearchableBytes(string inputPdfPath)
{
using var document = new PdfLoadedDocument(inputPdfPath);
using var processor = new OCRProcessor(TessDataPath);
processor.Settings.Language = Languages.English;
processor.PerformOCR(document);
using var outputStream = new MemoryStream();
document.Save(outputStream);
return outputStream.ToArray();
}
}using Syncfusion.OCRProcessor;
using Syncfusion.Pdf.Parsing;
public class SearchablePdfService
{
private const string TessDataPath = @"tessdata/";
public void ConvertToSearchable(string inputPdfPath, string outputPdfPath)
{
// Load document — mutable: PerformOCR modifies it in place
using var document = new PdfLoadedDocument(inputPdfPath);
using var processor = new OCRProcessor(TessDataPath);
processor.Settings.Language = Languages.English;
// Step 1: OCR modifies the document object
processor.PerformOCR(document);
// Step 2: Save the modified document to a separate output file
using var outputStream = new FileStream(outputPdfPath, FileMode.Create, FileAccess.Write);
document.Save(outputStream);
}
public byte[] ConvertToSearchableBytes(string inputPdfPath)
{
using var document = new PdfLoadedDocument(inputPdfPath);
using var processor = new OCRProcessor(TessDataPath);
processor.Settings.Language = Languages.English;
processor.PerformOCR(document);
using var outputStream = new MemoryStream();
document.Save(outputStream);
return outputStream.ToArray();
}
}Imports Syncfusion.OCRProcessor
Imports Syncfusion.Pdf.Parsing
Public Class SearchablePdfService
Private Const TessDataPath As String = "tessdata/"
Public Sub ConvertToSearchable(inputPdfPath As String, outputPdfPath As String)
' Load document — mutable: PerformOCR modifies it in place
Using document = New PdfLoadedDocument(inputPdfPath)
Using processor = New OCRProcessor(TessDataPath)
processor.Settings.Language = Languages.English
' Step 1: OCR modifies the document object
processor.PerformOCR(document)
' Step 2: Save the modified document to a separate output file
Using outputStream = New FileStream(outputPdfPath, FileMode.Create, FileAccess.Write)
document.Save(outputStream)
End Using
End Using
End Using
End Sub
Public Function ConvertToSearchableBytes(inputPdfPath As String) As Byte()
Using document = New PdfLoadedDocument(inputPdfPath)
Using processor = New OCRProcessor(TessDataPath)
processor.Settings.Language = Languages.English
processor.PerformOCR(document)
Using outputStream = New MemoryStream()
document.Save(outputStream)
Return outputStream.ToArray()
End Using
End Using
End Using
End Function
End ClassImports Syncfusion.OCRProcessor
Imports Syncfusion.Pdf.Parsing
Public Class SearchablePdfService
Private Const TessDataPath As String = "tessdata/"
Public Sub ConvertToSearchable(inputPdfPath As String, outputPdfPath As String)
' Load document — mutable: PerformOCR modifies it in place
Using document = New PdfLoadedDocument(inputPdfPath)
Using processor = New OCRProcessor(TessDataPath)
processor.Settings.Language = Languages.English
' Step 1: OCR modifies the document object
processor.PerformOCR(document)
' Step 2: Save the modified document to a separate output file
Using outputStream = New FileStream(outputPdfPath, FileMode.Create, FileAccess.Write)
document.Save(outputStream)
End Using
End Using
End Using
End Sub
Public Function ConvertToSearchableBytes(inputPdfPath As String) As Byte()
Using document = New PdfLoadedDocument(inputPdfPath)
Using processor = New OCRProcessor(TessDataPath)
processor.Settings.Language = Languages.English
processor.PerformOCR(document)
Using outputStream = New MemoryStream()
document.Save(outputStream)
Return outputStream.ToArray()
End Using
End Using
End Using
End Function
End ClassIronOCR方法:
using IronOcr;
public class SearchablePdfService
{
public void ConvertToSearchable(string inputPdfPath, string outputPdfPath)
{
var result = new IronTesseract().Read(inputPdfPath);
result.SaveAsSearchablePdf(outputPdfPath);
}
public byte[] ConvertToSearchableBytes(string inputPdfPath)
{
using var input = new OcrInput();
input.LoadPdf(inputPdfPath);
var result = new IronTesseract().Read(input);
// SaveAsSearchablePdf also accepts a MemoryStream
using var ms = new MemoryStream();
result.SaveAsSearchablePdf(ms);
return ms.ToArray();
}
}using IronOcr;
public class SearchablePdfService
{
public void ConvertToSearchable(string inputPdfPath, string outputPdfPath)
{
var result = new IronTesseract().Read(inputPdfPath);
result.SaveAsSearchablePdf(outputPdfPath);
}
public byte[] ConvertToSearchableBytes(string inputPdfPath)
{
using var input = new OcrInput();
input.LoadPdf(inputPdfPath);
var result = new IronTesseract().Read(input);
// SaveAsSearchablePdf also accepts a MemoryStream
using var ms = new MemoryStream();
result.SaveAsSearchablePdf(ms);
return ms.ToArray();
}
}Imports IronOcr
Public Class SearchablePdfService
Public Sub ConvertToSearchable(inputPdfPath As String, outputPdfPath As String)
Dim result = New IronTesseract().Read(inputPdfPath)
result.SaveAsSearchablePdf(outputPdfPath)
End Sub
Public Function ConvertToSearchableBytes(inputPdfPath As String) As Byte()
Using input As New OcrInput()
input.LoadPdf(inputPdfPath)
Dim result = New IronTesseract().Read(input)
' SaveAsSearchablePdf also accepts a MemoryStream
Using ms As New MemoryStream()
result.SaveAsSearchablePdf(ms)
Return ms.ToArray()
End Using
End Using
End Function
End ClassImports IronOcr
Public Class SearchablePdfService
Public Sub ConvertToSearchable(inputPdfPath As String, outputPdfPath As String)
Dim result = New IronTesseract().Read(inputPdfPath)
result.SaveAsSearchablePdf(outputPdfPath)
End Sub
Public Function ConvertToSearchableBytes(inputPdfPath As String) As Byte()
Using input As New OcrInput()
input.LoadPdf(inputPdfPath)
Dim result = New IronTesseract().Read(input)
' SaveAsSearchablePdf also accepts a MemoryStream
Using ms As New MemoryStream()
result.SaveAsSearchablePdf(ms)
Return ms.ToArray()
End Using
End Using
End Function
End ClassSyncfusion使用的可变文档模型 - 在保存之前,PerformOCR()在原地修改已加载的文档 - 被IronOCR的不可变读取然后输出模式替代。 该OcrResult对象持有识别文本,并可以保存为可搜索PDF、导出为纯文本或以结构化数据形式遍历,全部从相同结果中实现。 可搜索的 PDF 操作指南和可搜索的 PDF 示例涵盖了其他输出选项,包括 PDF/A 合规性设置。
基于流的PDF OCR流程
通过 HTTP 上传、消息队列或 blob 存储接收 PDF 文档的生产服务通常使用流而不是文件路径。 Syncfusion通过PdfLoadedDocument接受流,但tessdata路径限制仍适用 - tessdata文件夹必须存在于处理流的服务器上。
SyncfusionOCR 方法:
using Syncfusion.OCRProcessor;
using Syncfusion.Pdf.Parsing;
public class StreamOcrService
{
private const string TessDataPath = @"tessdata/";
public string ExtractFromStream(Stream pdfStream)
{
// Stream input works, but tessdata path constraint remains
using var document = new PdfLoadedDocument(pdfStream);
using var processor = new OCRProcessor(TessDataPath);
processor.Settings.Language = Languages.English;
processor.PerformOCR(document);
var sb = new StringBuilder();
foreach (PdfLoadedPage page in document.Pages)
sb.AppendLine(page.ExtractText());
return sb.ToString();
}
public async Task<string> ExtractFromStreamAsync(Stream pdfStream)
{
// 否 native async — must wrap in Task.Run
return await Task.Run(() => ExtractFromStream(pdfStream));
}
}using Syncfusion.OCRProcessor;
using Syncfusion.Pdf.Parsing;
public class StreamOcrService
{
private const string TessDataPath = @"tessdata/";
public string ExtractFromStream(Stream pdfStream)
{
// Stream input works, but tessdata path constraint remains
using var document = new PdfLoadedDocument(pdfStream);
using var processor = new OCRProcessor(TessDataPath);
processor.Settings.Language = Languages.English;
processor.PerformOCR(document);
var sb = new StringBuilder();
foreach (PdfLoadedPage page in document.Pages)
sb.AppendLine(page.ExtractText());
return sb.ToString();
}
public async Task<string> ExtractFromStreamAsync(Stream pdfStream)
{
// 否 native async — must wrap in Task.Run
return await Task.Run(() => ExtractFromStream(pdfStream));
}
}Imports Syncfusion.OCRProcessor
Imports Syncfusion.Pdf.Parsing
Public Class StreamOcrService
Private Const TessDataPath As String = "tessdata/"
Public Function ExtractFromStream(pdfStream As Stream) As String
' Stream input works, but tessdata path constraint remains
Using document = New PdfLoadedDocument(pdfStream)
Using processor = New OCRProcessor(TessDataPath)
processor.Settings.Language = Languages.English
processor.PerformOCR(document)
Dim sb = New StringBuilder()
For Each page As PdfLoadedPage In document.Pages
sb.AppendLine(page.ExtractText())
Next
Return sb.ToString()
End Using
End Using
End Function
Public Async Function ExtractFromStreamAsync(pdfStream As Stream) As Task(Of String)
' 否 native async — must wrap in Task.Run
Return Await Task.Run(Function() ExtractFromStream(pdfStream))
End Function
End ClassImports Syncfusion.OCRProcessor
Imports Syncfusion.Pdf.Parsing
Public Class StreamOcrService
Private Const TessDataPath As String = "tessdata/"
Public Function ExtractFromStream(pdfStream As Stream) As String
' Stream input works, but tessdata path constraint remains
Using document = New PdfLoadedDocument(pdfStream)
Using processor = New OCRProcessor(TessDataPath)
processor.Settings.Language = Languages.English
processor.PerformOCR(document)
Dim sb = New StringBuilder()
For Each page As PdfLoadedPage In document.Pages
sb.AppendLine(page.ExtractText())
Next
Return sb.ToString()
End Using
End Using
End Function
Public Async Function ExtractFromStreamAsync(pdfStream As Stream) As Task(Of String)
' 否 native async — must wrap in Task.Run
Return Await Task.Run(Function() ExtractFromStream(pdfStream))
End Function
End ClassIronOCR方法:
using IronOcr;
public class StreamOcrService
{
public string ExtractFromStream(Stream pdfStream)
{
using var input = new OcrInput();
input.LoadPdf(pdfStream); // accepts Stream directly
return new IronTesseract().Read(input).Text;
}
public async Task<string> ExtractFromStreamAsync(Stream pdfStream)
{
using var input = new OcrInput();
input.LoadPdf(pdfStream);
var ocr = new IronTesseract();
var result = await ocr.ReadAsync(input); // native async support
return result.Text;
}
}using IronOcr;
public class StreamOcrService
{
public string ExtractFromStream(Stream pdfStream)
{
using var input = new OcrInput();
input.LoadPdf(pdfStream); // accepts Stream directly
return new IronTesseract().Read(input).Text;
}
public async Task<string> ExtractFromStreamAsync(Stream pdfStream)
{
using var input = new OcrInput();
input.LoadPdf(pdfStream);
var ocr = new IronTesseract();
var result = await ocr.ReadAsync(input); // native async support
return result.Text;
}
}Imports IronOcr
Public Class StreamOcrService
Public Function ExtractFromStream(pdfStream As Stream) As String
Using input = New OcrInput()
input.LoadPdf(pdfStream) ' accepts Stream directly
Return New IronTesseract().Read(input).Text
End Using
End Function
Public Async Function ExtractFromStreamAsync(pdfStream As Stream) As Task(Of String)
Using input = New OcrInput()
input.LoadPdf(pdfStream)
Dim ocr = New IronTesseract()
Dim result = Await ocr.ReadAsync(input) ' native async support
Return result.Text
End Using
End Function
End ClassImports IronOcr
Public Class StreamOcrService
Public Function ExtractFromStream(pdfStream As Stream) As String
Using input = New OcrInput()
input.LoadPdf(pdfStream) ' accepts Stream directly
Return New IronTesseract().Read(input).Text
End Using
End Function
Public Async Function ExtractFromStreamAsync(pdfStream As Stream) As Task(Of String)
Using input = New OcrInput()
input.LoadPdf(pdfStream)
Dim ocr = New IronTesseract()
Dim result = Await ocr.ReadAsync(input) ' native async support
Return result.Text
End Using
End Function
End ClassStream,无需中间文件写入。 IronOCR还提供Task.Run()包装。 对于 Web API 控制器、Azure Functions 和其他异步服务模式,这是一个直接适用的 API。 流输入指南记录了所有流加载选项,包括图像流和多页 TIFF 流。 异步 OCR 指南涵盖了取消令牌支持和长时间运行的文档批处理的进度回调。
结构化段落和词语提取
Syncfusion的文本提取模型提供两个级别:通过page.ExtractText()的每页文本。 没有子页面结构——没有单词坐标,没有段落边界,也没有每个词元的置信度分数。 需要按位置定位特定字段或过滤低置信度标记的应用程序必须在连接字符串之上实现自己的解析逻辑。
SyncfusionOCR 方法:
using Syncfusion.OCRProcessor;
using Syncfusion.Pdf.Parsing;
public class StructuredExtractionService
{
private const string TessDataPath = @"tessdata/";
public Dictionary<int, string> ExtractPerPage(string pdfPath)
{
var pageTexts = new Dictionary<int, string>();
using var document = new PdfLoadedDocument(pdfPath);
using var processor = new OCRProcessor(TessDataPath);
processor.Settings.Language = Languages.English;
processor.PerformOCR(document);
// Page-level is the finest granularity available
int pageNum = 1;
foreach (PdfLoadedPage page in document.Pages)
{
pageTexts[pageNum] = page.ExtractText();
pageNum++;
}
return pageTexts;
// 否 word coordinates, no paragraph boundaries, no per-token confidence
}
}using Syncfusion.OCRProcessor;
using Syncfusion.Pdf.Parsing;
public class StructuredExtractionService
{
private const string TessDataPath = @"tessdata/";
public Dictionary<int, string> ExtractPerPage(string pdfPath)
{
var pageTexts = new Dictionary<int, string>();
using var document = new PdfLoadedDocument(pdfPath);
using var processor = new OCRProcessor(TessDataPath);
processor.Settings.Language = Languages.English;
processor.PerformOCR(document);
// Page-level is the finest granularity available
int pageNum = 1;
foreach (PdfLoadedPage page in document.Pages)
{
pageTexts[pageNum] = page.ExtractText();
pageNum++;
}
return pageTexts;
// 否 word coordinates, no paragraph boundaries, no per-token confidence
}
}Imports Syncfusion.OCRProcessor
Imports Syncfusion.Pdf.Parsing
Public Class StructuredExtractionService
Private Const TessDataPath As String = "tessdata/"
Public Function ExtractPerPage(pdfPath As String) As Dictionary(Of Integer, String)
Dim pageTexts = New Dictionary(Of Integer, String)()
Using document = New PdfLoadedDocument(pdfPath)
Using processor = New OCRProcessor(TessDataPath)
processor.Settings.Language = Languages.English
processor.PerformOCR(document)
' Page-level is the finest granularity available
Dim pageNum As Integer = 1
For Each page As PdfLoadedPage In document.Pages
pageTexts(pageNum) = page.ExtractText()
pageNum += 1
Next
End Using
End Using
Return pageTexts
' 否 word coordinates, no paragraph boundaries, no per-token confidence
End Function
End ClassImports Syncfusion.OCRProcessor
Imports Syncfusion.Pdf.Parsing
Public Class StructuredExtractionService
Private Const TessDataPath As String = "tessdata/"
Public Function ExtractPerPage(pdfPath As String) As Dictionary(Of Integer, String)
Dim pageTexts = New Dictionary(Of Integer, String)()
Using document = New PdfLoadedDocument(pdfPath)
Using processor = New OCRProcessor(TessDataPath)
processor.Settings.Language = Languages.English
processor.PerformOCR(document)
' Page-level is the finest granularity available
Dim pageNum As Integer = 1
For Each page As PdfLoadedPage In document.Pages
pageTexts(pageNum) = page.ExtractText()
pageNum += 1
Next
End Using
End Using
Return pageTexts
' 否 word coordinates, no paragraph boundaries, no per-token confidence
End Function
End ClassIronOCR方法:
using IronOcr;
public class StructuredExtractionService
{
public void ExtractWithStructure(string pdfPath)
{
var result = new IronTesseract().Read(pdfPath);
Console.WriteLine($"Overall confidence: {result.Confidence}%");
foreach (var page in result.Pages)
{
Console.WriteLine($"Page {page.PageNumber}: {page.Words.Length} words");
foreach (var paragraph in page.Paragraphs)
{
Console.WriteLine($" Paragraph at ({paragraph.X}, {paragraph.Y}):");
Console.WriteLine($" {paragraph.Text}");
}
}
}
public IEnumerable<string> ExtractHighConfidenceWords(string pdfPath, int minConfidence = 80)
{
var result = new IronTesseract().Read(pdfPath);
// Per-word confidence filtering — not possible with Syncfusion's page-level model
return result.Pages
.SelectMany(p => p.Words)
.Where(w => w.Confidence >= minConfidence)
.Select(w => w.Text);
}
}using IronOcr;
public class StructuredExtractionService
{
public void ExtractWithStructure(string pdfPath)
{
var result = new IronTesseract().Read(pdfPath);
Console.WriteLine($"Overall confidence: {result.Confidence}%");
foreach (var page in result.Pages)
{
Console.WriteLine($"Page {page.PageNumber}: {page.Words.Length} words");
foreach (var paragraph in page.Paragraphs)
{
Console.WriteLine($" Paragraph at ({paragraph.X}, {paragraph.Y}):");
Console.WriteLine($" {paragraph.Text}");
}
}
}
public IEnumerable<string> ExtractHighConfidenceWords(string pdfPath, int minConfidence = 80)
{
var result = new IronTesseract().Read(pdfPath);
// Per-word confidence filtering — not possible with Syncfusion's page-level model
return result.Pages
.SelectMany(p => p.Words)
.Where(w => w.Confidence >= minConfidence)
.Select(w => w.Text);
}
}Imports IronOcr
Public Class StructuredExtractionService
Public Sub ExtractWithStructure(pdfPath As String)
Dim result = New IronTesseract().Read(pdfPath)
Console.WriteLine($"Overall confidence: {result.Confidence}%")
For Each page In result.Pages
Console.WriteLine($"Page {page.PageNumber}: {page.Words.Length} words")
For Each paragraph In page.Paragraphs
Console.WriteLine($" Paragraph at ({paragraph.X}, {paragraph.Y}):")
Console.WriteLine($" {paragraph.Text}")
Next
Next
End Sub
Public Function ExtractHighConfidenceWords(pdfPath As String, Optional minConfidence As Integer = 80) As IEnumerable(Of String)
Dim result = New IronTesseract().Read(pdfPath)
' Per-word confidence filtering — not possible with Syncfusion's page-level model
Return result.Pages _
.SelectMany(Function(p) p.Words) _
.Where(Function(w) w.Confidence >= minConfidence) _
.Select(Function(w) w.Text)
End Function
End ClassImports IronOcr
Public Class StructuredExtractionService
Public Sub ExtractWithStructure(pdfPath As String)
Dim result = New IronTesseract().Read(pdfPath)
Console.WriteLine($"Overall confidence: {result.Confidence}%")
For Each page In result.Pages
Console.WriteLine($"Page {page.PageNumber}: {page.Words.Length} words")
For Each paragraph In page.Paragraphs
Console.WriteLine($" Paragraph at ({paragraph.X}, {paragraph.Y}):")
Console.WriteLine($" {paragraph.Text}")
Next
Next
End Sub
Public Function ExtractHighConfidenceWords(pdfPath As String, Optional minConfidence As Integer = 80) As IEnumerable(Of String)
Dim result = New IronTesseract().Read(pdfPath)
' Per-word confidence filtering — not possible with Syncfusion's page-level model
Return result.Pages _
.SelectMany(Function(p) p.Words) _
.Where(Function(w) w.Confidence >= minConfidence) _
.Select(Function(w) w.Text)
End Function
End Class结构化输出模型显示段落、行、单词和字符,并带有边界框坐标和单独的置信度分数。 这对于发票字段提取、表单解析和文档分类尤其有用——在这些工作流程中,知道文本在页面上的位置与知道文本的内容同样重要。 读取结果指南和OcrResult API 参考文档完整地描述了对象图。
并行执行的批量文档处理
高容量 OCR 服务可同时处理数十或数百份文档。 Syncfusion未说明OCRProcessor为线程安全,这迫使顺序处理或要求开发人员实施自己的实例池。 IronOCR实例可以安全地每线程创建,允许直接与Parallel.ForEach或PLINQ一起使用,而无需额外同步。
SyncfusionOCR 方法:
using Syncfusion.OCRProcessor;
using Syncfusion.Pdf.Parsing;
public class BatchOcrService
{
private const string TessDataPath = @"tessdata/";
public Dictionary<string, string> ProcessBatch(IEnumerable<string> pdfPaths)
{
var results = new Dictionary<string, string>();
// Sequential processing — OCRProcessor thread safety not guaranteed
foreach (var path in pdfPaths)
{
using var document = new PdfLoadedDocument(path);
using var processor = new OCRProcessor(TessDataPath);
processor.Settings.Language = Languages.English;
processor.PerformOCR(document);
var sb = new StringBuilder();
foreach (PdfLoadedPage page in document.Pages)
sb.AppendLine(page.ExtractText());
results[path] = sb.ToString();
}
return results;
}
}using Syncfusion.OCRProcessor;
using Syncfusion.Pdf.Parsing;
public class BatchOcrService
{
private const string TessDataPath = @"tessdata/";
public Dictionary<string, string> ProcessBatch(IEnumerable<string> pdfPaths)
{
var results = new Dictionary<string, string>();
// Sequential processing — OCRProcessor thread safety not guaranteed
foreach (var path in pdfPaths)
{
using var document = new PdfLoadedDocument(path);
using var processor = new OCRProcessor(TessDataPath);
processor.Settings.Language = Languages.English;
processor.PerformOCR(document);
var sb = new StringBuilder();
foreach (PdfLoadedPage page in document.Pages)
sb.AppendLine(page.ExtractText());
results[path] = sb.ToString();
}
return results;
}
}Imports Syncfusion.OCRProcessor
Imports Syncfusion.Pdf.Parsing
Public Class BatchOcrService
Private Const TessDataPath As String = "tessdata/"
Public Function ProcessBatch(pdfPaths As IEnumerable(Of String)) As Dictionary(Of String, String)
Dim results = New Dictionary(Of String, String)()
' Sequential processing — OCRProcessor thread safety not guaranteed
For Each path In pdfPaths
Using document = New PdfLoadedDocument(path)
Using processor = New OCRProcessor(TessDataPath)
processor.Settings.Language = Languages.English
processor.PerformOCR(document)
Dim sb = New StringBuilder()
For Each page As PdfLoadedPage In document.Pages
sb.AppendLine(page.ExtractText())
Next
results(path) = sb.ToString()
End Using
End Using
Next
Return results
End Function
End ClassImports Syncfusion.OCRProcessor
Imports Syncfusion.Pdf.Parsing
Public Class BatchOcrService
Private Const TessDataPath As String = "tessdata/"
Public Function ProcessBatch(pdfPaths As IEnumerable(Of String)) As Dictionary(Of String, String)
Dim results = New Dictionary(Of String, String)()
' Sequential processing — OCRProcessor thread safety not guaranteed
For Each path In pdfPaths
Using document = New PdfLoadedDocument(path)
Using processor = New OCRProcessor(TessDataPath)
processor.Settings.Language = Languages.English
processor.PerformOCR(document)
Dim sb = New StringBuilder()
For Each page As PdfLoadedPage In document.Pages
sb.AppendLine(page.ExtractText())
Next
results(path) = sb.ToString()
End Using
End Using
Next
Return results
End Function
End ClassIronOCR方法:
using IronOcr;
public class BatchOcrService
{
public Dictionary<string, string> ProcessBatch(IEnumerable<string> pdfPaths)
{
var results = new ConcurrentDictionary<string, string>();
// Parallel processing — IronTesseract is safe per-thread
Parallel.ForEach(pdfPaths, pdfPath =>
{
var ocr = new IronTesseract(); // one instance per thread
var text = ocr.Read(pdfPath).Text;
results[pdfPath] = text;
});
return new Dictionary<string, string>(results);
}
}using IronOcr;
public class BatchOcrService
{
public Dictionary<string, string> ProcessBatch(IEnumerable<string> pdfPaths)
{
var results = new ConcurrentDictionary<string, string>();
// Parallel processing — IronTesseract is safe per-thread
Parallel.ForEach(pdfPaths, pdfPath =>
{
var ocr = new IronTesseract(); // one instance per thread
var text = ocr.Read(pdfPath).Text;
results[pdfPath] = text;
});
return new Dictionary<string, string>(results);
}
}Imports IronOcr
Imports System.Collections.Concurrent
Public Class BatchOcrService
Public Function ProcessBatch(pdfPaths As IEnumerable(Of String)) As Dictionary(Of String, String)
Dim results = New ConcurrentDictionary(Of String, String)()
' Parallel processing — IronTesseract is safe per-thread
Parallel.ForEach(pdfPaths, Sub(pdfPath)
Dim ocr = New IronTesseract() ' one instance per thread
Dim text = ocr.Read(pdfPath).Text
results(pdfPath) = text
End Sub)
Return New Dictionary(Of String, String)(results)
End Function
End ClassImports IronOcr
Imports System.Collections.Concurrent
Public Class BatchOcrService
Public Function ProcessBatch(pdfPaths As IEnumerable(Of String)) As Dictionary(Of String, String)
Dim results = New ConcurrentDictionary(Of String, String)()
' Parallel processing — IronTesseract is safe per-thread
Parallel.ForEach(pdfPaths, Sub(pdfPath)
Dim ocr = New IronTesseract() ' one instance per thread
Dim text = ocr.Read(pdfPath).Text
results(pdfPath) = text
End Sub)
Return New Dictionary(Of String, String)(results)
End Function
End Class为并行处理每线程创建一个IronTesseract实例是记录在案的模式。 无需共享状态,无需锁争用,无需实例池基础设施。 多线程示例展示了典型文档批处理大小的吞吐量基准测试,速度优化指南涵盖了对延迟敏感的工作负载的引擎配置选项。
Syncfusion OCRAPI 到IronOCR映射参考
| Syncfusion OCR | IronOCR当量 | 备注 |
|---|---|---|
Syncfusion.PDF.OCR.Net.Core | IronOcr | 替换NuGet包 |
Syncfusion.OCRProcessor | IronOcr | 单一命名空间 |
Syncfusion.Pdf | 消除 | 不再需要 |
Syncfusion.Pdf.Parsing | 消除 | 不再需要 |
SyncfusionLicenseProvider.RegisterLicense() | IronOcr.License.LicenseKey = | 字符串赋值,无Suite注册 |
new OCRProcessor(tessdataPath) | new IronTesseract() | 没有路径参数 |
PdfLoadedDocument(filePath) | 直接将路径传递给ocr.Read(path) | 或使用LoadPdf() |
PdfLoadedDocument(stream) | input.LoadPdf(stream) | 流媒体支持是直接的 |
processor.Settings.Language = Languages.English | ocr.Language = OcrLanguage.English | OcrLanguage枚举 |
Languages.English | Languages.French | ocr.Language = OcrLanguage.English; ocr.AddSecondaryLanguage(OcrLanguage.French) | 加法模式取代位标志 |
processor.PerformOCR(document) | ocr.Read(input) | 直接返回OcrResult |
page.ExtractText() | result.Pages[i].Text | 无需循环即可读取全文 |
document.Pages迭代 | result.Pages[]数组 | 包括段落、单词、字符 |
OCR后document.Save(outputStream) | result.SaveAsSearchablePdf(path) | 专用方法 |
| Tessdata 验证逻辑 | 完全删除 | 没有 tessdata 可供验证 |
| 手动 tessdata 路径常量 | 完全删除 | IronOCR不需要 |
PdfBitmap图像到PDF转换 | input.LoadImage(imagePath) | 图像OCR无需PDF往返转换 |
| 无预处理 API | input.Deskew(), input.DeNoise(), input.Contrast() | 内置于OcrInput |
常见迁移问题和解决方案
问题 1:切换软件包后找不到 Tessdata 目录
Syncfusion OCR: tessdata 目录验证检查被编写为启动时或每次调用时的保护措施。 在移除Syncfusion并安装IronOCR后,此验证代码仍然可以编译(它使用System.IO,而不是Syncfusion命名空间)但是现在保护一个不再存在的操作。 保留这段代码是死代码,可能会让以后的开发人员感到困惑。
解决方案:完全删除所有tessdata验证逻辑。 移除File.Exists(Path.Combine(TessDataPath, ...))检查以及任何启动验证方法。 IronOCR不会抛出与 tessdata 相关的异常,因为不存在缺失的 tessdata:
// Delete these entirely — they have no equivalent in IronOCR
// private const string TessDataPath = @"tessdata/";
// private bool ValidateTessdata() { ... }
// The only error handling needed after migration:
try
{
return new IronTesseract().Read(pdfPath).Text;
}
catch (FileNotFoundException)
{
throw new ArgumentException($"PDF file not found: {pdfPath}");
}// Delete these entirely — they have no equivalent in IronOCR
// private const string TessDataPath = @"tessdata/";
// private bool ValidateTessdata() { ... }
// The only error handling needed after migration:
try
{
return new IronTesseract().Read(pdfPath).Text;
}
catch (FileNotFoundException)
{
throw new ArgumentException($"PDF file not found: {pdfPath}");
}Try
Return New IronTesseract().Read(pdfPath).Text
Catch ex As FileNotFoundException
Throw New ArgumentException($"PDF file not found: {pdfPath}")
End TryTry
Return New IronTesseract().Read(pdfPath).Text
Catch ex As FileNotFoundException
Throw New ArgumentException($"PDF file not found: {pdfPath}")
End Try问题 2:运行时语言文件不可用
Syncfusion OCR:语言CopyToOutputDirectory,并由构建系统复制。 在项目中移除tessdata文件夹后,与语言相关的CI步骤和.csproj条目可能仍引用已删除的文件,导致构建警告或管道失败。
解决方案:从.csproj文件和CI管道定义中移除所有与tessdata相关的条目。 改为以NuGet包的形式安装语言包:
# Languages install as NuGet packages — no manual file management
dotnet add package IronOcr.Languages.French
dotnet add package IronOcr.Languages.German
dotnet add package IronOcr.Languages.ChineseSimplified
// Language configuration after migration
var ocr = new IronTesseract();
ocr.Language = OcrLanguage.French;
ocr.AddSecondaryLanguage(OcrLanguage.German);
var result = ocr.Read("multilingual-report.pdf");// Language configuration after migration
var ocr = new IronTesseract();
ocr.Language = OcrLanguage.French;
ocr.AddSecondaryLanguage(OcrLanguage.German);
var result = ocr.Read("multilingual-report.pdf");' Language configuration after migration
Dim ocr As New IronTesseract()
ocr.Language = OcrLanguage.French
ocr.AddSecondaryLanguage(OcrLanguage.German)
Dim result = ocr.Read("multilingual-report.pdf")' Language configuration after migration
Dim ocr As New IronTesseract()
ocr.Language = OcrLanguage.French
ocr.AddSecondaryLanguage(OcrLanguage.German)
Dim result = ocr.Read("multilingual-report.pdf")多语言指南涵盖语言包安装和所有125+支持语言的OcrLanguage枚举值。
问题3:可搜索PDF输出字节顺序不同
Syncfusion OCR:通过在document.Save(stream)生成可搜索PDF。 一些下游字节数组使用者可能被编写为期望 Syncfusion 的特定 PDF 结构、元数据字段或生产者字符串。
**解决方案:**IronOCR的SaveAsSearchablePdf()生成带有文本层的标准PDF。 使用下游用户(PDF 查看器、搜索索引、归档系统)测试输出,以验证兼容性。 如果需要逐字节完全相同的输出,则比较文本可提取性(而不是原始字节)的过渡性测试是合适的验收标准:
// Verify the searchable PDF contains the expected text
var result = new IronTesseract().Read("scanned.pdf");
result.SaveAsSearchablePdf("output-searchable.pdf");
// Validation: confirm text layer is present and readable
var verificationText = new IronTesseract().Read("output-searchable.pdf").Text;
Assert.True(verificationText.Contains("expected content"));// Verify the searchable PDF contains the expected text
var result = new IronTesseract().Read("scanned.pdf");
result.SaveAsSearchablePdf("output-searchable.pdf");
// Validation: confirm text layer is present and readable
var verificationText = new IronTesseract().Read("output-searchable.pdf").Text;
Assert.True(verificationText.Contains("expected content"));' Verify the searchable PDF contains the expected text
Dim result = New IronTesseract().Read("scanned.pdf")
result.SaveAsSearchablePdf("output-searchable.pdf")
' Validation: confirm text layer is present and readable
Dim verificationText = New IronTesseract().Read("output-searchable.pdf").Text
Assert.True(verificationText.Contains("expected content"))' Verify the searchable PDF contains the expected text
Dim result = New IronTesseract().Read("scanned.pdf")
result.SaveAsSearchablePdf("output-searchable.pdf")
' Validation: confirm text layer is present and readable
Dim verificationText = New IronTesseract().Read("output-searchable.pdf").Text
Assert.True(verificationText.Contains("expected content"))问题 4:迁移尝试后 Docker 镜像大小增加
Syncfusion OCR:一些团队在测试期间会尝试迁移,同时将 tessdata 文件保留在 Docker 镜像中,以防万一。 这样一来,图像中既包含了 tessdata 层,也包含了IronOCR包,从而不必要地增加了图像大小。
解决方案:在构建迁移图像之前,从Dockerfile中删除tessdataCOPY层。 IronOCR软件包是独立的。 Docker部署指南提供了针对Alpine、Debian和Ubuntu目标平台的经过验证的基础镜像和配置:
# 消除 this layer entirely after migration
# COPY tessdata/ /app/tessdata/
#IronOCRrequires only the standard .NET runtime
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "YourService.dll"]
问题 5:两步 PerformOCR / ExtractText 模式没有直接对应模式
Syncfusion OCR:一些调用代码在方法之间传递ExtractText() - 依赖于文档对象的有状态变异。 该模式在IronOCR中不存在,因为Read()返回一个自包含的结果对象。
解决方案:重构任何拆分OCR/提取模式为单个方法,该方法接受文件路径或流并返回OcrResult。 结果对象包含所有内容——文本、页面、段落、置信度,以及保存为可搜索 PDF 的功能:
// Replace split PerformOCR / ExtractText pattern
public OcrResult ProcessDocument(string pdfPath)
{
// One call, immutable result, all data available
return new IronTesseract().Read(pdfPath);
}
// Callers decide what they need from the result
var result = service.ProcessDocument("contract.pdf");
var fullText = result.Text;
var confidence = result.Confidence;
result.SaveAsSearchablePdf("contract-searchable.pdf");// Replace split PerformOCR / ExtractText pattern
public OcrResult ProcessDocument(string pdfPath)
{
// One call, immutable result, all data available
return new IronTesseract().Read(pdfPath);
}
// Callers decide what they need from the result
var result = service.ProcessDocument("contract.pdf");
var fullText = result.Text;
var confidence = result.Confidence;
result.SaveAsSearchablePdf("contract-searchable.pdf");Public Function ProcessDocument(pdfPath As String) As OcrResult
' One call, immutable result, all data available
Return New IronTesseract().Read(pdfPath)
End Function
' Callers decide what they need from the result
Dim result = service.ProcessDocument("contract.pdf")
Dim fullText = result.Text
Dim confidence = result.Confidence
result.SaveAsSearchablePdf("contract-searchable.pdf")Public Function ProcessDocument(pdfPath As String) As OcrResult
' One call, immutable result, all data available
Return New IronTesseract().Read(pdfPath)
End Function
' Callers decide what they need from the result
Dim result = service.ProcessDocument("contract.pdf")
Dim fullText = result.Text
Dim confidence = result.Confidence
result.SaveAsSearchablePdf("contract-searchable.pdf")问题 6:迁移后社区许可证注册代码仍然保留
Syncfusion OCR:应用程序启动时的Syncfusion.Licensing.SyncfusionLicenseProvider.RegisterLicense()调用注册了套件许可证。 此调用通常在Startup.cs或静态初始化器中。 移除Syncfusion包后,这行代码会导致编译错误。
解决方案:删除SyncfusionLicenseProvider.RegisterLicense()调用并替换为IronOCR许可证初始化。 同时删除任何社区许可资格逻辑、合规性文档参考或有关收入和员工人数门槛的评论——这些概念均不适用于IronOCR:
// 消除 (causes compile error after package removal)
// Syncfusion.Licensing.SyncfusionLicenseProvider.RegisterLicense("SYNCFUSION-KEY");
// Add at application startup
IronOcr.License.LicenseKey = "YOUR-IRONOCR-KEY";// 消除 (causes compile error after package removal)
// Syncfusion.Licensing.SyncfusionLicenseProvider.RegisterLicense("SYNCFUSION-KEY");
// Add at application startup
IronOcr.License.LicenseKey = "YOUR-IRONOCR-KEY";' 消除 (causes compile error after package removal)
' Syncfusion.Licensing.SyncfusionLicenseProvider.RegisterLicense("SYNCFUSION-KEY");
' Add at application startup
IronOcr.License.LicenseKey = "YOUR-IRONOCR-KEY"' 消除 (causes compile error after package removal)
' Syncfusion.Licensing.SyncfusionLicenseProvider.RegisterLicense("SYNCFUSION-KEY");
' Add at application startup
IronOcr.License.LicenseKey = "YOUR-IRONOCR-KEY"Syncfusion OCR迁移检查清单
迁移前
在进行任何更改之前,请审核代码库,以确定所有Syncfusion OCR 的使用情况:
# Find all Syncfusion namespace imports
grep -r "using Syncfusion" --include="*.cs" .
# Find OCRProcessor usage
grep -r "OCRProcessor\|PerformOCR\|PdfLoadedDocument\|ExtractText" --include="*.cs" .
# Find tessdata path references
grep -r "TessDataPath\|tessdata\|traineddata" --include="*.cs" .
# Find Syncfusion license registration
grep -r "SyncfusionLicenseProvider\|RegisterLicense" --include="*.cs" .
# Find csproj tessdata copy rules
grep -r "tessdata\|traineddata" --include="*.csproj" .
# Find Dockerfile tessdata layers
grep -r "tessdata" Dockerfile* docker-compose*.yml .
在编写任何代码之前,先清点结果。 注意哪些文件包含 OCR 调用,哪些文件包含 tessdata 验证,以及哪些管道定义引用 tessdata 文件夹。
代码迁移
- 从所有
Syncfusion.Pdf.Net.Core和相关包。 - 在每个执行OCR的项目中运行
dotnet add package IronOcr。 - 通过NuGet安装任意非英语语言所用的语言包:
dotnet add package IronOcr.Languages.[Language]。 - 从所有服务类中删除
private const string TessDataPath常数。 - 删除所有tessdata验证方法(
ValidateTessdata()和类似保护机制)。 - 在应用程序启动时用
SyncfusionLicenseProvider.RegisterLicense()。 - 将
using Syncfusion.OCRProcessor; using Syncfusion.Pdf; using Syncfusion.Pdf.Parsing;替换为using IronOcr;。 - 将每个
new IronTesseract()。 - 将
ocr.Read(path).Text。 - 替换 Syncfusion 的按位语言标志(
Languages.English)。 | Languages.French plusocr.AddSecondaryLanguage()调用。 - 在
document.Save(stream)以获得可搜索的PDF输出。 - 用直接
ocr.Read(imagePath)替换图像到PDF转换往返。 - 从所有
.csproj文件中删除tessdataCopyToOutputDirectory条目。 - 从所有 CI/CD 流水线定义中删除 tessdata 下载步骤。
- 从所有Dockerfiles中删除tessdata
COPY层。
后迁移
- 验证 PDF OCR 在迁移前使用的相同样本文档上是否能生成预期的文本内容。
- 验证图像 OCR(JPG、PNG、BMP)无需任何 PDF 转换步骤即可正常工作。
- 使用已安装的NuGet语言包确认多语言文档能够被正确识别。
- 通过在 PDF 查看器中打开生成的文件并确认文本选择和搜索功能来测试可搜索的 PDF 输出。
- 在根据更新后的 Dockerfile 构建的全新 Docker 容器中运行应用程序,以确认不会发生与 tessdata 相关的启动错误。
- 确认应用程序在没有
Syncfusion.Licensing调用或任何Syncfusion命名空间引用的情况下启动。 - 验证
result.Confidence返回一个合理的值(通常是80-99%用于干净的文档)以确认OCR引擎处于活动状态。 - 通过运行并发 OCR 调用来测试并行批处理,并验证没有线程异常或损坏的结果。
- 比较迁移前后低质量或旋转扫描件的文本提取准确率,并指出自动预处理流程带来的改进。
迁移到IronOCR的主要优势
部署复杂性降至一个NuGet包。迁移后,每个环境 - 开发者工作站、CI运行器、预发布容器、生产服务器 - 仅需要一件事情:由构建系统还原的IronOcr NuGet包。 没有tessdata文件夹。 无需配置文件系统路径。 无需下载语言文件脚本。无需携带 100-500 MB 二进制数据的 Dockerfile 层。 容器镜像更小,CI 流水线更简单,新环境在首次构建时无需人工干预即可正确配置。
许可成本变得可预测且无需经常发生。一次性永久许可购买取代了以往每年按开发者续费的模式。 一个由五名开发人员组成的团队购买IronOCR Professional (2,999 美元)后,即可无限期拥有该库,并包含一年的更新服务。 没有收入门槛需要监控,没有员工人数限制需要跟踪,没有审计规定,也没有合规文件需要维护。 增长事件——新承包商、大合同、融资轮——不会触发许可审查。
OCR管道在没有外部依赖的情况下处理退化文档。纠偏、去噪、对比度增强、二值化和分辨率缩放可作为在OcrInput上的方法使用。 无需单独的图像库。 以前需要使用 System.Drawing 或 SkiaSharp 进行预处理才能处理的轻微旋转、扫描噪声或低对比度的文档,现在可以在同一个IronOCR调用中处理。 图像质量校正指南和预处理功能页面记录了所有可用的滤波器及其对识别准确率的影响。
结构化输出使字段级文档智能成为可能。OcrResult对象揭示完整的文档结构 - 页面、段落、行、单词和字符 - 带有边界框坐标和每个标记的置信分数。 以前需要解析连接的文本字符串来查找字段边界的应用程序,现在可以直接使用段落和单词坐标数据。 发票处理、表单提取和文档分类工作流程可以访问 Syncfusion 页面级模型无法提供的空间信息。PDF OCR 用例页面涵盖了常见的文档智能模式。
并行批处理处理不靠基础设施扩展。每线程创建一个IronTesseract实例是完整的线程策略 - 没有实例池、没有信号量管理、没有顺序处理限制。 每小时处理500个文档的批处理服务能够使用Parallel.ForEach和单行同步来饱和可用CPU核心。 这种自包含式引擎架构意味着每个线程独立运行,没有共享的可变状态。
超过 125 种语言无需二进制文件管理即可使用。每个语言包都通过标准包管理器以NuGet包的形式安装。 版本管理、更新获取和依赖关系解析由管理所有其他项目依赖关系的同一工具处理。 为服务添加日语或阿拉伯语OCR只需一个dotnet add package命令,而不是从GitHub仓库手动下载后更新部署管道。 语言索引列出了所有支持的脚本及其安装命令。
相关文章


