跳至页脚内容
视频

如何在C#中对车牌执行OCR

本指南面向将 OCR 工作负载从Kofax OmniPageCapture SDK(现以 Tungsten Automation 的名义销售)迁移到IronOCR的 .NET开发人员。 它涵盖了完整的迁移路径:移除 SDK 安装程序依赖项、取消引擎生命周期仪式、替换基于区域的识别模式以及实现错误处理的现代化。 无需事先阅读对比文章。

为什么要从Kofax OmniPage(Tungsten) 迁移?

OmniPage Capture SDK 的设计初衷是面向Enterprise文档管理基础架构,当时还没有NuGet、Docker 和 CI/CD 管道。 它在 2005 年做出的每一个合理的建筑选择,到了 2026 年都会造成摩擦。

在NuGet环境中,SDK 安装程序已无用武之地。所有运行 OmniPage 代码的开发机器、构建代理和生产服务器都需要在编译前执行 Tungsten SDK 安装程序。 没有 .csproj 包引用需要恢复。 升级 SDK 版本意味着需要在整个集群上重新运行安装程序。 新开发者必须联系 SDK 许可证的管理者并等待安装程序访问权限才能运行项目。

许可证文件是一个操作责任。 在每台调用 engine.Initialize() 的机器上,必须在特定路径下存在一个 .lic 文件。 部署到新服务器后,忘记了该文件,每次 OCR 调用都会失败,并出现许可证异常,而不是 OCR 错误。 使用浮动许可证和您的应用服务器与许可证服务器之间的网络分区意味着每次 Initialize() 调用都会失败,直到连接恢复,无论那台机器昨天是否成功验证了其许可证。

引擎生命周期管理在错误路径上泄露资源。 在所有可能的代码路径中,包括异常处理程序、提前返回和超时,必须调用 OmniPageEngine.Shutdown(),否则浮动许可证席位将保持锁定状态,直到许可证服务器的结帐超时结束。 在此约束条件下进行防御性编写意味着将每个集成点包装在 IDisposable 模式中,仅用于防止引擎关闭被跳过。

硬件指纹识别与现代部署方式存在冲突。OmniPage的激活与硬件绑定。 容器重启、虚拟机迁移和云自动扩展都涉及硬件身份变更,从而触发重新激活要求。 与自动扩缩容不兼容的部署模型,就是与云基础设施不兼容的部署模型。

按页计费模式在大规模应用时难以预测。文档处理量的激增——例如新客户上线或积压文档批量提交——会导致超出预算的账单,而这笔费用原本并未计入预算。 IronOCR在授权级别内是永久且无限制的:没有按页计费,没有配额,也没有超额计费。

采购流程会阻碍发货。OmniPage SDK 需要付费购买。 评估权限、定价和合同条款都需要经过 4-12 周的销售洽谈。 在产品交付期限内开发 OCR 功能的团队不能等待Enterprise采购周期。 dotnet add package IronOcr 在30秒内解决。 请参阅 IronOCR 许可页面 了解已发布的自助定价 — 从 $999 Lite 到 2,999美元的 Enterprise,皆为永久版本。

基本问题

OmniPage 在读取第一个字节之前需要完整的初始化仪式,在读取最后一个字节之后需要关闭仪式——每个调用点都必须管理这两个仪式:

// OmniPage: Ceremony required on EVERY entry point — init, process, shutdown
using var engine = new OmniPageEngine();
engine.SetLicenseFile(@"C:\Program Files\OmniPage\license.lic"); // File must exist here
engine.Initialize();   // Network call to license server — can fail for license reasons, not OCR reasons

var document = engine.CreateDocument();
document.AddPage("invoice.jpg");
document.Recognize(new RecognitionSettings { Language = "English" });
string text = document.GetText();
document.Dispose();    // Must not be skipped
engine.Shutdown();     // Must not be skipped — omitting this locks a floating seat
// OmniPage: Ceremony required on EVERY entry point — init, process, shutdown
using var engine = new OmniPageEngine();
engine.SetLicenseFile(@"C:\Program Files\OmniPage\license.lic"); // File must exist here
engine.Initialize();   // Network call to license server — can fail for license reasons, not OCR reasons

var document = engine.CreateDocument();
document.AddPage("invoice.jpg");
document.Recognize(new RecognitionSettings { Language = "English" });
string text = document.GetText();
document.Dispose();    // Must not be skipped
engine.Shutdown();     // Must not be skipped — omitting this locks a floating seat
Imports OmniPage

' OmniPage: Ceremony required on EVERY entry point — init, process, shutdown
Using engine As New OmniPageEngine()
    engine.SetLicenseFile("C:\Program Files\OmniPage\license.lic") ' File must exist here
    engine.Initialize()   ' Network call to license server — can fail for license reasons, not OCR reasons

    Dim document = engine.CreateDocument()
    document.AddPage("invoice.jpg")
    document.Recognize(New RecognitionSettings With {.Language = "English"})
    Dim text As String = document.GetText()
    document.Dispose()    ' Must not be skipped
    engine.Shutdown()     ' Must not be skipped — omitting this locks a floating seat
End Using
$vbLabelText   $csharpLabel
// IronOCR: One NuGet package, one line at startup, one call to read
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"; // At app startup — once, ever
var text = new IronTesseract().Read("invoice.jpg").Text;
// IronOCR: One NuGet package, one line at startup, one call to read
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"; // At app startup — once, ever
var text = new IronTesseract().Read("invoice.jpg").Text;
Imports IronOcr

' IronOCR: One NuGet package, one line at startup, one call to read
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY" ' At app startup — once, ever
Dim text = New IronTesseract().Read("invoice.jpg").Text
$vbLabelText   $csharpLabel

OmniPage 版本有八个不同的步骤、两个强制性的清理调用、一个网络依赖项和一个文件系统依赖项。 IronOCR版本有两个。

IronOCR与 Kofax OmniPage(钨钢版):功能对比

下表列出了与评估此迁移方案的团队最相关的功能。

特征 Kofax OmniPage SDK IronOCR
安装方法 自定义 SDK 安装程序(无需NuGet) dotnet add package IronOcr
首次OCR调用时间 4-12周(采购)+小时(设置) 30秒
许可机制 磁盘上的 .lic 文件 + 可选许可证服务器 应用程序启动时输入字符串键
硬件指纹识别 是的(虚拟机迁移后重新激活)
需要许可证服务器 是的(适用于浮动牌照)
按页面运行时计费 可用(可变)
公布价格 否(联系销售) 是的 ($999 / 1,499美元 / 2,999美元 永久)
年度维护费 许可证费用的 18% 至 25%。 可选项
发动机生命周期管理 必需的 (Initialize / Shutdown) 不要求
Windows x64
Linux 是的(2026年1月新增) 是的(所有版本)
macOS操作系统
Docker/Kubernetes 困难(镜像中包含安装程序和许可证文件) 简单易用(仅限NuGet包)
Azure/AWS Lambda 复杂(许可证服务器可达性) 简单明了
图像输入格式 JPG、PNG、BMP、TIFF、GIF 等格式
原生 PDF 输入 是的(可能需要模块许可证) 是的(内置,无需额外许可证)
多页 TIFF 文件
可搜索的 PDF 输出 是的 (result.SaveAsSearchablePdf())
自动预处理 设置对象配置 内置 + 显式管道 API
支持的语言 120+ 125+(独立的NuGet包)
结构化数据输出 是的(词坐标) 页、段落、行、单词、字符及其坐标
置信度评分 是的 (result.Confidence,每个字)
条形码读取 附加模块(单独授权) 内置的 (ocr.Configuration.ReadBarCodes = true)
线程安全 复杂(引擎共享限制) 完整的(每个线程一个 IronTesseract
CI/CD 流水线支持 每个代理都需要安装程序 标准 dotnet restore

快速入门:Kofax OmniPage 到IronOCR 的迁移

步骤 1:将 SDK 替换为NuGet包

OmniPage 没有需要移除的NuGet包。 移除 .csproj 文件中的手动 DLL 引用,并在迁移完成后从开发机器卸载 SDK。 然后安装IronOCR:

dotnet add package IronOcr

IronOCR NuGet包捆绑了 OCR 引擎、预处理过滤器和运行时组件。 无需单独的安装程序,无需本地 DLL 管理,无需组件注册。

步骤 2:更新命名空间

// Before (Kofax OmniPage)
using Kofax.OmniPage.CSDK;
using Kofax.OmniPageCSDK;
using CSDK;

// After (IronOCR)
using IronOcr;
// Before (Kofax OmniPage)
using Kofax.OmniPage.CSDK;
using Kofax.OmniPageCSDK;
using CSDK;

// After (IronOCR)
using IronOcr;
Imports IronOcr
$vbLabelText   $csharpLabel

步骤 3:初始化许可证

在应用程序启动时添加一行。这将取代 .lic 文件路径、许可证服务器配置和 engine.Initialize() 调用:

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

将密钥存储在环境变量中 (IRONOCR_LICENSE_KEY) 或 appsettings.json,而不是源代码中。 密钥是离线读取的——运行时不会发生网络调用。

代码迁移示例

引擎初始化错误路径替换

OmniPage 生命周期模型中最危险的方面不是成功之路,而是失败之路。 在 engine.Initialize()engine.Shutdown() 之间抛出的任何异常都可能导致浮动许可证席位锁定,如果没有达到 Shutdown()。 生产代码需要在每个文档处理调用周围添加 try/finally 块:

Kofax OmniPage 方法:

// OmniPage: try/finally required everywhere to protect license seat release
public string ProcessInvoice(string imagePath)
{
    var engine = new OmniPageEngine();
    engine.SetLicenseFile(@"C:\Program Files\OmniPage\license.lic");

    try
    {
        engine.Initialize(); // Contacts license server — failure here locks nothing
        var document = engine.CreateDocument();

        try
        {
            document.AddPage(imagePath);
            document.Recognize(new RecognitionSettings { Language = "English" });
            return document.GetText();
        }
        catch (RecognitionException ex)
        {
            // Log, rethrow — but Shutdown must still be called
            throw new OcrProcessingException("Recognition failed", ex);
        }
        finally
        {
            document.Dispose(); // Inner finally: release document
        }
    }
    catch (LicenseValidationException ex)
    {
        throw new OcrProcessingException("License validation failed", ex);
    }
    finally
    {
        engine.Shutdown(); // Outer finally: release license seat — MUST execute
    }
}
// OmniPage: try/finally required everywhere to protect license seat release
public string ProcessInvoice(string imagePath)
{
    var engine = new OmniPageEngine();
    engine.SetLicenseFile(@"C:\Program Files\OmniPage\license.lic");

    try
    {
        engine.Initialize(); // Contacts license server — failure here locks nothing
        var document = engine.CreateDocument();

        try
        {
            document.AddPage(imagePath);
            document.Recognize(new RecognitionSettings { Language = "English" });
            return document.GetText();
        }
        catch (RecognitionException ex)
        {
            // Log, rethrow — but Shutdown must still be called
            throw new OcrProcessingException("Recognition failed", ex);
        }
        finally
        {
            document.Dispose(); // Inner finally: release document
        }
    }
    catch (LicenseValidationException ex)
    {
        throw new OcrProcessingException("License validation failed", ex);
    }
    finally
    {
        engine.Shutdown(); // Outer finally: release license seat — MUST execute
    }
}
Imports System

' OmniPage: try/finally required everywhere to protect license seat release
Public Function ProcessInvoice(imagePath As String) As String
    Dim engine As New OmniPageEngine()
    engine.SetLicenseFile("C:\Program Files\OmniPage\license.lic")

    Try
        engine.Initialize() ' Contacts license server — failure here locks nothing
        Dim document = engine.CreateDocument()

        Try
            document.AddPage(imagePath)
            document.Recognize(New RecognitionSettings With {.Language = "English"})
            Return document.GetText()
        Catch ex As RecognitionException
            ' Log, rethrow — but Shutdown must still be called
            Throw New OcrProcessingException("Recognition failed", ex)
        Finally
            document.Dispose() ' Inner finally: release document
        End Try
    Catch ex As LicenseValidationException
        Throw New OcrProcessingException("License validation failed", ex)
    Finally
        engine.Shutdown() ' Outer finally: release license seat — MUST execute
    End Try
End Function
$vbLabelText   $csharpLabel

IronOCR方法:

// IronOCR: 否 license seat to release, no engine to shut down
public string ProcessInvoice(string imagePath)
{
    using var input = new OcrInput();
    input.LoadImage(imagePath);

    var ocr = new IronTesseract();
    var result = ocr.Read(input);
    return result.Text;
    // OcrInput disposal is automatic — no license implications on any code path
}
// IronOCR: 否 license seat to release, no engine to shut down
public string ProcessInvoice(string imagePath)
{
    using var input = new OcrInput();
    input.LoadImage(imagePath);

    var ocr = new IronTesseract();
    var result = ocr.Read(input);
    return result.Text;
    // OcrInput disposal is automatic — no license implications on any code path
}
Imports IronOcr

Public Function ProcessInvoice(imagePath As String) As String
    Using input As New OcrInput()
        input.LoadImage(imagePath)

        Dim ocr As New IronTesseract()
        Dim result = ocr.Read(input)
        Return result.Text
    End Using
    ' OcrInput disposal is automatic — no license implications on any code path
End Function
$vbLabelText   $csharpLabel

using 块在 OcrInput 上处理加载的图像数据的内存清理。 不存在用于保护许可证席位的 try/finally。 此方法中任何位置发生的异常都不会对方法本身作用域之外产生任何副作用。 有关配置选项(包括线程本地实例),请参阅IronTesseract 设置指南

基于区域的识别迁移

OmniPage 的主要结构化提取机制是区域定义:文档区域通过坐标边界和每个区域的识别类型(OCR、ICR、OMR、条形码)进行声明。 开发人员为每个文档模板定义 FormZone 对象,并将其传递给识别引擎。IronOCR 使用 CropRectangle 来实现相同的目标提取,而无需模板管理或区域类型声明:

Kofax OmniPage 方法:

// OmniPage: Zone-based form extraction with template management
public Dictionary<string, string> ExtractInvoiceFields(string invoicePath)
{
    using var engine = new OmniPageEngine();
    engine.SetLicenseFile(licensePath);
    engine.Initialize();

    // Define zones per document template
    var zones = new List<FormZone>
    {
        new FormZone { Name = "InvoiceNumber", Type = "OCR",
            X = 520, Y = 80, Width = 200, Height = 30 },
        new FormZone { Name = "InvoiceDate",   Type = "OCR",
            X = 520, Y = 120, Width = 200, Height = 30 },
        new FormZone { Name = "TotalAmount",   Type = "OCR",
            X = 520, Y = 580, Width = 200, Height = 30 },
        new FormZone { Name = "VendorName",    Type = "OCR",
            X = 50,  Y = 80,  Width = 300, Height = 40 }
    };

    var template = new FormTemplate { Name = "StandardInvoice", Zones = zones };
    var settings = new RecognitionSettings { Language = "English" };

    var document = engine.CreateDocument();
    document.AddPage(invoicePath);
    document.ApplyTemplate(template);
    document.Recognize(settings);

    var fields = new Dictionary<string, string>();
    foreach (var zone in zones)
        fields[zone.Name] = document.GetZoneText(zone.Name);

    document.Dispose();
    engine.Shutdown();
    return fields;
}
// OmniPage: Zone-based form extraction with template management
public Dictionary<string, string> ExtractInvoiceFields(string invoicePath)
{
    using var engine = new OmniPageEngine();
    engine.SetLicenseFile(licensePath);
    engine.Initialize();

    // Define zones per document template
    var zones = new List<FormZone>
    {
        new FormZone { Name = "InvoiceNumber", Type = "OCR",
            X = 520, Y = 80, Width = 200, Height = 30 },
        new FormZone { Name = "InvoiceDate",   Type = "OCR",
            X = 520, Y = 120, Width = 200, Height = 30 },
        new FormZone { Name = "TotalAmount",   Type = "OCR",
            X = 520, Y = 580, Width = 200, Height = 30 },
        new FormZone { Name = "VendorName",    Type = "OCR",
            X = 50,  Y = 80,  Width = 300, Height = 40 }
    };

    var template = new FormTemplate { Name = "StandardInvoice", Zones = zones };
    var settings = new RecognitionSettings { Language = "English" };

    var document = engine.CreateDocument();
    document.AddPage(invoicePath);
    document.ApplyTemplate(template);
    document.Recognize(settings);

    var fields = new Dictionary<string, string>();
    foreach (var zone in zones)
        fields[zone.Name] = document.GetZoneText(zone.Name);

    document.Dispose();
    engine.Shutdown();
    return fields;
}
Imports System
Imports System.Collections.Generic

' OmniPage: Zone-based form extraction with template management
Public Function ExtractInvoiceFields(invoicePath As String) As Dictionary(Of String, String)
    Using engine As New OmniPageEngine()
        engine.SetLicenseFile(licensePath)
        engine.Initialize()

        ' Define zones per document template
        Dim zones As New List(Of FormZone) From {
            New FormZone With {.Name = "InvoiceNumber", .Type = "OCR", .X = 520, .Y = 80, .Width = 200, .Height = 30},
            New FormZone With {.Name = "InvoiceDate", .Type = "OCR", .X = 520, .Y = 120, .Width = 200, .Height = 30},
            New FormZone With {.Name = "TotalAmount", .Type = "OCR", .X = 520, .Y = 580, .Width = 200, .Height = 30},
            New FormZone With {.Name = "VendorName", .Type = "OCR", .X = 50, .Y = 80, .Width = 300, .Height = 40}
        }

        Dim template As New FormTemplate With {.Name = "StandardInvoice", .Zones = zones}
        Dim settings As New RecognitionSettings With {.Language = "English"}

        Dim document = engine.CreateDocument()
        document.AddPage(invoicePath)
        document.ApplyTemplate(template)
        document.Recognize(settings)

        Dim fields As New Dictionary(Of String, String)()
        For Each zone In zones
            fields(zone.Name) = document.GetZoneText(zone.Name)
        Next

        document.Dispose()
        engine.Shutdown()
        Return fields
    End Using
End Function
$vbLabelText   $csharpLabel

IronOCR方法:

// IronOCR: CropRectangle targets specific regions — no template management
public Dictionary<string, string> ExtractInvoiceFields(string invoicePath)
{
    var fields = new Dictionary<string, string>();
    var ocr = new IronTesseract();

    // Extract each field by reading only the target region
    var fieldRegions = new Dictionary<string, CropRectangle>
    {
        ["InvoiceNumber"] = new CropRectangle(520, 80,  200, 30),
        ["InvoiceDate"]   = new CropRectangle(520, 120, 200, 30),
        ["TotalAmount"]   = new CropRectangle(520, 580, 200, 30),
        ["VendorName"]    = new CropRectangle(50,  80,  300, 40)
    };

    foreach (var (fieldName, region) in fieldRegions)
    {
        using var input = new OcrInput();
        input.LoadImage(invoicePath, region);
        fields[fieldName] = ocr.Read(input).Text.Trim();
    }

    return fields;
}
// IronOCR: CropRectangle targets specific regions — no template management
public Dictionary<string, string> ExtractInvoiceFields(string invoicePath)
{
    var fields = new Dictionary<string, string>();
    var ocr = new IronTesseract();

    // Extract each field by reading only the target region
    var fieldRegions = new Dictionary<string, CropRectangle>
    {
        ["InvoiceNumber"] = new CropRectangle(520, 80,  200, 30),
        ["InvoiceDate"]   = new CropRectangle(520, 120, 200, 30),
        ["TotalAmount"]   = new CropRectangle(520, 580, 200, 30),
        ["VendorName"]    = new CropRectangle(50,  80,  300, 40)
    };

    foreach (var (fieldName, region) in fieldRegions)
    {
        using var input = new OcrInput();
        input.LoadImage(invoicePath, region);
        fields[fieldName] = ocr.Read(input).Text.Trim();
    }

    return fields;
}
Imports System.Collections.Generic

' IronOCR: CropRectangle targets specific regions — no template management
Public Function ExtractInvoiceFields(invoicePath As String) As Dictionary(Of String, String)
    Dim fields As New Dictionary(Of String, String)()
    Dim ocr As New IronTesseract()

    ' Extract each field by reading only the target region
    Dim fieldRegions As New Dictionary(Of String, CropRectangle) From {
        {"InvoiceNumber", New CropRectangle(520, 80, 200, 30)},
        {"InvoiceDate", New CropRectangle(520, 120, 200, 30)},
        {"TotalAmount", New CropRectangle(520, 580, 200, 30)},
        {"VendorName", New CropRectangle(50, 80, 300, 40)}
    }

    For Each kvp In fieldRegions
        Dim fieldName = kvp.Key
        Dim region = kvp.Value
        Using input As New OcrInput()
            input.LoadImage(invoicePath, region)
            fields(fieldName) = ocr.Read(input).Text.Trim()
        End Using
    Next

    Return fields
End Function
$vbLabelText   $csharpLabel

每个 CropRectangle 以像素为单位指定 (x, y, width, height)。 OCR 引擎仅处理指定的区域,而不是整个页面——这与 OmniPage 中基于区域的处理所带来的效率优势相同。 基于区域的 OCR 指南 涵盖了坐标选择模式,包括如何使用 OcrInput 的多区域 API 从单一图像加载中提取多个区域。

结构化数据输出迁移

OmniPage 通过专有的导出管道公开文档结构:将格式设置应用于识别的文档,并将输出以指定的格式(RTF、XML、CSV、可搜索的 PDF)写入文件。 要访问词级坐标,需要使用显式位置查询来迭代结果迭代器。 IronOCR直接在结果对象上显示相同的结构化数据,无需二次导出步骤:

Kofax OmniPage 方法:

// OmniPage: Structured output requires format configuration and file export
public void ExtractStructuredContent(string imagePath, string outputDir)
{
    using var engine = new OmniPageEngine();
    engine.SetLicenseFile(licensePath);
    engine.Initialize();

    var settings = new RecognitionSettings { Language = "English" };
    var document = engine.CreateDocument();
    document.AddPage(imagePath);
    document.Recognize(settings);

    // Word-level coordinates through result iterator
    var iterator = document.GetResultIterator(ResultIteratorLevel.Word);
    while (iterator.MoveNext())
    {
        string word = iterator.GetText();
        var bounds = iterator.GetBoundingBox();
        double confidence = iterator.GetConfidence();
        Console.WriteLine($"Word: {word} at ({bounds.X},{bounds.Y}) conf:{confidence:F1}%");
    }

    // Structured export requires separate output format configuration
    var outputSettings = new OutputSettings
    {
        Format = OutputFormat.XML,
        IncludeCoordinates = true,
        IncludeConfidence = true
    };
    document.SaveAs(Path.Combine(outputDir, "output.xml"), outputSettings);

    document.Dispose();
    engine.Shutdown();
}
// OmniPage: Structured output requires format configuration and file export
public void ExtractStructuredContent(string imagePath, string outputDir)
{
    using var engine = new OmniPageEngine();
    engine.SetLicenseFile(licensePath);
    engine.Initialize();

    var settings = new RecognitionSettings { Language = "English" };
    var document = engine.CreateDocument();
    document.AddPage(imagePath);
    document.Recognize(settings);

    // Word-level coordinates through result iterator
    var iterator = document.GetResultIterator(ResultIteratorLevel.Word);
    while (iterator.MoveNext())
    {
        string word = iterator.GetText();
        var bounds = iterator.GetBoundingBox();
        double confidence = iterator.GetConfidence();
        Console.WriteLine($"Word: {word} at ({bounds.X},{bounds.Y}) conf:{confidence:F1}%");
    }

    // Structured export requires separate output format configuration
    var outputSettings = new OutputSettings
    {
        Format = OutputFormat.XML,
        IncludeCoordinates = true,
        IncludeConfidence = true
    };
    document.SaveAs(Path.Combine(outputDir, "output.xml"), outputSettings);

    document.Dispose();
    engine.Shutdown();
}
Imports System
Imports System.IO

Public Sub ExtractStructuredContent(imagePath As String, outputDir As String)
    Using engine As New OmniPageEngine()
        engine.SetLicenseFile(licensePath)
        engine.Initialize()

        Dim settings As New RecognitionSettings With {.Language = "English"}
        Dim document = engine.CreateDocument()
        document.AddPage(imagePath)
        document.Recognize(settings)

        ' Word-level coordinates through result iterator
        Dim iterator = document.GetResultIterator(ResultIteratorLevel.Word)
        While iterator.MoveNext()
            Dim word As String = iterator.GetText()
            Dim bounds = iterator.GetBoundingBox()
            Dim confidence As Double = iterator.GetConfidence()
            Console.WriteLine($"Word: {word} at ({bounds.X},{bounds.Y}) conf:{confidence:F1}%")
        End While

        ' Structured export requires separate output format configuration
        Dim outputSettings As New OutputSettings With {
            .Format = OutputFormat.XML,
            .IncludeCoordinates = True,
            .IncludeConfidence = True
        }
        document.SaveAs(Path.Combine(outputDir, "output.xml"), outputSettings)

        document.Dispose()
        engine.Shutdown()
    End Using
End Sub
$vbLabelText   $csharpLabel

IronOCR方法:

// IronOCR: Structured data directly on OcrResult — no export step
public void ExtractStructuredContent(string imagePath)
{
    var result = new IronTesseract().Read(imagePath);

    Console.WriteLine($"Confidence: {result.Confidence:F1}%");
    Console.WriteLine($"Pages: {result.Pages.Length}");

    foreach (var page in result.Pages)
    {
        foreach (var paragraph in page.Paragraphs)
        {
            Console.WriteLine($"[Paragraph at ({paragraph.X},{paragraph.Y})]");
            Console.WriteLine(paragraph.Text);

            foreach (var word in paragraph.Words)
            {
                // Per-word confidence and coordinates — no iterator needed
                Console.WriteLine(
                    $"  Word: '{word.Text}' " +
                    $"at ({word.X},{word.Y}) " +
                    $"conf: {word.Confidence:F1}%");
            }
        }
    }
}
// IronOCR: Structured data directly on OcrResult — no export step
public void ExtractStructuredContent(string imagePath)
{
    var result = new IronTesseract().Read(imagePath);

    Console.WriteLine($"Confidence: {result.Confidence:F1}%");
    Console.WriteLine($"Pages: {result.Pages.Length}");

    foreach (var page in result.Pages)
    {
        foreach (var paragraph in page.Paragraphs)
        {
            Console.WriteLine($"[Paragraph at ({paragraph.X},{paragraph.Y})]");
            Console.WriteLine(paragraph.Text);

            foreach (var word in paragraph.Words)
            {
                // Per-word confidence and coordinates — no iterator needed
                Console.WriteLine(
                    $"  Word: '{word.Text}' " +
                    $"at ({word.X},{word.Y}) " +
                    $"conf: {word.Confidence:F1}%");
            }
        }
    }
}
Public Sub ExtractStructuredContent(imagePath As String)
    Dim result = New IronTesseract().Read(imagePath)

    Console.WriteLine($"Confidence: {result.Confidence:F1}%")
    Console.WriteLine($"Pages: {result.Pages.Length}")

    For Each page In result.Pages
        For Each paragraph In page.Paragraphs
            Console.WriteLine($"[Paragraph at ({paragraph.X},{paragraph.Y})]")
            Console.WriteLine(paragraph.Text)

            For Each word In paragraph.Words
                ' Per-word confidence and coordinates — no iterator needed
                Console.WriteLine(
                    $"  Word: '{word.Text}' " &
                    $"at ({word.X},{word.Y}) " &
                    $"conf: {word.Confidence:F1}%")
            Next
        Next
    Next
End Sub
$vbLabelText   $csharpLabel

OcrResult 对象的层级 —— 页面、段落、行、单词、字符 —— 提供与 OmniPage 的结果迭代器相同的位置数据,但呈现为可导航的对象图,而不是顺序光标。 无需额外配置,即可在结果、页面、段落和单词级别获得置信度评分。 读取结果指南涵盖所有结构化数据属性,置信度评分指南涵盖逐词验证模式。

多页 TIFF 处理迁移

OmniPage 的多页 TIFF 工作流程需要从 TIFF 容器中逐页提取,每页都需要单独进行识别调用和文档处理。 这既会产生样板代码,也会产生与页面数量成正比的资源管理面积。 IronOCR可在一次调用中加载多帧 TIFF 文件:

Kofax OmniPage 方法:

// OmniPage: Page-by-page TIFF extraction with per-page lifecycle
public List<string> ExtractFromMultiPageTiff(string tiffPath)
{
    var pageTexts = new List<string>();

    using var engine = new OmniPageEngine();
    engine.SetLicenseFile(licensePath);
    engine.Initialize();

    // Open TIFF and iterate frames
    var tiffContainer = engine.OpenTiff(tiffPath);
    int pageCount = tiffContainer.GetPageCount();

    var settings = new RecognitionSettings { Language = "English" };

    for (int i = 0; i < pageCount; i++)
    {
        var page = tiffContainer.GetPage(i); // Extract frame
        var document = engine.CreateDocument();

        try
        {
            document.AddPage(page);
            document.Recognize(settings);
            pageTexts.Add(document.GetText());
        }
        finally
        {
            document.Dispose(); // Dispose per page to manage memory
        }
    }

    tiffContainer.Dispose();
    engine.Shutdown();
    return pageTexts;
}
// OmniPage: Page-by-page TIFF extraction with per-page lifecycle
public List<string> ExtractFromMultiPageTiff(string tiffPath)
{
    var pageTexts = new List<string>();

    using var engine = new OmniPageEngine();
    engine.SetLicenseFile(licensePath);
    engine.Initialize();

    // Open TIFF and iterate frames
    var tiffContainer = engine.OpenTiff(tiffPath);
    int pageCount = tiffContainer.GetPageCount();

    var settings = new RecognitionSettings { Language = "English" };

    for (int i = 0; i < pageCount; i++)
    {
        var page = tiffContainer.GetPage(i); // Extract frame
        var document = engine.CreateDocument();

        try
        {
            document.AddPage(page);
            document.Recognize(settings);
            pageTexts.Add(document.GetText());
        }
        finally
        {
            document.Dispose(); // Dispose per page to manage memory
        }
    }

    tiffContainer.Dispose();
    engine.Shutdown();
    return pageTexts;
}
Imports System.Collections.Generic

' OmniPage: Page-by-page TIFF extraction with per-page lifecycle
Public Function ExtractFromMultiPageTiff(tiffPath As String) As List(Of String)
    Dim pageTexts As New List(Of String)()

    Using engine As New OmniPageEngine()
        engine.SetLicenseFile(licensePath)
        engine.Initialize()

        ' Open TIFF and iterate frames
        Dim tiffContainer = engine.OpenTiff(tiffPath)
        Dim pageCount As Integer = tiffContainer.GetPageCount()

        Dim settings As New RecognitionSettings With {.Language = "English"}

        For i As Integer = 0 To pageCount - 1
            Dim page = tiffContainer.GetPage(i) ' Extract frame
            Dim document = engine.CreateDocument()

            Try
                document.AddPage(page)
                document.Recognize(settings)
                pageTexts.Add(document.GetText())
            Finally
                document.Dispose() ' Dispose per page to manage memory
            End Try
        Next

        tiffContainer.Dispose()
        engine.Shutdown()
    End Using

    Return pageTexts
End Function
$vbLabelText   $csharpLabel

IronOCR方法:

// IronOCR: Multi-frame TIFF loaded in one call — all pages in one result
public List<string> ExtractFromMultiPageTiff(string tiffPath)
{
    using var input = new OcrInput();
    input.LoadImageFrames(tiffPath); // All frames loaded automatically

    var result = new IronTesseract().Read(input);

    // Each TIFF frame becomes a page in the result
    return result.Pages.Select(page => page.Text).ToList();
}
// IronOCR: Multi-frame TIFF loaded in one call — all pages in one result
public List<string> ExtractFromMultiPageTiff(string tiffPath)
{
    using var input = new OcrInput();
    input.LoadImageFrames(tiffPath); // All frames loaded automatically

    var result = new IronTesseract().Read(input);

    // Each TIFF frame becomes a page in the result
    return result.Pages.Select(page => page.Text).ToList();
}
Imports System.Collections.Generic
Imports IronOcr

' IronOCR: Multi-frame TIFF loaded in one call — all pages in one result
Public Function ExtractFromMultiPageTiff(tiffPath As String) As List(Of String)
    Using input As New OcrInput()
        input.LoadImageFrames(tiffPath) ' All frames loaded automatically

        Dim result = New IronTesseract().Read(input)

        ' Each TIFF frame becomes a page in the result
        Return result.Pages.Select(Function(page) page.Text).ToList()
    End Using
End Function
$vbLabelText   $csharpLabel

LoadImageFrames 在一次调用中读取 TIFF 容器中的每一帧。 结果为每帧暴露一个 OcrResult.Page,在不需要手动迭代循环或每页清理的情况下,保留按页结构。有关生产 TIFF 批处理工作流,请参阅 TIFF 和 GIF 输入指南

线程安全并行处理迁移

OmniPage 的引擎是一个共享的、有状态的资源。 在线程之间共享一个 OmniPageEngine 实例需要外部同步,因为多线程并发调用 CreateDocument() 会产生交错状态。 安全模式——每线程一个引擎实例——与引擎的重量级初始化成本相冲突。IronOCR 实例不携带共享状态:为每个线程创建一个 IronTesseract,无需配合开销:

Kofax OmniPage 方法:

// OmniPage: Shared engine with locking to serialize document operations
public ConcurrentDictionary<string, string> ProcessBatchWithEngine(
    string[] imagePaths, string licensePath)
{
    var results = new ConcurrentDictionary<string, string>();
    var engineLock = new object();

    // One engine — document operations must be serialized
    using var engine = new OmniPageEngine();
    engine.SetLicenseFile(licensePath);
    engine.Initialize();

    var settings = new RecognitionSettings { Language = "English" };

    Parallel.ForEach(imagePaths, imagePath =>
    {
        lock (engineLock) // Serialize: one recognition at a time
        {
            var document = engine.CreateDocument();
            try
            {
                document.AddPage(imagePath);
                document.Recognize(settings);
                results[imagePath] = document.GetText();
            }
            finally
            {
                document.Dispose();
            }
        }
    });

    engine.Shutdown();
    return results;
}
// OmniPage: Shared engine with locking to serialize document operations
public ConcurrentDictionary<string, string> ProcessBatchWithEngine(
    string[] imagePaths, string licensePath)
{
    var results = new ConcurrentDictionary<string, string>();
    var engineLock = new object();

    // One engine — document operations must be serialized
    using var engine = new OmniPageEngine();
    engine.SetLicenseFile(licensePath);
    engine.Initialize();

    var settings = new RecognitionSettings { Language = "English" };

    Parallel.ForEach(imagePaths, imagePath =>
    {
        lock (engineLock) // Serialize: one recognition at a time
        {
            var document = engine.CreateDocument();
            try
            {
                document.AddPage(imagePath);
                document.Recognize(settings);
                results[imagePath] = document.GetText();
            }
            finally
            {
                document.Dispose();
            }
        }
    });

    engine.Shutdown();
    return results;
}
Imports System.Collections.Concurrent
Imports System.Threading.Tasks

' OmniPage: Shared engine with locking to serialize document operations
Public Function ProcessBatchWithEngine(imagePaths As String(), licensePath As String) As ConcurrentDictionary(Of String, String)
    Dim results As New ConcurrentDictionary(Of String, String)()
    Dim engineLock As New Object()

    ' One engine — document operations must be serialized
    Using engine As New OmniPageEngine()
        engine.SetLicenseFile(licensePath)
        engine.Initialize()

        Dim settings As New RecognitionSettings With {.Language = "English"}

        Parallel.ForEach(imagePaths, Sub(imagePath)
                                        SyncLock engineLock ' Serialize: one recognition at a time
                                            Dim document = engine.CreateDocument()
                                            Try
                                                document.AddPage(imagePath)
                                                document.Recognize(settings)
                                                results(imagePath) = document.GetText()
                                            Finally
                                                document.Dispose()
                                            End Try
                                        End SyncLock
                                    End Sub)
        engine.Shutdown()
    End Using

    Return results
End Function
$vbLabelText   $csharpLabel

IronOCR方法:

// IronOCR: One IronTesseract per thread — no locking, genuine parallelism
public ConcurrentDictionary<string, string> ProcessBatchInParallel(string[] imagePaths)
{
    var results = new ConcurrentDictionary<string, string>();

    Parallel.ForEach(imagePaths, imagePath =>
    {
        // Each thread creates its own instance — no shared state, no locks
        var ocr = new IronTesseract();
        using var input = new OcrInput();
        input.LoadImage(imagePath);

        var result = ocr.Read(input);
        results[imagePath] = result.Text;
    });

    return results;
}
// IronOCR: One IronTesseract per thread — no locking, genuine parallelism
public ConcurrentDictionary<string, string> ProcessBatchInParallel(string[] imagePaths)
{
    var results = new ConcurrentDictionary<string, string>();

    Parallel.ForEach(imagePaths, imagePath =>
    {
        // Each thread creates its own instance — no shared state, no locks
        var ocr = new IronTesseract();
        using var input = new OcrInput();
        input.LoadImage(imagePath);

        var result = ocr.Read(input);
        results[imagePath] = result.Text;
    });

    return results;
}
Imports System.Collections.Concurrent
Imports System.Threading.Tasks
Imports IronOcr

' IronOCR: One IronTesseract per thread — no locking, genuine parallelism
Public Function ProcessBatchInParallel(imagePaths As String()) As ConcurrentDictionary(Of String, String)
    Dim results As New ConcurrentDictionary(Of String, String)()

    Parallel.ForEach(imagePaths, Sub(imagePath)
        ' Each thread creates its own instance — no shared state, no locks
        Dim ocr As New IronTesseract()
        Using input As New OcrInput()
            input.LoadImage(imagePath)

            Dim result = ocr.Read(input)
            results(imagePath) = result.Text
        End Using
    End Sub)

    Return results
End Function
$vbLabelText   $csharpLabel

OmniPage 版本通过锁对所有识别操作进行序列化,从而否定了并行性。 IronOCR版本可以同时处理多个文档,无需协调。 对于高吞吐量批处理工作负载,请参阅 多线程示例速度优化指南

从扫描存档生成可搜索的 PDF 文件

OmniPage 的可搜索 PDF 输出需要在保存之前使用明确的 OutputSettingsOutputFormat 配置来组装识别管道。 IronOCR通过对结果对象进行一次方法调用,即可生成可搜索的 PDF 文件:

Kofax OmniPage 方法:

// OmniPage: Searchable PDF requires OutputSettings configuration before save
public void ConvertArchiveToSearchable(string[] scannedPdfPaths, string outputDirectory)
{
    using var engine = new OmniPageEngine();
    engine.SetLicenseFile(licensePath);
    engine.Initialize();

    var recognitionSettings = new RecognitionSettings
    {
        Language = "English",
        AccuracyMode = "Maximum",
        PreserveLayout = true
    };

    var outputSettings = new OutputSettings
    {
        Format = OutputFormat.SearchablePDF,
        Compression = PDFCompression.Standard,
        ImageQuality = 85,
        EmbedFonts = true
    };

    foreach (var pdfPath in scannedPdfPaths)
    {
        var document = engine.OpenPDF(pdfPath);
        document.RecognizeAll(recognitionSettings);

        string outputPath = Path.Combine(
            outputDirectory,
            Path.GetFileNameWithoutExtension(pdfPath) + "_searchable.pdf");

        document.SaveAs(outputPath, outputSettings);
        document.Dispose();
    }

    engine.Shutdown();
}
// OmniPage: Searchable PDF requires OutputSettings configuration before save
public void ConvertArchiveToSearchable(string[] scannedPdfPaths, string outputDirectory)
{
    using var engine = new OmniPageEngine();
    engine.SetLicenseFile(licensePath);
    engine.Initialize();

    var recognitionSettings = new RecognitionSettings
    {
        Language = "English",
        AccuracyMode = "Maximum",
        PreserveLayout = true
    };

    var outputSettings = new OutputSettings
    {
        Format = OutputFormat.SearchablePDF,
        Compression = PDFCompression.Standard,
        ImageQuality = 85,
        EmbedFonts = true
    };

    foreach (var pdfPath in scannedPdfPaths)
    {
        var document = engine.OpenPDF(pdfPath);
        document.RecognizeAll(recognitionSettings);

        string outputPath = Path.Combine(
            outputDirectory,
            Path.GetFileNameWithoutExtension(pdfPath) + "_searchable.pdf");

        document.SaveAs(outputPath, outputSettings);
        document.Dispose();
    }

    engine.Shutdown();
}
Imports System.IO

' OmniPage: Searchable PDF requires OutputSettings configuration before save
Public Sub ConvertArchiveToSearchable(scannedPdfPaths As String(), outputDirectory As String)
    Using engine As New OmniPageEngine()
        engine.SetLicenseFile(licensePath)
        engine.Initialize()

        Dim recognitionSettings As New RecognitionSettings With {
            .Language = "English",
            .AccuracyMode = "Maximum",
            .PreserveLayout = True
        }

        Dim outputSettings As New OutputSettings With {
            .Format = OutputFormat.SearchablePDF,
            .Compression = PDFCompression.Standard,
            .ImageQuality = 85,
            .EmbedFonts = True
        }

        For Each pdfPath As String In scannedPdfPaths
            Dim document = engine.OpenPDF(pdfPath)
            document.RecognizeAll(recognitionSettings)

            Dim outputPath As String = Path.Combine(outputDirectory, Path.GetFileNameWithoutExtension(pdfPath) & "_searchable.pdf")

            document.SaveAs(outputPath, outputSettings)
            document.Dispose()
        Next
    End Using

    engine.Shutdown()
End Sub
$vbLabelText   $csharpLabel

IronOCR方法:

// IronOCR: Searchable PDF output is one method call on the result
public void ConvertArchiveToSearchable(string[] scannedPdfPaths, string outputDirectory)
{
    var ocr = new IronTesseract();

    foreach (var pdfPath in scannedPdfPaths)
    {
        using var input = new OcrInput();
        input.LoadPdf(pdfPath); // All pages loaded automatically

        var result = ocr.Read(input);

        string outputPath = Path.Combine(
            outputDirectory,
            Path.GetFileNameWithoutExtension(pdfPath) + "_searchable.pdf");

        result.SaveAsSearchablePdf(outputPath);
        Console.WriteLine($"Processed: {Path.GetFileName(pdfPath)} ({result.Pages.Length} pages)");
    }
}
// IronOCR: Searchable PDF output is one method call on the result
public void ConvertArchiveToSearchable(string[] scannedPdfPaths, string outputDirectory)
{
    var ocr = new IronTesseract();

    foreach (var pdfPath in scannedPdfPaths)
    {
        using var input = new OcrInput();
        input.LoadPdf(pdfPath); // All pages loaded automatically

        var result = ocr.Read(input);

        string outputPath = Path.Combine(
            outputDirectory,
            Path.GetFileNameWithoutExtension(pdfPath) + "_searchable.pdf");

        result.SaveAsSearchablePdf(outputPath);
        Console.WriteLine($"Processed: {Path.GetFileName(pdfPath)} ({result.Pages.Length} pages)");
    }
}
Imports System.IO
Imports IronOcr

Public Sub ConvertArchiveToSearchable(scannedPdfPaths As String(), outputDirectory As String)
    Dim ocr As New IronTesseract()

    For Each pdfPath As String In scannedPdfPaths
        Using input As New OcrInput()
            input.LoadPdf(pdfPath) ' All pages loaded automatically

            Dim result = ocr.Read(input)

            Dim outputPath As String = Path.Combine(outputDirectory, Path.GetFileNameWithoutExtension(pdfPath) & "_searchable.pdf")

            result.SaveAsSearchablePdf(outputPath)
            Console.WriteLine($"Processed: {Path.GetFileName(pdfPath)} ({result.Pages.Length} pages)")
        End Using
    Next
End Sub
$vbLabelText   $csharpLabel

SaveAsSearchablePdf 将文本层嵌入 PDF 中,使其在文档管理系统中可以被索引,而不改变原始扫描的视觉外观。 该可搜索的 PDF 指南涵盖了文本图层选项, PDF OCR 示例演示了端到端的扫描 PDF 处理。

Kofax OmniPageAPI 到IronOCR映射参考

Kofax OmniPage IronOCR当量
using Kofax.OmniPage.CSDK; using IronOcr;
using Kofax.OmniPageCSDK; using IronOcr;
using CSDK; using IronOcr;
new OmniPageEngine() 不需要——没有引擎对象
engine.SetLicenseFile(path) IronOcr.License.LicenseKey = "key";
engine.Initialize() 不要求
engine.Shutdown() 不要求
engine.CreateDocument() new OcrInput()
document.AddPage(imagePath) input.LoadImage(imagePath)
engine.OpenPDF(pdfPath) input.LoadPdf(pdfPath)
engine.OpenTiff(tiffPath) input.LoadImageFrames(tiffPath)
document.Recognize(settings) new IronTesseract().Read(input)
document.RecognizeAll(settings) new IronTesseract().Read(input) (所有页面同时)
document.GetText() result.Text
document.GetZoneText(zoneName) input.LoadImage(path, cropRectangle) + result.Text
document.Dispose() using var input = new OcrInput() (自动)
iterator.GetText() result.Pages[n].Words[m].Text
iterator.GetBoundingBox() result.Pages[n].Words[m].X / Y / Width / Height
iterator.GetConfidence() result.Pages[n].Words[m].Confidence
engine.LoadLanguageDictionary("German") dotnet add package IronOcr.Languages.German
settings.PrimaryLanguage = "English" ocr.Language = OcrLanguage.English;
settings.SecondaryLanguages = new[] {"German"} ocr.Language = OcrLanguage.English + OcrLanguage.German;
settings.DeskewImage = true input.Deskew();
settings.DespeckleLevel = 2 input.DeNoise();
settings.ContrastEnhancement = true input.Contrast();
settings.AutoRotate = true input.Deskew(); (包括旋转校正)
document.SaveAs(path, outputSettings) result.SaveAsSearchablePdf(path)
OutputFormat.SearchablePDF result.SaveAsSearchablePdf(path)
OutputFormat.XML (带坐标) result.Pages / .Paragraphs / .Words 对象图
FormZone 带坐标边界 new CropRectangle(x, y, width, height)
按页许可计数 不适用
.lic 文件部署 不适用
许可证服务器配置 不适用

常见迁移问题和解决方案

问题 1:生产环境中出现"找不到许可证文件"的异常

Kofax OmniPage: 每个部署都要求在应用进程可以访问的特定路径中存在 .lic 文件。 如果部署管道没有明确地将许可证文件复制到目标服务器,则会在引擎初始化时导致 FileNotFoundExceptionLicenseException。 安全团队经常会标记将 .lic 文件嵌入到容器镜像中或将其提交到源代码控制。

解决方案: IronOCR从字符串中读取其许可证。 将密钥存储为环境变量,并在启动时读取——无需部署文件,无需配置路径:

// At application startup — reads IRONOCR_LICENSE_KEY from environment
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE_KEY")
    ?? throw new InvalidOperationException("IronOCR license key not configured.");
// At application startup — reads IRONOCR_LICENSE_KEY from environment
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE_KEY")
    ?? throw new InvalidOperationException("IronOCR license key not configured.");
Imports System

' At application startup — reads IRONOCR_LICENSE_KEY from environment
IronOcr.License.LicenseKey = If(Environment.GetEnvironmentVariable("IRONOCR_LICENSE_KEY"), Throw New InvalidOperationException("IronOCR license key not configured."))
$vbLabelText   $csharpLabel

在 Docker 中,传递 --env IRONOCR_LICENSE_KEY=YOUR-KEY。 在 Azure 应用服务中,将其设置为应用程序设置。 在 Kubernetes 中,通过 Secret 注入它。 无需文件挂载,无需路径配置,无需对嵌入式许可证文件进行安全审查。

问题 2:进程崩溃后浮动许可证席位被锁定

Kofax OmniPage: 当应用程序进程崩溃、被看门狗终止或通过 Environment.FailFast 退出时,engine.Shutdown() 不执行。 浮动许可证席位将一直处于签出状态,直到许可证服务器超时为止——通常为 30-60 分钟。 在容器化环境中,Pod 频繁重启,这种行为会耗尽许可证池。

解决方案: IronOCR没有出示驾照席位的概念。 进程崩溃不会释放任何资源,也不会锁定任何外部系统。 下一个流程立即开始,无需等待座位:

// IronOCR: Process crash has no license implications whatsoever
// Start processing immediately after any failure
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var result = new IronTesseract().Read("document.jpg"); // 否 seat to check out
// IronOCR: Process crash has no license implications whatsoever
// Start processing immediately after any failure
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var result = new IronTesseract().Read("document.jpg"); // 否 seat to check out
' IronOCR: Process crash has no license implications whatsoever
' Start processing immediately after any failure
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Dim result = New IronTesseract().Read("document.jpg") ' 否 seat to check out
$vbLabelText   $csharpLabel

对于具有弹性的ASP.NET Core服务,请参阅异步 OCR 指南,了解与托管服务干净利落地集成和优雅关闭的模式。

问题 3:Docker 镜像构建失败 — 安装程序无法以非交互方式运行

Kofax OmniPage: OmniPage SDK 安装程序是一个 GUI 或半交互式安装程序,不能在 docker build 上下文中清晰运行。 尝试将 SDK 集成到容器镜像中的团队在安装程序许可协议或组件选择步骤中遇到失败。 变通方法(静默安装开关、预提取的 DLL 副本)没有文档记录,并且与版本相关。

解决方案:IronOCR的 Dockerfile 是三行:

FROM mcr.microsoft.com/dotnet/aspnet:8.0
RUN apt-get update && apt-get install -y libgdiplus  # Single Linux dependency
COPY --from=build /app/publish /app
WORKDIR /app
ENTRYPOINT ["dotnet", "YourApp.dll"]

libgdiplus 是唯一的系统依赖。IronOCRNuGet 包在构建阶段时由 dotnet restore 恢复,就像任何其他包引用一样。 Docker 部署指南涵盖 Debian 和 Alpine 基础镜像,而Linux 部署指南涵盖特定于发行版的软件包名称。

问题 4:虚拟机迁移或云自动扩展后需要重新激活

Kofax OmniPage: OmniPage 的激活与硬件身份绑定。 在物理主机之间迁移虚拟机、自动扩展到新实例或用新镜像替换容器的云环境中,会触发硬件身份变更,需要重新激活。 事故发生过程中联系 Tungsten 支持部门进行系统重启会增加操作风险。

解决方案:IronOCR的许可证密钥与硬件无关。 同一个密钥可以在任何机器、任何容器、任何云区域以及许可层级内的任何数量的自动扩展实例上使用。 无需重新激活,无需联系客服,无需停机:

// Identical startup code on any hardware topology
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
bool valid = IronOcr.License.IsLicensed; // Verify without a network call
// Identical startup code on any hardware topology
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
bool valid = IronOcr.License.IsLicensed; // Verify without a network call
' Identical startup code on any hardware topology
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Dim valid As Boolean = IronOcr.License.IsLicensed ' Verify without a network call
$vbLabelText   $csharpLabel

问题 5:识别设置对象没有IronOCR等效项

Kofax OmniPage: OmniPage 使用一个 RecognitionSettings 对象(或根据 SDK 版本使用 PreprocessingSettings)配置引擎的文档行为。 迁移团队希望在IronOCR中有一个并行的配置对象。

解决方案:IronOCR将配置拆分为两个部分:在 OcrInput 上进行预处理操作和在 IronTesseract 上的引擎设置。 没有可供实例化的设置对象:

// OmniPage settings object →IronOCRmethod calls on OcrInput
var ocr = new IronTesseract();
ocr.Language = OcrLanguage.English;              // RecognitionSettings.Language
// ocr.Configuration.TesseractVersion = ...     // Engine version already optimized

using var input = new OcrInput();
input.LoadImage(imagePath);
input.Deskew();                                  // settings.DeskewImage = true
input.DeNoise();                                 // settings.DespeckleLevel = 2
input.Contrast();                                // settings.ContrastEnhancement = true

var result = ocr.Read(input);
// OmniPage settings object →IronOCRmethod calls on OcrInput
var ocr = new IronTesseract();
ocr.Language = OcrLanguage.English;              // RecognitionSettings.Language
// ocr.Configuration.TesseractVersion = ...     // Engine version already optimized

using var input = new OcrInput();
input.LoadImage(imagePath);
input.Deskew();                                  // settings.DeskewImage = true
input.DeNoise();                                 // settings.DespeckleLevel = 2
input.Contrast();                                // settings.ContrastEnhancement = true

var result = ocr.Read(input);
Imports IronOcr

' OmniPage settings object → IronOCR method calls on OcrInput
Dim ocr As New IronTesseract()
ocr.Language = OcrLanguage.English ' RecognitionSettings.Language
' ocr.Configuration.TesseractVersion = ... ' Engine version already optimized

Using input As New OcrInput()
    input.LoadImage(imagePath)
    input.Deskew() ' settings.DeskewImage = true
    input.DeNoise() ' settings.DespeckleLevel = 2
    input.Contrast() ' settings.ContrastEnhancement = true

    Dim result = ocr.Read(input)
End Using
$vbLabelText   $csharpLabel

图像质量校正指南列出了所有可用的预处理方法,并指导用户哪些滤镜适用于哪些文档质量配置文件。

问题 6:区域模板文件无法直接移植

Kofax OmniPage: OmniPage 表单模板将区域定义存储在专有的 .fdt.fpf 模板文件中,这些文件定义了每种文档类别的字段位置、识别类型和验证规则。 IronOCR无法导入这些文件。

解决方案: 从模板文件中提取坐标数据(它们是基于 XML 的)并将每个区域转换为 CropRectangle。 字段名将成为字典键; 区域坐标直接映射到 (x, y, width, height) 参数:

// Convert OmniPage zone definition toIronOCRCropRectangle
// OmniPage zone: Name="TotalDue" X="490" Y="612" Width="180" Height="28"
//IronOCRequivalent:
var totalDueRegion = new CropRectangle(490, 612, 180, 28);

using var input = new OcrInput();
input.LoadImage(invoicePath, totalDueRegion);
string totalDue = new IronTesseract().Read(input).Text.Trim();
// Convert OmniPage zone definition toIronOCRCropRectangle
// OmniPage zone: Name="TotalDue" X="490" Y="612" Width="180" Height="28"
//IronOCRequivalent:
var totalDueRegion = new CropRectangle(490, 612, 180, 28);

using var input = new OcrInput();
input.LoadImage(invoicePath, totalDueRegion);
string totalDue = new IronTesseract().Read(input).Text.Trim();
Imports IronOcr

' Convert OmniPage zone definition to IronOCR CropRectangle
' OmniPage zone: Name="TotalDue" X="490" Y="612" Width="180" Height="28"
' IronOCR equivalent:
Dim totalDueRegion As New CropRectangle(490, 612, 180, 28)

Using input As New OcrInput()
    input.LoadImage(invoicePath, totalDueRegion)
    Dim totalDue As String = New IronTesseract().Read(input).Text.Trim()
End Using
$vbLabelText   $csharpLabel

对于每页区域不同的文档,使用不同的 CropRectangle 值加载同一图像的不同区域,而不是重新加载文件。区域剪裁示例 演示了如何从单一图像源读取多个区域。

Kofax OmniPage迁移清单

迁移前

找出代码库中所有与 OmniPage 集成的点:

# Find all OmniPage namespace references
grep -r "Kofax\|OmniPage\|CSDK" --include="*.cs" --include="*.csproj" .

# Find engine lifecycle calls
grep -r "Initialize\|Shutdown\|SetLicenseFile" --include="*.cs" .

# Find document and zone creation patterns
grep -r "CreateDocument\|AddPage\|FormZone\|RecognitionSettings\|PreprocessingSettings" --include="*.cs" .

# Find output format configuration
grep -r "OutputSettings\|OutputFormat\|SaveAs\|GetText" --include="*.cs" .

# Find license file path references
grep -r "\.lic\|license\.lic\|licensePath" --include="*.cs" --include="*.config" --include="*.json" .
# Find all OmniPage namespace references
grep -r "Kofax\|OmniPage\|CSDK" --include="*.cs" --include="*.csproj" .

# Find engine lifecycle calls
grep -r "Initialize\|Shutdown\|SetLicenseFile" --include="*.cs" .

# Find document and zone creation patterns
grep -r "CreateDocument\|AddPage\|FormZone\|RecognitionSettings\|PreprocessingSettings" --include="*.cs" .

# Find output format configuration
grep -r "OutputSettings\|OutputFormat\|SaveAs\|GetText" --include="*.cs" .

# Find license file path references
grep -r "\.lic\|license\.lic\|licensePath" --include="*.cs" --include="*.config" --include="*.json" .
SHELL

开工前盘点:

  • 统计包含 OmniPage 命名空间导入的文件数量
  • 列出所有 FormTemplateFormZone 定义并提取其坐标数据
  • 确定加载了哪些语言词典并将其映射到 IronOcr.Languages.* NuGet 包
  • 注意所使用的输出格式(可搜索 PDF、纯文本、XML)及其IronOCR对应格式。
  • 在部署脚本和配置中找到所有 .lic 文件引用

代码迁移

  1. 从所有 .csproj 文件中删除 OmniPage DLL 引用
  2. 在每个执行 OCR 的项目中运行 dotnet add package IronOcr
  3. 为每种使用的语言添加语言包:dotnet add package IronOcr.Languages.[Language]
  4. 在每个应用程序入口点添加 IronOcr.License.LicenseKey = ...;(Program.cs、Startup.cs 或服务主机)
  5. 将所有 using CSDK; 和相关命名空间导入替换为 using IronOcr;
  6. 移除所有 OmniPageEngine 构造、SetLicenseFileInitialize 调用
  7. 移除所有 engine.Shutdown() 调用和仅用于确保关闭的 IDisposable 实现
  8. new OcrInput() + input.LoadImage() 替换 engine.CreateDocument() + document.AddPage()
  9. input.LoadPdf()(所有页面一次调用)替换 engine.OpenPDF() + 每页迭代
  10. input.LoadImageFrames() 替换 engine.OpenTiff() + 每帧循环
  11. new IronTesseract().Read(input) 替换 document.Recognize(settings)
  12. FormZone 坐标数据转换为 CropRectangle(x, y, width, height) 实例
  13. document.GetZoneText() 替换为每区域的 input.LoadImage(path, cropRectangle) + result.Text
  14. result.SaveAsSearchablePdf(path) 替换可搜索 PDF 的 document.SaveAs(path, outputSettings)
  15. result.Pages / .Paragraphs / .Words 属性遍历替换结果迭代器模式
  16. 移除 PreprocessingSettings 对象并用显式的 input.Contrast() 方法调用替换布尔标志
  17. 从部署脚本、配置文件和基础设施即代码模板中移除许可证文件路径

后迁移

  • 从实际语料库中选取 100 多份具有代表性的文档样本,验证 OCR 识别的准确性——逐行与 OmniPage 的输出结果(包括发票、合同和表格)进行比较。
  • 确认基于 CropRectangle 的区域提取返回的文本与 OmniPage GetZoneText() 输出匹配
  • 测试多页 PDF 处理:验证页数、页面顺序和文本连续性
  • 测试多帧TIFF处理:确认所有帧均已处理且帧顺序保持不变
  • 验证可搜索的 PDF 输出是否可在所使用的文档管理系统(SharePoint、OpenText 等)中建立索引。
  • 运行并行处理测试,同时处理 50 个以上的文档,并确认不存在线程安全问题。
  • 测试语言包覆盖率:通过 IronOcr.Languages.* 加载每个以前使用的语言词典并验证识别质量
  • 确认进程崩溃和容器重启不需要任何许可证恢复操作
  • 在 CI 代理上运行 Docker 构建 — 验证 dotnet restore 解析 IronOCR,无需安装步骤
  • 验证置信度评分是按词计算的,并且符合任何下游验证工作流程的数据质量要求。
  • 检查应用程序在没有任何路径下存在 .lic 文件时是否能正常启动

迁移到IronOCR的主要优势

部署复杂性从几周降至几分钟。 以前需要 SDK 安装程序访问、.lic 文件部署、许可证服务器防火墙配置和基础设施团队协同的项目现在通过 dotnet restore 部署。 一位新开发者克隆了代码库并运行了项目。 CI/CD 流水线会配置一台新的生产服务器。容器镜像无需安装步骤即可构建。

许可证风险完全消失。 没有浮动席位可耗尽,没有结帐超时等待,没有硬件指纹需要重新激活,也没有 .lic 文件需要在部署管道中保护。 凌晨 3 点应用程序崩溃不会释放任何资源,也不会锁定任何资源。 值班工程师重新启动了该流程; OCR功能立即恢复。 IronOCR产品页面涵盖所有许可级别。

跨平台部署已成为标准的NuGet目标。macOS开发机器无需平台例外即可正常工作。 Linux 生产服务器不需要 2026 年 1 月的 SDK 版本。 Docker 容器从一个标准 mcr.microsoft.com/dotnet/aspnet 基础镜像构建,包含一个系统依赖。 在 .csproj 中的同一 IronOcr 包引用在 Windows、Linux 和 macOS 上生成工作构建。 有关云特定配置,请参阅Azure 部署指南AWS 部署指南

并行吞吐量随硬件扩展而扩展。OmniPage的共享引擎架构需要锁定机制来串行化并发识别操作。IronOCR的无状态 IronTesseract 实例与可用的 CPU 核心线性扩展。 将批处理服务器的核心数增加一倍,吞吐量就会增加一倍,而无需更改配置或额外许可。

结构化数据访问即刻实现。 WordsCharacters 全部以可导航的 .NET 对象图形式暴露完整的文档结构。 每个词的置信度和坐标都是每个词对象的属性。 访问位置数据不需要辅助导出管道、格式配置或文件输出。

成本固定且可预测。OmniPage的费用组合包括 SDK 许可费、年度维护费和按页面计费的运行时费用,其成本随使用量增加而增长,并每年重复收取。IronOCR的 $999–2,999美元永久是一次性购买。 每年处理 50 万页的工作流程,如果按页收费,几周内就能收回IronOCR许可证的成本。 IronOCR文档中心教程无需支持合同即可使用。

请注意Kofax OmniPage, OpenPDF和Tesseract是其各自所有者的注册商标。 本网站与Google、Kofax或LibrePDF无关系、未获授权或赞助。 所有产品名称、徽标和品牌均为各自所有者的财产。 比较仅供参考,反映撰写时公开可用的信息。

常见问题解答

我为什么要从 Kofax OmniPage 迁移到 IronOCR?

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

从 Kofax OmniPage 迁移到 IronOCR 时,主要的代码变更有哪些?

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

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

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

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

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

IronOCR 如何处理 Kofax OmniPage 单独安装的语言数据?

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

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

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

迁移后如何配置 IronOCR 许可?

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

IronOCR 能否像 Kofax OmniPage 那样处理 PDF 文件?

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

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

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

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

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

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

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

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

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

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

钢铁支援团队

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