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. 图片质量很重要:模糊、倾斜的图片识别率很差
相关推荐
甲枫叶几秒前
【claude产品经理系列13】核心功能实现——需求的增删改查全流程
java·前端·人工智能·python·产品经理·ai编程
蓝帆傲亦18 分钟前
Vue.js 大数据处理全景解析:从加载策略到渲染优化的完全手册
前端·vue.js·flutter
不会敲代码119 分钟前
React组件通信实战:从Todo应用彻底搞懂父子、子父、兄弟通信
前端·react.js
SuperEugene19 分钟前
字符串处理实战:模板字符串、split/join、正则的 80% 用法
前端·javascript·面试
wuhen_n20 分钟前
前端构建工具:从Rollup到Vite
前端
钟智强20 分钟前
深度剖析CVE-2023-41064与CVE-2023-4863:libwebp堆溢出漏洞的技术解剖与PoC构建实录
前端·后端
钟智强21 分钟前
MySQL客户端惊现高危漏洞CVE-2023-21980,可导致远程代码执行
前端·后端
Cache技术分享22 分钟前
332. Java Stream API - Java Stream 实战进阶:按年份找出合作最多的作者对
前端·后端
SuperEugene23 分钟前
前端正则表达式完全指南:从手写不出到随手就来
前端·正则表达式
九狼24 分钟前
Flutter Riverpod + MVI 状态管理实现的提示词优化器
前端·flutter·github