跳转到主要内容
此场景仅适用于 Windows。
在此场景中,ABBYY FineReader Engine 运行在“扫描计算机”上,由该计算机执行扫描并将图像保存为文件。 此场景可作为其他场景的一部分,用于文档处理的前期阶段,即获取文档的电子版本以供后续处理。使用示例包括:为归档而扫描文档、获取文档的可编辑版本,以及从文档中提取特定数据。 纸质文档经过扫描后,图像会以电子格式保存,从而生成高质量的电子版打印文档。 文档可能会经过以下处理阶段:
  1. 扫描
文档可通过扫描仪提供的两种扫描接口之一 (TWAIN 或 WIA) 进行扫描,也可以使用 ABBYY 自有的扫描接口,或者在不使用扫描接口的情况下进行扫描。
  1. 已扫描图像的预处理
扫描完成后,可以对图像进行预处理。预处理包括去噪点、校正扭曲的文本行、颜色反转、去除黑边,以及校正图像方向或分辨率。对开页可拆分为两张单独的图像。处理后的图像可以保存为多种图像格式,例如 JPEG、TIFF、BMP。

场景实现

下面将详细介绍在此场景中使用 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,以及(如适用)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 提供了用于管理扫描源的 ScanManager 对象。该对象的 ScanSources 属性可用于获取所有可用扫描仪的列表,而 FindScanSources 方法则允许您根据扫描仪所提供的 API 类型及用于设置扫描选项的用户界面类型来筛选扫描仪。要扫描文件并将其保存到磁盘,可以使用 ScanSource 对象的以下两种方法之一:Scan 方法在扫描完成后才会返回;BeginScan 方法则启动异步扫描操作并立即返回。您可以实现 IScanCallback 接口,并通过它获取扫描进度的通知。以下参数可通过 ABBYY FineReader Engine 12 API 访问:亮度、色彩、分辨率、图像压缩类型、图像旋转角度、扫描区域大小、双面扫描模式、自动进纸器模式、页面间暂停时间等。扫描参数通过 ScanSource 对象的 ScanSettings 属性进行设置。该属性用于访问 ScanSourceSettings 对象,后者进而提供对扫描源扫描设置的访问。同步扫描图像:
  1. 创建 ScanManager 对象。您可以通过 CreateScanManager 方法的输入参数指定是否写入扫描日志。
  2. 使用 ScanManager 对象的 FindScanSources 方法选择扫描源,并指定扫描仪应支持的 API 和 UI 类型。
  3. 如果您选择不显示让用户自行设置扫描选项的对话框,请使用所选 ScanSourceScanSettings 属性来调整扫描选项。在 ScanSourceSettings 对象的相应属性中,为亮度、分辨率和其他参数选择适当的值。您可以使用 ScanSource 的 Capabilities 属性查看此扫描仪提供哪些设置。
  4. 指定用于存储扫描页面的文件夹名称。该文件夹名称应为 BSTR 类型的变量,例如 ScanFolder。
  5. 运行 ScanSource object 的 Scan method,并将要向用户显示的 dialog box 类型作为参数传入 (传递 SSUIT_None 常量可不显示任何对话框) ,同时将 ScanFolder 路径指定为包含结果的文件夹路径。
  6. 此方法会以 StringsCollection 对象的形式返回图像文件名。您可以从该 StringsCollection 对象中获取图像文件名,然后像处理普通图像文件一样处理这些文件。
要异步扫描图像:
  1. 创建一个实现 IScanCallback 接口的对象。对于异步扫描操作,来自回调接口的通知非常有用。
  2. 创建 ScanManager 对象。您可以通过 CreateScanManager 方法的输入参数指定是否写入扫描日志。
  3. 使用 ScanManager 对象的 FindScanSources 方法选择扫描源,并指定扫描仪应支持的 API 和 UI 类型。
  4. 如果您选择不显示供用户自行设置扫描选项的对话框,请使用所选 ScanSource 的 ScanSettings 属性来调整扫描选项。在 ScanSourceSettings 对象的相应属性中,为亮度、分辨率和其他参数选择适当的值。您可以通过 ScanSource 的 Capabilities 属性检查此扫描仪支持哪些设置。
  5. 指定用于存储扫描页面的文件夹名称。该文件夹名称应为 BSTR 变量,例如 ScanFolder。
  6. 运行 ScanSource object 的 BeginScan method,并将要向用户显示的对话框类型作为参数传入 (传递 SSUIT_None 常量则不显示任何对话框) ,以及用于存放结果的文件夹的 ScanFolder 路径。您还需要传入指向所创建回调 object 的指针。
  7. 图像文件路径会通过 IScanCallback 接口的 OnImageScanned 通知返回,操作完成则通过 OnScanComplete 通知发出信号。由于这些方法的实现由您提供,因此您可以自行决定在每种情况下执行什么操作。例如,收到图像文件路径后,您可以像处理磁盘上的其他任何文件一样处理它,并按常规方式将其加载到 FineReader Engine 中进行处理。
在扫描完成且收到 OnScanComplete 通知之前,您都不能再次调用 BeginScan 或 Scan,即使使用的是其他扫描仪也不行。异步扫描的优势不在于可以同时执行多个扫描操作,而在于在扫描新图像的同时,您可以处理已经收到的图像。
以下是异步扫描场景的示例代码:

C#

// 实现 IScanCallback 接口
class ScanningCallback :FREngine.IScanCallback
{
  // 构造函数
  public ScanningCallback() {}
  ...
  // 提供 IScanCallback 方法的实现
  public void OnError(string sourceName, string message) { // 在此处报告或处理错误 }
  public void OnImageScanned(string sourceName, string Path, ref bool cancel) { // 在此处添加扫描图像时需要执行的操作 }
  public void OnScanComplete() { // 在此处添加扫描完成后希望执行的操作 }
  // 私有变量
  ...
}
// 创建扫描管理器,并关闭日志记录
scanManager = engineLoader.Engine.CreateScanManager(false);
// 创建按 API 和 UI 类型筛选的扫描源列表
FREngine.IScanSources sources = scanManager.FindScanSources(UIType, APIType);
// 仅作示例,从集合中选择第一个扫描源
FREngine.IScanSource selectedSource = sources[0];
// 获取扫描源的设置和功能
FREngine.IScanSourceSettings settings = selectedSource.ScanSettings;
FREngine.IScanSourceCapabilities capabilities = selectedSource.Capabilities;
// 若扫描仪支持双面模式,则将扫描选项设置为双面模式
settings.DuplexMode = capabilities.HasDuplexMode;
// 启用多页扫描,并将页面间延迟设置为 10 秒
settings.MultipleImagesEnabled = true;
settings.PauseBetweenPagesMode = FREngine.ScanPauseModeEnum.SPM_PresetDelay;
settings.Delay = 10;
// 设置结果文件夹的路径
string scanFolder = "D:\\SampleImages";
// 创建回调对象
IScanningCallback callback = new ScanningCallback();
// 开始扫描
selectedSource.BeginScan(SSUIT_None, scanFolder, callback);
使用完 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 Opening.Scanning 如果您修改了标准场景,请相应调整所需模块。您还需要指定应用程序使用的界面语言、识别语言以及其他附加功能 (例如,如果您需要识别 CJK languages 中的文本,则需要使用 Processing.OCR.CJK) 。有关更多信息,请参阅 Working with the FREngineDistribution.csv File

其他优化

以下是帮助文件中介绍各处理阶段参数设置的相关章节,您可以在其中找到更多信息:
  • 文档分隔
    • 在这种情况下,您可能需要将传入的图像流分组成多个文档。例如,您可能已知每个文档的页数,或者需要确保在第一个文档的最后一页与下一个文档的第一页之间插入带条形码的分隔页。请参阅条形码识别场景

另请参阅

基本使用场景的实现