如何在 .NET Maui 中执行 OCR
本指南将引导.NET开发人员完成从LEADTOOLS OCR到IronOCR的完整迁移。 它涵盖了从NuGet包替换到完整代码迁移的每一个步骤,并提供了受 LEADTOOLS 初始化仪式、多命名空间结构和基于文件的许可证部署模型影响最大的模式的前后对比示例。
为什么要从LEADTOOLS OCR迁移?
LEADTOOLS OCR 集成于一个Enterprise图像平台中,该平台的历史可以追溯到 1990 年,其 API 也反映了这一历史渊源。 最低工作配置需要四个NuGet包、四个命名空间、部署到已知路径的两个物理许可证文件、在引擎之前初始化的编解码器层、从三个选项中选择的引擎类型,以及将运行时二进制文件加载到内存中的阻塞启动调用。 所有这些操作都在识别出单个字符之前执行。 迁移到IronOCR 的团队可以消除整个层级——一个软件包、一个命名空间、一行代码即可设置许可证密钥。
**基于文件的许可证部署在容器中中断。**LEADTOOLS需要两个物理文件——LEADTOOLS.LIC.KEY——在运行应用程序的每台机器上的特定路径中可读。 在 Docker 容器中,这些文件要么必须嵌入到镜像中(在层历史记录中公开它们),要么必须在运行时挂载(需要在每个编排环境中进行卷协调)。 Azure Functions 和 AWS Lambda 没有提供许可证文件部署的机制,只能通过变通方法来实现。 CI/CD 流水线需要文件位于启动调用使用的确切路径,否则应用程序会在处理任何文档之前抛出异常。 IronOCR将这两个文件替换为适合环境变量、Kubernetes 密钥或 Azure Key Vault 引用的字符串。
**一个任务的四个包。**一个可行的LEADTOOLS OCR项目至少需要Leadtools, Leadtools.Ocr, Leadtools.Forms.DocumentWriters。 受密码保护的PDF支持需要一个可能未包含在购买的捆绑包中的额外Leadtools.Pdf模块。 每个软件包必须存在、兼容且版本一致。 IronOCR以一个NuGet包的形式提供。 所有功能——原生 PDF 输入、可搜索 PDF 输出、预处理、条形码读取——均已包含。
**引擎生命周期创建维护面。**LEADTOOLS将OCR引擎包装在一个Dispose()之前,否则应用程序会产生错误。 LEADTOOLS批处理器的生产实施通常包括在文档块之间调用GC.Collect() / RasterImage实例。 IronOCR使用标准using块。 OcrInput是唯一需要处理的对象。
生产环境中出现捆绑包混淆问题。LEADTOOLS不公开定价。 文档影像 SDK(包含 OCR 功能)预计每位开发者每年需花费 3,000 至 8,000 美元。 没有PDF模块购买OCR模块的团队在生产中对受密码保护的文档运行RasterSupport.IsLocked()返回true时发现这一差距。 OmniPage 引擎的准确性级别需要在购买 LEADTOOLS 之外单独购买 Kofax 许可协议。 IronOCR为每个级别都提供了所有功能的授权。 没有二级供应商关系,也没有单独的加密文档模块。
初始化成本影响冷启动。engine.Startup()是一个阻塞调用,将运行时文件加载到内存中。 在冷启动延迟至关重要的无服务器环境中(例如 Azure Functions、AWS Lambda),在进行任何识别工作之前需要 500 至 2000 毫秒的阻塞初始化是一个结构性问题。 IronOCR使用延迟初始化。 IronTesseract实例在第一次使用时初始化,并且在同一进程中的后续调用完全不支付初始化成本。
基本问题
LEADTOOLS 需要四个命名空间、四个NuGet包,并且在识别字符之前必须执行启动仪式:
// LEADTOOLS: Four namespaces, four packages, six steps before recognition
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;
using Leadtools.Forms.DocumentWriters;
RasterSupport.SetLicense(licPath, File.ReadAllText(keyPath)); // Step 1: two files on disk
var codecs = new RasterCodecs(); // Step 2: codec layer
var engine = OcrEngineManager.CreateEngine(OcrEngineType.LEAD); // Step 3: engine factory
engine.Startup(codecs, null, null, runtimePath); // Step 4: blocking startup
// ... still need to create document, add page, call Recognize(), extract text
// IronOCR: One namespace, one package, one line
using IronOcr;
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var text = new IronTesseract().Read("document.jpg").Text;
IronOCR与 LEADTOOLS OCR:功能对比
下表直接映射了两个库之间的功能。
| 特征 | LEADTOOLS OCR | IronOCR |
|---|---|---|
| 需要NuGet包 | 最少4个(PDF格式需要更多) | 1 (IronOcr) |
| 许可机制 | .LIC + .LIC.KEY文件对 | 字符串键 |
| 许可证部署 | 每台生产机器上的文件 | 环境变量或配置 |
| 定价模式 | 每位开发商每年 3,000 美元至 15,000 美元以上(预估) | $999–$2,399一次性永久 |
| 引擎初始化 | 手动Startup()与运行时路径 | 自动的,懒惰的 |
| 发动机关闭 | 手动Dispose()之前 | 不要求 |
| 编解码器层 | 所有图像加载都需要RasterCodecs | 不要求 |
| PDF 输入 | 逐页光栅化循环 | 本机LoadPdf() |
| 受密码保护的PDF | 需要单独的Leadtools.Pdf模块 | 内置Password参数 |
| 可搜索的 PDF 输出 | DocumentWriter + PdfDocumentOptions + document.Save() | result.SaveAsSearchablePdf() |
| 预处理 | 单独的命令类 (DeskewCommand, DespeckleCommand, 等) | OcrInput上的内置过滤方法 |
| 多页 TIFF 文件 | 使用CodecsLoadByteOrder手动帧迭代 | input.LoadImageFrames() |
| 结构化输出 | 页面和区域级别 | 页码、段落、行号、单词、字符及其坐标 |
| 置信度评分 | OcrPageRecognizeStatus枚举 | result.Confidence作为百分比 |
| 条形码读取 | 独立的 LEADTOOLS 条形码模块 | 内置 (ReadBarCodes = true) |
| 线程安全 | 需要精心管理 | 完整(一线程一个IronTesseract) |
| 支持的语言 | 60–120(取决于发动机) | 通过NuGet语言包提供 125 多个语言包 |
| 语言部署 | tessdata files or engine-bundled files | 按语言NuGet包 |
| 跨平台 | 支持按平台运行时配置 | Windows、Linux、macOS、Docker、Azure、AWS |
| Docker部署 | .KEY文件必须安装或烘焙 | 标准dotnet publish |
| 商业支持 | 是 | 是 |
快速入门:LEADTOOLS OCR 到IronOCR 的迁移
步骤 1:替换 NuGet 软件包
删除所有 LEADTOOLS 软件包:
dotnet remove package Leadtools
dotnet remove package Leadtools.Ocr
dotnet remove package Leadtools.Codecs
dotnet remove package Leadtools.Forms.DocumentWriters
dotnet remove package Leadtools.Pdf
从NuGet安装IronOCR :
步骤 2:更新命名空间
将所有LEADTOOLS using指令替换为单一IronOCR导入:
// Before (LEADTOOLS)
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;
using Leadtools.Forms.DocumentWriters;
// After (IronOCR)
using IronOcr;
步骤 3:初始化许可证
删除所有.LIC / .LIC.KEY文件引用。 在应用程序启动时添加IronOCR许可证密钥:
// Single line replaces the entire file-based license setup
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
// Production pattern: pull from environment variable or secrets manager
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE");
许可证密钥可以存储在任何标准的.NET机密管理模式中——appsettings.json, Azure密钥保管库, AWS Secrets Manager, 或Kubernetes秘密。 应用程序二进制文件无需与任何其他文件一同部署。
代码迁移示例
发动机启动和关闭生命周期移除
LEADTOOLS 强制要求采用严格的服务类模式,因为引擎生命周期必须进行显式管理。 构造函数启动引擎,Dispose()之前的代码路径都会产生运行错误。
LEADTOOLS OCR 方法:
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;
// Service class required purely to manage engine lifecycle
public class LeadtoolsOcrService : IDisposable
{
private IOcrEngine _engine;
private RasterCodecs _codecs;
private readonly string _runtimePath;
public LeadtoolsOcrService(string licPath, string keyPath, string runtimePath)
{
_runtimePath = runtimePath;
// License setup — two files, both must be present
RasterSupport.SetLicense(licPath, File.ReadAllText(keyPath));
// Codec layer — required before engine creation
_codecs = new RasterCodecs();
// Engine factory — engine type determines capability and cost
_engine = OcrEngineManager.CreateEngine(OcrEngineType.LEAD);
// Blocking startup — loads runtime into memory (500–2000ms)
_engine.Startup(_codecs, null, null, _runtimePath);
}
public bool IsReady => _engine?.IsStarted ?? false;
public string Process(string imagePath)
{
if (!IsReady)
throw new InvalidOperationException("Engine not started");
using var image = _codecs.Load(imagePath);
using var doc = _engine.DocumentManager.CreateDocument();
var page = doc.Pages.AddPage(image, null);
page.Recognize(null);
return page.GetText(-1);
}
public void Dispose()
{
// Order is mandatory: Shutdown before Dispose
if (_engine?.IsStarted == true)
_engine.Shutdown();
_engine?.Dispose();
_codecs?.Dispose();
}
}
IronOCR方法:
using IronOcr;
// No lifecycle management needed — the service class becomes trivial
public class OcrService
{
private readonly IronTesseract _ocr = new IronTesseract();
// Always ready — no IsStarted check needed
public string Process(string imagePath) => _ocr.Read(imagePath).Text;
// No Dispose() needed for the engine
// No Startup(), no Shutdown(), no codec layer
}
IronTesseract实例在第一次使用时初始化。 无构造函数参数,无运行时路径,无Startup()调用。 上面的服务类完全不需要实现using模式处理自己的清理。 IronTesseract 设置指南涵盖了配置选项,包括语言选择和生产场景的性能调优。
多帧 TIFF 批量处理
LEADTOOLS通过查询编解码器以获取总帧计数来处理多帧TIFF文件,然后在每个lastPage参数迭代。 每帧图像都必须手动删除,否则内存会不断累积。
LEADTOOLS OCR 方法:
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;
public class LeadtoolsTiffBatchService
{
private readonly IOcrEngine _engine;
private readonly RasterCodecs _codecs;
public List<string> ProcessMultiFrameTiff(string tiffPath)
{
var pageTexts = new List<string>();
// Must query page count before iterating
var info = _codecs.GetInformation(tiffPath, true);
int frameCount = info.TotalPages;
using var document = _engine.DocumentManager.CreateDocument();
for (int frameNum = 1; frameNum <= frameCount; frameNum++)
{
// Load one frame at a time — must specify firstPage/lastPage
using var frameImage = _codecs.Load(
tiffPath,
0, // bitsPerPixel
CodecsLoadByteOrder.BgrOrGray,
frameNum, // firstPage
frameNum); // lastPage
var page = document.Pages.AddPage(frameImage, null);
page.Recognize(null);
pageTexts.Add(page.GetText(-1));
// GC pressure accumulates if disposal is missed on any frame
}
return pageTexts;
}
public Dictionary<string, List<string>> ProcessTiffDirectory(string directoryPath)
{
var results = new Dictionary<string, List<string>>();
foreach (var tiffFile in Directory.GetFiles(directoryPath, "*.tiff"))
{
results[tiffFile] = ProcessMultiFrameTiff(tiffFile);
// Manual GC between files to prevent memory growth
GC.Collect();
GC.WaitForPendingFinalizers();
}
return results;
}
}
IronOCR方法:
using IronOcr;
public class TiffBatchService
{
private readonly IronTesseract _ocr = new IronTesseract();
public List<string> ProcessMultiFrameTiff(string tiffPath)
{
using var input = new OcrInput();
input.LoadImageFrames(tiffPath); // All frames loaded automatically
var result = _ocr.Read(input);
// Per-frame text available through result.Pages
return result.Pages.Select(p => p.Text).ToList();
}
public Dictionary<string, List<string>> ProcessTiffDirectory(string directoryPath)
{
var results = new Dictionary<string, List<string>>();
foreach (var tiffFile in Directory.GetFiles(directoryPath, "*.tiff"))
{
results[tiffFile] = ProcessMultiFrameTiff(tiffFile);
}
return results;
}
// Parallel processing across files — thread-safe out of the box
public Dictionary<string, List<string>> ProcessTiffDirectoryParallel(string directoryPath)
{
var concurrentResults = new System.Collections.Concurrent.ConcurrentDictionary<string, List<string>>();
var tiffFiles = Directory.GetFiles(directoryPath, "*.tiff");
Parallel.ForEach(tiffFiles, tiffFile =>
{
using var input = new OcrInput();
input.LoadImageFrames(tiffFile);
var result = new IronTesseract().Read(input);
concurrentResults[tiffFile] = result.Pages.Select(p => p.Text).ToList();
});
return new Dictionary<string, List<string>>(concurrentResults);
}
}
LoadImageFrames()在一调用中读取来自TIFF的所有帧。 没有帧数查询,没有循环,没有显式逐帧释放资源。 并行版本为每个线程创建一个IronTesseract实例,这是正确的模式—对于完整的线程模型,请参见多线程示例。 对于 TIFF 特有的输入选项, TIFF 和 GIF 输入指南涵盖帧选择和多格式处理。
文档编写流程简化
LEADTOOLS可搜索的PDF创建需要在引擎上配置一个document.Save()。 这些步骤中的每一个都是一个单独的对象,并且需要单独的 API 调用。
LEADTOOLS OCR 方法:
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;
using Leadtools.Forms.DocumentWriters;
public class LeadtoolsDocumentWriterService
{
private readonly IOcrEngine _engine;
private readonly RasterCodecs _codecs;
public void CreateSearchablePdfFromImages(string[] imagePaths, string outputPath)
{
using var document = _engine.DocumentManager.CreateDocument();
foreach (var imagePath in imagePaths)
{
using var image = _codecs.Load(imagePath);
var page = document.Pages.AddPage(image, null);
page.Recognize(null);
}
// DocumentWriter configuration — four properties to set before save
var pdfOptions = new PdfDocumentOptions
{
DocumentType = PdfDocumentType.Pdf,
ImageOverText = true, // Image layer visible, text layer searchable
Linearized = false,
Title = "Searchable Output"
};
// Apply options to the engine's writer instance
_engine.DocumentWriterInstance.SetOptions(DocumentFormat.Pdf, pdfOptions);
// Save with format enum — the format must match the options set above
document.Save(outputPath, DocumentFormat.Pdf, null);
}
public void CreateSearchablePdfFromPdf(string inputPdfPath, string outputPath)
{
var pdfInfo = _codecs.GetInformation(inputPdfPath, true);
using var document = _engine.DocumentManager.CreateDocument();
for (int i = 1; i <= pdfInfo.TotalPages; i++)
{
using var pageImage = _codecs.Load(inputPdfPath, 0,
CodecsLoadByteOrder.BgrOrGray, i, i);
var page = document.Pages.AddPage(pageImage, null);
page.Recognize(null);
}
var pdfOptions = new PdfDocumentOptions
{
DocumentType = PdfDocumentType.Pdf,
ImageOverText = true,
Title = Path.GetFileNameWithoutExtension(inputPdfPath)
};
_engine.DocumentWriterInstance.SetOptions(DocumentFormat.Pdf, pdfOptions);
document.Save(outputPath, DocumentFormat.Pdf, null);
}
}
IronOCR方法:
using IronOcr;
public class SearchablePdfService
{
private readonly IronTesseract _ocr = new IronTesseract();
public void CreateSearchablePdfFromImages(string[] imagePaths, string outputPath)
{
using var input = new OcrInput();
foreach (var imagePath in imagePaths)
input.LoadImage(imagePath);
var result = _ocr.Read(input);
result.SaveAsSearchablePdf(outputPath); // DocumentWriter pipeline: gone
}
public void CreateSearchablePdfFromPdf(string inputPdfPath, string outputPath)
{
using var input = new OcrInput();
input.LoadPdf(inputPdfPath);
var result = _ocr.Read(input);
result.SaveAsSearchablePdf(outputPath);
}
// Get bytes directly — useful for streaming responses in ASP.NET
public byte[] CreateSearchablePdfBytes(string inputPdfPath)
{
using var input = new OcrInput();
input.LoadPdf(inputPdfPath);
return _ocr.Read(input).SaveAsSearchablePdfBytes();
}
}
PdfDocumentOptions + SetOptions() + document.Save()链。 图像覆盖文本图层的行为是自动的。 有关完整的可搜索 PDF 输出文档,可搜索 PDF 操作指南涵盖了输出选项,可搜索 PDF 示例展示了与ASP.NET响应流的集成。
多区域油田提取迁移
LEADTOOLS基于区域的OCR使用带有LeadRect边界, OcrZone对象。 多个区域被添加到单个页面并在一个page.Recognize()调用中识别,然后按区域索引提取。 区域索引与插入顺序一致,这意味着提取循环必须保持该顺序。
LEADTOOLS OCR 方法:
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;
public class LeadtoolsFormFieldExtractor
{
private readonly IOcrEngine _engine;
private readonly RasterCodecs _codecs;
// Invoice field extraction using named zones
public InvoiceFields ExtractInvoiceFields(string invoicePath)
{
using var image = _codecs.Load(invoicePath);
using var document = _engine.DocumentManager.CreateDocument();
var page = document.Pages.AddPage(image, null);
// Must clear auto-detected zones before adding custom ones
page.Zones.Clear();
// Zone definitions — index order matters for extraction
var zoneDefinitions = new[]
{
new { Name = "InvoiceNumber", X = 450, Y = 80, W = 200, H = 30 },
new { Name = "InvoiceDate", X = 450, Y = 115, W = 200, H = 30 },
new { Name = "VendorName", X = 50, Y = 150, W = 300, H = 40 },
new { Name = "TotalAmount", X = 450, Y = 600, W = 200, H = 30 }
};
foreach (var def in zoneDefinitions)
{
var zone = new OcrZone
{
Bounds = new LeadRect(def.X, def.Y, def.W, def.H),
ZoneType = OcrZoneType.Text,
CharacterFilters = OcrZoneCharacterFilters.None,
RecognitionModule = OcrZoneRecognitionModule.Auto
};
page.Zones.Add(zone);
}
page.Recognize(null);
// Extract by index — must match insertion order exactly
return new InvoiceFields
{
InvoiceNumber = page.Zones[0].Text?.Trim(),
InvoiceDate = page.Zones[1].Text?.Trim(),
VendorName = page.Zones[2].Text?.Trim(),
TotalAmount = page.Zones[3].Text?.Trim()
};
}
}
public class InvoiceFields
{
public string InvoiceNumber { get; set; }
public string InvoiceDate { get; set; }
public string VendorName { get; set; }
public string TotalAmount { get; set; }
}
IronOCR方法:
using IronOcr;
public class FormFieldExtractor
{
private readonly IronTesseract _ocr = new IronTesseract();
// Each field gets its own CropRectangle-scoped Read() call
// No zone index management, no zone ordering dependency
public InvoiceFields ExtractInvoiceFields(string invoicePath)
{
return new InvoiceFields
{
InvoiceNumber = ReadRegion(invoicePath, 450, 80, 200, 30),
InvoiceDate = ReadRegion(invoicePath, 450, 115, 200, 30),
VendorName = ReadRegion(invoicePath, 50, 150, 300, 40),
TotalAmount = ReadRegion(invoicePath, 450, 600, 200, 30)
};
}
private string ReadRegion(string imagePath, int x, int y, int width, int height)
{
using var input = new OcrInput();
input.LoadImage(imagePath, new CropRectangle(x, y, width, height));
return _ocr.Read(input).Text.Trim();
}
// Batch: extract the same field from many invoices in parallel
public Dictionary<string, string> ExtractInvoiceNumbersBatch(string[] invoicePaths)
{
var results = new System.Collections.Concurrent.ConcurrentDictionary<string, string>();
Parallel.ForEach(invoicePaths, invoicePath =>
{
using var input = new OcrInput();
input.LoadImage(invoicePath, new CropRectangle(450, 80, 200, 30));
results[invoicePath] = new IronTesseract().Read(input).Text.Trim();
});
return new Dictionary<string, string>(results);
}
}
page.Zones.Clear()调用,也不需要进行识别状态检查。 基于区域的 OCR 指南涵盖单区域和多区域提取模式。 有关完整的发票字段提取教程,请参阅发票 OCR 教程。
基于词坐标的结构化数据提取
LEADTOOLS 结构化输出在页面和区域级别运行。 要获取带边界框坐标的词级数据,开发人员从识别的区域内访问OcrWord对象。 API 需要在识别之后处理区域集合,并按区域迭代单词列表。
LEADTOOLS OCR 方法:
using Leadtools;
using Leadtools.Ocr;
using Leadtools.Codecs;
public class LeadtoolsStructuredExtractor
{
private readonly IOcrEngine _engine;
private readonly RasterCodecs _codecs;
public List<WordLocation> ExtractWordsWithLocations(string imagePath)
{
var wordLocations = new List<WordLocation>();
using var image = _codecs.Load(imagePath);
using var document = _engine.DocumentManager.CreateDocument();
var page = document.Pages.AddPage(image, null);
page.Recognize(null);
// Access words through the zone collection
foreach (OcrZone zone in page.Zones)
{
foreach (OcrWord word in zone.Words)
{
wordLocations.Add(new WordLocation
{
Text = word.Value,
X = word.Bounds.X,
Y = word.Bounds.Y,
Width = word.Bounds.Width,
Height = word.Bounds.Height,
Confidence = word.Confidence
});
}
}
return wordLocations;
}
}
public class WordLocation
{
public string Text { get; set; }
public int X { get; set; }
public int Y { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public int Confidence { get; set; }
}
IronOCR方法:
using IronOcr;
public class StructuredExtractor
{
private readonly IronTesseract _ocr = new IronTesseract();
public List<WordLocation> ExtractWordsWithLocations(string imagePath)
{
var result = _ocr.Read(imagePath);
// Five-level hierarchy: Pages > Paragraphs > Lines > Words > Characters
return result.Pages
.SelectMany(page => page.Paragraphs)
.SelectMany(para => para.Lines)
.SelectMany(line => line.Words)
.Select(word => new WordLocation
{
Text = word.Text,
X = word.X,
Y = word.Y,
Width = word.Width,
Height = word.Height,
Confidence = (int)word.Confidence
})
.ToList();
}
// Paragraph-level extraction with position data
public void PrintDocumentStructure(string imagePath)
{
var result = _ocr.Read(imagePath);
Console.WriteLine($"Document confidence: {result.Confidence}%");
foreach (var page in result.Pages)
{
Console.WriteLine($"Page {page.PageNumber}:");
foreach (var paragraph in page.Paragraphs)
{
Console.WriteLine($" Paragraph at ({paragraph.X}, {paragraph.Y}):");
Console.WriteLine($" {paragraph.Text}");
}
}
}
}
IronOCR的结果层次从Characters。 每个级别都提供Confidence。 LEADTOOLS 基于区域的访问模式消失了——无需区域迭代即可访问单词数据。 读取结果操作指南涵盖了完整的输出模型以及坐标访问模式。 OcrResult API 参考文档记录了结果层次结构中的每个属性。
##LEADTOOLS OCRAPI 到IronOCR映射参考
| LEADTOOLS OCR | IronOCR |
|---|---|
RasterSupport.SetLicense(licPath, keyContent) | IronOcr.License.LicenseKey = "key" |
new RasterCodecs() | 不要求 |
OcrEngineManager.CreateEngine(OcrEngineType.LEAD) | new IronTesseract() |
engine.Startup(codecs, null, null, runtimePath) | 不要求 |
engine.IsStarted | 无需(随时准备) |
engine.Shutdown() | 不要求 |
engine.Dispose() | 不要求 |
_codecs.Load(imagePath) | input.LoadImage(imagePath) |
_codecs.Load(path, 0, BgrOrGray, page, page) | input.LoadImageFrames(path) |
_codecs.GetInformation(path, true).TotalPages | 无需 — 自动 |
engine.DocumentManager.CreateDocument() | 不要求 |
document.Pages.AddPage(image, null) | input.LoadImage(imagePath) |
page.Recognize(null) | Read()的一部分) |
page.GetText(-1) | result.Text |
page.RecognizeStatus | result.Confidence(百分比) |
OcrZone { Bounds = new LeadRect(x, y, w, h) } | new CropRectangle(x, y, w, h) |
page.Zones.Clear() | 不要求 |
page.Zones.Add(zone) | input.LoadImage(path, cropRect) |
zone.Words / word.Bounds | result.Pages[n].Words / word.Y |
DeskewCommand().Run(image) | input.Deskew() |
DespeckleCommand().Run(image) | input.DeNoise() |
AutoBinarizeCommand().Run(image) | input.Binarize() |
ContrastBrightnessCommand().Run(image) | input.Contrast() |
new PdfDocumentOptions { ImageOverText = true } | 由SaveAsSearchablePdf()自动处理 |
engine.DocumentWriterInstance.SetOptions(format, opts) | 不要求 |
document.Save(path, DocumentFormat.Pdf, null) | result.SaveAsSearchablePdf(path) |
_codecs.Options.Pdf.Load.Password = password | input.LoadPdf(path, Password: password) |
RasterSupport.IsLocked(RasterSupportType.Document) | IronOcr.License.IsLicensed |
常见迁移问题和解决方案
问题 1:许可证文件路径解析失败
LEADTOOLS:bin/Release、Docker容器和IIS应用程序池之间有所不同。 一个常见的失败模式是开发中有效但在生产中由于工作目录更改而抛出"License file not found"的路径。
**解决方案:**彻底删除两个许可证文件和SetLicense()调用。 替换为从环境变量读取值的单个字符串赋值语句:
// Remove this:
// RasterSupport.SetLicense(licPath, File.ReadAllText(keyPath));
// Replace with this:
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE")
?? throw new InvalidOperationException("IRONOCR_LICENSE environment variable not set");
字符串键在所有环境下的行为都相同。 将其存储在与数据库连接字符串相同的密钥管理器中。
问题 2:引擎未启动异常
**LEADTOOLS:**在IsStarted检查来保护这一点。
解决方案:IronTesseract不需要启动调用,也没有启动/停止状态。 可以删除IsStarted防护和整个生命周期服务类:
// Remove the guard:
// if (!_engine.IsStarted)
// throw new InvalidOperationException("Engine not started");
// IronTesseract is always ready — just call Read()
var result = _ocr.Read(imagePath);
问题 3:批处理中的栅格图像内存累积
**LEADTOOLS:**迭代PDF页面或图片文件的批处理创建在循环中using块退出之前抛出的异常,或缺失调用的手动处理模式—未释放的图像会在内存中累积。 LEADTOOLS生产代码通常包括批次之间的GC.Collect() / GC.WaitForPendingFinalizers()调用作为补偿机制。
**解决方案:**删除所有GC.Collect()调用。 using块中进行范围限定:
// Remove this pattern:
// GC.Collect();
// GC.WaitForPendingFinalizers();
// Replace with standard using scope:
foreach (var filePath in filePaths)
{
using var input = new OcrInput();
input.LoadImage(filePath);
var text = _ocr.Read(input).Text;
// input disposed here — no accumulation
}
有关更多内存优化指导,请参阅 内存分配减少博客文章。
问题 4:DocumentWriterInstance 选项在调用间持久存在
LEADTOOLS:engine.DocumentWriterInstance.SetOptions()修改共享引擎写入器实例上的状态。 如果一个代码路径与document.Save()之前未重置这些选项,则先前调用的输出格式仍将保留。 这是对共享引擎实例的一种有状态的副作用。
解决方案: IronOCR没有共享写入器状态。 每个SaveAsSearchablePdf()调用都是独立的:
// Remove the options setup:
// var pdfOptions = new PdfDocumentOptions { ... };
// _engine.DocumentWriterInstance.SetOptions(DocumentFormat.Pdf, pdfOptions);
// document.Save(outputPath, DocumentFormat.Pdf, null);
// Replace with:
result.SaveAsSearchablePdf(outputPath);
每次调用都会生成带有图像叠加层和可搜索文本层的标准 PDF 输出。 调用之间没有可重置的共享选项状态。
问题 5:错误的捆绑包 — 运行时缺少模块
**LEADTOOLS:**购买了Document Imaging SDK的团队可能发现RasterException。 当购买的软件包不包含 PDF 模块时,就会发生这种情况,并且该错误仅在生产运行时才会出现。
解决方案: IronOCR在每个许可级别都包含所有功能。 没有单独的PDF模块,没有加密文件的附加组件,也没有更高精度引擎的二级供应商。在安装单个IronOcr包之后,完整的功能集不需要任何额外购买即可使用:
问题 6:重新排序后区域索引不匹配
**LEADTOOLS:**区域提取使用位置索引——page.Zones[0].Text, page.Zones.Add()中的插入顺序。 重新排列区域定义以匹配更改后的表单布局,会悄无声息地破坏提取操作,因为它会移动所有后续索引。
**解决方案:**IronOCR使用每个字段的命名变量CropRectangle。 重新排列字段定义不会影响提取结果,因为每个字段的作用域都是独立的:
// Each field is independent — reorder freely without breaking extraction
var invoiceNumber = ReadRegion(imagePath, 450, 80, 200, 30);
var invoiceDate = ReadRegion(imagePath, 450, 115, 200, 30);
var vendorName = ReadRegion(imagePath, 50, 150, 300, 40);
var totalAmount = ReadRegion(imagePath, 450, 600, 200, 30);
##LEADTOOLS OCR迁移检查清单
迁移前
在进行任何更改之前,请审核代码库,以确定所有 LEADTOOLS 的使用情况:
# Find all LEADTOOLS namespace imports
grep -rn "using Leadtools" --include="*.cs" .
# Find engine lifecycle calls
grep -rn "OcrEngineManager\|\.Startup(\|\.Shutdown()" --include="*.cs" .
# Find license file references
grep -rn "SetLicense\|LEADTOOLS\.LIC\|\.LIC\.KEY" --include="*.cs" .
# Find RasterCodecs usage
grep -rn "RasterCodecs\|_codecs\.Load\|GetInformation" --include="*.cs" .
# Find DocumentWriter usage
grep -rn "DocumentWriterInstance\|PdfDocumentOptions\|DocumentFormat\." --include="*.cs" .
# Find zone-based OCR
grep -rn "OcrZone\|page\.Zones\|LeadRect\|ZoneType" --include="*.cs" .
# Find GC workarounds to remove
grep -rn "GC\.Collect\|WaitForPendingFinalizers" --include="*.cs" .
请注意购买的是哪个 LEADTOOLS 套装——OCR 模块、PDF 模块和引擎类型(LEAD、Tesseract 或 OmniPage)——以确保在迁移后验证期间测试等效的IronOCR功能。
代码迁移
- 移除所有LEADTOOLS NuGet包:
Leadtools,Leadtools.Ocr,Leadtools.Codecs,Leadtools.Forms.DocumentWriters,Leadtools.Pdf - 安装
IronOcrNuGet包 - 用
using IronOcr;替换所有LEADTOOLSusing指令 - 从项目和部署工件中删除
.LIC.KEY文件 - 在应用程序启动时用
RasterSupport.SetLicense(licPath, keyContent) - 删除所有只为管理
IDisposable服务类 - 用
OcrEngineManager.CreateEngine()+engine.Startup() - 在
_codecs.Load(imagePath) - 用
input.LoadImageFrames(tiffPath)替换多帧TIFF页面循环 - 用
input.LoadPdf(pdfPath)替换PDF页面迭代循环 - 将
document.Pages.AddPage()+page.Recognize(null)+ocr.Read(input).Text - 将
OcrZone+input.LoadImage(path, new CropRectangle(x, y, w, h)) - 将
PdfDocumentOptions+DocumentWriterInstance.SetOptions()+result.SaveAsSearchablePdf(path) - 将预处理命令类 (
DeskewCommand,DespeckleCommand,AutoBinarizeCommand) 替换为input.Deskew(),input.DeNoise(),input.Binarize()在OcrInput实例上 - 删除所有为补偿LEADTOOLS内存管理而添加的
GC.Collect()/GC.WaitForPendingFinalizers()调用
后迁移
- 验证识别出的文本输出是否与 LEADTOOLS 在具有代表性的图像和 PDF 样本上的输出一致
- 使用
result.Confidence确认置信分数在预期范围内 - 测试多帧 TIFF 处理生成的页数与 LEADTOOLS 帧迭代循环生成的页数相同
- 验证可搜索的 PDF 输出是否可在 PDF 阅读器(Adobe Acrobat 或同类软件)中进行文本搜索。
- 测试基于区域的字段提取功能,并将其与生产发票或表单中已知的有效字段值进行比较。
- 验证无需单独的
Leadtools.Pdf模块即可进行受密码保护的PDF解密 - 在负载下运行批处理并确认无内存增长(首先删除所有
GC.Collect()) - 测试 Docker 和 CI/CD 部署(不使用许可证文件)——确认环境变量中的许可证密钥字符串能够正确解析。
- 使用
Parallel.ForEach测试并行处理以验证线程安全 - 确认结构化数据提取(
result.Pages,page.Paragraphs,page.Words)返回正确的坐标
迁移到IronOCR的主要优势
**部署变为无状态。**LEADTOOLS .LIC.KEY文件是每个部署环境必须携带的工件。 将它们嵌入的容器会将许可证数据暴露在镜像历史记录中。 安装它们的容器需要进行体积协调。 迁移到IronOCR后,许可证是环境变量中的一个字符串。 部署工件是一个标准的NuGet引用。 没有文件,没有路径,没有挂载策略。 Docker 部署指南和Azure 部署指南展示了容器化环境的完整设置。
**四个包变为一个。**迁移减少了Leadtools, Leadtools.Ocr, Leadtools.Codecs, IronOcr引用。 该软件包包含了所有功能——预处理、原生 PDF 输入、可搜索 PDF 输出、条形码读取、结构化数据提取、125 多种语言支持。 功能集与购买的是哪个套餐无关。
**批处理消除了手动内存管理。**LEADTOOLS批处理代码携带防御性RasterImage处置以防止多文档运行期间的内存积累。 IronOCR的using块中作用域自动处理清理。 使用IronTesseract实例——在没有同步代码的情况下提供多核吞吐量。 请参阅速度优化指南,了解如何调整生产环境的吞吐量。
总成本可预测。LEADTOOLS为五名开发人员组成的团队提供的预估成本在第一年为 15,000 美元至 40,000 美元,每年维护费用约为许可费用的 20% 至 25%,用于接收更新。IronOCRProfessional售价 2,999 美元,一次性永久购买即可获得 10 位开发人员和 10 个部署地点的使用权。 包含一年的更新服务。 更新期结束后继续使用无需额外付费。 IronOCR授权页面直接公布所有等级价格,无需销售咨询。
**无需区域设置的结构化数据。**迁移后,带边框坐标的词级、行级和段落级数据直接在OcrResult上可用——无需区域定义。 五级层次结构(Pages, Paragraphs, Lines, Words, Characters)每个都揭示位置坐标和每个元素的置信分数。 需要 LEADTOOLS 区域配置才能实现结构化提取的应用程序,可以通过更简单的 API 获取更丰富的数据。 OCR 结果功能页面总结了完整的输出模型。
