如何在 .NET Maui 中執行 OCR
本指引為 .NET 開發者提供完整的LEADTOOLS OCR遷移至 IronOCR 之過程。 涵蓋從 NuGet 套件替換到完整的程式碼遷移,並針對 LEADTOOLS 的初始化程式、多重命名空間結構和基於文件的授權部署模型,提供前後例子。
為什麼要從LEADTOOLS OCR遷移
LEADTOOLS OCR 內含一個企業成像平台,其根源可追溯至1990年,其 API 反映了此一血統。 最低工作配置需要四個 NuGet 套件、四個命名空間、兩個已知路徑下的實體授權檔案、一個在引擎之前初始化的編解碼層、從三個選項中選擇一個引擎型別,以及一個載入運行時二進制檔至記憶體的阻塞啟動呼叫。 所有這些都會在識別單個字元之前運行。 遷移至IronOCR的團隊消除了整個層級——只需一個套件、一個命名空間、一行程式碼來設定授權金鑰。
基於文件的授權部署在容器中失效。 LEADTOOLS 需要兩個實體檔案——LEADTOOLS.LIC 和 LEADTOOLS.LIC.KEY——需在每台運行應用程式的機器上的特定路徑中可讀取。 在 Docker 容器中,這些文件必須烘焙進映像(在層次歷史中暴露它們)或者在運行時掛載(在每個協同環境中需要音量協調)。 Azure Functions 和 AWS Lambda 無法不使用變通方法進行授權文件部署。 CI/CD 管道需要在啟動呼叫使用的確切路徑上存在這些文件,否則應用程式會在處理單個文件之前拋出錯誤。IronOCR將這兩個文件替換為可放入環境變數、Kubernetes 密碼或 Azure Key Vault 引用中的字串。
四個套件完成一項任務。 一個正常運行的LEADTOOLS OCR專案至少需要 Leadtools.Codecs 和 Leadtools.Forms.DocumentWriters。 受密碼保護的 PDF 支持需要一個可能未在購買的捆綁包中包含的額外 Leadtools.Pdf 模組。 每個套件必須存在、相容且版本一致。IronOCR以單一 NuGet 套件提供。 包括所有功能——原生 PDF 輸入、可搜索 PDF 輸出、預處理、條碼閱讀。
引擎生命週期建立了一個維護表面。 LEADTOOLS 將 OCR 引擎包裝在一個 IDisposable 服務類中,而不是因為符合 .NET 慣例,而是因為 Shutdown() 呼叫必須在 Dispose() 之前進行,否則應用程式會產生錯誤。 LEADTOOLS 批次處理器的生產實現通常在文件塊間包含 GC.Collect() / GC.WaitForPendingFinalizers() 呼叫,以補償積累的 RasterImage 實例。IronOCR使用標準 using 區塊。 OcrInput 是唯一需要處置的物件。
捆綁混淆在生產中出現。 LEADTOOLS 不公開發佈定價。 包含 OCR 的文件成像 SDK 估計每年每名開發人員花費 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 vs LEADTOOLS OCR:功能比較
下列表格直接映射這兩個程式庫間的功能。
| 功能 | LEADTOOLS OCR | IronOCR |
|---|---|---|
| 所需的NuGet包 | 至少 4(PDF 更加) | 1 (IronOcr) |
| 授權機制 | .LIC + .LIC.KEY 文件配對 | 字串密鑰 |
| 許可部署 | 每台生產機上的文件 | 環境變數或配置 |
| 價格模型 | $3,000–$15,000+/個開發者/年(估計) | $999–$2,399 一次性永久 |
| 引擎初始化 | 手動 Startup() 和運行路徑 | 自動,延遲 |
| 引擎關閉 | 手動 Shutdown() 在 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(引擎依賴) | 125+ 通過 NuGet 語言包 |
| 語言部署 | 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:初始化許可證
刪除所有 RasterSupport.SetLicense() 呼叫和 .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 Key Vault,AWS Secrets Manager,或 Kubernetes 密碼。 無需將任何文件與應用程式二進制文件一起部署。
程式碼遷移範例
引擎啟動和關閉生命周期移除
LEADTOOLS 強制一個嚴格的服務類模式,因爲引擎生命週期必須顯式管理。 構造函式啓動引擎,Dispose() 以正確的順序關閉引擎,任何程式碼路徑在 Shutdown() 之前跳過 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() 调用。 上述服務類無需實作 IDisposable——引擎是無狀態的,且每個 Read() 呼叫中使用的 OcrInput 物件透過標準 using 模式自行完成清除。 IronTesseract 設置指南 涵蓋了配置選項,包括語言選擇和生產場景的性能調整。
多幀 TIFF 批次處理
LEADTOOLS 通過查詢編解碼器總幀數來處理多幀 TIFF 文件,然後在每個 _codecs.Load() 調用上迴圈設置顯式的 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 建立需要在引擎上配置一個 DocumentWriter 實例,構建一個包含輸出型別和覆蓋設定的 PdfDocumentOptions 物件,通過 SetOptions() 應用選項,然後使用格式枚舉調用 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();
}
}
SaveAsSearchablePdf() 替換整個 PdfDocumentOptions + SetOptions() + document.Save() 鏈。 圖像覆文字層行為是自動的。 如需完整的可搜索 PDF 輸出文件,可搜索 PDF 使用指南 涵蓋輸出選項,可搜索 PDF 範例 展示如何整合 ASP.NET 響應流。
多區域字段提取迁移
LEADTOOLS 基於區域的 OCR 使用 OcrZone 物件與 LeadRect 邊界、OcrZoneType 和 OcrZoneCharacterFilters 屬性。 多個區域被新增至單一頁面,在一次 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);
}
}
CropRectangle 直接傳遞給 LoadImage() 替換整個 OcrZone 設置。沒有需要跟踪的區域索引,沒有需要的 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 的結果層次結構從 Pages 經過 Words 和 Characters。 每一級別暴露 Text 和 Confidence。 LEADTOOLS 的基於區域的存取模式消失了——無需區域迭代即可達到文字資料。 讀取結果使用指南 涵蓋了具有座標存取模式的完整輸出模型。 OcrResult API 參考 記錄了結果層次結構的每個屬性。
LEADTOOLS OCR API 到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.LoadPdf(path) 或 input.LoadImageFrames(path) |
_codecs.GetInformation(path, true).TotalPages | 不需要——自動 |
engine.DocumentManager.CreateDocument() | 不需要 |
document.Pages.AddPage(image, null) | input.LoadImage(imagePath) |
page.Recognize(null) | ocr.Read(input)(識別是 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.X, 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:RasterSupport.SetLicense() 解析 .LIC 和 .LIC.KEY 文件路徑相對於工作目錄,此工作目錄在 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:**在 engine.Startup() 完成之前或在 engine.Shutdown() 被調用后(例如,在關閉期間的處理結束前置用和使用競賽狀況所發生)調用任何識別方法,會擲出"引擎未啟動"InvalidOperationException。長命服務類必須防範此狀況並進行 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:批量處理時的 RasterImage 記憶體積累
**LEADTOOLS:**無論哪個程式碼路徑沒有正確處理 RasterImage 實例——由於在 using 區塊退出之前擲出的異常或在缺少手動調用處置模式中——未釋放的影像會堆積在記憶中。 LEADTOOLS 的生產程式碼通常在批次之間包含 GC.Collect() / GC.WaitForPendingFinalizers() 呼叫作為補償機制。
**解決方案:**請刪除所有 GC.Collect() 呼叫。 OcrInput 是IronOCR管道中的唯一 IDisposable,其範疇僅限於每批次操作的標準 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() 修改了共享引擎寫入實例的狀態。 如果一條路徑使用 DocumentType = PdfDocumentType.PdfA 設置了 PdfDocumentOptions,而後續調用在 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:**購買了文件成像 SDK 的團隊可能發現 Leadtools.Pdf 型別不可用,或者加密的 PDF 頁面在生產中運行時拋出含有 RasterExceptionCode.FeatureNotSupported 的 RasterException。 這發生在已購買的捆綁包不包括 PDF 模塊時,錯誤僅在生產中運行時纔會出現。
**解決方案:**IronOCR所有許可層都有全部功能。 沒有單獨的 PDF 模塊,沒有加密文件的附加程式,也沒有高精度引擎的次級供應商。安裝單個 IronOcr 套件後,所有功能集隨即可用,無需額外購買:
問題6:區域索引在重新排序後不匹配
**LEADTOOLS:**區域提取使用位置索引 - page.Zones[1].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 vs Tesseract vs OmniPage) - 確保在遷移後驗證期間測試等效的IronOCR功能。
程式碼遷移
- 删除所有 LEADTOOLS NuGet 包:
Leadtools.Pdf - 安裝
IronOcrNuGet 套件 - 使用
using IronOcr;替換所有 LEADTOOLSusing指令 - 從項目和部署工件中刪除
.LIC和.LIC.KEY文件 - 在應用啟動時用
IronOcr.License.LicenseKey = "key"替換RasterSupport.SetLicense(licPath, keyContent) - 刪除所有僅用於管理
IOcrEngine和RasterCodecs生命周期的IDisposable服務類 - 用
new IronTesseract()替換OcrEngineManager.CreateEngine()+engine.Startup() - 在
using var input = new OcrInput()區塊內用input.LoadImage(imagePath)替換_codecs.Load(imagePath) - 用
input.LoadImageFrames(tiffPath)替換多幀 TIFF 頁面迴圈 - 用
input.LoadPdf(pdfPath)替代 PDF 頁面迭代迴圈 - 用
ocr.Read(input).Text替代document.Pages.AddPage()+page.Recognize(null)+page.GetText(-1) - 用
input.LoadImage(path, new CropRectangle(x, y, w, h))替換OcrZone+page.Zones.Add()模式 - 用
result.SaveAsSearchablePdf(path)替代PdfDocumentOptions+DocumentWriterInstance.SetOptions()+document.Save() - 使用
input.DeNoise()和input.Binarize()代替預處理命令類 (DeskewCommand,DespeckleCommand,AutoBinarizeCommand) 在OcrInput實例上 - 刪除爲補償 LEADTOOLS 記憶體管理而新增的所有
GC.Collect()/GC.WaitForPendingFinalizers()调用
遷移後
- 驗證已識別的文字輸出是否與 LEADTOOLS 輸出一致,並對代表性樣本的圖像和 PDF 進行測試
- 使用
result.Confidence確認信心水準在預期範圍內 - 測試多幀 TIFF 處理是否產生與 LEADTOOLS 幀迭代迴圈相同數量的頁面
- 驗證可搜索的 PDF 輸出能否在 PDF 閱讀器(Adobe Acrobat 或同類)中進行文字搜索
- 根據生產發票或表單中的已知良好字段值測試基於區域的字段提取
- 確認密碼保護的 PDF 解密無需單獨的
Leadtools.Pdf模块即可工作 - 在高負載下進行批量處理,確認沒有記憶體增長(先移除所有
GC.Collect()) - 無需許可證文件的 Docker 和 CI/CD 部署測試- 確認字串許可密鑰從環境變數正確解析
- 使用
Parallel.ForEach進行並行處理的測試,以驗證執行緒安全性 - 確認結構化資料提取 (
result.Pages,page.Paragraphs,page.Words) 返回正確的坐標
遷移至IronOCR的主要好處
部署變得無狀態。 LEADTOOLS .LIC 和 .LIC.KEY 文件是每個部署環境必須攜帶的工件。 將其烘焙進的容器在鏡像歷史中暴露許可資料。 與之掛載的容器需要音量協調。 遷移到IronOCR之後,許可證是一個環境變數中的字串。 部署工件是一個標準的 NuGet 參考。 沒有文件,沒有路徑,沒有安裝策略。 適合容器化環境完整設置的 Docker 部署指南 和 Azure 部署指南。
**四個套件合為一體。**遷移將 Leadtools.Forms.DocumentWriters 和可選的 Leadtools.Pdf 減少到一個 IronOcr 引用。 所有能力——預處理、原生 PDF 輸入、可搜索 PDF 輸出、條碼閱讀、結構化資料提取、125+ 語言支持——都包括在這個一個包中。 功能設置不依賴於購買了哪個包。
批處理消除手動記憶體管理。 LEADTOOLS 批次碼處載防禦 GC.Collect() 調用和顯式 RasterImage 清除以防止多文件執行期間記憶體積累。IronOCR的 OcrInput 受標準 using 區塊範疇管理,自動處理清理。 使用 Parallel.ForEach 進行的以執行緒安全爲基礎的並行處理 - 每個執行緒一個 IronTesseract 實例 - 提供多核吞吐量,無需同步程式碼。 查看速度優化指南中的生產吞吐量調優。
**可預見的總成本。**五名開發人員團隊的 LEADTOOLS 估計成本在第一年花費 15,000–40,000 美元,每年約 20–25% 的許可證成本用於接收更新。IronOCRProfessional 以 2,999 美元的一次性永久購買覆蓋十名開發人員和十個部署地點。 一年更新期包括在內。 在更新期之後繼續使用無需額外支付。 IronOCR 授權頁面 直接發佈所有層價格,無需銷售協商。
結構化資料無需區域設置。 經過遷移後,具有邊界框座標的字元級、行間級和段落級資料直接可在 OcrResult 上獲取 —— 無需區域定義。 五級層次結構 (Pages, Paragraphs, Lines, Words, Characters) 各自暴露位置座標和每個元素的信心水準。 需要 LEADTOOLS 區域配置以實現結構化提取的應用能夠從簡單的 API 中獲得更加豐富的資料。 OCR 結果功能頁 總結了完整輸出模型。
[(Adobe Acrobat、Kofax OmniPage、LEADTOOLS 和 Tesseract 是各自所有者的註冊商標。 本網站與 Adobe Inc.、Apryse、Google、Kofax 或 LEAD Technologies 無關,也未受其認可或贊助。 所有產品名稱、標誌和品牌均為其各自所有者的財產。 比較僅供資訊用途,並反映撰寫時獲得的公開資訊。)}]
