Tesseract.js OCR 中文识别

项目里需要从图片中提取文字,最好不用后端服务。调研了一圈,最后选了 Tesseract.js。

为什么选 Tesseract.js

主要几个原因:

  1. 纯前端,不需要后端支持
  2. 支持中文识别(虽然准确率一般,但够用)
  3. 能返回每个字的位置信息,我需要这个来定位文本

其他的方案要么不支持中文,要么要收费,要么只能用后端。

基础使用很简单
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% 的准确率。

常见的错误:

  1. 形状相似的字混淆(比如"己"和"已")
  2. 印刷质量差的时候识别不出来
  3. 复杂排版的时候位置不准

但对我们的场景够用了,因为我们主要用来辅助定位,不是精确提取。

性能优化:图片预处理

我后来发现,如果先对图片做一下预处理,准确率会高很多。

比如:

  • 转灰度
  • 二值化
  • 去噪

但我没在前端做,因为会影响性能。如果图片质量差,我会建议用户重新上传清晰一点的。

还有个坑: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);
  // 在这个位置画标注框
}
几个踩坑总结
  1. 中文准确率一般:别指望 100% 准确,大概 70-80%
  2. 第一次会下载语言包:20MB 左右,要等一会儿
  3. logger 很吵:记得过滤一下
  4. words 数组很好用:如果要定位文本位置,一定要用这个
  5. 图片质量很重要:模糊、倾斜的图片识别率很差
相关推荐
wuhen_n几秒前
破冰——建立我们的AI开发实验环境
前端·javascript
HelloReader5 分钟前
Flutter 自适应布局一套代码适配手机和平板(十二)
前端
牛奶8 分钟前
HTTP裸奔,HTTPS穿盔甲——它们有什么区别?
前端·http·https
梓言10 分钟前
tailwindcss构建执行npm exec tailwindcss init -p 报错
前端
哈罗哈皮11 分钟前
龙虾(openclaw)本地快速安装及使用教程
前端·aigc·ai编程
用户231154445305811 分钟前
React中实现“双向绑定”效果的几种方式
前端
HelloReader12 分钟前
Flutter Sliver 高级滚动打造 iOS 通讯录体验(十三)
前端
a11177644 分钟前
程序化几何背景生成器(html 开源)
前端·开源·html
浮笙若有梦1 小时前
我开源了一个比 Ant Design Table 更好用的高性能虚拟表格
前端·vue.js
一只程序熊1 小时前
vite-cool-unix-ctx] Unexpected token l in JSON at position 0
java·服务器·前端