tesseract.js OCR 实战指南:从基础识别、区域裁剪到多 Worker 并行调度
本文基于仓库文档 docs/examples.md,系统讲解 tesseract.js 的八类核心用法:基础识别、进度日志、多语言加载、字符白名单、页面分割模式(PSM)、PDF 输出、局部图像识别(rectangle)以及基于 Scheduler 的多 Worker 并行加速。读完本文,你既能直接复制运行这些示例,也能通过源码路径理解每个参数在 tesseract.js 内部的真实作用机制。
前置说明:示例依赖哪些 API
所有示例都围绕 tesseract.js 包导出的两个核心构造函数展开。查看仓库入口 src/index.js 可以看到,包对外暴露的能力包括 createWorker、createScheduler,以及常量对象 languages、OEM、PSM 和 setLogging。文档开头也提示可以查看 examples 目录,其中 examples/node/ 与 examples/browser/ 分别存放 Node 端和浏览器端的可直接运行脚本。
createWorker 的完整签名定义在 src/createWorker.js:
module.exports = async (langs = 'eng', oem = OEM.LSTM_ONLY, _options = {}, config = {}) => {
从源码结构看,四个参数依次为:语言(字符串或数组)、OCR 引擎模式(默认 OEM.LSTM_ONLY,即纯 LSTM 引擎,常量定义见 src/constants/OEM.js)、运行时选项(如 logger、corePath、langPath 等)和额外的 Tesseract 配置。需要特别注意的是,文档示例中的顶层 await createWorker(...) 写法要求代码运行在支持顶层 await 的 ESM 环境(例如 Node 下的 .mjs 文件);而仓库自带的 Node 示例 examples/node/recognize.js 采用 async IIFE 包裹的写法,两种风格等价,可任选其一。
另外,worker.recognize 的第三个参数 output 默认值为 { text: true }(见 src/createWorker.js),即默认只返回文本结果;要获取 PDF 等其他输出格式,需要显式传入第三个参数,后文 PDF 小节会用到这一点。
基础用法:创建 Worker 并识别一张图片
文档给出的最小可用示例:
const { createWorker } = require('tesseract.js');
const worker = await createWorker('eng');
(async () => {
const { data: { text } } = await worker.recognize('https://tesseract.projectnaptha.com/img/eng_bw.png');
console.log(text);
await worker.terminate();
})();
结合源码可以补充几点文档未展开的细节:
await createWorker('eng')返回的不是一个"空壳",而是一个已经加载完成的 worker。src/createWorker.js 中,createWorker在内部按顺序执行loadInternal → loadLanguageInternal → initializeInternal三个作业后才 resolve,即 Tesseract WASM 内核与语言模型(eng 的 traineddata)的下载、解压、初始化都已在后台 worker 中完成。文档也因此在注释中标明 "workers now come pre-loaded"。worker.recognize(image)的入参可以是 URL、本地文件路径或 ImageData/Buffer 等,Node 端的加载逻辑由 src/worker/node/loadImage.js 处理;仓库示例 examples/node/recognize.js 就演示了传入本地路径tests/assets/images/cosmic.png的用法。await worker.terminate()用于释放 worker 线程资源(Node 端 worker 基于worker_threads派生,见 src/worker-script/node/index.js),批量任务结束后应显式调用。
观察识别进度:logger 选项
const { createWorker } = require('tesseract.js');
const worker = await createWorker('eng', 1, {
logger: m => console.log(m), // Add logger here
});
(async () => {
const { data: { text } } = await worker.recognize('https://tesseract.projectnaptha.com/img/eng_bw.png');
console.log(text);
await worker.terminate();
})();
这里 createWorker 的第二个参数 1 即 OEM.LSTM_ONLY(与默认值相同,写出只是为了显式指定引擎模式)。
进度回调的触发链路在源码中非常清晰:src/createWorker.js 在构造时从 _options 中单独拆出 logger 与 errorHandler;当后台 worker 回传的消息 status === 'progress' 时,onMessage 处理器 会调用 logger({ ...data, userJobId: jobId }),把内核上报的 status、progress 等字段连同作业 ID 一起交给你的回调。这就是长任务中打印加载/识别进度条的标准方式。
多语言识别:用 "+" 分隔或数组
const { createWorker } = require('tesseract.js');
const worker = await createWorker(['eng', 'chi_tra']);
(async () => {
const { data: { text } } = await worker.recognize('https://tesseract.projectnaptha.com/img/eng_bw.png');
console.log(text);
await worker.terminate();
})();
两种等价写法都受支持。从 src/createWorker.js 可以看到 const currentLangs = typeof langs === 'string' ? langs.split('+') : langs;,即传入 'eng+chi_tra' 这样的字符串会被按 + 拆分,与数组形式完全一致。可用的语言代码(如 chi_tra 繁体中文、jpn、fra 等)汇总在 src/constants/languages.js 与 docs/tesseract_lang_list.md 中。
限制可识别字符:tessedit_char_whitelist
const { createWorker } = require('tesseract.js');
const worker = await createWorker('eng');
(async () => {
await worker.setParameters({
tessedit_char_whitelist: '0123456789',
});
const { data: { text } } = await worker.recognize('https://tesseract.projectnaptha.com/img/eng_bw.png');
console.log(text);
await worker.terminate();
})();
setParameters 会把参数打包成一个 action: 'setParameters' 的作业发往后台 worker(见 src/createWorker.js),worker 脚本侧通过 api.SetVariable 将其应用到 Tesseract 内核。典型场景是识别纯数字的车牌、表格编号等,白名单能显著降低误识别。需要注意 tessedit_char_whitelist 会同时约束大小写字母,若只要数字,就不要把字母也加进白名单。
页面分割模式:tessedit_pageseg_mode 与 PSM 常量
原文档建议参考 Tesseract 上游源码中 publictypes.h 的 PSM 定义;在本仓库中,这些模式已被整理为可直接导入的字符串常量 src/constants/PSM.js:
| 常量 | 值 | 含义 |
|---|---|---|
PSM.OSD_ONLY |
'0' |
仅运行方向与脚本检测 |
PSM.AUTO_OSD |
'1' |
自动 + OSD |
PSM.AUTO_ONLY |
'2' |
自动,但无 OSD |
PSM.AUTO |
'3' |
默认,完全自动 |
PSM.SINGLE_COLUMN |
'4' |
均匀的单栏文本 |
PSM.SINGLE_BLOCK_VERT_TEXT |
'5' |
竖向文本块 |
PSM.SINGLE_BLOCK |
'6' |
单个文本块 |
PSM.SINGLE_LINE |
'7' |
单行文本 |
PSM.SINGLE_WORD |
'8' |
单个单词 |
PSM.CIRCLE_WORD |
'9' |
环形单词 |
PSM.SINGLE_CHAR |
'10' |
单个字符 |
PSM.SPARSE_TEXT |
'11' |
页面上散布的文本 |
PSM.SPARSE_TEXT_OSD |
'12' |
散布文本 + OSD |
PSM.RAW_LINE |
'13' |
单行,不经过 Tesseract 内部分析 |
文档示例使用 PSM.SINGLE_BLOCK,适用于图中只有一整块文字、不需要版面分析的场景:
const { createWorker, PSM } = require('tesseract.js');
const worker = await createWorker('eng');
(async () => {
await worker.setParameters({
tessedit_pageseg_mode: PSM.SINGLE_BLOCK,
});
const { data: { text } } = await worker.recognize('https://tesseract.projectnaptha.com/img/eng_bw.png');
console.log(text);
await worker.terminate();
})();
在 worker 脚本 src/worker-script/index.js 中可以看到,tessedit_pageseg_mode 最终通过 api.SetVariable('tessedit_pageseg_mode', String(psmInit)) 生效;同时识别结束后的 api.RestoreParameters()(L435-L437)会把本次临时改动的参数还原,避免污染后续作业。
生成 PDF 输出
文档在此处指引查看 examples 目录,两个可运行参考分别是浏览器端 examples/browser/download-pdf.html 与 Node 端 examples/node/download-pdf.js。Node 示例的核心代码:
const worker = await createWorker();
const { data: { text, pdf } } = await worker.recognize(image, { pdfTitle: 'Example PDF' }, { pdf: true });
fs.writeFileSync('tesseract-ocr-result.pdf', Buffer.from(pdf));
对照 recognize 的签名 recognize(image, opts, output)(src/createWorker.js):第二参数 { pdfTitle: 'Example PDF' } 是 tesseract 侧选项,指定生成的 PDF 标题;第三参数 { pdf: true } 是输出选项,打开后结果对象的 data 中才会包含 pdf 字段(一个 Uint8Array),Node 端再经 Buffer.from 落盘即可。默认 output 只有 { text: true },所以不显式传 { pdf: true } 是拿不到 PDF 的。
只识别图像的一部分:rectangle 参数
tesseract.js 支持传入 { left, top, width, height } 形式的矩形,只对该区域做识别。文档给出三个递进的示例。
单个矩形
const { createWorker } = require('tesseract.js');
const worker = await createWorker('eng');
const rectangle = { left: 0, top: 0, width: 500, height: 250 };
(async () => {
const { data: { text } } = await worker.recognize('https://tesseract.projectnaptha.com/img/eng_bw.png', { rectangle });
console.log(text);
await worker.terminate();
})();
底层实现位于 src/worker-script/index.js:recognize 作业中检测到 options.rectangle 为对象时,调用 api.SetRectangle(rec.left, rec.top, rec.width, rec.height) 告知内核只对裁剪区域做识别,而图像数据本身不需要在 JS 层裁切。rectangle 属于一组 tesseract.js 专属选项(tessjsOptions,src/worker-script/index.js#L340,还包括 pdfTitle、pdfTextOnly、rotateAuto、rotateRadians),它们不会被转发给 Tesseract 内核。
多个矩形(串行)
由于一次 recognize 只能指定一个矩形,多个区域最简单的做法就是循环调用:
const { createWorker } = require('tesseract.js');
const worker = await createWorker('eng');
const rectangles = [
{ left: 0, top: 0, width: 500, height: 250 },
{ left: 500, top: 0, width: 500, height: 250 },
];
(async () => {
const values = [];
for (let i = 0; i < rectangles.length; i++) {
const { data: { text } } = await worker.recognize('https://tesseract.projectnaptha.com/img/eng_bw.png', { rectangle: rectangles[i] });
values.push(text);
}
console.log(values);
await worker.terminate();
})();
多个矩形(用 scheduler 并行识别)
串行循环在区域较多时会明显变慢,此时可以让多个 worker 同时工作:
const { createWorker, createScheduler } = require('tesseract.js');
const scheduler = createScheduler();
const worker1 = await createWorker('eng');
const worker2 = await createWorker('eng');
const rectangles = [
{ left: 0, top: 0, width: 500, height: 250 },
{ left: 500, top: 0, width: 500, height: 250 },
];
(async () => {
scheduler.addWorker(worker1);
scheduler.addWorker(worker2);
const results = await Promise.all(rectangles.map((rectangle) => (
scheduler.addJob('recognize', 'https://tesseract.projectnaptha.com/img/eng_bw.png', { rectangle })
)));
console.log(results.map(r => r.data.text));
await scheduler.terminate();
})();
用多个 Worker 提速:createScheduler 作业调度
文档的最后一个示例是把同一张图片派发 10 个识别作业,由 2 个 worker 并行消化:
const { createWorker, createScheduler } = require('tesseract.js');
const scheduler = createScheduler();
const worker1 = await createWorker('eng');
const worker2 = await createWorker('eng');
(async () => {
scheduler.addWorker(worker1);
scheduler.addWorker(worker2);
/** Add 10 recognition jobs */
const results = await Promise.all(Array(10).fill(0).map(() => (
scheduler.addJob('recognize', 'https://tesseract.projectnaptha.com/img/eng_bw.png')
)))
console.log(results);
await scheduler.terminate(); // It also terminates all workers.
})();
Scheduler 的实现完全位于 src/createScheduler.js,其调度模型可以从源码中逐条确认:
addJob(action, ...payload)会把作业压入jobQueue,并立即尝试dequeue()(src/createScheduler.js)。dequeue扫描当前所有 worker,找到第一个空闲者(不在runningWorkers中)执行队首作业;作业完成后在finally中再次触发dequeue,形成"完成一个、补一个"的流水线(L20-L30)。- 在没有任何 worker 的情况下调用
addJob会直接抛错:You need to have at least one worker before adding jobs(L61-L66)。 scheduler.terminate()会遍历并终止挂名其下的全部 worker,同时清空jobQueue(L68-L73),这解释了文档示例注释中 "It also terminates all workers" 的含义——不需要再逐个worker.terminate()。- 调度器还暴露
getQueueLen()与getNumWorkers(),可用于监控积压情况。
仓库自带的 examples/node/scheduler.js 与上文示例思路一致:创建 4 个 worker 注册进 scheduler,然后对同一张图派发 4 个并行 recognize 作业,注释中也说明这是为了演示批量任务加速,实际业务中通常是不同的图片。浏览器端的并行示例可参考 examples/browser/basic-scheduler.html,它与 Node 端共用同一套 scheduler API。
小结:示例与仓库文件对照
| 用法 | 文档章节 | 可运行参考 | 关键源码 |
|---|---|---|---|
| 基础识别 | basic | examples/node/recognize.js | src/createWorker.js |
| 进度日志 | with detailed progress | examples/node/recognize.js | src/createWorker.js#L219-L221 |
| 多语言 | multiple languages | src/constants/languages.js | src/createWorker.js#L33 |
| 字符白名单 | whitelist char | — | src/createWorker.js#L160-L166 |
| 页面分割模式 | pageseg mode | src/constants/PSM.js | src/worker-script/index.js#L395-L397 |
| PDF 输出 | pdf output | examples/node/download-pdf.js、examples/browser/download-pdf.html | src/createWorker.js#L168-L176 |
| 局部识别 | part of the image | — | src/worker-script/index.js#L415-L418 |
| 多 Worker 并行 | multiple workers | examples/node/scheduler.js | src/createScheduler.js |
上述示例在 Node 与浏览器端行为一致:createWorker 内部会根据运行环境选择对应的 worker 启动方式(src/worker/node/ 使用 worker_threads,src/worker/browser/ 使用 Web Worker),因此文档示例代码可以基本原样迁移到两种环境,只需注意浏览器中图片输入形式与文件系统落盘方式的差异(可进一步参考 docs/api.md 与 docs/workers_vs_schedulers.md)。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
