跳转到主要内容
在字段级识别场景中,系统会识别较短的文本片段,以提取特定字段中的数据。在这种场景下,识别质量至关重要。 该场景也可作为更复杂场景的一部分使用,在这些场景中,需要从文档中提取有意义的数据 (例如,将纸质文档中的数据录入信息系统和数据库,或在文档管理系统中自动对文档进行分类和编制索引) 。 在该场景中,系统会识别部分字段中的多行文本,或者小幅图像上的全部文本。系统会为每个识别出的字符计算置信度评分。随后,这些置信度评分可用于检查识别结果。此外,系统还可为文本中的单词和字符存储多个识别变体,随后可将其用于投票算法,以提高识别质量。 在该场景中,小文本片段的处理在某些方面与其他场景中的相同步骤有所不同:
  1. 对扫描图像或照片进行预处理
待识别的图像可能包含标记和背景噪声,这两者都会妨碍识别。因此,在此阶段会去除所有不需要的标记和背景噪声。
  1. 识别小文本片段
在识别小文本片段时,待识别的数据类型是预先已知的。因此,可以通过使用外部词典、正则表达式、自定义识别语言和字母表,以及限制 string 中的字符数,来提高识别质量。文本字段可包含印刷体、工整手写体和手写体文本。
  1. 处理识别出的数据
该场景要求尽可能高的识别准确率,以尽量减少数据核验工作。系统可为每个识别出的单词或字符计算置信度评分,并提供多个识别变体,随后多个引擎可通过应用投票算法从中选择最佳候选项。

场景实现

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

C#

public class EngineLoader : IDisposable
{
    public EngineLoader()
    {
        // 使用 FREngine.dll 的完整路径、您的 Customer Project ID,
        // 以及(如适用)在线许可证令牌文件的路径和在线许可证密码来初始化这些变量
        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("无法加载 " + enginePath);
            }
            IntPtr initializeEnginePtr = GetProcAddress(dllHandle, "InitializeEngine");
            if (initializeEnginePtr == IntPtr.Zero)
            {
                throw new Exception("找不到 InitializeEngine 函数");
            }
            IntPtr deinitializeEnginePtr = GetProcAddress(dllHandle, "DeinitializeEngine");
            if (deinitializeEnginePtr == IntPtr.Zero)
            {
                throw new Exception("找不到 DeinitializeEngine 函数");
            }
            IntPtr dllCanUnloadNowPtr = GetProcAddress(dllHandle, "DllCanUnloadNow");
            if (dllCanUnloadNowPtr == IntPtr.Zero)
            {
                throw new Exception("找不到 DllCanUnloadNow 函数");
            }
            // 将指针转换为委托
            initializeEngine = (InitializeEngine)Marshal.GetDelegateForFunctionPointer(
                initializeEnginePtr, typeof(InitializeEngine));
            deinitializeEngine = (DeinitializeEngine)Marshal.GetDelegateForFunctionPointer(
                deinitializeEnginePtr, typeof(DeinitializeEngine));
            dllCanUnloadNow = (DllCanUnloadNow)Marshal.GetDelegateForFunctionPointer(
                dllCanUnloadNowPtr, typeof(DllCanUnloadNow));
            // 调用 InitializeEngine 函数
            // 传入在线许可证文件的路径和在线许可证密码
            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;
}

C++ (COM)

// 请将以下变量初始化为 FREngine.dll 的路径、您的 FineReader Engine Customer Project ID,
// 以及(如适用)Online License 令牌路径和 Online License 密码
wchar_t* FreDllPath;
wchar_t* CustomerProjectId;
wchar_t* LicensePath;  // 如果不使用 Online License,请将这些变量赋值为空字符串
wchar_t* LicensePassword;
// FREngine.dll 的句柄
static HMODULE libraryHandle = 0;
// 全局 FineReader Engine 对象
FREngine::IEnginePtr Engine;
void LoadFREngine()
{
    if( Engine != 0 ) {
    // 已加载
    return;
    }
    // 第一步:加载 FREngine.dll
    if( libraryHandle == 0 ) {
        libraryHandle = LoadLibraryEx( FreDllPath, 0, LOAD_WITH_ALTERED_SEARCH_PATH );
        if( libraryHandle == 0 ) {
            throw L"加载 ABBYY FineReader Engine 时出错";
        }
    }
    // 第二步:获取 Engine 对象
    typedef HRESULT ( STDAPICALLTYPE* InitializeEngineFunc )( BSTR, BSTR, BSTR, BSTR, 
        BSTR, VARIANT_BOOL, FREngine::IEngine** );
    InitializeEngineFunc pInitializeEngine =
    ( InitializeEngineFunc )GetProcAddress( libraryHandle, "InitializeEngine" );
    if( pInitializeEngine == 0 || pInitializeEngine( CustomerProjectId, LicensePath, 
        LicensePassword, L"", L"", VARIANT_FALSE, &Engine ) != S_OK ) {
    UnloadFREngine();
    throw L"加载 ABBYY FineReader Engine 时出错";
    }
}
可以使用 Engine 对象的 LoadPredefinedProfile 方法选择最合适的设置。该方法接收配置文件名称作为输入参数。对于此场景,可以使用名为 FieldLevelRecognition 的预定义配置文件来选择最合适的设置。有关配置文件的更多信息,请参见 使用配置文件

C#

// 加载预定义配置文件
engine.LoadPredefinedProfile("FieldLevelRecognition");

C++ (COM)

// 加载预定义配置文件
Engine->LoadPredefinedProfile( L"FieldLevelRecognition" );
如果您希望更改处理所使用的设置,请使用相应的参数对象。有关更多信息,请参见下方的 其他优化 部分。
ABBYY FineReader Engine 提供了一个用于处理多页文档的 FRDocument 对象。要加载文档图像并进行预处理,您应创建 FRDocument 对象并向其中添加图像。您可以执行以下任一操作:

C#

// 从图像文件创建 FRDocument 对象
FREngine.IFRDocument frDocument = engine.CreateFRDocumentFromImage( "C:\\MyImage.tif", null );

C++ (COM)

// 打开图像文件并创建 FRDocument 对象
FREngine::IFRDocumentPtr frDocument = Engine->CreateFRDocumentFromImage( L"C:\\MyImage.tif", 0 );
现在,您需要创建包含这些字段的块,并为每个块指定块类型以及其中数据的已知特征。使用 Analyze 方法对文档执行版面分析,或者手动添加包含所需识别字段的块。有关说明,请参阅 Working with Layout and Blocks现在,您可以为每个字段指定各自的识别参数。例如,如果某个字段包含文本,请使用 ITextBlock::RecognizerParams 属性:
  • 借助 RecognizerParams 对象的 TextTypes 属性设置文本类型。例如,如果字段包含按邮政编码样式书写的数字,请使用 TT_Index 文本类型。
  • 使用 SetPredefinedTextLanguage 方法设置语言。如果您已知字段中包含的信息类型,使用 special predefined languages (仅限 Windows) 会很有帮助。例如,如果字段包含美国地址,请选择 English_US_Address 预定义语言。这样可以提高文本识别的可靠性。
  • 如果您需要使用识别变体来进一步验证结果 (如下文第 6 步所述) ,请设置 RecognizerParams 对象的 SaveCharacterRecognitionVariants 和 SaveWordRecognitionVariants 属性。请注意,此设置不适用于手写或手写体文本。
有关识别不同类型字段的更多详细信息,请参阅 Recognizing CheckmarksRecognizing Handwritten TextsRecognizing BarcodesRecognizing Words with Spaces 各节。

C#

// 分析文档版面
frDocument.Analyze( null, null, null );
// 假设我们知道版面中的第一个块
// 包含一个美国地址
FREngine.ITextBlock addressBlock = frDocument.Pages[0].Layout.Blocks[0].GetAsTextBlock();
FREngine.IRecognizerParams paramsAddressBlock = addressBlock.RecognizerParams;
paramsAddressBlock.SetPredefinedTextLanguage( "English_US_Address" );
// 启用收集识别变体
paramsAddressBlock.SaveCharacterRecognitionVariants = true;
paramsAddressBlock.SaveWordRecognitionVariants = true;
// 以同样的方式设置其他版面块的属性
...

C++ (COM)

// 分析文档版面
frDocument->Analyze( 0, 0, 0 );
// 假设我们知道版面中的第一个块
// 包含一个美国地址
FREngine::ILayoutBlocksPtr layoutBlocks = frDocument->Pages->Item( 0 )->Layout->Blocks;
FREngine::IRecognizerParamsPtr paramsAddressBlock = layoutBlocks->Item( 0 )->GetAsTextBlock()->RecognizerParams;
paramsAddressBlock->SetPredefinedTextLanguage( L"English_US_Address" );
// 启用收集识别变体
paramsAddressBlock->SaveCharacterRecognitionVariants = VARIANT_TRUE;
paramsAddressBlock->SaveWordRecognitionVariants = VARIANT_TRUE;
// 以同样的方式设置其他版面块的属性
...
由于文档版面已经分析完毕,并且你还对其进行了额外修改,因此不要再次调用分析方法。请使用 Recognize 方法,该方法会对文档中的所有页面执行识别和页面合成。在此场景中,你需要从字段中提取数据,而不是导出已识别的文档,因此不需要进行文档合成。

C#

// 识别文档
// 无需指定参数,因为这些参数已由处理配置文件设置
frDocument.Recognize( null, null );

C++ (COM)

// 识别文档
// 无需指定参数,因为这些参数已由处理配置文件设置
frDocument->Recognize( 0, 0 );
使用 Text 对象访问识别出的文本片段 (可通过文本块的 ITextBlock::Text 属性获取该对象) 。使用 Paragraphs 属性获取该片段中的段落集合,并使用 IParagraphs::Item 方法访问各个段落。IParagraph::Text 属性可用于访问段落中识别出的文本。你还可以使用 IParagraph::Words 获取段落中的单词集合。使用 IWords::Item 方法访问集合中的各个单词。IWord::Text 属性返回包含该识别单词的行。使用 Word 对象的 GetRecognitionVariants 方法,或 Paragraph 对象的 GetWordRecognitionVariants 方法,可获取某个单词的识别变体。可通过 Paragraph 对象的 GetCharParams 方法访问单个字符的属性。该方法可访问 CharParams 对象,其中包含识别出的字符参数。字符的识别变体可通过 ICharParams::CharacterRecognitionVariants 属性获取。有关文本处理的详细信息,请参阅 Working with Text。有关在投票算法中使用 Engine 的信息,请参阅 Using Voting API完成对 FRDocument 对象的操作后,请释放该对象使用的所有资源。请使用 IFRDocument::Close 方法。
完成 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;
}

C++ (COM)

void UnloadFREngine()
{
 if( libraryHandle == 0 ) {
  return;
 }
 // 释放 Engine 对象
 Engine = 0;
 // 反初始化 FineReader Engine
 typedef HRESULT ( STDAPICALLTYPE* DeinitializeEngineFunc )();
 DeinitializeEngineFunc pDeinitializeEngine =
  ( DeinitializeEngineFunc )GetProcAddress( libraryHandle, "DeinitializeEngine" );
 if( pDeinitializeEngine == 0 || pDeinitializeEngine() != S_OK ) {
  throw L"卸载 ABBYY FineReader Engine 时出错";
 }
 // 现在可以安全释放 FREngine.dll 库
 FreeLibrary( libraryHandle );
 libraryHandle = 0;
}

所需资源

您可以使用 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 如果您修改了标准场景,请相应调整所需模块。您还需要指定应用程序使用的界面语言、识别语言以及其他附加功能 (例如,如果您需要打开 PDF 文件,则使用 Opening.PDF;如果您需要识别 CJK languages 中的文本,则使用 Processing.OCR.CJK) 。有关更多信息,请参阅 使用 FREngineDistribution.csv 文件

其他优化

以下是帮助文件中提供更多信息的章节,您可以在其中了解如何为各个处理阶段设置参数:

另请参阅

基本使用场景的实现