65.9K
CodeProject 正在变化。 阅读更多。
Home

Tesseract Act。

2024年9月3日

MIT

10分钟阅读

viewsIcon

4512

downloadIcon

90

一个 Visual Studio Code 扩展,一个方便的 Tesseract OCR UI 封装——采用 VSCode 风格:它对图像文件中找到的数据执行光学字符识别,并在文本编辑器中打开识别结果。

Tesseract Act Logo

目录

动机
Tesseract 简介
实现、问题和解决方案
引言
事件不足
启用
外部应用程序的识别
单向默认
病态情况
异步 OCR
版本
结论

动机

我想我们都讨厌纸质文档。但在许多情况下,我们别无选择。愚蠢的金融、法律、保险或政府服务不断给我们发送无法忽视的纸质文档。在某些国家,所谓的数字政府如果不是灾难性的,也是出了名的弱。结合扫描仪、OCR 和图形编辑器,我们仍然可以整合纸质数据并生成可维护的响应文档。

我最近才开始使用 Tesseract OCR,又是一次注册。在此之前,我只尝试过不同的 OCR 工具,我拒绝了 Tesseract,因为它的旧版本不是 Unicode,这是一个巨大的禁忌。然而,最新版本相当可用。当我测试它并发现它可接受时,我通过制作一个 Visual Studio Code 扩展开始实际使用它。当产品成熟时,我将其发布到 Visual Studio Marketplace

这个扩展看起来非常有用和方便,并且变得相当受欢迎。在这篇文章中,我想分享一些与此扩展相关的特定问题的发现。它们相对简单,但花了一些时间才弄清楚。它们与两件事相关:1) 处理非文本页面,以及 2) 使用外部应用程序。

Tesseract 简介

Tesseract 可以安装在 Linux、Windows 和 MacOS 上。

源代码可在 GitHub 上获取

可以单独安装用于其他语言的训练数据。不同模型的训练数据可以在 Tesseract OCR 账户页面 tesseract-ocr 上找到的单独的pinned Git 仓库中找到。安装语言包的最简单方法是将单个文件 *.traineddata 复制到 Tesseract 可执行目录的“tessdata”子目录中。当前安装的语言列表可以通过命令行获取

tesseract --list-langs

有关更多详细信息,请参阅 Tesseract 文档仓库

实现、问题和解决方案

引言

Tesseract Act 扩展是一个非常小的产品。它方便解释一些问题。我不想解释整个 Visual Studio 扩展编写概念,但我希望依赖一般读者对它们的了解。最好在 原始文档 中找到。我的扩展的完整当前源代码总是可以在 代码仓库 中找到。

用户级别文档可以在 Visual Studio Marketplace代码仓库 中找到。

该扩展只有两个命令:“Tesseract: Recognize Text”和“Tesseract: Language”,以及“Settings”中的一个参数:“tesseract.executableFileLocation”。用户选择的语言将为当前工作区持久记住。

事件不足

第一个相当微不足道的问题花费了最多的调查。问题是我们需要捕获当 Tesseract OCR 可以处理的文件类型的图像文件在编辑器中加载或选择时的事件。我们总是需要这样的东西,至少是为了启用或禁用与应用程序相关的命令。

问题在于 Visual Studio Code 在文本编辑方面表现出色,但它不提供与文本文件相应事件类似的事件。以下是文本文件可以方便完成的事情

exports.activate = context => {
    //...
    // not applicable to the present extension:
    context.subscriptions.push(
        vscode.workspace.onDidOpenTextDocument(
            textDocument => doSomething(textDocument)));
};

此外,我们可以使用 textDocument.languageId 对不同的语言执行不同的操作。

我们不能对任意文件做同样的事情。解决方案稍微复杂一些。我们需要处理两个事件的组合,onDidChangeTabGroupsonDidChangeTabs——只有这个组合才能涵盖所有情况。这是魔法的工作原理

exports.activate = context => {
    //...
    const tabGroupSet = vscode.window.tabGroups;
    context.subscriptions.push(
        tabGroupSet.onDidChangeTabGroups(() => updateEnablement()));
    context.subscriptions.push(
        tabGroupSet.onDidChangeTabs(() => updateEnablement()));
};

我认为 updateEnablement 应该做什么已经很明显了。有关全面信息,请参阅 源代码。但是,我需要澄清如何找到活动文件并确定它是否适用于 OCR 操作。

启用

这是函数 updateEnablement 的骨架

exports.activate = context => {
    vscode.commands.executeCommand(
        definitionSet.commands.setContext,
        definitionSet.commands.setLanguageContext,
        tesseractExecutableFound);
    const isGoodImage = () => {
        if (!statusBarItem) return false;
        const file = activeUri();
        if (!file) return false;
        return definitionSet.isImageFileSupported(file);
    };
    // using isGoodImage...
};

请注意,file 被找到为 activeUri,并且文件名为与 Tesseract 的兼容性进行了测试,使用了 definitionSet.isImageFileSupported

以下是查找活动 URI 的方法

const activeUri = () =>
    vscode.window?.tabGroups?.activeTabGroup?.activeTab?.input?.uri?.fsPath;

我们还需要找出文件是否受 Tesseract OCR 支持。我们只能阅读 Tesseract 文档,其中列出了指示兼容文件类型的后缀集

const definitionSet = {
    //...
    supportedInputFileTypes: ["png", "jpg", "jpeg", "tif", "tiff", "gif", "webp", "bmp"],
    isImageFileSupported: function(file) {
        if (!file) return false;
        file = file.toLowerCase();
        return this.supportedInputFileTypes.some(
            suffix => file.endsWith(`.${suffix}`));
    }, //isImageFileSupported
    //...
};

最后,使用特殊命令 "setContext" 启用或禁用命令。这是一个字符串,在 Visual Studio Code API 中预定义,definitionSet.commands.setContext 等于此值。上下文通过在“package.json”中声明的 when 子句工作。请在 “package.json” 中找到行 "when": "tesseract.act.recognize.enabled""when": "tesseract.act.language.enabled"

外部应用程序的识别

设置参数“tesseract.executableFileLocation”的默认值是字符串 "tesseract"。它不一定是可执行文件位置,但它可以是一个 command。通常,它通过将可执行部分的位置包含在系统 PATH 中,供所有或某些用户使用。在许多情况下,我个人避免 PATH 修改。请注意,Visual Studio Code 应用程序不知道 PATH 的内容,因此我们无法直接检查可执行路径是否存在。此外,这还不够。

Tesseract 应用程序在激活事件(请参阅 “package.json” 中的 "activationEvents")和设置更改时触发的事件中被识别。这是由函数 changeConfigurationHandle(context) 完成的,请参阅其代码。

Tesseract OCR 应用程序的存在不依赖于可执行文件的存在,因为用户可能输入不相关的文件名。相反,检查依赖于 childProcess.execSync。应用程序被执行,并捕获异常以检查是否成功

const commandExists = fileOrCommand => {
    try {
        childProcess.execSync(
            definitionSet.tesseract.commandLineLanguages(
                fileOrCommand));
    } catch { // all cases
        return false;
    } //exception
    return true;
}; //commandExists

Tesseract OCR 进程是同步执行的,因为实际上没有执行 OCR,这使得执行速度很快。

有时这种方法被称为进攻性,与在这种情况下不太有用的防御性相反。

单向默认

请注意,函数 commandExists 使用传递给 Tesseract OCR 的命令行参数,用于报告已安装的语言。报告的其中一种语言可以用作传递给应用程序的参数,以指定文本识别的语言。

这里有一个微妙的情况:当省略语言参数时,将使用默认语言。这是英语。扩展的 UI 中没有语言选择,因为默认值意味着不选择。当从未选择语言时,状态栏项目显示:“Tesseract Language: default”。当用户选择任何语言时,无法返回此条件。用户只能选择不同的语言,但不能返回“不选择”条件。为什么会这样,它是如何工作的?

实际上,可以回到“不选择”的情况。为此,用户需要清除工作区数据。顺便说一句,它的位置取决于平台和编辑器版本,但可以使用 VSCode 命令“Developer: Open User Data Folder”找到。工作区存储的确切位置没有文档,但截至撰写本文时,它可以在子目录“User/workspaceStorage”中找到。重要的是,删除此目录以及“Backup”、“Cache”和“CachedData”目录中的所有文件是安全的。

当用户从列表中选择任何语言时,该选择会存储在工作区状态中

const setState = (context, language) => {
    context.workspaceState.update(definitionSet.tesseractLanguageKey, language);
}; //setState
const getState = context =>
    context.workspaceState.get(definitionSet.tesseractLanguageKey);

可以通过调用 context.workspaceState.update(definitionSet.tesseractLanguageKey, undefined) 来删除状态,但为什么要这样做呢?

根据一般的 VSCode 样式,我还在使用 vscode.window.createStatusBarItem 创建的状态栏项目中显示选择,它也作为一个选择命令工作。当尚未做出选择时,它显示“Tesseract Language: default”。当至少做出一次选择时,它显示所选语言,并且无法返回默认值。此外,当用户做出选择时,我显示可用语言列表,将已选择的语言放在顶部,并使其选中。有关更多详细信息,请参阅函数 selectLanguage

我看不出再次显示默认值的任何意义。它将需要一个额外的冗余 UI 元素,并且很难向用户解释。我只能想象两种用户。一种永远不会选择语言,总是使用默认的英语。至少更改过一次语言的用户可能知道应该使用哪种语言,并且可能需要在给定工作区中自觉地更改选择。所有当前安装的语言无论如何都在那里。

此外,按用户存储选择也没有意义。显然,文档的语言只与用户处理这些文档的工作区相关。

如果有人对我的默认设置相关决策有不同意见,我想知道其动机。

病态情况

语言选择问题引发了一个问题:当用户在操作期间安装或卸载语言时会发生什么?

首先,当编辑器甚至输入图像文件加载时,更改 Tesseract 数据无法向 Visual Studio Code 发送任何通知。已安装的 Tesseract 语言的修改列表只有在用户更改活动工作区或重新加载编辑器时才会出现。

当用户从 Tesseract 中卸载已经选择的语言时会发生什么?甚至可以通过删除训练数据文件来卸载默认语言英语。

让我们测试一下。选择了缺失的语言,并且图像识别命令已启用,所以我们激活它。我们可以看到错误消息

Error opening data file {absolute path to} eng.traineddata

如果用户在 Visual Studio Code 会话期间卸载或删除 tesseract,可能会发生另一种异常情况。这是另一种相对罕见的情况,此时没有任何东西可以向编辑器发送通知。如果它发生在会话之间或之后修改设置,Tesseract Code 应用程序仍然会被识别——请参阅外部应用程序的识别部分。但当它发生在会话内部时,问题只有在尝试执行 OCR 时才会被识别。我们来测试一下

'tesseract' is not recognized as an internal or external command,
operable program or batch file.

我认为这种行为是相当公平的。当然,这是因为错误信息是从子进程捕获的,并透明地表示为 VSCode 错误消息。顺便说一句,最后一点,让我们快速看一下用于识别的子进程。

异步 OCR

最后,让我们考虑一个众所周知的、微不足道但非常重要的方面:Tesseract OCR 的异步执行。只有在识别图像时才需要它,因为它可能需要相当长的时间。同时,识别结果何时出现在编辑器中并不重要——用户在操作期间可能需要做其他事情,并且这要求编辑器的 UI 始终响应。

const recognizeText = (context, configuration) => {
    //...
    const act = () => {
        childProcess.exec(commandLine, (error, stdout, stderr) => {
            // examine stdout and strerr for errors,
            // handle them, etc.
            // ...
            if (!error && fileSystem.existsSync(outputFileName))
                vscode.workspace.openTextDocument(outputFileName).
                then(document =>
                    vscode.window.showTextDocument(
                        document, { preview: true }));
        });
    }; //act
    // Figure out if the user's confirmation for file overwrite
    // is required, call act(...) with or without confirmation,
    // or cancel...
}; // recognizeText

请注意同步和异步操作的组合以及两步回调-Promise 链,childProcess.exec 作为参数接受的回调,以及 vscode.workspace.openTextDocument 创建的 Promise。

版本

请参阅 Visual Studio Marketplace 上产品页面上的 版本历史记录

结论

我希望本文中描述的简单细节涵盖了使用外部应用程序的广泛扩展。

我很乐意尝试回答与 Visual Studio Code 扩展编写相关的任何问题。我也非常感谢任何问题、评论、建议,特别是批评。

© . All rights reserved.