OCR前端识别插件Tesseract.js

Tesseract.js 网站上所说,它支持 100 多种语言,自动文本定位和脚本检测,用于阅读段落、单词和字符边界框的简单界面。

Tesseract 的最新版本第 4 版于 2018 年 10 月发布,它包含一个新的 OCR 引擎,该引擎使用基于长短期记忆(LSTM) 的神经网络系统,旨在产生更准确的结果。

Tesseract.js 

一个几乎能识别出图片中所有语言的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
    }
}

 

posted @ 2023-02-16 17:54  陈晓猛  阅读(6577)  评论(0编辑  收藏  举报