
從 ABBYY FineReader 遷移到 IronOCR
本指南指導.NET開發人員每一步驟替換ABBYY FineReader Engine SDK為IronOCR。 涵蓋了去除COM依賴和SDK安裝程式工件的機械步驟,並將ABBYY的API映射到IronOCR等效,以提供在生產ABBYY整合中最常見的前後程式碼範例。 遷移的目標是那些已決定ABBYY的企業成本和部署複雜性不再符合其專案需求的團隊。
為什麼從ABBYY FineReader遷移
ABBYY FineReader Engine是一個功能強大的OCR平台,但其架構是為了企業Windows店舖與專用基礎設施團隊而設計的。 當.NET團隊的實際工作負載是發票處理、合約數位化或掃描表單提取時,該架構變成了一個責任,而不是資產。
COM互操作債務隨時間增長。 在.NET中,每個ABBYY整合都通過COM互操作層運行。 COM物件需要顯式生命周期管理:建立、初始化、處理,然後在finally塊中關閉,否則流程會泄漏記憶體。 每個接觸ABBYY的程式碼路徑都承擔此模式。 經過兩到三年的功能新增後,生命周期儀式通過服務類、背景工作者和請求處理程式進行傳播。 結果是每個與OCR相關的類中有30-50%的樣板在切換至IronTesseract時完全消失。
SDK安裝程式阻礙了現代部署模式。 ABBYY通過Windows SDK安裝程式部署,該安裝程式在硬編碼路徑放置二進制文件、語言資料、運行時文件和許可證文件。 將使用ABBYY的服務容器化需要從該安裝程式輸出烤製300+MB的自定義基礎映像,或在啟動時掛載具有許可證文件的卷。這兩種方法都不適合標準Kubernetes或雲原生流水線。IronOCR是一個NuGet包:同樣的dotnet restore產生完整的OCR引擎。
按頁面收費將體積變成了成本中心。 ABBYY基於體積的許可模式對超過包含門檻的每個頁面收費。 一個應用在啟動時每月處理50,000份文件,兩年後達到500,000份,其OCR成本隨著其成功而成比例增長。 IronOCR對許可證收取固定費用——每月處理200萬頁的團隊其許可費用與每月處理2000頁的團隊完全相同。
語言資料需要人工部署協調。 ABBYY語言包作為文件位於SDK運行時目錄中。 新增語言意味著識別正確的資料文件,將它們複製到每個部署目標的正確路徑中,並更新CI/CD腳本來包含它們。 在IronOCR中,新增法語是dotnet add package IronOcr.Languages.French——包管理器處理其餘事宜。
許可證文件故障在無預警情況下衝擊生產環境。 ABBYY許可證以loader.GetEngineObject()運行時位於特定的磁盤路徑上。 如果這些文件在新的生產伺服器上缺失——錯誤的部署腳本、文件複製失敗、權限問題——該調用在啟動時拋出。過期的許可證以同樣的方式失敗。 IronOCR的許可是一個鍵值,可以在啟動程式碼中分配,並可儲存在任何秘密管理器中,通過IronOcr.License.IsValidLicense進行檢查,在應用接受流量之前進行驗證。
執行緒安全需要單一共享引擎實例。 ABBYY的引擎不是簡單地執行緒安全的,無法同時從多個執行緒進行CreateFRDocument調用。 生產實現使用鎖定策略或處理器池。 IronOCR的IronTesseract是無狀態的:每個執行緒啟動一個實例,並發運行識別,無需鎖定,完成時釋放。
根本問題
// ABBYY: COM loader + license path validation + profile load — before a single pixel of OCR runs
var loader = new EngineLoader();
var engine = loader.GetEngineObject(
@"C:\Program Files\ABBYY SDK\FineReader Engine\Bin", // Breaks on every new machine
@"C:\Program Files\ABBYY SDK\License" // Fails if .lic file is missing
);
engine.LoadPredefinedProfile("DocumentConversion_Accuracy");
var langParams = engine.CreateLanguageParams();
langParams.Languages.Add("English");' ABBYY: COM loader + license path validation + profile load — before a single pixel of OCR runs
Dim loader As New EngineLoader()
Dim engine = loader.GetEngineObject(
"C:\Program Files\ABBYY SDK\FineReader Engine\Bin", ' Breaks on every new machine
"C:\Program Files\ABBYY SDK\License" ' Fails if .lic file is missing
)
engine.LoadPredefinedProfile("DocumentConversion_Accuracy")
Dim langParams = engine.CreateLanguageParams()
langParams.Languages.Add("English")' ABBYY: COM loader + license path validation + profile load — before a single pixel of OCR runs
Dim loader As New EngineLoader()
Dim engine = loader.GetEngineObject(
"C:\Program Files\ABBYY SDK\FineReader Engine\Bin", ' Breaks on every new machine
"C:\Program Files\ABBYY SDK\License" ' Fails if .lic file is missing
)
engine.LoadPredefinedProfile("DocumentConversion_Accuracy")
Dim langParams = engine.CreateLanguageParams()
langParams.Languages.Add("English")// IronOCR: the entire initialization
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var ocr = new IronTesseract();// IronOCR: the entire initialization
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var ocr = new IronTesseract();Imports IronOcr
' IronOCR: the entire initialization
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Dim ocr As New IronTesseract()Imports IronOcr
' IronOCR: the entire initialization
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Dim ocr As New IronTesseract()IronOCR對比ABBYY FineReader:功能比較
下表涵蓋了評估此遷移的團隊所需的相關功能。
| 功能 | ABBYY FineReader Engine | IronOCR |
|---|---|---|
| 安裝 | SDK安裝程式(Windows) | dotnet add package IronOcr |
| 獲取 | 聯繫銷售(4-12週) | 自助 NuGet |
| 授權模式 | Enterprise,每伺服器或每頁 | 永久性,$999-$2,999一次性 |
| 許可管理 | .lic + .key 文件在磁盤上 | 程式碼或環境變數中的鍵值 |
| .NET整合 | COM 互操作 | 本地 .NET |
| COM依賴 | 是 | 不是 |
| 執行緒安全 | 需要鎖定策略 | 完整的(每個執行緒一個IronTesseract) |
| 支持的語言 | 190+ | 125+ |
| 語言安裝 | SDK路徑中的運行時資料文件 | NuGet 語言套件 |
| PDF輸入 | 是(通過CreatePDFFile) | 是(本地,input.LoadPdf()) |
| 可搜尋的 PDF 輸出 | 是(導出流水線) | 是(result.SaveAsSearchablePdf()) |
| 自動預處理 | 基於配置文件 | 內建(Deskew、DeNoise、Contrast、Binarize、Sharpen) |
| 基於區域的OCR | 區域物件(SetBounds) | CropRectangle參數 |
| 條形碼讀取 | 是 | 是(ocr.Configuration.ReadBarCodes = true) |
| 跨平台 | Windows, Linux, macOS | Windows、Linux、macOS、Docker、Azure、AWS |
| Docker部署 | 需要自定義基礎映像 | 標準.NET基礎映像+libgdiplus |
| 置信度評分 | 是 | 是(result.Confidence) |
| 獲得第一次OCR結果的時間 | 4-12 週(採購) | 同一天 |
快速入門:ABBYY FineReader到IronOCR遷移
步驟1:替換NuGet包
ABBYY FineReader Engine沒有NuGet包。 通過解除安裝SDK和從專案文件中刪除手動程式集引用來刪除它:
<!-- Remove these lines from your .csproj -->
<Reference Include="FREngine">
<HintPath>C:\Program Files\ABBYY SDK\FineReader Engine\Bin\FREngine.dll</HintPath>
</Reference>
然後從Visual Studio的References節點中刪除FREngine.dll COM互操作引用,或直接從專案文件中刪除相應條目。 從NuGet安裝IronOCR:
步驟2:更新命名空間
// Before (ABBYY)
using FREngine;
using ABBYY.FineReader;
// After (IronOCR)
using IronOcr;// Before (ABBYY)
using FREngine;
using ABBYY.FineReader;
// 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"將密鑰儲存在環境變數或秘密管理器中,用於生產部署:
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE_KEY");IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE_KEY");Imports System
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE_KEY")Imports System
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE_KEY")程式碼遷移範例
在Windows服務和無狀態IronTesseract中的引擎生命周期
ABBYY的引擎初始化儀式應放在服務包裝器中,因為IEngine物件的建立成本很高。 大多數生產整合將引擎包裝在具有顯式啟動和閉包方法的singleton服務中。
ABBYY FineReader方法:
using FREngine;
public class DocumentOcrService : IHostedService, IDisposable
{
private IEngine _engine;
public Task StartAsync(CancellationToken cancellationToken)
{
// Step 1: Create loader — requiresCOM 互操作registration
var loader = new EngineLoader();
// Step 2: Load engine from SDK path — throws if license files are missing
_engine = loader.GetEngineObject(
@"C:\Program Files\ABBYY SDK\FineReader Engine\Bin",
@"C:\Program Files\ABBYY SDK\License"
);
// Step 3: Load profile before any recognition work
_engine.LoadPredefinedProfile("DocumentConversion_Accuracy");
return Task.CompletedTask;
}
public string ProcessDocument(string imagePath)
{
// Document must be created and destroyed per call
var document = _engine.CreateFRDocument();
try
{
document.AddImageFile(imagePath, null, null);
document.Process(null);
return document.PlainText.Text;
}
finally
{
document.Close(); // Memory leaks if omitted
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
_engine = null; // COM cleanup
return Task.CompletedTask;
}
public void Dispose() => _engine = null;
}using FREngine;
public class DocumentOcrService : IHostedService, IDisposable
{
private IEngine _engine;
public Task StartAsync(CancellationToken cancellationToken)
{
// Step 1: Create loader — requiresCOM 互操作registration
var loader = new EngineLoader();
// Step 2: Load engine from SDK path — throws if license files are missing
_engine = loader.GetEngineObject(
@"C:\Program Files\ABBYY SDK\FineReader Engine\Bin",
@"C:\Program Files\ABBYY SDK\License"
);
// Step 3: Load profile before any recognition work
_engine.LoadPredefinedProfile("DocumentConversion_Accuracy");
return Task.CompletedTask;
}
public string ProcessDocument(string imagePath)
{
// Document must be created and destroyed per call
var document = _engine.CreateFRDocument();
try
{
document.AddImageFile(imagePath, null, null);
document.Process(null);
return document.PlainText.Text;
}
finally
{
document.Close(); // Memory leaks if omitted
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
_engine = null; // COM cleanup
return Task.CompletedTask;
}
public void Dispose() => _engine = null;
}Imports FREngine
Public Class DocumentOcrService
Implements IHostedService, IDisposable
Private _engine As IEngine
Public Function StartAsync(cancellationToken As CancellationToken) As Task Implements IHostedService.StartAsync
' Step 1: Create loader — requires COM 互操作 registration
Dim loader = New EngineLoader()
' Step 2: Load engine from SDK path — throws if license files are missing
_engine = loader.GetEngineObject(
"C:\Program Files\ABBYY SDK\FineReader Engine\Bin",
"C:\Program Files\ABBYY SDK\License"
)
' Step 3: Load profile before any recognition work
_engine.LoadPredefinedProfile("DocumentConversion_Accuracy")
Return Task.CompletedTask
End Function
Public Function ProcessDocument(imagePath As String) As String
' Document must be created and destroyed per call
Dim document = _engine.CreateFRDocument()
Try
document.AddImageFile(imagePath, Nothing, Nothing)
document.Process(Nothing)
Return document.PlainText.Text
Finally
document.Close() ' Memory leaks if omitted
End Try
End Function
Public Function StopAsync(cancellationToken As CancellationToken) As Task Implements IHostedService.StopAsync
_engine = Nothing ' COM cleanup
Return Task.CompletedTask
End Function
Public Sub Dispose() Implements IDisposable.Dispose
_engine = Nothing
End Sub
End ClassImports FREngine
Public Class DocumentOcrService
Implements IHostedService, IDisposable
Private _engine As IEngine
Public Function StartAsync(cancellationToken As CancellationToken) As Task Implements IHostedService.StartAsync
' Step 1: Create loader — requires COM 互操作 registration
Dim loader = New EngineLoader()
' Step 2: Load engine from SDK path — throws if license files are missing
_engine = loader.GetEngineObject(
"C:\Program Files\ABBYY SDK\FineReader Engine\Bin",
"C:\Program Files\ABBYY SDK\License"
)
' Step 3: Load profile before any recognition work
_engine.LoadPredefinedProfile("DocumentConversion_Accuracy")
Return Task.CompletedTask
End Function
Public Function ProcessDocument(imagePath As String) As String
' Document must be created and destroyed per call
Dim document = _engine.CreateFRDocument()
Try
document.AddImageFile(imagePath, Nothing, Nothing)
document.Process(Nothing)
Return document.PlainText.Text
Finally
document.Close() ' Memory leaks if omitted
End Try
End Function
Public Function StopAsync(cancellationToken As CancellationToken) As Task Implements IHostedService.StopAsync
_engine = Nothing ' COM cleanup
Return Task.CompletedTask
End Function
Public Sub Dispose() Implements IDisposable.Dispose
_engine = Nothing
End Sub
End ClassIronOCR方法:
using IronOcr;
public class DocumentOcrService
{
//不是startup, no shutdown, no COM lifecycle
// IronTesseract is stateless — create per call or reuse per thread
public string ProcessDocument(string imagePath)
{
return new IronTesseract().Read(imagePath).Text;
}
}using IronOcr;
public class DocumentOcrService
{
//不是startup, no shutdown, no COM lifecycle
// IronTesseract is stateless — create per call or reuse per thread
public string ProcessDocument(string imagePath)
{
return new IronTesseract().Read(imagePath).Text;
}
}Imports IronOcr
Public Class DocumentOcrService
'不是startup, no shutdown, no COM lifecycle
' IronTesseract is stateless — create per call or reuse per thread
Public Function ProcessDocument(ByVal imagePath As String) As String
Return (New IronTesseract()).Read(imagePath).Text
End Function
End ClassImports IronOcr
Public Class DocumentOcrService
'不是startup, no shutdown, no COM lifecycle
' IronTesseract is stateless — create per call or reuse per thread
Public Function ProcessDocument(ByVal imagePath As String) As String
Return (New IronTesseract()).Read(imagePath).Text
End Function
End ClassIronTesseract沒有引擎生命周期。 它在首次使用時內部初始化,不需要顯式關機。 托管的服務包裝器、StopAsync方法都會消失。 如果應用並發處理文件,每個執行緒建立其自己的IronTesseract實例——無需鎖定。 IronTesseract設置指南涵蓋了配置選項,包括Configuration屬性。
識別語言設置
ABBYY語言配置涉及建立一個LanguageParams物件,新增必須與已安裝資料文件匹配的語言名稱字串,並在處理任何文件之前將這些參數與引擎關聯。 每新增一種語言需要運行時路徑中的相應資料文件。
ABBYY FineReader方法:
using FREngine;
// Engine must already be initialized with sdkPath and licensePath
private void ConfigureLanguages(IEngine engine, string[] languageCodes)
{
// Create language parameters object
var langParams = engine.CreateLanguageParams();
// Add each language — string names must match installed data file names
// Missing data file causes runtime failure
foreach (var lang in languageCodes)
{
langParams.Languages.Add(lang); // e.g., "English", "French", "German"
}
// Language params are associated at the profile level, not per-document
// Changing languages requires reloading profile or reinitializing engine
engine.LoadPredefinedProfile("DocumentConversion_Accuracy");
}
public string RecognizeFrenchDocument(IEngine engine, string imagePath)
{
var langParams = engine.CreateLanguageParams();
langParams.Languages.Add("French"); // Requires FrenchLanguage data files at runtime path
var document = engine.CreateFRDocument();
try
{
document.AddImageFile(imagePath, null, null);
document.Process(null);
return document.PlainText.Text;
}
finally
{
document.Close();
}
}using FREngine;
// Engine must already be initialized with sdkPath and licensePath
private void ConfigureLanguages(IEngine engine, string[] languageCodes)
{
// Create language parameters object
var langParams = engine.CreateLanguageParams();
// Add each language — string names must match installed data file names
// Missing data file causes runtime failure
foreach (var lang in languageCodes)
{
langParams.Languages.Add(lang); // e.g., "English", "French", "German"
}
// Language params are associated at the profile level, not per-document
// Changing languages requires reloading profile or reinitializing engine
engine.LoadPredefinedProfile("DocumentConversion_Accuracy");
}
public string RecognizeFrenchDocument(IEngine engine, string imagePath)
{
var langParams = engine.CreateLanguageParams();
langParams.Languages.Add("French"); // Requires FrenchLanguage data files at runtime path
var document = engine.CreateFRDocument();
try
{
document.AddImageFile(imagePath, null, null);
document.Process(null);
return document.PlainText.Text;
}
finally
{
document.Close();
}
}Imports FREngine
' Engine must already be initialized with sdkPath and licensePath
Private Sub ConfigureLanguages(engine As IEngine, languageCodes As String())
' Create language parameters object
Dim langParams = engine.CreateLanguageParams()
' Add each language — string names must match installed data file names
' Missing data file causes runtime failure
For Each lang In languageCodes
langParams.Languages.Add(lang) ' e.g., "English", "French", "German"
Next
' Language params are associated at the profile level, not per-document
' Changing languages requires reloading profile or reinitializing engine
engine.LoadPredefinedProfile("DocumentConversion_Accuracy")
End Sub
Public Function RecognizeFrenchDocument(engine As IEngine, imagePath As String) As String
Dim langParams = engine.CreateLanguageParams()
langParams.Languages.Add("French") ' Requires FrenchLanguage data files at runtime path
Dim document = engine.CreateFRDocument()
Try
document.AddImageFile(imagePath, Nothing, Nothing)
document.Process(Nothing)
Return document.PlainText.Text
Finally
document.Close()
End Try
End FunctionImports FREngine
' Engine must already be initialized with sdkPath and licensePath
Private Sub ConfigureLanguages(engine As IEngine, languageCodes As String())
' Create language parameters object
Dim langParams = engine.CreateLanguageParams()
' Add each language — string names must match installed data file names
' Missing data file causes runtime failure
For Each lang In languageCodes
langParams.Languages.Add(lang) ' e.g., "English", "French", "German"
Next
' Language params are associated at the profile level, not per-document
' Changing languages requires reloading profile or reinitializing engine
engine.LoadPredefinedProfile("DocumentConversion_Accuracy")
End Sub
Public Function RecognizeFrenchDocument(engine As IEngine, imagePath As String) As String
Dim langParams = engine.CreateLanguageParams()
langParams.Languages.Add("French") ' Requires FrenchLanguage data files at runtime path
Dim document = engine.CreateFRDocument()
Try
document.AddImageFile(imagePath, Nothing, Nothing)
document.Process(Nothing)
Return document.PlainText.Text
Finally
document.Close()
End Try
End FunctionIronOCR方法:
using IronOcr;
// Single language — install IronOcr.Languages.French via NuGet first
var ocr = new IronTesseract();
ocr.Language = OcrLanguage.French;
var result = ocr.Read("french-document.jpg");
Console.WriteLine(result.Text);
// Multiple simultaneous languages — operator overload, no data file management
var multiOcr = new IronTesseract();
multiOcr.Language = OcrLanguage.French + OcrLanguage.German + OcrLanguage.English;
var multiResult = multiOcr.Read("multilingual-contract.jpg");
Console.WriteLine(multiResult.Text);using IronOcr;
// Single language — install IronOcr.Languages.French via NuGet first
var ocr = new IronTesseract();
ocr.Language = OcrLanguage.French;
var result = ocr.Read("french-document.jpg");
Console.WriteLine(result.Text);
// Multiple simultaneous languages — operator overload, no data file management
var multiOcr = new IronTesseract();
multiOcr.Language = OcrLanguage.French + OcrLanguage.German + OcrLanguage.English;
var multiResult = multiOcr.Read("multilingual-contract.jpg");
Console.WriteLine(multiResult.Text);Imports IronOcr
' Single language — install IronOcr.Languages.French via NuGet first
Dim ocr As New IronTesseract()
ocr.Language = OcrLanguage.French
Dim result = ocr.Read("french-document.jpg")
Console.WriteLine(result.Text)
' Multiple simultaneous languages — operator overload, no data file management
Dim multiOcr As New IronTesseract()
multiOcr.Language = OcrLanguage.French + OcrLanguage.German + OcrLanguage.English
Dim multiResult = multiOcr.Read("multilingual-contract.jpg")
Console.WriteLine(multiResult.Text)Imports IronOcr
' Single language — install IronOcr.Languages.French via NuGet first
Dim ocr As New IronTesseract()
ocr.Language = OcrLanguage.French
Dim result = ocr.Read("french-document.jpg")
Console.WriteLine(result.Text)
' Multiple simultaneous languages — operator overload, no data file management
Dim multiOcr As New IronTesseract()
multiOcr.Language = OcrLanguage.French + OcrLanguage.German + OcrLanguage.English
Dim multiResult = multiOcr.Read("multilingual-contract.jpg")
Console.WriteLine(multiResult.Text)語言包作為標準NuGet包安裝(dotnet add package IronOcr.Languages.French)。 無需手動部署資料文件,無路徑配置,切換語言時無需重新初始化引擎。 多語言指南涵蓋了合併語言,語言索引列出了所有125+ 可用包。
多幀TIFF處理
ABBYY通過迭代幀並將每個幀作為單獨的文件頁面來處理多頁TIFF文件。 必須從TIFF物件中檢索幀數,然後將每一幀單獨新增到文件容器中。
ABBYY FineReader方法:
using FREngine;
public string ProcessMultiFrameTiff(IEngine engine, string tiffPath)
{
var document = engine.CreateFRDocument();
try
{
// Must add each frame individually — no automatic multi-frame handling
// Page count requires reading the TIFF metadata before processing
var imageInfo = engine.CreateImageInfo();
imageInfo.LoadImageFile(tiffPath);
int frameCount = imageInfo.FrameCount;
for (int i = 0; i < frameCount; i++)
{
// Each frame added with its frame index via image processing params
var imgParams = engine.CreateImageProcessingParams();
imgParams.FrameIndex = i;
document.AddImageFile(tiffPath, imgParams, null);
}
document.Process(null);
return document.PlainText.Text;
}
finally
{
document.Close();
}
}using FREngine;
public string ProcessMultiFrameTiff(IEngine engine, string tiffPath)
{
var document = engine.CreateFRDocument();
try
{
// Must add each frame individually — no automatic multi-frame handling
// Page count requires reading the TIFF metadata before processing
var imageInfo = engine.CreateImageInfo();
imageInfo.LoadImageFile(tiffPath);
int frameCount = imageInfo.FrameCount;
for (int i = 0; i < frameCount; i++)
{
// Each frame added with its frame index via image processing params
var imgParams = engine.CreateImageProcessingParams();
imgParams.FrameIndex = i;
document.AddImageFile(tiffPath, imgParams, null);
}
document.Process(null);
return document.PlainText.Text;
}
finally
{
document.Close();
}
}Imports FREngine
Public Function ProcessMultiFrameTiff(engine As IEngine, tiffPath As String) As String
Dim document = engine.CreateFRDocument()
Try
' Must add each frame individually — no automatic multi-frame handling
' Page count requires reading the TIFF metadata before processing
Dim imageInfo = engine.CreateImageInfo()
imageInfo.LoadImageFile(tiffPath)
Dim frameCount As Integer = imageInfo.FrameCount
For i As Integer = 0 To frameCount - 1
' Each frame added with its frame index via image processing params
Dim imgParams = engine.CreateImageProcessingParams()
imgParams.FrameIndex = i
document.AddImageFile(tiffPath, imgParams, Nothing)
Next
document.Process(Nothing)
Return document.PlainText.Text
Finally
document.Close()
End Try
End FunctionImports FREngine
Public Function ProcessMultiFrameTiff(engine As IEngine, tiffPath As String) As String
Dim document = engine.CreateFRDocument()
Try
' Must add each frame individually — no automatic multi-frame handling
' Page count requires reading the TIFF metadata before processing
Dim imageInfo = engine.CreateImageInfo()
imageInfo.LoadImageFile(tiffPath)
Dim frameCount As Integer = imageInfo.FrameCount
For i As Integer = 0 To frameCount - 1
' Each frame added with its frame index via image processing params
Dim imgParams = engine.CreateImageProcessingParams()
imgParams.FrameIndex = i
document.AddImageFile(tiffPath, imgParams, Nothing)
Next
document.Process(Nothing)
Return document.PlainText.Text
Finally
document.Close()
End Try
End FunctionIronOCR方法:
using IronOcr;
// LoadImageFrames handles multi-frame TIFF automatically
using var input = new OcrInput();
input.LoadImageFrames("scanned-batch.tiff");
var ocr = new IronTesseract();
var result = ocr.Read(input);
// Per-page results accessible directly
foreach (var page in result.Pages)
{
Console.WriteLine($"Frame {page.PageNumber}: {page.Text.Length} characters");
Console.WriteLine(page.Text);
}using IronOcr;
// LoadImageFrames handles multi-frame TIFF automatically
using var input = new OcrInput();
input.LoadImageFrames("scanned-batch.tiff");
var ocr = new IronTesseract();
var result = ocr.Read(input);
// Per-page results accessible directly
foreach (var page in result.Pages)
{
Console.WriteLine($"Frame {page.PageNumber}: {page.Text.Length} characters");
Console.WriteLine(page.Text);
}Imports IronOcr
' LoadImageFrames handles multi-frame TIFF automatically
Using input As New OcrInput()
input.LoadImageFrames("scanned-batch.tiff")
Dim ocr As New IronTesseract()
Dim result = ocr.Read(input)
' Per-page results accessible directly
For Each page In result.Pages
Console.WriteLine($"Frame {page.PageNumber}: {page.Text.Length} characters")
Console.WriteLine(page.Text)
Next
End UsingImports IronOcr
' LoadImageFrames handles multi-frame TIFF automatically
Using input As New OcrInput()
input.LoadImageFrames("scanned-batch.tiff")
Dim ocr As New IronTesseract()
Dim result = ocr.Read(input)
' Per-page results accessible directly
For Each page In result.Pages
Console.WriteLine($"Frame {page.PageNumber}: {page.Text.Length} characters")
Console.WriteLine(page.Text)
Next
End UsingOcrInput.LoadImageFrames 讀取多頁TIFF中的每個幀,無需手動迭代。 結果通過result.Pages提供每頁存取,包括文字、座標資料和每幀的置信度。 TIFF輸入指南涵蓋了多幀TIFF和動畫GIF處理。
並行批量處理
ABBYY基於COM的引擎在沒有同步策略的情況下不能安全地從多個執行緒同步調用CreateFRDocument。 生產批量處理器通常維持引擎實例池或通過鎖定進行序列化存取。 這兩種方法都新增了IronOCR消除的基礎設施。
ABBYY FineReader方法:
using FREngine;
using System.Collections.Concurrent;
using System.Threading;
public class AbbyyBatchProcessor
{
// Pool required because engine is not safely concurrent
private readonly SemaphoreSlim _engineLock = new SemaphoreSlim(1, 1);
private IEngine _engine;
public async Task<Dictionary<string, string>> ProcessBatchAsync(string[] imagePaths)
{
var results = new ConcurrentDictionary<string, string>();
// Must serialize — one document at a time through single engine
foreach (var imagePath in imagePaths)
{
await _engineLock.WaitAsync();
try
{
var document = _engine.CreateFRDocument();
try
{
document.AddImageFile(imagePath, null, null);
document.Process(null);
results[imagePath] = document.PlainText.Text;
}
finally
{
document.Close();
}
}
finally
{
_engineLock.Release();
}
}
return new Dictionary<string, string>(results);
}
}using FREngine;
using System.Collections.Concurrent;
using System.Threading;
public class AbbyyBatchProcessor
{
// Pool required because engine is not safely concurrent
private readonly SemaphoreSlim _engineLock = new SemaphoreSlim(1, 1);
private IEngine _engine;
public async Task<Dictionary<string, string>> ProcessBatchAsync(string[] imagePaths)
{
var results = new ConcurrentDictionary<string, string>();
// Must serialize — one document at a time through single engine
foreach (var imagePath in imagePaths)
{
await _engineLock.WaitAsync();
try
{
var document = _engine.CreateFRDocument();
try
{
document.AddImageFile(imagePath, null, null);
document.Process(null);
results[imagePath] = document.PlainText.Text;
}
finally
{
document.Close();
}
}
finally
{
_engineLock.Release();
}
}
return new Dictionary<string, string>(results);
}
}Imports FREngine
Imports System.Collections.Concurrent
Imports System.Threading
Public Class AbbyyBatchProcessor
' Pool required because engine is not safely concurrent
Private ReadOnly _engineLock As New SemaphoreSlim(1, 1)
Private _engine As IEngine
Public Async Function ProcessBatchAsync(imagePaths As String()) As Task(Of Dictionary(Of String, String))
Dim results As New ConcurrentDictionary(Of String, String)()
' Must serialize — one document at a time through single engine
For Each imagePath In imagePaths
Await _engineLock.WaitAsync()
Try
Dim document = _engine.CreateFRDocument()
Try
document.AddImageFile(imagePath, Nothing, Nothing)
document.Process(Nothing)
results(imagePath) = document.PlainText.Text
Finally
document.Close()
End Try
Finally
_engineLock.Release()
End Try
Next
Return New Dictionary(Of String, String)(results)
End Function
End ClassImports FREngine
Imports System.Collections.Concurrent
Imports System.Threading
Public Class AbbyyBatchProcessor
' Pool required because engine is not safely concurrent
Private ReadOnly _engineLock As New SemaphoreSlim(1, 1)
Private _engine As IEngine
Public Async Function ProcessBatchAsync(imagePaths As String()) As Task(Of Dictionary(Of String, String))
Dim results As New ConcurrentDictionary(Of String, String)()
' Must serialize — one document at a time through single engine
For Each imagePath In imagePaths
Await _engineLock.WaitAsync()
Try
Dim document = _engine.CreateFRDocument()
Try
document.AddImageFile(imagePath, Nothing, Nothing)
document.Process(Nothing)
results(imagePath) = document.PlainText.Text
Finally
document.Close()
End Try
Finally
_engineLock.Release()
End Try
Next
Return New Dictionary(Of String, String)(results)
End Function
End ClassIronOCR方法:
using IronOcr;
using System.Collections.Concurrent;
using System.Threading.Tasks;
public class OcrBatchProcessor
{
public Dictionary<string, string> ProcessBatch(string[] imagePaths)
{
var results = new ConcurrentDictionary<string, string>();
// IronTesseract is thread-safe — one instance per thread, fully parallel
Parallel.ForEach(imagePaths, imagePath =>
{
var ocr = new IronTesseract(); // Each thread owns its instance
var result = ocr.Read(imagePath);
results[imagePath] = result.Text;
});
return new Dictionary<string, string>(results);
}
}using IronOcr;
using System.Collections.Concurrent;
using System.Threading.Tasks;
public class OcrBatchProcessor
{
public Dictionary<string, string> ProcessBatch(string[] imagePaths)
{
var results = new ConcurrentDictionary<string, string>();
// IronTesseract is thread-safe — one instance per thread, fully parallel
Parallel.ForEach(imagePaths, imagePath =>
{
var ocr = new IronTesseract(); // Each thread owns its instance
var result = ocr.Read(imagePath);
results[imagePath] = result.Text;
});
return new Dictionary<string, string>(results);
}
}Imports IronOcr
Imports System.Collections.Concurrent
Imports System.Threading.Tasks
Public Class OcrBatchProcessor
Public Function ProcessBatch(imagePaths As String()) As Dictionary(Of String, String)
Dim results = New ConcurrentDictionary(Of String, String)()
' IronTesseract is thread-safe — one instance per thread, fully parallel
Parallel.ForEach(imagePaths, Sub(imagePath)
Dim ocr = New IronTesseract() ' Each thread owns its instance
Dim result = ocr.Read(imagePath)
results(imagePath) = result.Text
End Sub)
Return New Dictionary(Of String, String)(results)
End Function
End ClassImports IronOcr
Imports System.Collections.Concurrent
Imports System.Threading.Tasks
Public Class OcrBatchProcessor
Public Function ProcessBatch(imagePaths As String()) As Dictionary(Of String, String)
Dim results = New ConcurrentDictionary(Of String, String)()
' IronTesseract is thread-safe — one instance per thread, fully parallel
Parallel.ForEach(imagePaths, Sub(imagePath)
Dim ocr = New IronTesseract() ' Each thread owns its instance
Dim result = ocr.Read(imagePath)
results(imagePath) = result.Text
End Sub)
Return New Dictionary(Of String, String)(results)
End Function
End Class每個IronTesseract實例是獨立的。 Parallel.ForEach 飽和可用CPU核心,無需任何共享狀態、鎖定或序列化。 ABBYY版本的文件即使在異步包裝器下也是順序處理; IronOCR版本真正並行地處理它們。 多執行緒範例展示了此模式的時間比較。 如需高層次吞吐量控制,請參閱速度優化指南。
文件導出流水線
ABBYY通過其FileExportFormatEnum值支持多種導出格式。 導出到DOCX、RTF或純文字要求建立特定格式的導出參數物件,然後調用document.Export,並將適當的枚舉值和參數物件傳遞給它。
ABBYY FineReader方法:
using FREngine;
public class AbbyyExporter
{
private IEngine _engine;
public void ExportToMultipleFormats(string imagePath, string outputDir)
{
var document = _engine.CreateFRDocument();
try
{
document.AddImageFile(imagePath, null, null);
document.Process(null);
string baseName = Path.GetFileNameWithoutExtension(imagePath);
// Export as plain text
document.Export(
Path.Combine(outputDir, baseName + ".txt"),
FileExportFormatEnum.FEF_TextUnicodeDefaults,
null
);
// Export as searchable PDF (requires PDF export params)
var pdfParams = _engine.CreatePDFExportParams();
pdfParams.Scenario = PDFExportScenarioEnum.PDES_Balanced;
pdfParams.UseOriginalPaperSize = true;
document.Export(
Path.Combine(outputDir, baseName + ".pdf"),
FileExportFormatEnum.FEF_PDF,
pdfParams
);
// Export as DOCX
var docxParams = _engine.CreateDOCXExportParams();
document.Export(
Path.Combine(outputDir, baseName + ".docx"),
FileExportFormatEnum.FEF_DOCX,
docxParams
);
}
finally
{
document.Close();
}
}
}using FREngine;
public class AbbyyExporter
{
private IEngine _engine;
public void ExportToMultipleFormats(string imagePath, string outputDir)
{
var document = _engine.CreateFRDocument();
try
{
document.AddImageFile(imagePath, null, null);
document.Process(null);
string baseName = Path.GetFileNameWithoutExtension(imagePath);
// Export as plain text
document.Export(
Path.Combine(outputDir, baseName + ".txt"),
FileExportFormatEnum.FEF_TextUnicodeDefaults,
null
);
// Export as searchable PDF (requires PDF export params)
var pdfParams = _engine.CreatePDFExportParams();
pdfParams.Scenario = PDFExportScenarioEnum.PDES_Balanced;
pdfParams.UseOriginalPaperSize = true;
document.Export(
Path.Combine(outputDir, baseName + ".pdf"),
FileExportFormatEnum.FEF_PDF,
pdfParams
);
// Export as DOCX
var docxParams = _engine.CreateDOCXExportParams();
document.Export(
Path.Combine(outputDir, baseName + ".docx"),
FileExportFormatEnum.FEF_DOCX,
docxParams
);
}
finally
{
document.Close();
}
}
}Imports FREngine
Public Class AbbyyExporter
Private _engine As IEngine
Public Sub ExportToMultipleFormats(imagePath As String, outputDir As String)
Dim document = _engine.CreateFRDocument()
Try
document.AddImageFile(imagePath, Nothing, Nothing)
document.Process(Nothing)
Dim baseName As String = Path.GetFileNameWithoutExtension(imagePath)
' Export as plain text
document.Export(
Path.Combine(outputDir, baseName & ".txt"),
FileExportFormatEnum.FEF_TextUnicodeDefaults,
Nothing
)
' Export as searchable PDF (requires PDF export params)
Dim pdfParams = _engine.CreatePDFExportParams()
pdfParams.Scenario = PDFExportScenarioEnum.PDES_Balanced
pdfParams.UseOriginalPaperSize = True
document.Export(
Path.Combine(outputDir, baseName & ".pdf"),
FileExportFormatEnum.FEF_PDF,
pdfParams
)
' Export as DOCX
Dim docxParams = _engine.CreateDOCXExportParams()
document.Export(
Path.Combine(outputDir, baseName & ".docx"),
FileExportFormatEnum.FEF_DOCX,
docxParams
)
Finally
document.Close()
End Try
End Sub
End ClassImports FREngine
Public Class AbbyyExporter
Private _engine As IEngine
Public Sub ExportToMultipleFormats(imagePath As String, outputDir As String)
Dim document = _engine.CreateFRDocument()
Try
document.AddImageFile(imagePath, Nothing, Nothing)
document.Process(Nothing)
Dim baseName As String = Path.GetFileNameWithoutExtension(imagePath)
' Export as plain text
document.Export(
Path.Combine(outputDir, baseName & ".txt"),
FileExportFormatEnum.FEF_TextUnicodeDefaults,
Nothing
)
' Export as searchable PDF (requires PDF export params)
Dim pdfParams = _engine.CreatePDFExportParams()
pdfParams.Scenario = PDFExportScenarioEnum.PDES_Balanced
pdfParams.UseOriginalPaperSize = True
document.Export(
Path.Combine(outputDir, baseName & ".pdf"),
FileExportFormatEnum.FEF_PDF,
pdfParams
)
' Export as DOCX
Dim docxParams = _engine.CreateDOCXExportParams()
document.Export(
Path.Combine(outputDir, baseName & ".docx"),
FileExportFormatEnum.FEF_DOCX,
docxParams
)
Finally
document.Close()
End Try
End Sub
End ClassIronOCR方法:
using IronOcr;
public class OcrExporter
{
public void ExportToMultipleFormats(string imagePath, string outputDir)
{
var ocr = new IronTesseract();
var result = ocr.Read(imagePath);
string baseName = Path.GetFileNameWithoutExtension(imagePath);
// Plain text — direct property access
File.WriteAllText(
Path.Combine(outputDir, baseName + ".txt"),
result.Text
);
// Searchable PDF — one method call, no parameter objects
result.SaveAsSearchablePdf(
Path.Combine(outputDir, baseName + ".pdf")
);
// hOCR format — for document management systems
result.SaveAsHocrFile(
Path.Combine(outputDir, baseName + ".hocr")
);
}
}using IronOcr;
public class OcrExporter
{
public void ExportToMultipleFormats(string imagePath, string outputDir)
{
var ocr = new IronTesseract();
var result = ocr.Read(imagePath);
string baseName = Path.GetFileNameWithoutExtension(imagePath);
// Plain text — direct property access
File.WriteAllText(
Path.Combine(outputDir, baseName + ".txt"),
result.Text
);
// Searchable PDF — one method call, no parameter objects
result.SaveAsSearchablePdf(
Path.Combine(outputDir, baseName + ".pdf")
);
// hOCR format — for document management systems
result.SaveAsHocrFile(
Path.Combine(outputDir, baseName + ".hocr")
);
}
}Imports IronOcr
Imports System.IO
Public Class OcrExporter
Public Sub ExportToMultipleFormats(imagePath As String, outputDir As String)
Dim ocr As New IronTesseract()
Dim result = ocr.Read(imagePath)
Dim baseName As String = Path.GetFileNameWithoutExtension(imagePath)
' Plain text — direct property access
File.WriteAllText(
Path.Combine(outputDir, baseName & ".txt"),
result.Text
)
' Searchable PDF — one method call, no parameter objects
result.SaveAsSearchablePdf(
Path.Combine(outputDir, baseName & ".pdf")
)
' hOCR format — for document management systems
result.SaveAsHocrFile(
Path.Combine(outputDir, baseName & ".hocr")
)
End Sub
End ClassImports IronOcr
Imports System.IO
Public Class OcrExporter
Public Sub ExportToMultipleFormats(imagePath As String, outputDir As String)
Dim ocr As New IronTesseract()
Dim result = ocr.Read(imagePath)
Dim baseName As String = Path.GetFileNameWithoutExtension(imagePath)
' Plain text — direct property access
File.WriteAllText(
Path.Combine(outputDir, baseName & ".txt"),
result.Text
)
' Searchable PDF — one method call, no parameter objects
result.SaveAsSearchablePdf(
Path.Combine(outputDir, baseName & ".pdf")
)
' hOCR format — for document management systems
result.SaveAsHocrFile(
Path.Combine(outputDir, baseName & ".hocr")
)
End Sub
End ClassIronOCR的.Text並提供不需要參數物件或格式枚舉的輸出方法。 SaveAsSearchablePdf調用處理PDF導出一步到位,而ABBYY需要三步參數/導出順序。 可搜索PDF指南涵蓋了頁面範圍選項和壓縮設置。 hOCR導出指南涵蓋了HOCR格式,用於消費位置感知的OCR輸出系統。
ABBYY FineReader API到IronOCR映射參考
| ABBYY FineReader Engine | IronOCR 等效 |
|---|---|
new EngineLoader() | 不需要 |
loader.GetEngineObject(sdkPath, licensePath) | new IronTesseract() |
engine.LoadPredefinedProfile("...") | 不需要(內部處理) |
engine.CreateLanguageParams() | 不需要 |
langParams.Languages.Add("French") | ocr.Language = OcrLanguage.French |
langParams.Languages.Add("English") + langParams.Languages.Add("German") | ocr.Language = OcrLanguage.English + OcrLanguage.German |
engine.CreateFRDocument() | new OcrInput() |
engine.CreateFRDocumentFromImage(path, null) | ocr.Read(path) |
document.AddImageFile(path, null, null) | input.LoadImage(path) |
imageInfo.LoadImageFile(tiff) + frameCount迴圈 | input.LoadImageFrames(tiff) |
engine.CreatePDFFile() 然後pdfFile.Open(path, null, null) | input.LoadPdf(path) |
document.Process(null) | ocr.Read(input) |
document.PlainText.Text | result.Text |
frDocument.Pages[i].PlainText.Text | result.Pages[i].Text |
page.Layout.Blocks + BlockTypeEnum.BT_Table檢查 | result.Pages + 單詞座標資料 |
block.GetAsTableBlock() | result.Pages[i].Lines(帶座標) |
engine.CreatePDFExportParams() | 不需要 |
document.Export(path, FEF_PDF, params) | result.SaveAsSearchablePdf(path) |
document.Export(path, FEF_TextUnicodeDefaults, null) | File.WriteAllText(path, result.Text) |
engine.CreateDOCXExportParams() + 導出 | 不直接支持 |
document.Close() | 由OcrInput上處理 |
_engine.GetLicenseInfo().ExpirationDate | IronOcr.License.IsValidLicense |
許可文件(ABBYY.key) | IronOcr.License.LicenseKey = "key" |
engine.CreateZone() + zone.SetBounds(x, y, w, h) | new CropRectangle(x, y, width, height) |
常見的遷移問題与解決方案
問題1:刪除SDK後的COM註冊錯誤
ABBYY: 從專案引用中刪除Could not load type 'FREngine.EngineLoader'或舊命名空間保留的類的COM互操作錯誤而失敗。
解決方案:在移除引用之前搜索所有ABBYY.FineReader的用法。 任何實現OcrInput上:
// Before: explicit Close in finally
var document = _engine.CreateFRDocument();
try { document.Process(null); }
finally { document.Close(); }
// After: using pattern on OcrInput
using var input = new OcrInput();
input.LoadImage(imagePath);
var result = new IronTesseract().Read(input);// Before: explicit Close in finally
var document = _engine.CreateFRDocument();
try { document.Process(null); }
finally { document.Close(); }
// After: using pattern on OcrInput
using var input = new OcrInput();
input.LoadImage(imagePath);
var result = new IronTesseract().Read(input);Option Strict On
Option Infer On
' Before: explicit Close in finally
Dim document = _engine.CreateFRDocument()
Try
document.Process(Nothing)
Finally
document.Close()
End Try
' After: using pattern on OcrInput
Using input As New OcrInput()
input.LoadImage(imagePath)
Dim result = New IronTesseract().Read(input)
End UsingOption Strict On
Option Infer On
' Before: explicit Close in finally
Dim document = _engine.CreateFRDocument()
Try
document.Process(Nothing)
Finally
document.Close()
End Try
' After: using pattern on OcrInput
Using input As New OcrInput()
input.LoadImage(imagePath)
Dim result = New IronTesseract().Read(input)
End Using問題2:識別配置檔案無對應的等效
ABBYY: 調用engine.LoadPredefinedProfile("FieldLevelRecognition")的程式碼使用ABBYY專用的配置檔案來平衡精確度與吞吐量。 IronOCR中無名為Profile的對應屬性。
解決方案: IronOCR通過IronTesseract.Configuration提供相同的權衡。 為了速度優化,請設置ocr.Configuration.TesseractVersion = TesseractVersion.Tesseract5(預設)並減少預處理過濾器。 為了實現最大的精確度,請新增完整的預處理流水線:
// Speed-optimized
var ocr = new IronTesseract();
//不是preprocessing — fastest path
var result = ocr.Read("clean-document.jpg");
// Accuracy-optimized for difficult inputs
var ocr = new IronTesseract();
using var input = new OcrInput();
input.LoadImage("degraded-scan.jpg");
input.Deskew();
input.DeNoise();
input.Contrast();
input.Binarize();
var result = ocr.Read(input);// Speed-optimized
var ocr = new IronTesseract();
//不是preprocessing — fastest path
var result = ocr.Read("clean-document.jpg");
// Accuracy-optimized for difficult inputs
var ocr = new IronTesseract();
using var input = new OcrInput();
input.LoadImage("degraded-scan.jpg");
input.Deskew();
input.DeNoise();
input.Contrast();
input.Binarize();
var result = ocr.Read(input);' Speed-optimized
Dim ocr As New IronTesseract()
'不是preprocessing — fastest path
Dim result = ocr.Read("clean-document.jpg")
' Accuracy-optimized for difficult inputs
Dim ocr As New IronTesseract()
Using input As New OcrInput()
input.LoadImage("degraded-scan.jpg")
input.Deskew()
input.DeNoise()
input.Contrast()
input.Binarize()
Dim result = ocr.Read(input)
End Using' Speed-optimized
Dim ocr As New IronTesseract()
'不是preprocessing — fastest path
Dim result = ocr.Read("clean-document.jpg")
' Accuracy-optimized for difficult inputs
Dim ocr As New IronTesseract()
Using input As New OcrInput()
input.LoadImage("degraded-scan.jpg")
input.Deskew()
input.DeNoise()
input.Contrast()
input.Binarize()
Dim result = ocr.Read(input)
End Using影像質量校正指南 說明了哪些過濾器解決了哪些輸入質量問題。 速度優化指南涵蓋了降低乾淨文件處理時間的配置屬性。
問題3:許可證文件部署步驟仍然存在於CI/CD中
ABBYY: 構建管道中通常包含一步,從安全儲存庫將ABBYY.key文件複製到部署目標。 遷移後,團隊有時會遺漏刪除此步驟,留下引用不再存在的文件路徑的無效部署程式碼。
解決方案: 完全刪除許可證文件複製步驟。 用環境變數註入步驟替換它:
# Remove these CI/CD steps after migration:
# - name: Copy ABBYY license files
# run: |
# cp $SECRETS_PATH/ABBYY.lic $DEPLOY_PATH/License/
# cp $SECRETS_PATH/ABBYY.key $DEPLOY_PATH/License/
# Add this instead (environment variable injection):
# - name: SetIronOCRlicense
# env:
# IRONOCR_LICENSE_KEY: ${{secrets.IRONOCR_LICENSE}}
然後在應用程式啟動時:
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE_KEY")
?? throw new InvalidOperationException("IRONOCR_LICENSE_KEY not set");IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE_KEY")
?? throw new InvalidOperationException("IRONOCR_LICENSE_KEY not set");Imports System
IronOcr.License.LicenseKey = If(Environment.GetEnvironmentVariable("IRONOCR_LICENSE_KEY"), Throw New InvalidOperationException("IRONOCR_LICENSE_KEY not set"))Imports System
IronOcr.License.LicenseKey = If(Environment.GetEnvironmentVariable("IRONOCR_LICENSE_KEY"), Throw New InvalidOperationException("IRONOCR_LICENSE_KEY not set"))問題4:引擎非執行緒安全——現有鎖定程式碼
ABBYY: 從多個執行緒中調用ABBYY的應用程式通常包含lock語句,或執行緒本地引擎實例,以避免COM執行緒問題。 此同步程式碼是特定於ABBYY的執行緒模型的。
解決方案: 刪除所有包裹ABBYY調用的同步程式碼。 IronOCR的IronTesseract可以安全地按執行緒實例化:
// Remove all of this:
// private readonly SemaphoreSlim _engineLock = new SemaphoreSlim(1, 1);
// await _engineLock.WaitAsync();
// try { ... } finally { _engineLock.Release(); }
// Replace with:
Parallel.ForEach(documents, doc =>
{
var ocr = new IronTesseract(); // One per thread — no lock needed
results[doc.Id] = ocr.Read(doc.Path).Text;
});// Remove all of this:
// private readonly SemaphoreSlim _engineLock = new SemaphoreSlim(1, 1);
// await _engineLock.WaitAsync();
// try { ... } finally { _engineLock.Release(); }
// Replace with:
Parallel.ForEach(documents, doc =>
{
var ocr = new IronTesseract(); // One per thread — no lock needed
results[doc.Id] = ocr.Read(doc.Path).Text;
});Imports System.Threading.Tasks
Parallel.ForEach(documents, Sub(doc)
Dim ocr = New IronTesseract() ' One per thread — no lock needed
results(doc.Id) = ocr.Read(doc.Path).Text
End Sub)Imports System.Threading.Tasks
Parallel.ForEach(documents, Sub(doc)
Dim ocr = New IronTesseract() ' One per thread — no lock needed
results(doc.Id) = ocr.Read(doc.Path).Text
End Sub)問題5:TIFF的FrameCount模式
ABBYY: 使用OcrInput.LoadImageFrames內部處理幀枚舉。
解決方案: 完全刪除幀計數迴圈:
// Remove:
// var imageInfo = engine.CreateImageInfo();
// imageInfo.LoadImageFile(tiffPath);
// for (int i = 0; i < imageInfo.FrameCount; i++) { document.AddImageFile(...) }
// Replace with:
using var input = new OcrInput();
input.LoadImageFrames("multi-page-scan.tiff");
var result = new IronTesseract().Read(input);
// result.Pages contains one entry per TIFF frame// Remove:
// var imageInfo = engine.CreateImageInfo();
// imageInfo.LoadImageFile(tiffPath);
// for (int i = 0; i < imageInfo.FrameCount; i++) { document.AddImageFile(...) }
// Replace with:
using var input = new OcrInput();
input.LoadImageFrames("multi-page-scan.tiff");
var result = new IronTesseract().Read(input);
// result.Pages contains one entry per TIFF frameImports IronOcr
' Remove:
' Dim imageInfo = engine.CreateImageInfo()
' imageInfo.LoadImageFile(tiffPath)
' For i As Integer = 0 To imageInfo.FrameCount - 1
' document.AddImageFile(...)
' Next
' Replace with:
Using input As New OcrInput()
input.LoadImageFrames("multi-page-scan.tiff")
Dim result = New IronTesseract().Read(input)
' result.Pages contains one entry per TIFF frame
End UsingImports IronOcr
' Remove:
' Dim imageInfo = engine.CreateImageInfo()
' imageInfo.LoadImageFile(tiffPath)
' For i As Integer = 0 To imageInfo.FrameCount - 1
' document.AddImageFile(...)
' Next
' Replace with:
Using input As New OcrInput()
input.LoadImageFrames("multi-page-scan.tiff")
Dim result = New IronTesseract().Read(input)
' result.Pages contains one entry per TIFF frame
End Using問題6:DOCX導出無直接對應
ABBYY: document.Export(path, FileExportFormatEnum.FEF_DOCX, docxParams)會產生一個Word文件。 IronOCR不直接產生DOCX輸出。
解決方案: IronOCR生成可搜索的PDF和結構化文字資料。 對於需要DOCX輸出的工作流,實際的遷移途徑是生成可搜索的PDF並在下游轉換,或提取結構化文字並使用如Open XML SDK這樣的庫寫入DOCX:
//IronOCRto searchable PDF (closest equivalent)
var result = new IronTesseract().Read(inputPath);
result.SaveAsSearchablePdf(outputPath.Replace(".docx", ".pdf"));
// Or extract structured text for downstream DOCX generation
foreach (var paragraph in result.Paragraphs)
{
Console.WriteLine(paragraph.Text);
// Write to DOCX via Open XML SDK or similar
}//IronOCRto searchable PDF (closest equivalent)
var result = new IronTesseract().Read(inputPath);
result.SaveAsSearchablePdf(outputPath.Replace(".docx", ".pdf"));
// Or extract structured text for downstream DOCX generation
foreach (var paragraph in result.Paragraphs)
{
Console.WriteLine(paragraph.Text);
// Write to DOCX via Open XML SDK or similar
}'IronOCR to searchable PDF (closest equivalent)
Dim result = New IronTesseract().Read(inputPath)
result.SaveAsSearchablePdf(outputPath.Replace(".docx", ".pdf"))
' Or extract structured text for downstream DOCX generation
For Each paragraph In result.Paragraphs
Console.WriteLine(paragraph.Text)
' Write to DOCX via Open XML SDK or similar
Next'IronOCR to searchable PDF (closest equivalent)
Dim result = New IronTesseract().Read(inputPath)
result.SaveAsSearchablePdf(outputPath.Replace(".docx", ".pdf"))
' Or extract structured text for downstream DOCX generation
For Each paragraph In result.Paragraphs
Console.WriteLine(paragraph.Text)
' Write to DOCX via Open XML SDK or similar
Next讀取結果指南涵蓋了存取段落、行、單詞和字元級座標資料以進行下游處理。
ABBYY FineReader遷移檢查表
遷移前的任務
在做任何更改之前審核程式碼庫:
# Find all files using ABBYY namespaces
grep -r "using FREngine" --include="*.cs" .
grep -r "using ABBYY" --include="*.cs" .
# Find engine lifecycle patterns
grep -r "EngineLoader\|GetEngineObject\|LoadPredefinedProfile" --include="*.cs" .
# Find document lifecycle patterns
grep -r "CreateFRDocument\|document\.Close\|AddImageFile\|document\.Process" --include="*.cs" .
# Find language configuration
grep -r "CreateLanguageParams\|langParams\.Languages" --include="*.cs" .
# Find export calls
grep -r "FileExportFormatEnum\|CreatePDFExportParams\|document\.Export" --include="*.cs" .
# Find license file references in CI/CD and deployment scripts
grep -r "ABBYY\.lic\|ABBYY\.key" .
記錄持有IFRDocument字段的每個類。 注意哪些導出格式正在使用—— DOCX輸出需要另一種方法(見上面的問題6)。
程式碼更新任務
- 從所有
FREngine.dll引用 - 對每個使用ABBYY的專案運行
dotnet add package IronOcr - 在應用啟動時新增
Program.cs或啟動類) - 為每個非英語語言安裝語言NuGet包(
dotnet add package IronOcr.Languages.French,等等) - 刪除所有
LoadPredefinedProfile調用 - 刪除所有
langParams.Languages.Add調用 - 用
engine.CreateFRDocument()+document.AddImageFile()+document.Process() - 用
input.LoadImageFrames(tiffPath)替換多幀TIFF迴圈 - 用
document.PlainText.Text - 用
frDocument.Pages[i].PlainText.Text - 用
document.Export(..., FEF_PDF, pdfParams) - 將所有
OcrInput上 - 刪除
SemaphoreSlim和序列化ABBYY引擎存取的鎖定程式碼 - 將
engine.CreateZone()/zone.SetBounds()/new CropRectangle(x, y, width, height) - 從CI/CD管道中移除許可證文件複製步驟
- 更新Docker映像——移除SDK安裝層,為Linux目標新增
libgdiplus
遷移後測試
- 在每種文件型別的代表性樣本上驗證文字提取輸出(發票、合同、掃描表格)
- 確認多頁TIFF處理返回的頁數與ABBYY生成的幀數相同
- 用於ABBYY基線比較的相同輸入測試多語言文件
- 驗證可搜索的PDF輸出在Adobe Reader和瀏覽器PDF查看器中是否可以進行文字搜索
- 與生產並發級運行並行批量處理器,確認無異常發生
- 在已知良好的文件上檢查
result.Confidence,以建立質量門限的基線 - 測試在階段部署環境中從環境變數中初始化許可鍵
- 驗證Docker映像構建並運行OCR,無需安裝ABBYY SDK卷掛載
- 確認CI/CD管道在沒有許可證文件複制步驟的情況下完成
- 在批量處理器上運行記憶體分析器,確認
using位置)
遷移至IronOCR的主要好處
部署複雜性減少了一個數量級。 每個ABBYY部署均需安裝SDK、放置許可證文件、配置運行時路徑,以及在應用程式啟動之前驗證文件是否位於正確路徑。 IronOCR作為一個NuGet依賴關係進行部署。 dotnet publish生成包含OCR引擎的自包含工件。 Docker部署指南和Azure安裝指南顯示了完整的配置——兩者都放在單頁上。
COM互操作消失。 移除COM層消除了整個類別的運行時故障:新機器上的COM註冊錯誤、單元執行緒不匹配、try/finally樣板。 程式碼庫縮小。 錯誤表面與之一起縮小。
體積增長不再觸發預算審查。 IronOCR的永久許可覆蓋無限制的文件量。 在第一年每月處理10,000份文件,第三年每月處理2,000,000份文件的應用程式承擔相同的OCR許可成本。 沒有按頁面計數器,沒有超額發票,沒有體積層級重新談判。 許可頁面顯示所有層級——Professonal許可含10名開發者提供對任何部署目標處理任何文件量的支援費用為$2,999。
跨平台部署開啟新的基礎設施選項。 ABBYY的COM層需要Windows。 想要將文件處理移動到Linux容器的團隊出於成本或密度原因無法實現此願望。 IronOCR在Windows、Linux和macOS上從同一個NuGet包運行一致。 從ABBYY遷移將Windows約束從應用程式堆疊的OCR層中刪除。 Linux部署指南和AWS安裝指南各自覆蓋了環境的完整設置。
沒有基礎設施工作可以進行並行吞吐。 序列化ABBYY引擎存取的鎖定策略已經消失。 Parallel.ForEach,然後獲取結果。 吞吐量隨著可用的CPU核心而擴展,無需任何額外程式碼。 多執行緒範例顯示了在多核硬體上的時間改善。
語言配置是包引用。 將德語或日語OCR支援新增到ABBYY整合中意味着識別資料文件,將它們部署到每台目標機器的運行時路徑,並在文件缺失時處理故障。 使用IronOCR,dotnet add package IronOcr.Languages.German將語言包作為版本控制的可重現的NuGet依賴關係新增。 包管理器確保資料在每次構建時保持存在。 自定義語言包指南涵蓋了自定義語言模型的訓練和在專業化域的部署。
相關文章


