跳转到主要内容
处理纸质文档时,您需要查找并纠正错误或有意做出的更改。使用 Document Comparison API 可以快速高效地查找这些更改。 此场景用于将合同、银行文件等特别重要的文档与其副本进行比较。比较结果包含有关内容类型 (仅文本) 、修改类型 (删除、插入或修改) 以及这些差异在原件和副本中位置的信息。您可以获取检测到的差异列表或任意更改区域,并将比较结果保存到外部文件中,以便后续处理或长期存储。 要比较文档或页面,通过扫描获得或以电子格式保存的文件通常需要经过多个处理阶段,每个阶段都有其各自的特点:
  1. 扫描文件或图像的预处理
如果文件及其副本包含某些缺陷或刻意添加的标记 (如签名或印章) ,则在识别前需要进行预处理。
  1. 在完整还原文档结构和格式的情况下进行识别
识别文档时,会检测出文档中的各种版面元素 (文本、表格、图像、分隔符等) 。在文档合成过程中,会还原文档的逻辑结构;而页面合成则能够完整还原文档格式 (字体、样式等) 。
  1. 文档或页面比较
要将文档或页面与其副本进行比较,请使用通过 ABBYY FineReader Engine 识别的文件。您可以比较同一文档的两个不同格式版本。比较完成后,您会获得包含更改列表的结果,并可据此获取更改位置的信息。如果您使用人工校验,可以利用这些信息高亮显示文本中的更改,从而减轻操作员的工作负担。
  1. 导出为外部格式
您还可以将比较结果保存为 XML 和 DOCX 格式。 下文所述过程也可参考适用于 Linux 和 macOS 的 Document Comparison 示例,以及适用于 Windows 的 Document Comparison 演示工具。

场景实现

本主题中提供的代码示例仅适用于 Windows。
下面将详细介绍在此场景中使用 ABBYY FineReader Engine 的推荐方式。
要开始使用 ABBYY FineReader Engine,您需要创建 Engine 对象。Engine 对象是 ABBYY FineReader Engine 对象层次结构中的顶层对象,提供各种全局设置、一些处理 method,以及用于创建其他对象的方法。要创建 Engine 对象,您可以使用 InitializeEngine 函数。另请参阅加载 Engine 对象的其他方法 (Win) 。

C#

public class EngineLoader : IDisposable
{
    public EngineLoader()
    {
        // 使用 FREngine.dll 的完整路径、您的 Customer Project ID,
        // 以及(如适用)Online License 令牌文件的路径和 Online License 密码初始化这些变量
        string enginePath = "";
        string customerProjectId = "";
        string licensePath = "";
        string licensePassword = "";
        // 加载 FREngine.dll 库
        dllHandle = LoadLibraryEx(enginePath, IntPtr.Zero, LOAD_WITH_ALTERED_SEARCH_PATH);
           
        try
        {
            if (dllHandle == IntPtr.Zero)
            {
                throw new Exception("Can't load " + enginePath);
            }
            IntPtr initializeEnginePtr = GetProcAddress(dllHandle, "InitializeEngine");
            if (initializeEnginePtr == IntPtr.Zero)
            {
                throw new Exception("Can't find InitializeEngine function");
            }
            IntPtr deinitializeEnginePtr = GetProcAddress(dllHandle, "DeinitializeEngine");
            if (deinitializeEnginePtr == IntPtr.Zero)
            {
                throw new Exception("Can't find DeinitializeEngine function");
            }
            IntPtr dllCanUnloadNowPtr = GetProcAddress(dllHandle, "DllCanUnloadNow");
            if (dllCanUnloadNowPtr == IntPtr.Zero)
            {
                throw new Exception("Can't find DllCanUnloadNow function");
            }
            // 将指针转换为委托
            initializeEngine = (InitializeEngine)Marshal.GetDelegateForFunctionPointer(
                initializeEnginePtr, typeof(InitializeEngine));
            deinitializeEngine = (DeinitializeEngine)Marshal.GetDelegateForFunctionPointer(
                deinitializeEnginePtr, typeof(DeinitializeEngine));
            dllCanUnloadNow = (DllCanUnloadNow)Marshal.GetDelegateForFunctionPointer(
                dllCanUnloadNowPtr, typeof(DllCanUnloadNow));
            // 调用 InitializeEngine 函数
            // 传入 Online License 文件路径和 Online License 密码
            int hresult = initializeEngine(customerProjectId, licensePath, licensePassword, 
                "", "", false, ref engine);
            Marshal.ThrowExceptionForHR(hresult);
        }
        catch (Exception)
        {
            // 释放 FREngine.dll 库
            engine = null;
            // 在调用 FreeLibrary 之前删除所有对象
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
            FreeLibrary(dllHandle);
            dllHandle = IntPtr.Zero;
            initializeEngine = null;
            deinitializeEngine = null;
            dllCanUnloadNow = null;
            throw;
        }
    }
    // Kernel32.dll 函数
    [DllImport("kernel32.dll")]
    private static extern IntPtr LoadLibraryEx(string dllToLoad, IntPtr reserved, uint flags);
    private const uint LOAD_WITH_ALTERED_SEARCH_PATH = 0x00000008;
    [DllImport("kernel32.dll")]
    private static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);
    [DllImport("kernel32.dll")]
    private static extern bool FreeLibrary(IntPtr hModule);
    // FREngine.dll 函数
    [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode)]
    private delegate int InitializeEngine(string customerProjectId, string licensePath, 
        string licensePassword, string tempFolder, string dataFolder, bool isSharedCPUCoresMode, 
        ref FREngine.IEngine engine);
    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    private delegate int DeinitializeEngine();
    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    private delegate int DllCanUnloadNow();
    // 私有变量
    private FREngine.IEngine engine = null;
    // FREngine.dll 句柄
    private IntPtr dllHandle = IntPtr.Zero;
    private InitializeEngine initializeEngine = null;
    private DeinitializeEngine deinitializeEngine = null;
    private DllCanUnloadNow dllCanUnloadNow = null;
}
ABBYY FineReader Engine 提供 FRDocument 对象,用于处理多页文档。使用此对象可以保留文档的逻辑结构,保留原始文本、分栏、字体、样式等。如果您想比较页面,请使用 FRPage 对象。要加载单个文档的图像并进行预处理,您应创建 FRDocument 对象并向其中添加图像。您可以采用以下任一方式:

C#

// 从图像文件创建 FRDocument 对象
FREngine.IFRDocument frDocument = engine.CreateFRDocumentFromImage( "C:\\MyImage.tif", null );
要识别文档,建议您使用 FRDocument 对象的分析和识别方法。该对象提供了一整套用于文档分析、识别和合成的方法。最便捷的一体化方法是 Process 方法,它可同时完成文档分析、识别和合成。它还能以最高效的方式利用多处理器和多核系统的并行处理功能。不过,您也可以使用 PreprocessAnalyzeRecognizeSynthesize 方法,依次执行预处理、分析、识别和合成。
您可以通过加载合适的预定义配置文件,为文档设置识别参数 (更多信息请参见 Working with Profiles) 。

C#

// 使用默认参数处理文档
// 如有需要,您可以更改这些参数,例如预先加载一个配置文件
frDocument.Process( null );
要将文档或页面与其副本进行比较,请执行以下操作:
  1. 确保您的 ABBYY FineReader Engine 许可证支持 Compare Documents 模块。
  2. 使用 Engine 对象的 CreateComparator 方法创建一个 Comparator 对象。
  3. [可选] 使用 ComparisonParams 对象,将各属性设置为所需的值。
  4. 调用 Comparator 对象的 CompareDocuments 方法,将原始文档与副本进行比较。该方法将返回一个 ComparisonResult 对象,其中包含检测到的更改信息。

C#

// 执行文档比较
FREngine.IComparator comparator = engine.CreateComparator();
FREngine.IComparisonResult comparatorResult = 
    comparator.CompareDocuments( referenceFRDocument, userFRDocument, null, null );
ComparisonResult 对象包含完整的差异列表,并提供用于获取单个页面差异的方法。您可以使用 GetChangesForReferencePageGetChangesForUserPage 方法,访问原始文档及其副本中的更改。使用 ChangeLocation 对象可获取更改位置信息,使用其 RegionForPage 属性可获取该更改在指定页面上的区域。

C#

// 获取检测到的修改及其在原始文档中的位置信息
FREngine.IChanges changes = comparatorResult.Changes;
foreach( FREngine.IChange change in changes ) {
      FREngine.ModificationTypeEnum modificationType = change.ModificationType;
      FREngine.IChangeLocation referenceLocation = change.ReferenceLocation;
      // 现在,您可以在页面上高亮这些更改,供人工操作员检查
      ... 
}
要导出比较结果,请调用 ComparisonResult 对象的 Export 方法,并将文件路径作为输入参数传入。数据可以保存为 XML 文件或带修订记录的 DOCX 文件。C#
// 保存为 XML 格式
comparisonResult.Export( "C:\\ComparisonResult.xml", FREngine.ComparatorExportFormatEnum.CEF_Xml, null );
在使用完 ABBYY FineReader Engine 后,您需要卸载 Engine 对象。为此,请使用导出的 DeinitializeEngine 函数。

C#

public class EngineLoader : IDisposable
{
    // 卸载 FineReader Engine
    public void Dispose()
    {
        if (engine == null)
        {
            // Engine 尚未加载
            return;
        }
        engine = null;
        // 在调用 FreeLibrary 前删除所有对象
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
        int hresult = deinitializeEngine();
 
        hresult = dllCanUnloadNow();
        if (hresult == 0)
        {
            FreeLibrary(dllHandle);
        }
        dllHandle = IntPtr.Zero;
        initializeEngine = null;
        deinitializeEngine = null;
        dllCanUnloadNow = null;
        // 清理完成后抛出异常
        Marshal.ThrowExceptionForHR(hresult);
    }
    // Kernel32.dll 函数
    [DllImport("kernel32.dll")]
    private static extern IntPtr LoadLibraryEx(string dllToLoad, IntPtr reserved, uint flags);
    private const uint LOAD_WITH_ALTERED_SEARCH_PATH = 0x00000008;
    [DllImport("kernel32.dll")]
    private static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);
    [DllImport("kernel32.dll")]
    private static extern bool FreeLibrary(IntPtr hModule);
    // FREngine.dll 函数
    [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode)]
    private delegate int InitializeEngine( string customerProjectId, string LicensePath, string LicensePassword, , , , ref FREngine.IEngine engine);
    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    private delegate int DeinitializeEngine();
    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    private delegate int DllCanUnloadNow();
    // 私有变量
    private FREngine.IEngine engine = null;
    // FREngine.dll 的句柄
    private IntPtr dllHandle = IntPtr.Zero;
    private InitializeEngine initializeEngine = null;
    private DeinitializeEngine deinitializeEngine = null;
    private DllCanUnloadNow dllCanUnloadNow = null;
}

所需资源

您可以使用 FREngineDistribution.csv 文件,自动生成应用程序运行所需文件的列表。对于此场景的处理,请在第 5 列 (RequiredByModule) 中选择以下值: Core Core.Resources Opening Opening, Processing Processing Processing.OCR Processing.OCR, Processing.ICR Processing.OCR.NaturalLanguages Processing.OCR.NaturalLanguages, Processing.ICR.NaturalLanguages Export Export, Processing 如果您修改了标准场景,请相应调整所需模块。您还需要指定应用程序使用的界面语言、识别语言以及其他附加功能 (例如,如果需要打开 PDF 文件,则指定 Opening.PDF;如果需要识别 CJK languages 中的文本,则指定 Processing.OCR.CJK) 。有关更多信息,请参阅 Working with the FREngineDistribution.csv File

针对特定任务的进一步优化

以下概述了帮助主题,其中包含有关在不同处理阶段自定义设置的更多信息:
  • 扫描 - 仅限 Windows
    • 扫描
      介绍 ABBYY FineReader Engine 的文档扫描场景。
  • 识别
    • 预处理、分析、识别和合成的调优参数
      使用分析、识别和合成参数对象自定义文档处理。
    • PageProcessingParams 对象
      此对象可用于自定义分析和识别参数。使用此对象,您可以指定必须检测哪些图像和文本特征 (反相图像、方向、条形码、识别语言、识别误差范围) 。
    • SynthesisParamsForPage 对象
      此对象包含在合成过程中负责恢复页面格式的参数。
    • SynthesisParamsForDocument 对象
      此对象可用于自定义文档合成,即恢复其结构和格式。
    • MultiProcessingParams 对象 - 适用于 Linux 和 Windows
      在处理大量图像时,并行处理会很有帮助。在这种情况下,图像打开和预处理、版面分析、识别以及导出期间的处理负载将分配到各个处理器核心上,从而加快处理速度。
      读取模式 (并行或顺序) 通过 MultiProcessingMode 属性进行设置。RecognitionProcessesCount 属性用于控制可启动的进程数。

另请参见

基本使用场景实现