光学字符识别或光学字符阅读器 (OCR) 是将文本图像转换为机器编码文本的过程。例如,您可以为书页拍照,然后通过 OCR 软件运行以提取文本。
在这篇博文中,我们将使用Tesseract OCR 库。Tesseract 是用 C/C++ 编写的,最初是在 1985 年至 1994 年间由惠普开发的。惠普于 2005 年开源了该软件。从那时起,谷歌一直在开发和维护它。
2018 年 10 月发布的最新版本 4 包含一个新的 OCR 引擎,该引擎使用基于 LSTM 的神经网络系统,这应该会显着提高准确性。第 4 版开箱即用支持 123 种语言。源代码托管在 GitHub 上:https ://github.com/tesseract-ocr/tesseract
如前所述,Tesseract 引擎是用 C++ 编写的,并不在浏览器中运行。使用 C++ 引擎的唯一方法是将图片从 Web 应用程序发送到服务器,通过引擎运行它并发送回文本。
但是几年来,Tesseract C++ 引擎的 JavaScript 端口存在,它在浏览器中运行并且不依赖于任何服务器端代码。该库名为 Tesseract.js,您可以在 GitHub 上找到源代码:https
://github.com/naptha/tesseract.js该引擎最初是用ASM.js编写的,最近已移植到 WebAssembly。
我们将使用该库的第 2 版。版本 2 是 Tesseract 4.1 的 WebAssembly 端口。当浏览器不支持 WebAssembly 时,该库回退到 ASM.js。
通过从 CDN 加载 Tesseract.js 将其添加到项目中
或通过安装它npm
。
npm install tesseract.js
该库提供了recognize
将图像作为输入并返回具有识别文本的对象的方法。这里是一个简单的例子
const exampleImage = 'https://tesseract.projectnaptha.com/img/eng_bw.png';
const worker = Tesseract.createWorker({
logger: m => console.log(m)
});
Tesseract.setLogging(true);
work();
async function work() {
await worker.load();
await worker.loadLanguage('eng');
await worker.initialize('eng');
let result = await worker.detect(exampleImage);
console.log(result.data);
result = await worker.recognize(exampleImage);
console.log(result.data);
await worker.terminate();
}
基本的.html
论据
该recognize
方法需要一个图像作为第一个参数。该库支持 bmp、jpg、png 和 pbm 格式的图像。
图像可以提供给该方法
img
, video
, 或canvas
元素
)更多信息参见官方文档:
https ://github.com/naptha/tesseract.js/blob/master/docs/image-format.md
在调用该recognize
方法之前,您必须使用 创建一个 worker createWorker()
,使用 加载 tesseract.js-core 脚本load()
,使用 加载一种或多种语言的机器学习模型loadLanguage(...)
,最后使用 初始化 Tesseract API initialize(...)
。您指定为initialize
调用参数的语言可以是您加载的语言的子集loadLanguage()
。
下一个示例预先加载了英语和西班牙语的模型,但随后仅将英语用于下一次 API 调用。
await worker.loadLanguage('eng+spa');
await worker.initialize('eng');
稍后在应用程序中,您只需通过另一个initialize(...)
调用切换到另一种语言。
进步
OCR 过程会运行几秒钟,如果您想向用户显示进度信息,您可以配置一个进度侦听器。您可以通过将对象作为参数传递给createWorker()
调用来做到这一点。该logger
属性需要一个在识别过程中被多次调用的函数。
const worker = Tesseract.createWorker({
logger: m => console.log(m)
});
您在 logger 函数中作为参数获得的对象包含 worker 和 job id 以及属性status
和progress
. status
是一个描述当前操作的字符串,progress
是一个介于 0 和 1 之间的数字,以百分比表示当前进度。
进度示例:
{workerId: "Worker-0-4d98d", jobId: "Job-0-a37f4", status: "recognizing text", progress: 0.8285714285714286}
如果您需要更多内部信息,请将日志记录标志设置为 true。然后该库将更多信息打印到开发人员控制台中:setLogging(true)
结果
该recognize()
方法返回一个 Promise。您从成功调用中获得的对象包含data
保存有关已识别文本的信息的属性。您可以使用text
包含已识别文本作为一个字符串的属性访问文本,或者通过lines
、paragraphs
、words
或symbols
属性访问它。每个组元素都包含一个confidence
分数,可以告诉您引擎的信心程度。分数是一个介于 0 和 100 之间的数字;较高的值表示较高的置信度。
{
text: "Mild Splendour of the various-vested Nig ..."
hocr: "
tsv: "1 1 0 0 0 0 0 0 1486 ..."
box: null
unlv: null
osd: null
confidence: 90
blocks: [{...}]
psm: "SINGLE_BLOCK"
oem: "DEFAULT"
version: "4.0.0-825-g887c"
paragraphs: [{...}]
lines: (8) [{...}, ...]
words: (58) [{...}, {...}, ...]
symbols: (295) [{...}, {...}, ...]
}
当您使用 和 属性访问文本时lines
,paragraphs
您将获得按行和段落分组的文本。包含一个包含每个已识别单词的words
数组,并symbols
允许您访问每个字符。
这些属性的每个元素都包含bbox
表示边界框的 x/y 坐标的属性。在演示应用程序中,我使用此信息在所选文本周围绘制一个矩形。
words
这是数组中元素的示例。该text
属性包含单词,并confidence
告诉我们置信度分数。line
, 并paragraph
引用该单词所在的行和段落对象。symbols
是一个单独保存每个字符 ( M
, i
, l
, d
) 的数组。
{
symbols: Array(4)
0: {choices: Array(1), image: null, text: "M", confidence: 99.03752899169922, baseline: {...}, ...}
1: {choices: Array(1), image: null, text: "i", confidence: 98.83988952636719, baseline: {...}, ...}
2: {choices: Array(1), image: null, text: "l", confidence: 99.01886749267578, baseline: {...}, ...}
3: {choices: Array(1), image: null, text: "d", confidence: 99.0378646850586, baseline: {...}, ...}
choices: [{...}]
text: "Mild"
confidence: 91.87923431396484
baseline: {x0: 38, y0: 84, x1: 167, y1: 85, has_baseline: true}
bbox: {x0: 38, y0: 34, x1: 167, y1: 85}
is_numeric: false
in_dictionary: false
direction: "LEFT_TO_RIGHT"
language: "eng"
is_bold: false
is_italic: false
is_underlined: false
is_monospace: false
is_serif: false
is_smallcaps: false
font_size: 17
font_id: -1
font_name: ""
page: ...
block: ...
paragraph: {lines: Array(8), text: "Mild Splendour ...", confidence: 91.35659790039062, ...}
line: {words: Array(6), text: "Mild Splendour ...", confidence: 92.46450805664062, ...}
}
如果您想查看完整的结果对象,请访问 URL Basic Usage并打开开发者控制台。
探测
Tesseract.js 库提供的另一种方法是detect()
. 此方法尝试检测方向和脚本。就像recognize()
这个方法需要一个图像作为第一个参数并返回一个 Promise。
const result = await worker.detect(image);
结果对象包含属性data
,它包含有关检测到的脚本和方向的信息以及相应的置信度分数。
{
tesseract_script_id: 1
script: "Latin"
script_confidence: 39.58333969116211
orientation_degrees: 0
orientation_confidence: 29.793731689453125
}
更多信息参见官方文档:
https ://github.com/naptha/tesseract.js/blob/master/docs/api.md#worker-detect
清理
该库在 Web Worker 中运行 OCR 引擎。如果您的应用程序不再需要 worker,您应该使用worker.terminate()
.
更多示例
查看文档页面以获取更多代码示例:
https ://github.com/naptha/tesseract.js/blob/master/docs/examples.md
演示应用
在本节中,我将展示如何将 Tesseract.js 库合并到 Angular/Ionic 应用程序中。
您可以在 GitHub 上找到完整应用程序的源代码:
https ://github.com/ralscha/blog2019/blob/master/webocr
演示应用程序托管在我的服务器上,您可以通过以下 URL 访问它:
https ://omed.hplar.ch/webocr/
演示应用程序不依赖任何服务器端代码,OCR 在 Web 浏览器本地运行,不向服务器发送任何数据。
该应用程序基于 Ionic 空白起始模板。首先,我在项目中添加了最新版本的 Tesseract.js npm install tesseract.js
。
在 TypeScript 代码中,我使用
import {createWorker, RecognizeResult} from 'tesseract.js';
type="file"
作为输入,应用程序使用类型文件 ( )的输入标记。每次用户选择一个文件时,onFileChange
都会调用该方法,该方法从输入标签中提取 File 对象并将其传递给该recognize()
方法。
<input #fileSelector (change)="onFileChange($event)" accept="image/*" style="display: none;"
type="file">
主页.html
然后,选定的图片也会加载到 Image 对象中。该应用程序不使用img
标签来显示图片。相反,它将图片绘制到画布中。我在这里使用画布,因为每当用户单击文本时,应用程序都会在文本周围绘制矩形。
async onFileChange(event: Event): Promise<void> {
// @ts-ignore
this.selectedFile = event.target.files[0];
this.progressStatus = '';
this.progress = null;
this.result = null;
this.words = null;
this.symbols = null;
this.selectedLine = null;
this.selectedWord = null;
this.selectedSymbol = null;
this.image = new Image();
this.image.onload = () => this.drawImageScaled(this.image);
this.image.src = URL.createObjectURL(this.selectedFile);
/*
const worker = createWorker({
logger: progress => {
this.progressStatus = progress.status;
this.progress = progress.progress;
this.progressBar.set(progress.progress * 100);
this.changeDetectionRef.markForCheck();
}
});
*/
const worker = createWorker({
workerPath: 'tesseract-202/worker.min.js',
corePath: 'tesseract-202/tesseract-core.wasm.js',
logger: progress => {
this.progressStatus = progress.status;
this.progress = progress.progress;
this.progressBar.set(progress.progress * 100);
this.changeDetectionRef.markForCheck();
}
});
await worker.load();
await worker.loadLanguage(this.language);
await worker.initialize(this.language);
this.progressBar.set(0);
try {
if (this.selectedFile) {
const recognizeResult = await worker.recognize(this.selectedFile);
if (recognizeResult) {
this.result = recognizeResult.data;
} else {
this.result = null;
}
await worker.terminate();
}
} catch (e) {
this.progressStatus = e;
this.progress = null;
} finally {
this.progressBar.complete();
this.progressStatus = null;
this.progress = null;
}
// reset file input
// @ts-ignore
event.target.value = null;
}
主页.ts
为了显示结果,我使用了Angular Material 项目中的列表元素。您可以使用该命令将 Angular Material 添加到任何 Angular 项目。ng add @angular/material
这里是单词列表的模板:
<div *ngIf="words" class="table-container mat-elevation-z1 ion-margin-top">
<table [dataSource]="words" mat-table>
<ng-container matColumnDef="text">
<th *matHeaderCellDef mat-header-cell>Wordth>
<td *matCellDef="let word" mat-cell> {{word.text}} td>
ng-container>
<ng-container matColumnDef="confidence">
<th *matHeaderCellDef mat-header-cell>Confidenceth>
<td *matCellDef="let word" mat-cell> {{word.confidence | number:'1.2-2'}} %td>
ng-container>
<tr *matHeaderRowDef="elementColumns; sticky: true" mat-header-row>tr>
<tr (click)="onWordClick(word)" *matRowDef="let word; columns: elementColumns;"
[ngClass]="{'highlight': selectedWord === word}"
mat-row>tr>
table>
div>
主页.html
当用户单击列表项时,应用程序会在所选文本周围绘制一个矩形
drawBBox(bbox: { x0: number, x1: number, y0: number, y1: number }): void {
if (bbox) {
this.redrawImage();
if (this.ratio === null) {
throw new Error('ratio not set');
}
this.ctx.beginPath();
this.ctx.moveTo(bbox.x0 * this.ratio, bbox.y0 * this.ratio);
this.ctx.lineTo(bbox.x1 * this.ratio, bbox.y0 * this.ratio);
this.ctx.lineTo(bbox.x1 * this.ratio, bbox.y1 * this.ratio);
this.ctx.lineTo(bbox.x0 * this.ratio, bbox.y1 * this.ratio);
this.ctx.closePath();
this.ctx.strokeStyle = '#bada55';
this.ctx.lineWidth = 2;
this.ctx.stroke();
}
}
onLineClick(line: Line): void {
this.words = line.words;
this.drawBBox(line.bbox);
this.symbols = null;
this.selectedLine = line;
this.selectedWord = null;
this.selectedSymbol = null;
}
主页.ts
自托管
当您的应用程序创建并初始化 TesseractWorker 时,该库将下载几个文件。
const worker = createWorker({...});
await worker.load();
await worker.loadLanguage(this.language);
await worker.initialize(this.language);
默认情况下,这些文件不是打包应用程序的一部分,Tesseract.js 将从 3rd 方服务器下载这些文件。最新版本将下载这些文件:
- https://unpkg.com/[email protected]/dist/worker.min.js (56 KB)
- https://unpkg.com/[email protected]/tesseract-core.wasm.js (1.0 MB)
- https://tessdata.projectnaptha.com/4.0.0/eng.traineddata.gz (10.4 MB)
如果应用程序调用该detect()
方法,将下载一个附加文件。
- https://tessdata.projectnaptha.com/4.0.0/osd.traineddata.gz (4.1 MB)
当您编写从本地服务器下载文件要快得多的内部应用程序时,这不是最佳解决方案。或者您希望完全控制所有应用程序资源,而不用担心这些第 3 方服务器的可用性。如果其中一台服务器不工作,您的应用程序将不再工作。
无论是什么原因,您都可以轻松地自行托管这些文件。Web Worker JavaScript 和 Web Assembly 文件是 Tesseract.js npm 包的一部分,因此我们可以将这两个文件从 node_modules 目录复制到 build 文件夹。在 Angular 应用程序中,您可以通过将以下两个条目添加到 build->options->assets 数组来完成此操作。
"glob": "worker.min.js",
"input": "node_modules/tesseract.js/dist/",
"output": "./tesseract-202"
},
{
"glob": "tesseract-core.wasm.js",
"input": "node_modules/tesseract.js-core/",
"output": "./tesseract-202"
}
],
角.json
Angular CLI 负责将这两个文件复制到构建文件夹。在应用程序中,我们需要告诉 Tesseract.js 它必须从本地目录加载这两个文件,而不是从 unpkg.com 获取它们。
const worker = createWorker({
workerPath: 'tesseract-202/worker.min.js',
corePath: 'tesseract-202/tesseract-core.wasm.js',
...
});
该库从 tessdata.projectnaptha.com 下载的机器学习模型不作为 npm 包提供。有不同的方式来自行托管它们。如果您只需要支持几种语言,您可以从 Git 存储库 ( GitHub - naptha/tessdata: Tesseract Language Trained Data ) 下载文件,并在构建过程中使用 npm 脚本将它们放入构建文件夹。
请注意,这些文件非常大,每次构建应用程序时下载它们可能需要一些时间。
您可以下载所需的文件并将它们复制到您控制的 HTTP 服务器上,而不是将它们添加到项目中。如果您需要支持多种语言,您可以克隆整个存储库。
git clone https://github.com/naptha/tessdata.git
下载所有文件需要 4.8 GB 的磁盘空间!
每种语言都提供三种不同的版本:正常、快速和最佳。最好的版本为您提供更高的 OCR 准确性,但缺点是您的应用程序必须下载更大的机器学习模型文件。对于英语,最佳文件大小为 12.2 MB,普通 10.4 MB,快速 1.89 MB。
与 Web Worker 文件一样,您需要在作为参数传递给createWorker
.
const worker = createWorker({
workerPath: 'tesseract-202/worker.min.js',
corePath: 'tesseract-202/tesseract-core.wasm.js',
langPath: 'https://myserver/4.0.0',
...
});
该库通过组合langPath
+ 语言代码 + '.traineddata.gz'创建 URL
更多信息请访问官方文档页面:
https ://github.com/naptha/tesseract.js/blob/master/docs/local-installation.md