ppu-paddle-ocr
Version:
Lightweight, probably the fastest PaddleOCR SDK in TypeScript. Runs anywhere JavaScript runs: Node.js, Bun, Deno, mobile react-native, web browsers, and browser extensions. Docker & CLI supported. The official SDK is browser-only. Accurate text detection
1 lines • 6.11 kB
JavaScript
export class BaseDetectionService{options;debugging;session;platform;engine;lastDetectionCanvas=null;constructor(platform,session,options={},debugging={},engine="opencv"){this.platform=platform;this.session=session;this.options={...DEFAULT_DETECTION_OPTIONS,...options};this.debugging={...DEFAULT_DEBUGGING_OPTIONS,...debugging};if(engine==="opencv"&&!this.platform.imageProcessor){this.engine="canvas-native"}else{this.engine=engine}}log(message){if(this.debugging.verbose){console.log(`[DetectionService] ${message}`)}}async run(image){this.log("Starting text detection process");try{let canvasToProcess;if(this.platform.isCanvas(image)){canvasToProcess=image}else if(this.engine==="opencv"&&this.platform.imageProcessor){canvasToProcess=await this.platform.imageProcessor.prepareCanvas(image)}else{canvasToProcess=await this.platform.canvas.prepareCanvas(image)}let input=await this.preprocessDetection(canvasToProcess);let detection=await this.runInference(input.tensor,input.width,input.height);if(!detection){console.error("Text detection failed (output tensor is null)");return[]}let detectedBoxes=this.postprocessDetection(detection,input);if(this.debugging.debug&&this.debugging.debugFolder&&this.lastDetectionCanvas){await this.debugDetectionCanvas(this.lastDetectionCanvas,input.width,input.height);await this.debugDetectedBoxes(canvasToProcess,detectedBoxes)}this.log(`Detected ${detectedBoxes.length} text boxes in image`);return detectedBoxes}catch(error){console.error("Error during text detection:",error instanceof Error?error.message:String(error));return[]}}async preprocessDetection(canvas){const{width:originalWidth,height:originalHeight}=canvas;let maxSideLength=this.options.maxSideLength??640;const{width:resizeW,height:resizeH,ratio:resizeRatio}=calculateResizeDimensions(originalWidth,originalHeight,maxSideLength);let width=Math.ceil(resizeW/32)*32;let height=Math.ceil(resizeH/32)*32;let paddedCanvas=this.platform.createCanvas(width,height);let paddedCtx=paddedCanvas.getContext("2d");paddedCtx.drawImage(canvas,0,0,originalWidth,originalHeight,0,0,resizeW,resizeH);let mean=this.options.mean??[0.485,0.456,0.406];let stdDeviation=this.options.stdDeviation??[0.229,0.224,0.225];let tensor=imageToTensor(paddedCanvas,width,height,mean,stdDeviation);this.log(`Detection preprocessed: original(${originalWidth}x${originalHeight}), `+`model_input(${width}x${height}), resize_ratio: ${resizeRatio.toFixed(4)}, engine: ${this.engine}`);return{tensor,width,height,resizeRatio,originalWidth,originalHeight}}async runInference(tensor,width,height){let inputTensor;try{this.log("Running detection inference...");inputTensor=new this.platform.ort.Tensor("float32",tensor,[1,3,height,width]);let feeds={x:inputTensor};let results=await this.session.run(feeds);let outputTensor=results[this.session.outputNames[0]||"sigmoid_0.tmp_0"];this.log("Detection inference complete!");if(!outputTensor){console.error(`Output tensor ${this.session.outputNames[0]} not found in detection results`);return null}return outputTensor.data}catch(error){console.error("Error during model inference:",error instanceof Error?error.message:String(error));throw error}finally{inputTensor?.dispose()}}postprocessDetection(detection,input,minBoxAreaOnPadded=this.options.minimumAreaThreshold??50,paddingVertical=this.options.paddingVertical||0.4,paddingHorizontal=this.options.paddingHorizontal||0.6){this.log("Post-processing detection results...");const{width,height,resizeRatio,originalWidth,originalHeight}=input;let canvas=tensorToCanvas(detection,width,height,this.platform.createCanvas.bind(this.platform));this.lastDetectionCanvas=canvas;if(this.engine==="opencv"&&this.platform.imageProcessor){return this.postprocessWithOpenCV(canvas,width,height,resizeRatio,originalWidth,originalHeight,minBoxAreaOnPadded,paddingVertical,paddingHorizontal)}return this.postprocessWithCanvasNative(canvas,resizeRatio,originalWidth,originalHeight,minBoxAreaOnPadded,paddingVertical,paddingHorizontal)}postprocessWithOpenCV(canvas,width,height,resizeRatio,originalWidth,originalHeight,minBoxAreaOnPadded,paddingVertical,paddingHorizontal){let ip=this.platform.imageProcessor;let processor=new ip.ImageProcessor(canvas);try{processor.grayscale().convert({rtype:ip.cv.CV_8UC1});let contours=new ip.Contours(processor.toMat(),{mode:ip.cv.RETR_LIST,method:ip.cv.CHAIN_APPROX_SIMPLE});let boxes=extractBoxesFromContours(contours,width,height,resizeRatio,originalWidth,originalHeight,minBoxAreaOnPadded,paddingVertical,paddingHorizontal);contours.destroy();this.log(`Found ${boxes.length} potential text boxes (opencv)`);return boxes}finally{processor.destroy()}}postprocessWithCanvasNative(canvas,resizeRatio,originalWidth,originalHeight,minBoxAreaOnPadded,paddingVertical,paddingHorizontal){let processor=this.platform.canvas.createProcessor(canvas).grayscale().threshold({thresh:127});let regions=processor.findRegions({foreground:"light",minArea:minBoxAreaOnPadded,thresh:0,padding:{vertical:paddingVertical,horizontal:paddingHorizontal},scale:1/resizeRatio});let boxes=extractBoxesFromRegions(regions,originalWidth,originalHeight);this.log(`Found ${boxes.length} potential text boxes (canvas-native)`);return boxes}async debugDetectionCanvas(canvas,_width,_height){let dir=this.debugging.debugFolder??"";await this.platform.saveDebugImage(canvas,"detection-debug",dir);this.log(`Probability map visualized and saved to: ${dir}`)}async debugDetectedBoxes(image,boxes){let canvas=this.platform.isCanvas(image)?image:await this.platform.canvas.prepareCanvas(image);let ctx=canvas.getContext("2d");for(let box of boxes){const{x,y,width,height}=box;this.platform.canvas.getToolkit().drawLine({ctx,x,y,width,height})}let dir=this.debugging.debugFolder??"";await this.platform.saveDebugImage(canvas,"boxes-debug",dir);this.log(`Boxes visualized and saved to: ${dir}`)}}import{DEFAULT_DEBUGGING_OPTIONS,DEFAULT_DETECTION_OPTIONS}from"../constants.js";import{calculateResizeDimensions,extractBoxesFromContours,extractBoxesFromRegions}from"./detection/box-geometry.js";import{imageToTensor,tensorToCanvas}from"./detection/image-tensor.js";