OCR前端识别插件Tesseract.js
Tesseract.js 网站上所说,它支持 100 多种语言,自动文本定位和脚本检测,用于阅读段落、单词和字符边界框的简单界面。
Tesseract 的最新版本第 4 版于 2018 年 10 月发布,它包含一个新的 OCR 引擎,该引擎使用基于长短期记忆(LSTM) 的神经网络系统,旨在产生更准确的结果。
一个几乎能识别出图片中所有语言的JS库。
官网:http://tesseract.projectnaptha.com/
git:https://github.com/naptha/tesseract.js
Tesseract.js使用脚本标签,webpack / browserify和节点,安装之后,进行如下操作:
Tesseract.recognize(myImage) .progress(function (p) { console.log('progress', p) }) .then(function (result) { console.log('result', result) })
了解 Tesseract API
要真正了解 Tesseract 的工作原理,我们需要分解它的一些 API 及其组件。根据 Tesseract.js 文档,有两种方法可以使用它。以下是第一种方法及其分解:
Tesseract.recognize( image,language, { logger: m => console.log(m) } ) .catch (err => { console.error(err); }) .then(result => { console.log(result); }) }
该recognize方法将图像作为第一个参数,语言(可以是多个)作为第二个参数,{ logger: m => console.log(me) }最后一个参数。
Tesseract 支持的图像格式是 jpg、png、bmp 和 pbm,它们只能作为元素(img、视频或画布)、文件对象 ( <input>)、blob 对象、图像的路径或 URL 和 base64 编码图像提供。
语言以字符串形式提供,例如eng. 该+符号可用于连接多种语言,如eng+chi_tra. 语言参数用于确定要在图像处理中使用的训练语言数据。
{ logger: m => console.log(m) }对于获取有关正在处理的图像的进度的信息非常有用。
logger 属性采用一个函数,该函数将在 Tesseract 处理图像时被多次调用。logger 函数的参数应该是一个具有workerId, jobId,status和progress属性的对象:
{ workerId: ‘worker-200030’, jobId: ‘job-734747’, status: ‘recognizing text’, progress: ‘0.9’ }
progress是一个介于 0 和 1 之间的数字,以百分比表示图像识别过程的进度。
Tesseract 自动生成对象作为 logger 函数的参数,但也可以手动提供。随着识别过程的发生,每次调用函数时都会更新logger对象属性。因此,它可用于显示转换进度条、更改应用程序的某些部分或用于实现任何所需的结果。
上面代码中的result是图像识别过程的结果。的每个属性result都有属性 bbox 作为其边界框的 x/y 坐标。
以下是result对象的属性、含义或用途:
{ text: "I am codingnninja from Nigeria..." hocr: "<div class='ocr_page' id= ..." tsv: "1 1 0 0 0 0 0 0 1486 ..." box: null unlv: null osd: null confidence: 90 blocks: [{...}] psm: "SINGLE_BLOCK" oem: "DEFAULT" version: "4.0.0-825-g887c" paragraphs: [{...}] lines: (5) [{...}, ...] words: (47) [{...}, {...}, ...] symbols: (240) [{...}, {...}, ...] }
text
:所有识别的文本为字符串。lines
:每个已识别的文本行的数组。words
:每个已识别单词的数组。symbols
:每个识别的字符的数组。paragraphs
:每个已识别段落的数组。我们将在本文后面讨论“信心”。
Tesseract 也可以更强制地使用,如:
import { createWorker } from 'tesseract.js'; const worker = createWorker({ logger: m => console.log(m) }); (async () => { await worker.load(); await worker.loadLanguage('eng'); await worker.initialize('eng'); const { data: { text } } = await worker.recognize('https://tesseract.projectnaptha.com/img/eng_bw.png'); console.log(text); await worker.terminate(); })();
这种方法与第一种方法有关,但实现方式不同。
createWorker(options)创建一个创建 Tesseract 工作者的 Web 工作者或节点子进程。工作人员帮助设置 Tesseract OCR 引擎。该load()方法加载 Tesseract 核心脚本,loadLanguage()加载作为字符串提供给它的任何语言,initialize()确保 Tesseract 完全可以使用,然后使用识别方法处理提供的图像。terminate() 方法停止工作程序并清理所有内容。
我还可以建一个util类
const { createWorker } = require('tesseract.js') const sharp = require('sharp') class OcrService { constructor(debug=''){ this.worker this.debug = debug } async initWorker(){ this.worker = createWorker() await this.worker.load() await this.worker.loadLanguage('eng') await this.worker.initialize('eng') } async terminateWorker(){ await this.worker.terminate() this.worker = '' } async getOcrData(img){ if(!this.worker){ await this.initWorker() } const data = await this.worker.recognize(img) return data } async getTextPosition(text,img){ const words = await(await this.getOcrData(img)).data.words for (let i in words){ if (words[i].text == text){ const {x0,y0,x1,y1} = words[i].bbox const [left,top,width,height] = [x0,y0,x1-x0,y1-y0] if (this.debug){ console.log('text position:', [left,top,width,height]) } return [left,top,width,height] } } return [] } async recognizeText(img){ const text = await (await this.getOcrData(img)).data.text if (this.debug){ console.log('text in image:') console.log(text) } return text } async textInImage(text,img){ const result = await this.recognizeText(img) return (result.indexOf(text) == -1) ? false : true } async bufferImg(img,left,top,width,height){ const sharpImg = await sharp(img) const imgData = await sharpImg.metadata() sharpImg.grayscale() sharpImg.gamma() if (width == 0){ width = imgData.width - left } if (height == 0){ height = imgData.height - top } const buffer = await sharpImg.extract({ left: left, top: top, width: width, height: height }).toBuffer() if (this.debug){ const imgArea = [left,top,width,height] console.log('image area:', imgArea) await sharpImg.extract({ left: left, top: top, width: width, height: height }).toFile('debug.png') } return buffer } async calcCropParams(img,left,top,width,height,area=[]){ const sharpImg = await sharp(img) const imgData = await sharpImg.metadata() const areaHeight = parseInt(imgData.height / 3) const areaWidth = parseInt(imgData.width / 2) let [finalLeft, finalTop, finalWidth, finalHeight] = [0,0,0,0] for (let i in area){ switch(area[i]){ case 'left': finalWidth = areaWidth break case 'right': finalLeft = areaWidth break case 'top': finalHeight = areaHeight break case 'middle': finalTop = areaHeight finalHeight = areaHeight break case 'bottom': finalTop = areaHeight * 2 break } } finalLeft += left finalWidth -= left finalTop += top finalHeight -= top finalWidth = (width == 0) ? finalWidth : width finalHeight = (height == 0) ? finalHeight : height return [finalLeft, finalTop, finalWidth, finalHeight] } async cropImg(screenshot,left,top,width,height,area){ const cropParams = await this.calcCropParams(screenshot,left,top,width,height,area) const buffer = await this.bufferImg(screenshot,...cropParams) return buffer } } module.exports.OcrService = OcrService
用recognize去识别图片,用bbox获取边界框 x/y 坐标,然后具体再计算做调整
使用对象调用当前的方法
const { OcrService } = require("./service") async function bufferScreenshot(platform){ let base64Data switch(platform){ case 'web': base64Data = await browser.takeScreenshot() break case 'panel': base64Data = await panel.takeScreenshot() break case 'android': base64Data = await android.takeScreenshot() break default: base64Data = await browser.takeScreenshot() } const screenshot = Buffer.from(base64Data,'base64') return screenshot } let ocrProcessor = new OcrService() export const ocr = { getTextFrom: async (platform,{left=0,top=0,width=0,height=0,area=[]}) => { const screenshot = await bufferScreenshot(platform) const buffer = await ocrProcessor.cropImg(screenshot,left,top,width,height,area) const text = await ocrProcessor.recognizeText(buffer) await ocrProcessor.terminateWorker() return text }, getTextPosition: async (platform,text,{left=0,top=0,width=0,height=0,area=[]}) => { const screenshot = await bufferScreenshot(platform) const [leftOffset, topOffset, w, h] = await ocrProcessor.calcCropParams(screenshot,left,top,width,height,area) const buffer = await ocrProcessor.cropImg(screenshot,left,top,width,height,area) const result = await ocrProcessor.getTextPosition(text,buffer) await ocrProcessor.terminateWorker() if (result.length != 0){ const [rLeft,rTop,rWidth,rHeight] = result return [leftOffset + rLeft, topOffset + rTop, rWidth, rHeight] }else{ return [] } }, waitUntilTextInScreen: async (platform,text,{left=0,top=0,width=0,height=0,area=[],timeout=40000}) => { await driver.waitUntil(async () => { const screenshot = await bufferScreenshot(platform) const buffer = await ocrProcessor.cropImg(screenshot,left,top,width,height,area) const result = await ocrProcessor.textInImage(text,buffer) return result },{ timeout:timeout, timeoutMsg: `Could not find the text "${text}".` }) await ocrProcessor.terminateWorker() }, debug:() => { ocrProcessor = new OcrService('debug') return ocr } }