项目里需要从图片中提取文字,最好不用后端服务。调研了一圈,最后选了 Tesseract.js。
为什么选 Tesseract.js
主要几个原因:
- 纯前端,不需要后端支持
- 支持中文识别(虽然准确率一般,但够用)
- 能返回每个字的位置信息,我需要这个来定位文本
其他的方案要么不支持中文,要么要收费,要么只能用后端。
基础使用很简单
javascript
import Tesseract from "tesseract.js";
const result = await Tesseract.recognize(image, "chi_sim+eng", {
logger: (m) => console.log(m),
});
console.log(result.data.text); // 识别的文本
chi_sim 是简体中文,eng 是英文。+ 号表示同时识别两种语言。
第一次运行会自动下载语言包,大概 20MB 左右,会有点慢。之后就会缓存了。
但有个坑:词语位置信息
我需要的不只是文本,还要每个词在图片上的位置,这样才能定位到原文。
Tesseract.js 可以返回 words 数组,每个词有 bbox 信息:
javascript
const words = result.data.words.map((w) => ({
bbox: w.bbox, // [x0, y0, x1, y1]
text: w.text,
}));
bbox 是一个四元组 [x0, y0, x1, y1],表示词语的左上角和右下角坐标。
问题来了:怎么找到特定文本的位置?
比如我要找"工程工作说明书"这几个字,怎么从 words 数组里定位?
我写了个简单的算法:
javascript
function getBBoxesForText(words, target) {
const targetArr = target.split("");
for (let i = 0; i < words.length; i++) {
let match = true;
// 逐字匹配
for (let j = 0; j < targetArr.length; j++) {
if (!words[i + j] || words[i + j].text !== targetArr[j]) {
match = false;
break;
}
}
if (match) {
// 合并 bbox
const bboxes = words.slice(i, i + targetArr.length).map((w) => w.bbox);
const x0 = Math.min(...bboxes.map((b) => b.x0));
const y0 = Math.min(...bboxes.map((b) => b.y0));
const x1 = Math.max(...bboxes.map((b) => b.x1));
const y1 = Math.max(...bboxes.map((b) => b.y1));
return { x0, y0, x1, y1 };
}
}
return null;
}
这个算法就是逐字匹配,然后把匹配到的字的 bbox 合并成一个大的矩形。
但是准确率一般
Tesseract.js 对中文的识别准确率确实不如百度、腾讯这些商用 OCR。我用几十张图片测试,大概 70-80% 的准确率。
常见的错误:
- 形状相似的字混淆(比如"己"和"已")
- 印刷质量差的时候识别不出来
- 复杂排版的时候位置不准
但对我们的场景够用了,因为我们主要用来辅助定位,不是精确提取。
性能优化:图片预处理
我后来发现,如果先对图片做一下预处理,准确率会高很多。
比如:
- 转灰度
- 二值化
- 去噪
但我没在前端做,因为会影响性能。如果图片质量差,我会建议用户重新上传清晰一点的。
还有个坑:logger 太吵
Tesseract.js 识别过程中会不断输出日志,比如进度、状态等等。
javascript
const result = await Tesseract.recognize(image, "chi_sim+eng", {
logger: (m) => console.log(m), // 这个会输出一堆
});
我后来改成只在关键节点输出:
javascript
const result = await Tesseract.recognize(image, "chi_sim+eng", {
logger: (m) => {
if (m.status === 'recognizing text') {
console.log(`识别进度: ${Math.round(m.progress * 100)}%`);
}
},
});
这样清爽多了。
最后封装了一下
为了复用,我把 OCR 功能封装成工具函数:
javascript
// OCRProcessor.js
export async function recognizeImage(image) {
const result = await Tesseract.recognize(image, "chi_sim+eng", {
logger: (m) => {
if (m.status === 'recognizing text') {
console.log(`识别进度: ${Math.round(m.progress * 100)}%`);
}
},
});
if (!result.data || !result.data.text) {
throw new Error("OCR识别失败");
}
const words = result.data.words.map((w) => ({
bbox: w.bbox,
text: w.text,
}));
return { text: result.data.text, words };
}
export function getBBoxesForText(words, target) {
// ... 算法逻辑
}
使用起来就很简单:
javascript
const { text, words } = await recognizeImage(imageUrl);
const bbox = getBBoxesForText(words, "工程工作说明书");
if (bbox) {
console.log("找到了位置:", bbox);
// 在这个位置画标注框
}
几个踩坑总结
- 中文准确率一般:别指望 100% 准确,大概 70-80%
- 第一次会下载语言包:20MB 左右,要等一会儿
- logger 很吵:记得过滤一下
- words 数组很好用:如果要定位文本位置,一定要用这个
- 图片质量很重要:模糊、倾斜的图片识别率很差