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. 图片质量很重要:模糊、倾斜的图片识别率很差
相关推荐
qq_177767372 小时前
React Native鸿蒙跨平台实现应用介绍页,实现了应用信息卡片展示、特色功能网格布局、权限/联系信息陈列、评分展示、模态框详情交互等通用场景
javascript·react native·react.js·ecmascript·交互·harmonyos
wuhen_n2 小时前
JavaScript内存管理与执行上下文
前端·javascript
Hi_kenyon2 小时前
理解vue中的ref
前端·javascript·vue.js
jin1233223 小时前
基于React Native鸿蒙跨平台地址管理是许多电商、外卖、物流等应用的重要功能模块,实现了地址的添加、编辑、删除和设置默认等功能
javascript·react native·react.js·ecmascript·harmonyos
2501_920931703 小时前
React Native鸿蒙跨平台医疗健康类的血压记录,包括收缩压、舒张压、心率、日期、时间、备注和状态
javascript·react native·react.js·ecmascript·harmonyos
落霞的思绪4 小时前
配置React和React-dom为CDN引入
前端·react.js·前端框架
Hacker_Z&Q4 小时前
CSS 笔记2 (属性)
前端·css·笔记
Anastasiozzzz4 小时前
LeetCode Hot100 295. 数据流的中位数 MedianFinder
java·服务器·前端
橙露4 小时前
React Hooks 深度解析:从基础使用到自定义 Hooks 的封装技巧
javascript·react.js·ecmascript