跳至页脚内容
视频

从 ABBYY FineReader 迁移到 IronOCR

本指南将引导.NET开发人员逐步完成用IronOCR替换 ABBYY FineReader 引擎 SDK 的每一步。 它涵盖了移除 COM 依赖项和 SDK 安装程序工件的机械步骤,将 ABBYY 的 API 映射到IronOCR 的等效项,并为生产 ABBYY 集成中最常见的模式提供了前后代码示例。 迁移目标是那些决定ABBYY的企业成本和部署复杂性不再符合其项目要求的团队。

为什么要从 ABBYY FineReader 迁移?

ABBYY FineReader Engine 是一个功能强大的 OCR 平台,但其架构是为拥有专门基础设施团队的EnterpriseWindows 应用而设计的。 当.NET团队的实际工作是发票处理、合同数字化或扫描表单提取时,这种架构就变成了负担而不是资产。

COM 互操作性债务会随着时间推移而累积。所有 ABBYY 在.NET中的集成都需要通过 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
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")
$vbLabelText   $csharpLabel
// 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()
$vbLabelText   $csharpLabel

IronOCR与 ABBYY FineReader:功能对比

下表列出了评估此迁移方案的团队需要考虑的功能。

特征 ABBYY FineReader 引擎 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()
自动预处理 基于个人资料 内置功能(桌面倾斜校正、降噪、对比度调整、二值化、锐化)
基于区域的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 并从项目文件中删除手动程序集引用即可将其移除:


<Reference Include="FREngine">
  <HintPath>C:\Program Files\ABBYY SDK\FineReader Engine\Bin\FREngine.dll</HintPath>
</Reference>

<Reference Include="FREngine">
  <HintPath>C:\Program Files\ABBYY SDK\FineReader Engine\Bin\FREngine.dll</HintPath>
</Reference>
XML

然后从Visual Studio的引用节点中删除FREngine.dll COM互操作引用,或者直接从项目文件中删除相应的条目。 从NuGet安装IronOCR :

dotnet add package 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 IronOcr
$vbLabelText   $csharpLabel

步骤 3:初始化许可证

在应用程序启动时,在任何 OCR 调用之前,添加此代码一次:

IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
$vbLabelText   $csharpLabel

将密钥存储在环境变量或密钥管理器中,以便进行生产部署:

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")
$vbLabelText   $csharpLabel

代码迁移示例

Windows 服务中的引擎生命周期与无状态 IronTesseract 的比较

ABBYY的引擎初始化过程应该放在服务包装中,因为IEngine对象的创建成本较高。 大多数生产集成都将引擎封装在一个单例服务中,并具有明确的启动和销毁方法。

ABBYY FineReader 方法:

using FREngine;

public class DocumentOcrService : IHostedService, IDisposable
{
    private IEngine _engine;

    public Task StartAsync(CancellationToken cancellationToken)
    {
        // Step 1: Create loader — requires COM互操作 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 — requires COM互操作 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
Imports System.Threading
Imports System.Threading.Tasks

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 As 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 Class
$vbLabelText   $csharpLabel

IronOCR方法:

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(imagePath As String) As String
        Return (New IronTesseract()).Read(imagePath).Text
    End Function
End Class
$vbLabelText   $csharpLabel

IronTesseract没有引擎生命周期。 它在首次使用时会自动内部初始化,无需显式关闭。 托管服务包装、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 Function
$vbLabelText   $csharpLabel

IronOCR方法:

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)
$vbLabelText   $csharpLabel

语言包安装为标准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 Function
$vbLabelText   $csharpLabel

IronOCR方法:

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 Using
$vbLabelText   $csharpLabel

OcrInput.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 Class
$vbLabelText   $csharpLabel

IronOCR方法:

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 Class
$vbLabelText   $csharpLabel

每个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 Class
$vbLabelText   $csharpLabel

IronOCR方法:

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 Class
$vbLabelText   $csharpLabel

IronOCR的.Text,并提供不需要参数对象或格式枚举的输出方法。 SaveAsSearchablePdf调用以一行处理PDF导出,而ABBYY需要三步参数/导出序列。 该可搜索的 PDF 指南涵盖了页面范围选项和压缩设置。 hOCR 导出指南涵盖了使用位置感知 OCR 输出的系统的 HOCR 格式。

ABBYY FineReader API 到IronOCR映射参考

ABBYY FineReader 引擎 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



' 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
$vbLabelText   $csharpLabel

问题二:认可概况没有等效项

ABBYY: 调用engine.LoadPredefinedProfile("FieldLevelRecognition")的代码使用ABBYY特定的配置文件来在精确度和吞吐量之间取得平衡。 没有名为Profile的IronOCR等效属性。

解决方案: 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);
Imports IronTesseract

' 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
$vbLabelText   $csharpLabel

图像质量校正指南解释了哪些滤波器可以解决哪些输入质量问题。 速度优化指南涵盖了可减少处理干净文档时间的配置属性。

问题 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}}
# 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}}
YAML

在应用程序启动过程中:

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"))
$vbLabelText   $csharpLabel

问题 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)
$vbLabelText   $csharpLabel

问题5:CreateImageInfo / FrameCount 模式用于TIFF

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 frame
Imports 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
$vbLabelText   $csharpLabel

问题 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
}
Imports IronOcr

' 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
$vbLabelText   $csharpLabel

读取结果指南涵盖了如何访问段落、行、单词和字符级坐标数据以进行下游处理。

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" .
# 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" .
SHELL

记录每一个持有IFRDocument字段的类。 请注意所使用的导出格式——DOCX 输出需要采用其他方法(参见上面的问题 6)。

代码更新任务

  1. 从所有.csproj引用
  2. 在每个使用ABBYY的项目中运行dotnet add package IronOcr
  3. 在应用程序启动时添加Program.cs或启动类)
  4. 为每种非英语语言安装语言NuGet包(dotnet add package IronOcr.Languages.French等)
  5. 删除所有LoadPredefinedProfile调用
  6. 删除所有langParams.Languages.Add调用
  7. engine.CreateFRDocument() + document.AddImageFile() + document.Process()
  8. input.LoadImageFrames(tiffPath)替换多帧TIFF循环
  9. document.PlainText.Text
  10. frDocument.Pages[i].PlainText.Text
  11. document.Export(..., FEF_PDF, pdfParams)
  12. 将所有OcrInput
  13. 删除SemaphoreSlim和ABBYY引擎访问序列化的锁定代码
  14. engine.CreateZone() / zone.SetBounds() / page.Zones.Add()
  15. 从 CI/CD 流水线中移除许可证文件复制步骤
  16. 更新Docker镜像——移除SDK安装层,增加Linux目标的libgdiplus

迁移后测试

  • 对每种文档类型(发票、合同、扫描表格)的代表性样本进行文本提取输出验证
  • 确认多页 TIFF 处理返回的页数与 ABBYY 生成的帧的页数相同
  • 使用与 ABBYY 基线比较相同的输入测试多语言文档
  • 验证可搜索的PDF输出文件在Adobe Reader和浏览器PDF查看器中是否可进行文本搜索。
  • 以生产环境并发级别运行并行批处理处理器,并确认没有异常。
  • 在已知良好的文档上检查result.Confidence以建立质量关卡的基准门槛
  • 在暂存部署环境中,测试从环境变量初始化许可证密钥
  • 验证 Docker 镜像在不挂载 ABBYY SDK 卷的情况下能否构建并运行 OCR。
  • 确认 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 许可费用保持不变。没有按页计费,没有超额计费,也没有根据使用量重新协商费用等级。 许可页面显示了所有级别——售价 2,999 美元的Professional版许可允许 10 位开发人员处理任意数量的部署目标上的任何数据量。

跨平台部署开辟了新的基础架构选择。ABBYY COM 层需要 Windows 系统。 出于成本或密度方面的考虑,一些团队希望将文档处理迁移到 Linux 容器中,但遭到了阻止。 IronOCR在 Windows、Linux 和 macOS 上均可通过同一个NuGet包以完全相同的方式运行。 从 ABBYY 迁移后,应用程序堆栈的 OCR 层不再受 Windows 限制。 Linux 部署指南AWS 部署指南涵盖了每个环境的完整设置。

无需基础设施建设即可实现并行吞吐量。之前用于串行化 ABBYY 引擎访问的锁定策略已不再适用。 Parallel.ForEach,获取结果。 吞吐量随可用 CPU 核心数的增加而扩展,无需任何额外代码。 多线程示例展示了在多核硬件上实现更快的运行时间。

语言配置是一个软件包参考。向 ABBYY 集成添加德语或日语 OCR 支持意味着需要识别数据文件,将它们部署到每台目标机器的运行时路径,并在文件缺失时处理故障。 使用IronOCR,dotnet add package IronOcr.Languages.German将语言包作为一个版本化的、可再现的NuGet依赖项添加。 软件包管理器确保每次构建时数据都存在。 自定义语言包指南涵盖了针对特定领域的自定义语言模型的训练和部署。

本网站与 ABBYY FineReader 或 Tesseract 没有隶属关系,也未获得其认可或资助。 本网站与ABBYY或Google无关联、未授权或赞助。 所有产品名称、徽标和品牌均为各自所有者的财产。 比较仅供参考,反映撰写时公开可用的信息。)}]

常见问题解答

我为什么要从 ABBYY FineReader Engine 迁移到 IronOCR?

常见的驱动因素包括消除 COM 互操作的复杂性、取代基于文件的许可证管理、避免按页计费、启用 Docker/容器部署以及采用与标准 .NET 工具集成的 NuGet 原生工作流程。

从 ABBYY FineReader Engine 迁移到 IronOCR 时,主要的代码变更有哪些?

将 ABBYY FineReader 初始化序列替换为 IronTesseract 实例化,移除 COM 生命周期管理(显式的创建/加载/关闭模式),并更新结果属性名称。这样可以显著减少样板代码行数。

我该如何安装 IronOCR 以开始迁移?

在软件包管理器控制台中运行“Install-Package IronOcr”,或在命令行界面中运行“dotnet add package IronOcr”。语言包是单独的软件包:例如,法语语言包需要运行“dotnet add package IronOcr.Languages.French”。

IronOCR 在标准商业文档的 OCR 准确率方面是否能与 ABBYY FineReader Engine 相媲美?

IronOCR 对标准商业内容(包括发票、合同、收据和打印表格)的识别准确率很高。图像预处理滤镜(例如去倾斜、去噪、对比度增强)可进一步提高对质量较差输入文件的识别率。

IronOCR 如何处理 ABBYY FineReader Engine 单独安装的语言数据?

IronOCR 中的语言数据以 NuGet 包的形式分发。“dotnet add package IronOcr.Languages.German”命令用于安装德语支持。无需手动放置文件或指定目录路径。

从 ABBYY FineReader Engine 迁移到 IronOCR 是否需要对部署基础架构进行更改?

IronOCR 比 ABBYY FineReader Engine 需要的底层架构变更更少。它无需 SDK 二进制文件路径、许可证文件放置位置或许可证服务器配置。NuGet 包包含完整的 OCR 引擎,许可证密钥是应用程序代码中设置的一个字符串。

迁移后如何配置 IronOCR 许可?

在应用程序启动代码中,将 IronOcr.License.LicenseKey 赋值为 "YOUR-KEY"。在 Docker 或 Kubernetes 中,将此密钥存储为环境变量,并在启动时读取。使用 License.IsValidLicense 在接受流量之前进行验证。

IronOCR 能否像 ABBYY FineReader 那样处理 PDF 文件?

是的。IronOCR 可以读取原生 PDF 和扫描版 PDF。实例化 IronTesseract,调用 ocr.Read(input) 方法(其中 input 可以是 PDF 路径或 OcrPdfInput),然后遍历 OcrResult 页面。无需单独的 PDF 渲染流程。

IronOCR 如何处理大批量处理中的线程问题?

IronTesseract 可以安全地按线程实例化。在 Parallel.ForEach 或 Task 池中为每个线程启动一个实例,并发运行 OCR,并在完成后释放每个实例。无需全局状态或锁定。

IronOCR在提取文本后支持哪些输出格式?

IronOCR 返回结构化结果,包括文本、词坐标、置信度评分和页面结构。导出选项包括纯文本、可搜索 PDF 和用于下游处理的结构化结果对象。

对于可扩展的工作负载而言,IronOCR 的定价是否比 ABBYY FineReader Engine 更可预测?

IronOCR采用永久统一费率许可模式,不收取任何页数或数量费用。无论您处理1万页还是1000万页,许可费用都保持不变。批量许可和团队许可选项请参见IronOCR定价页面。

从 ABBYY FineReader Engine 迁移到 IronOCR 后,我现有的测试会发生什么变化?

迁移后,对提取的文本内容进行断言的测试应该继续通过。验证 API 调用模式或 COM 对象生命周期的测试需要更新,以反映 IronOCR 更简化的初始化和结果模型。

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

钢铁支援团队

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