零基础用 Tesseract.js 把图片变成可复制文字:从 1 个 HTML 文件到批量识别实战
【免费下载链接】tesseract.jsPure Javascript OCR for more than 100 Languages 📖🎉🖥项目地址: https://gitcode.com/GitHub_Trending/te/tesseract.js
接手公司旧系统时,我遇到过一个尴尬场景:财务部门每天要手工录入上百张银行回单上的交易信息,眼睛盯屏幕盯到干涩,还时不时敲错数字。后来我给他们的浏览器页面加了一个按钮——上传图片,文字自动出现在输入框里。这个按钮背后的引擎,就是本文要讲的 Tesseract.js。
Tesseract.js 是一个纯 JavaScript 编写的 OCR(光学字符识别)库,支持 100 多种语言的文字识别。它把经典的 Tesseract OCR 引擎编译成了 WebAssembly,意味着不需要安装任何本地软件、不需要配置后端服务,一个 HTML 文件加几行代码就能在浏览器里完成文字提取。
读完本文你将收获:
- 弄清楚 Worker、语言数据、进度事件这三个核心概念,不再被报错信息吓到
- 用 3 行核心代码写出第一个能跑的图片识别页面
- 学会把识别速度从"每张图等半天"优化到"批量并行秒出"
- 掌握中英文混合识别、指定区域识别等让结果更准的实用技巧
- 避开新手最容易踩的 5 个坑(PDF、手写体、内存、缓存、首次加载)
动手之前,先弄懂三个核心概念
很多教程上来就甩代码,跑通了却不知道为什么,遇到问题就抓瞎。所以我先用一分钟讲清楚 Tesseract.js 的骨架,之后每一段代码你都能看懂它"在干什么、为什么这么写"。
第一个概念:Worker(识别工作单元)
createWorker创建的对象,内部会启动一个独立的 Web Worker 线程,专门跑 OCR 引擎。图片发进去,识别完把文本返回给主线程。它的特点是可以复用——创建一次,反复喂图片,全部识别完再销毁。这也是性能优化的关键,后面会细说。
第二个概念:语言数据(traineddata)
识别"中文"和识别"英文"用的是不同的模型文件。首次使用时,Tesseract.js 会自动从网络下载对应的.traineddata文件并缓存到浏览器 IndexedDB,第二次打开页面就不需要重复下载了。这也是为什么你第一次运行会比第二次慢很多——那不是卡死,是在下载模型。
第三个概念:进度事件(logger)
识别是异步的,过程包含"加载语言包 → 初始化 → 识别 → 输出"多个阶段。通过logger回调,你可以把每一阶段的进度打出来或渲染成进度条,用户就知道"程序还在干活,不是死机了"。
第一版:一个 HTML 文件跑通图片识别
理论讲完,直接上第一个能运行的例子。新建一个index.html,复制以下内容:
<!DOCTYPE html> <html> <head> <!-- 通过国内 CDN 引入,免安装 --> <script src='https://cdn.jsdelivr.net/npm/tesseract.js@5/dist/tesseract.min.js'></script> </head> <body> <input type="file" id="uploader" accept="image/*"> <pre id="result">识别结果将显示在这里</pre> <script> (async () => { // ① 创建 Worker:'eng' 表示识别英文,1 是识别引擎模式 const worker = await Tesseract.createWorker('eng', 1, { logger: m => console.log(`进度: ${m.status} ${(m.progress * 100).toFixed(1)}%`) }); // ② 绑定文件选择事件 document.getElementById('uploader').addEventListener('change', async (e) => { const file = e.target.files[0]; if (!file) return; // ③ 识别图片并取出文本 const { data: { text } } = await worker.recognize(file); document.getElementById('result').textContent = text; }); })(); </script> </body> </html>代码就三件事:创建 Worker → 监听文件上传 → recognize 拿结果。用浏览器打开这个文件,选一张印刷体英文图片试试。
上面这张图是项目自带的标准测试样本,包含重复的 "The quick brown dog jumped over the lazy fox" 等句子。识别它,你会得到几乎一字不差的文本。控制台里能看到完整的进度流水:loading tesseract core→initializing tesseract→recognizing text→ 完成。
💡提示:createWorker的完整签名是createWorker(langs, oem, options)。oem参数填1表示使用 LSTM 神经网络模型(默认且效果最好),一般不用改。
把识别做快做稳:Worker 复用与提前预热
第一版能跑了,但如果你照着网上的老教程写,很可能会写出这种代码:每次上传图片都createWorker一次。这是我见过最多的性能杀手。
createWorker要完成加载引擎核心、下载语言数据、初始化模型三步,每一步都耗时。在浏览器缓存命中、网络状况好的情况下,这个过程也要花好几秒。如果你上传 10 张图就创建 10 次 Worker,光初始化就耗掉大半时间,内存还被反复占用又释放。
正确的姿势是:Worker 全局只创建一次,循环喂图片,最后 terminate 一次。这也是项目官方示例 examples/browser/basic-efficient.html 采用的模式:
// Worker 在页面加载时创建一次 const worker = await Tesseract.createWorker("eng", 1, { logger: function(m){ console.log(m); } }); // 每次上传复用同一个 Worker const recognize = async function(evt){ const files = evt.target.files; for (let i=0; i<files.length; i++) { const ret = await worker.recognize(files[i]); console.log(ret.data.text); } } const elm = document.getElementById('uploader'); elm.addEventListener('change', recognize);再进一步,如果你知道用户大概率会用到 OCR,可以在页面加载后、用户还没选图片前就提前创建 Worker 并下载语言包。等用户真正上传图片时,识别几乎是即点即出。官方的性能文档(docs/performance.md)专门强调了这个"提前预热"策略。
⚠️注意:如果应用只有少量用户需要 OCR,就不要在页面加载时就下载约 15MB 的引擎与语言包,等用户点开"识别"功能时再初始化,更划算。
让识别结果更准:中英文混合与区域限定
基础识别没问题了,但真实场景往往更刁钻:图片里既有中文又有英文,或者整张图有很多干扰信息,我只想要其中一小块。
中英文混合识别:语言代码用+拼接即可,中文简体是chi_sim:
const worker = await Tesseract.createWorker('chi_sim+eng', 1, { logger: m => console.log(m) }); const ret = await worker.recognize('mixed-language.png'); console.log('识别结果:', ret.data.text);只识别指定区域:如果你的场景是"票据上的金额栏""证件的号码区",用rectangle限定矩形区域,能显著提升准确率和速度——引擎不必在整张图上找文字:
// 仅识别图片中 left:0, top:0, 宽300, 高200 的区域 const { data: { text } } = await worker.recognize(imageFile, { rectangle: { left: 0, top: 0, width: 300, height: 200 } });限定字符集:当你知道识别目标只会出现某些字符时(比如银行卡号、验证码只有数字),用setParameters告诉引擎,准确率立竿见影:
await worker.setParameters({ tessedit_pageseg_mode: Tesseract.PSM.SINGLE_LINE, // 单行文本模式 tessedit_char_whitelist: '0123456789' // 只允许数字 });比如上面这张银行账单类图片,如果业务只要识别金额数字,配合SINGLE_LINE和数字白名单,识别速度与准确率都会比"全图无脑识别"好得多。
💡提示:PSM(页面分割模式)是 Tesseract 最常用的调优旋钮。默认是整块文本(SINGLE_BLOCK),遇到单行、单字符、稀疏文本等特殊情况,换对应模式往往比调其他参数更有效。完整参数可参考项目文档 docs/api.md 与 docs/tesseract_lang_list.md。
批量图片不再排队:把 45 秒压到 15 秒的 Scheduler
前面说了 Worker 要复用,但一个 Worker 同时只能处理一张图。当你要识别几十张图时,单 Worker 串行处理就成了瓶颈。项目文档的对比数据很直观:单 Worker 处理 10 张图平均耗时 45 秒,而 4 个 Worker 并行处理只需约 15 秒。
并行方案就是 Scheduler(调度器)。它的作用是把一批 Worker 组成一个池子,自动把识别任务分发给空闲的 Worker:
const scheduler = Tesseract.createScheduler(); // 批量创建 4 个 Worker 并加入调度器 for (let i = 0; i < 4; i++) { const worker = await Tesseract.createWorker('eng'); scheduler.addWorker(worker); } // 并发提交 10 个识别任务 const results = await Promise.all( imageFiles.map(file => scheduler.addJob('recognize', file)) ); // 汇总结果 const allTexts = results.map(r => r.data.text); await scheduler.terminate(); // 会同时终止池内所有 Worker项目官方示例 examples/browser/basic-scheduler.html 就是这么组织的,你可以直接对照学习。
⚠️注意三点:
- Worker 数量不是越多越好。每个 Worker 内存占用很高,建议不超过 CPU 核心数,否则容易崩溃。
- 池内 Worker 必须"同构"——同样的语言、同样的参数。因为 Scheduler 不保证任务分给哪个 Worker,如果 Worker 配置不同,识别结果会随分配对象漂移。
- Node.js 后端里长期跑的服务,建议每处理几百个任务就把 Scheduler 终止重建一次。原因是 WebAssembly 的内存只会扩张不回收,且引擎会不断把见过的词写进内部词典,跑久了内存和词典都会膨胀(详见 docs/workers_vs_schedulers.md)。
从浏览器到后端:Node.js 端 30 秒接入
浏览器端搞定后,你会发现 Node.js 端几乎零学习成本——API 完全一样,只是引入方式不同:
npm install tesseract.js然后写一个recognize.js:
const { createWorker } = require('tesseract.js'); (async () => { // 与浏览器端完全相同的调用方式 const worker = await createWorker('chi_sim+eng', 1, { logger: m => console.log(m), }); const { data: { text } } = await worker.recognize('./tests/assets/images/meditations.jpg'); console.log('识别结果:', text); await worker.terminate(); // 识别完释放资源 })();项目里的 Node 示例位于 examples/node/,包含识别、批量调度、图片预处理、PDF 场景等多个可直接运行的脚本。服务端做 OCR 的典型姿势是:前端上传图片 → 后端 Worker 识别 → 返回结构化文本。如果你要把识别结果转成 PDF 或 JSON,仓库里 examples/node/download-pdf.js 就是现成参考。
新手最容易踩的 5 个坑
坑 1:识别 PDF 文件失败。Tesseract.js 不支持直接吃 PDF。要么先用 PDF.js 这类库把 PDF 渲染成 PNG 再识别,要么换用基于它扩展的 Scribe.js。文档 docs/faq.md 里有完整说明。
坑 2:拿手写体测试,结果惨不忍睹。Tesseract 的模型假设是印刷体,手写识别效果天然很差,任何参数都救不回来。想识别手写体请另寻专用方案。
坑 3:升级到 v6/v7 后,识别结果少了东西。从 v6 开始,除text外的输出格式(如 hocr)默认关闭了。需要时得显式打开:
const ret = await worker.recognize(image, {}, { hocr: true });坑 4:报Cannot find module或找不到 worker。某些打包工具会打乱文件路径,导致主线程找不到 worker 脚本。手动指定workerPath即可修复:
const worker = await createWorker('eng', 1, { workerPath: './node_modules/tesseract.js/src/worker-script/node/index.js' });坑 5:把语言数据缓存关了。老版本缓存有 bug,导致很多教程教人设置cacheMethod: 'none'。这个 bug 在 v4.0.6 已修复,现在保持默认缓存即可。关了缓存等于每次访问都重新下载约 2MB 的语言包,纯属自残。
要点回顾与行动清单
最后,把全文压成一张速查卡,实践时对照着用:
- Worker 全局一个:创建一次、复用到底、最后 terminate,永远别在循环里 createWorker
- Scheduler 管并行:批量任务用 4 个 Worker 并行,数量别超过 CPU 核心数,池内配置要一致
- 语言包会缓存:首次加载慢是正常的,别关缓存,别把首次耗时当成死机
- 调优三件套:
rectangle限定区域、PSM选对版式、char_whitelist锁死字符集 - 明确边界:不支持 PDF、不支持手写体,别在这两条路上浪费时间
- 服务端要"复位":Node 里长跑的服务,每几百个任务重建一次 Scheduler
现在就可以行动:新建一个index.html,粘贴第一版的代码,从项目 tests/assets/images/ 里挑一张测试图,把文字识别跑通。然后给它加上进度条、接上批量上传,你的第一个 OCR 小工具就诞生了。等熟练之后,再把语言切成chi_sim+eng,去识别你的真实业务单据——相信我,财务同事会感谢你的。
【免费下载链接】tesseract.jsPure Javascript OCR for more than 100 Languages 📖🎉🖥项目地址: https://gitcode.com/GitHub_Trending/te/tesseract.js
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考