mirror of
				https://github.com/PaddlePaddle/FastDeploy.git
				synced 2025-10-31 20:02:53 +08:00 
			
		
		
		
	 0a22979c35
			
		
	
	0a22979c35
	
	
	
		
			
			* add application include paddle.js web demo and xcx * cp PR #5 * add readme * fix comments and link * fix xcx readme * fix Task 1 * fix bugs * refine readme * delete ocrxcx readme * refine readme * fix bugs * delete old readme * 200px to 300px * revert 200px to 300px Co-authored-by: Jason <jiangjiajun@baidu.com>
		
			
				
	
	
		
			338 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			338 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* global wx, Page */
 | ||
| import * as paddlejs from '@paddlejs/paddlejs-core';
 | ||
| import '@paddlejs/paddlejs-backend-webgl';
 | ||
| import clipper from 'js-clipper';
 | ||
| import { divide, enableBoundaryChecking, plus } from 'number-precision';
 | ||
| // eslint-disable-next-line no-undef
 | ||
| const plugin = requirePlugin('paddlejs-plugin');
 | ||
| const Polygon = require('d3-polygon');
 | ||
| 
 | ||
| global.wasm_url = 'pages/index/wasm/opencv_js.wasm.br';
 | ||
| const CV = require('./wasm/opencv.js');
 | ||
| 
 | ||
| plugin.register(paddlejs, wx);
 | ||
| 
 | ||
| const imgList = [
 | ||
|     'https://paddlejs.bj.bcebos.com/xcx/ocr.png',
 | ||
|     './img/width.png'
 | ||
| ];
 | ||
| 
 | ||
| // eslint-disable-next-line max-lines-per-function
 | ||
| const outputBox = res => {
 | ||
|     const thresh = 0.3;
 | ||
|     const box_thresh = 0.5;
 | ||
|     const max_candidates = 1000;
 | ||
|     const min_size = 3;
 | ||
|     const width = 960;
 | ||
|     const height = 960;
 | ||
|     const pred = res;
 | ||
|     const segmentation = [];
 | ||
|     pred.forEach(item => {
 | ||
|         segmentation.push(item > thresh ? 255 : 0);
 | ||
|     });
 | ||
| 
 | ||
|     function get_mini_boxes(contour) {
 | ||
|         // 生成最小外接矩形
 | ||
|         const bounding_box = CV.minAreaRect(contour);
 | ||
|         const points = [];
 | ||
|         const mat = new CV.Mat();
 | ||
|         // 获取矩形的四个顶点坐标
 | ||
|         CV.boxPoints(bounding_box, mat);
 | ||
|         for (let i = 0; i < mat.data32F.length; i += 2) {
 | ||
|             const arr = [];
 | ||
|             arr[0] = mat.data32F[i];
 | ||
|             arr[1] = mat.data32F[i + 1];
 | ||
|             points.push(arr);
 | ||
|         }
 | ||
| 
 | ||
|         function sortNumber(a, b) {
 | ||
|             return a[0] - b[0];
 | ||
|         }
 | ||
|         points.sort(sortNumber);
 | ||
|         let index_1 = 0;
 | ||
|         let index_2 = 1;
 | ||
|         let index_3 = 2;
 | ||
|         let index_4 = 3;
 | ||
|         if (points[1][1] > points[0][1]) {
 | ||
|             index_1 = 0;
 | ||
|             index_4 = 1;
 | ||
|         }
 | ||
|         else {
 | ||
|             index_1 = 1;
 | ||
|             index_4 = 0;
 | ||
|         }
 | ||
| 
 | ||
|         if (points[3][1] > points[2][1]) {
 | ||
|             index_2 = 2;
 | ||
|             index_3 = 3;
 | ||
|         }
 | ||
|         else {
 | ||
|             index_2 = 3;
 | ||
|             index_3 = 2;
 | ||
|         }
 | ||
|         const box = [
 | ||
|             points[index_1],
 | ||
|             points[index_2],
 | ||
|             points[index_3],
 | ||
|             points[index_4]
 | ||
|         ];
 | ||
|         const side = Math.min(bounding_box.size.height, bounding_box.size.width);
 | ||
|         mat.delete();
 | ||
|         return {
 | ||
|             points: box,
 | ||
|             side
 | ||
|         };
 | ||
|     }
 | ||
| 
 | ||
|     function box_score_fast(bitmap, _box) {
 | ||
|         const h = height;
 | ||
|         const w = width;
 | ||
|         const box = JSON.parse(JSON.stringify(_box));
 | ||
|         const x = [];
 | ||
|         const y = [];
 | ||
|         box.forEach(item => {
 | ||
|             x.push(item[0]);
 | ||
|             y.push(item[1]);
 | ||
|         });
 | ||
|         // clip这个函数将将数组中的元素限制在a_min, a_max之间,大于a_max的就使得它等于 a_max,小于a_min,的就使得它等于a_min。
 | ||
|         const xmin = clip(Math.floor(Math.min(...x)), 0, w - 1);
 | ||
|         const xmax = clip(Math.ceil(Math.max(...x)), 0, w - 1);
 | ||
|         const ymin = clip(Math.floor(Math.min(...y)), 0, h - 1);
 | ||
|         const ymax = clip(Math.ceil(Math.max(...y)), 0, h - 1);
 | ||
|         // eslint-disable-next-line new-cap
 | ||
|         const mask = new CV.Mat.zeros(ymax - ymin + 1, xmax - xmin + 1, CV.CV_8UC1);
 | ||
|         box.forEach(item => {
 | ||
|             item[0] = Math.max(item[0] - xmin, 0);
 | ||
|             item[1] = Math.max(item[1] - ymin, 0);
 | ||
|         });
 | ||
|         const npts = 4;
 | ||
|         const point_data = new Uint8Array(box.flat());
 | ||
|         const points = CV.matFromArray(npts, 1, CV.CV_32SC2, point_data);
 | ||
|         const pts = new CV.MatVector();
 | ||
|         pts.push_back(points);
 | ||
|         const color = new CV.Scalar(255);
 | ||
|         // 多个多边形填充
 | ||
|         CV.fillPoly(mask, pts, color, 1);
 | ||
|         const sliceArr = [];
 | ||
|         for (let i = ymin; i < ymax + 1; i++) {
 | ||
|             sliceArr.push(...bitmap.slice(960 * i + xmin, 960 * i + xmax + 1));
 | ||
|         }
 | ||
|         const mean = num_mean(sliceArr, mask.data);
 | ||
|         mask.delete();
 | ||
|         points.delete();
 | ||
|         pts.delete();
 | ||
|         return mean;
 | ||
|     }
 | ||
| 
 | ||
|     function clip(data, min, max) {
 | ||
|         return data < min ? min : data > max ? max : data;
 | ||
|     }
 | ||
| 
 | ||
|     function unclip(box) {
 | ||
|         const unclip_ratio = 1.6;
 | ||
|         const area = Math.abs(Polygon.polygonArea(box));
 | ||
|         const length = Polygon.polygonLength(box);
 | ||
|         const distance = area * unclip_ratio / length;
 | ||
|         const tmpArr = [];
 | ||
|         box.forEach(item => {
 | ||
|             const obj = {
 | ||
|                 X: 0,
 | ||
|                 Y: 0
 | ||
|             };
 | ||
|             obj.X = item[0];
 | ||
|             obj.Y = item[1];
 | ||
|             tmpArr.push(obj);
 | ||
|         });
 | ||
|         const offset = new clipper.ClipperOffset();
 | ||
|         offset.AddPath(tmpArr, clipper.JoinType.jtRound, clipper.EndType.etClosedPolygon);
 | ||
|         const expanded = [];
 | ||
|         offset.Execute(expanded, distance);
 | ||
|         let expandedArr = [];
 | ||
|         expanded[0] && expanded[0].forEach(item => {
 | ||
|             expandedArr.push([item.X, item.Y]);
 | ||
|         });
 | ||
|         expandedArr = [].concat(...expandedArr);
 | ||
|         return expandedArr;
 | ||
|     }
 | ||
| 
 | ||
|     function num_mean(data, mask) {
 | ||
|         let sum = 0;
 | ||
|         let length = 0;
 | ||
|         for (let i = 0; i < data.length; i++) {
 | ||
|             if (mask[i]) {
 | ||
|                 sum = plus(sum, data[i]);
 | ||
|                 length++;
 | ||
|             }
 | ||
|         }
 | ||
|         return divide(sum, length);
 | ||
|     }
 | ||
| 
 | ||
|     // eslint-disable-next-line new-cap
 | ||
|     const src = new CV.matFromArray(960, 960, CV.CV_8UC1, segmentation);
 | ||
|     const contours = new CV.MatVector();
 | ||
|     const hierarchy = new CV.Mat();
 | ||
|     // 获取轮廓
 | ||
|     CV.findContours(src, contours, hierarchy, CV.RETR_LIST, CV.CHAIN_APPROX_SIMPLE);
 | ||
|     const num_contours = Math.min(contours.size(), max_candidates);
 | ||
|     const boxes = [];
 | ||
|     const scores = [];
 | ||
|     const arr = [];
 | ||
|     for (let i = 0; i < num_contours; i++) {
 | ||
|         const contour = contours.get(i);
 | ||
|         let {
 | ||
|             points,
 | ||
|             side
 | ||
|         } = get_mini_boxes(contour);
 | ||
|         if (side < min_size) {
 | ||
|             continue;
 | ||
|         }
 | ||
|         const score = box_score_fast(pred, points);
 | ||
|         if (box_thresh > score) {
 | ||
|             continue;
 | ||
|         }
 | ||
|         let box = unclip(points);
 | ||
|         // eslint-disable-next-line new-cap
 | ||
|         const boxMap = new CV.matFromArray(box.length / 2, 1, CV.CV_32SC2, box);
 | ||
|         const resultObj = get_mini_boxes(boxMap);
 | ||
|         box = resultObj.points;
 | ||
|         side = resultObj.side;
 | ||
|         if (side < min_size + 2) {
 | ||
|             continue;
 | ||
|         }
 | ||
|         box.forEach(item => {
 | ||
|             item[0] = clip(Math.round(item[0]), 0, 960);
 | ||
|             item[1] = clip(Math.round(item[1]), 0, 960);
 | ||
|         });
 | ||
|         boxes.push(box);
 | ||
|         scores.push(score);
 | ||
|         arr.push(i);
 | ||
|         boxMap.delete();
 | ||
|     }
 | ||
|     src.delete();
 | ||
|     contours.delete();
 | ||
|     hierarchy.delete();
 | ||
|     return {
 | ||
|         boxes,
 | ||
|         scores
 | ||
|     };
 | ||
| };
 | ||
| 
 | ||
| let detectRunner;
 | ||
| 
 | ||
| Page({
 | ||
|     data: {
 | ||
|         imgList: imgList,
 | ||
|         imgInfo: {},
 | ||
|         result: '',
 | ||
|         loaded: false
 | ||
|     },
 | ||
| 
 | ||
|     onLoad() {
 | ||
|         enableBoundaryChecking(false);
 | ||
|         const me = this;
 | ||
|         detectRunner = new paddlejs.Runner({
 | ||
|             modelPath: 'https://paddleocr.bj.bcebos.com/PaddleJS/PP-OCRv3/ch/ch_PP-OCRv3_det_infer_js_960/model.json',
 | ||
|             mean: [0.485, 0.456, 0.406],
 | ||
|             std: [0.229, 0.224, 0.225],
 | ||
|             bgr: true,
 | ||
|             webglFeedProcess: true
 | ||
|         });
 | ||
|         detectRunner.init().then(_ => {
 | ||
|             me.setData({
 | ||
|                 loaded: true
 | ||
|             });
 | ||
|         });
 | ||
|     },
 | ||
| 
 | ||
|     selectImage(event) {
 | ||
|         const imgPath = this.data.imgList[event.target.dataset.index];
 | ||
|         this.getImageInfo(imgPath);
 | ||
|     },
 | ||
| 
 | ||
|     getImageInfo(imgPath) {
 | ||
|         const me = this;
 | ||
|         wx.getImageInfo({
 | ||
|             src: imgPath,
 | ||
|             success: imgInfo => {
 | ||
|                 const {
 | ||
|                     path,
 | ||
|                     width,
 | ||
|                     height
 | ||
|                 } = imgInfo;
 | ||
| 
 | ||
|                 const canvasPath = imgPath.includes('http') ? path : imgPath;
 | ||
|                 const canvasId = 'myCanvas';
 | ||
|                 const ctx = wx.createCanvasContext(canvasId);
 | ||
|                 let sw = 960;
 | ||
|                 let sh = 960;
 | ||
|                 let x = 0;
 | ||
|                 let y = 0;
 | ||
|                 if (height / width >= 1) {
 | ||
|                     sw = Math.round(sh * width / height);
 | ||
|                     x = Math.floor((960 - sw) / 2);
 | ||
|                 }
 | ||
|                 else {
 | ||
|                     sh = Math.round(sw * height / width);
 | ||
|                     y = Math.floor((960 - sh) / 2);
 | ||
|                 }
 | ||
|                 ctx.drawImage(canvasPath, x, y, sw, sh);
 | ||
|                 ctx.draw(false, () => {
 | ||
|                     // API 1.9.0 获取图像数据
 | ||
|                     wx.canvasGetImageData({
 | ||
|                         canvasId: canvasId,
 | ||
|                         x: 0,
 | ||
|                         y: 0,
 | ||
|                         width: 960,
 | ||
|                         height: 960,
 | ||
|                         success(res) {
 | ||
|                             me.predict({
 | ||
|                                 data: res.data,
 | ||
|                                 width: 960,
 | ||
|                                 height: 960
 | ||
|                             }, {
 | ||
|                                 canvasPath,
 | ||
|                                 sw,
 | ||
|                                 sh,
 | ||
|                                 x,
 | ||
|                                 y
 | ||
|                             });
 | ||
|                         }
 | ||
|                     });
 | ||
|                 });
 | ||
|             }
 | ||
|         });
 | ||
|     },
 | ||
| 
 | ||
|     predict(res, img) {
 | ||
|         const me = this;
 | ||
|         detectRunner.predict(res, function (data) {
 | ||
|             // 获取坐标
 | ||
|             const points = outputBox(data);
 | ||
|             me.drawCanvasPoints(img, points.boxes);
 | ||
|             me.setData({
 | ||
|                 result: JSON.stringify(points.boxes)
 | ||
|             });
 | ||
|         });
 | ||
|     },
 | ||
| 
 | ||
|     drawCanvasPoints(img, points) {
 | ||
|         const canvasId = 'result';
 | ||
|         const ctx = wx.createCanvasContext(canvasId);
 | ||
|         ctx.drawImage(img.canvasPath, img.x, img.y, img.sw, img.sh);
 | ||
|         points.length && points.forEach(point => {
 | ||
|             // 开始一个新的绘制路径
 | ||
|             ctx.beginPath();
 | ||
|             // 设置线条颜色为蓝色
 | ||
|             ctx.strokeStyle = 'blue';
 | ||
|             // 设置路径起点坐标
 | ||
|             ctx.moveTo(point[0][0], point[0][1]);
 | ||
|             ctx.lineTo(point[1][0], point[1][1]);
 | ||
|             ctx.lineTo(point[2][0], point[2][1]);
 | ||
|             ctx.lineTo(point[3][0], point[3][1]);
 | ||
|             ctx.closePath();
 | ||
|             ctx.stroke();
 | ||
|         });
 | ||
|         ctx.draw();
 | ||
|     }
 | ||
| });
 |