Canvas绘图及描框

canvas绘制图片和画识别框问题

canvas知识

前言

先说一下业务需求,此处的功能是用于微信OCR文字识别的PC端和小程序端应用。要求上传本地图片后,图片以及识别结果能够展示,为便于测试要求在识别图片上对识别的文字用矩形框及序号框出,与结果一一对应。

记录下此处的实现方法及难点:

1、首先图片显示不要使用img标签,使用canvas来描绘,我们需要得到的是img的宽高属性。小程序使用:wx.getImageInfo,vue中直接new Image()

2、我们要知道两个比例:(1)图片与画布的比例,用于图片自适应画布大小。(2)图片自身缩放比例,
用于识别框同比例缩放。

3、计算居中显示的偏移量:(画布-图片)/2;调用canvas的api接口绘图ctx.drawImage

4、根据后台返回的坐标点(原图)进行描框和缩放ctx.strokeRect,对序号框字体进行优化,自适应框的大小,此处小程序和pc不同。

分别附带小程序及vue的完整代码

小程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
canvasToFile(ctx) {
const cWidth = 640,
cHeight = 480;

wx.getImageInfo({
src: SRC,
success: res => {
// 原始图大小
let imgWidth = res.width,
imgHeight = res.height;
// 相对画布等比例缩放
let n = 1;
if (imgWidth > imgHeight) {
n = imgWidth / cWidth;
imgWidth = cWidth;
imgHeight = imgHeight / n;
} else {
n = imgHeight / cHeight;
imgHeight = cHeight;
imgWidth = imgWidth / n;
}
// 图片自身缩放比例
let widthScal = imgWidth / res.width;
let zoom = {
width: widthScal,
height: widthScal
}
// 偏移x,y位移量
let right_move = (cWidth - imgWidth) / 2;
let bottom_move = (cHeight - imgHeight) / 2;
// 缩放展示画布图片
ctx.scale(0.5, 0.5);
ctx.drawImage(SRC, right_move, bottom_move, imgWidth, imgHeight);
ctx.draw();
// 画矩形识别框
setTimeout(function() {
let value = wx.getStorageSync('ocr');
if (value.hasOwnProperty('items')) {
let length = value.items.length;
let item = value.items;
for (let i = 0; i < length; i++) {
let show = item[i];
// 原图坐标
let X1 = show.pos.left_top.x;
let Y1 = show.pos.left_top.y;
let X2 = show.pos.right_bottom.x;
let Y2 = show.pos.right_bottom.y;
let startPoint = [X1, Y1],
endPoint = [X2, Y2];
// 等比例缩放坐标
let Num_x = startPoint[0] * zoom.width + right_move;
let Num_y = startPoint[1] * zoom.height + bottom_move;
let Num_width = (endPoint[0] - startPoint[0]) * zoom.width;
let Num_height = (endPoint[1] - startPoint[1]) * zoom.height;
ctx.setStrokeStyle('blue');
// 分别画两个画布的矩形框
ctx.strokeRect(Num_x, Num_y, Num_width, Num_height);
// 区分文字横向,竖向展示时,数字框位置
if (Num_width < Num_height) {
let top_x = Num_x;
let top_y = (startPoint[1] - (endPoint[0] - startPoint[0])) * zoom.height + bottom_move;
let w_h = Num_width;
ctx.setFontSize(w_h);
ctx.setStrokeStyle('#006bff');
ctx.setFillStyle('#006bff');
ctx.fillText(i + 1, Num_x + (w_h / 2), Num_y, w_h);
ctx.strokeRect(top_x, top_y, w_h, w_h);
ctx.setTextAlign('center');
} else {
let left_X = (startPoint[0] - (endPoint[1] - startPoint[1])) * zoom.width + right_move;
let left_Y = Num_y;
let W_H = Num_height;
ctx.setFontSize(W_H);
ctx.setStrokeStyle('#006bff');
ctx.setFillStyle('#006bff');
ctx.fillText(i + 1, left_X + W_H / 2, left_Y + W_H, W_H);
ctx.strokeRect(left_X, left_Y, W_H, W_H);
ctx.setTextAlign('center');
}
}
ctx.draw(true)
// 导出图片到url数组中,注意像素值
ctx.draw(true, setTimeout(function() {
let dpr = wx.getSystemInfoSync().pixelRatio
wx.canvasToTempFilePath({
x: right_move,
y: bottom_move,
width: imgWidth,
height: imgHeight,
destWidth: imgWidth * dpr,
destHeight: imgHeight * dpr,
canvasId: 'canvas_pre',
success(res) {
url.push(res.tempFilePath)
}
})
}, 100));
}
}, 1200)
}
})
},

在onload调用该方法并初始化:

1
2
3
4
const ctx = wx.createCanvasContext('canvas')
ctx.clearRect(0, 0, 320, 240)
ctx.draw()
this.canvasToFile(ctx)

vue中实现:

调canvas组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<Canvas @upload="upload" :result_show="result_title"
:list="ocr_comm" :show="loading" :err="err"></Canvas>
</template>

<script>
import Canvas from 'components/HomeChildren/Canvas/Canvas'

export default {
name: "ocr_comm",
components: {
Canvas
},
data() {
return {
CW: '400px',
CH: '400px',
result_title: '通用OCR识别结果',
ocr_comm: [],
loading: false,
err: false
}
},

图片自适应及绘图,使用async解决异步影响:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
async upload(file, canvas) {
if (!file) return;
// 加载图标显示
this.loading = true
// 图片缩放比例
let widthScal = 0
// 先基于FileReader进行文件的读取
let fileExample = new FileReader();
fileExample.readAsDataURL(file);
fileExample.onload = ev => {
// 创建新图片
this.IMAGE = new Image();
this.IMAGE.src = ev.target.result;
this.IMAGE.onload = () => {
this.IW = this.IMAGE.width;
this.IH = this.IMAGE.height;
const NCW = parseInt(this.CW);
const NCH = parseInt(this.CW);
// 重新按照比例计算宽高
let n = 1;
if (this.IW > this.IH) {
n = this.IW / NCW;
this.IW = NCW;
this.IH = this.IH / n;
} else {
n = this.IH / NCH;
this.IH = NCH;
this.IW = this.IW / n;
}
this.IL = (NCW - this.IW) / 2;
this.IT = (NCH - this.IH) / 2;
widthScal = this.IW / this.IMAGE.width;
// 绘制图片
this.CTX = canvas.getContext("2d");
// 清空画布
this.CTX.clearRect(0, 0, parseInt(this.CW), parseInt(this.CH));
// 绘制图片
this.CTX.drawImage(this.IMAGE, this.IL, this.IT, this.IW, this.IH);
};
}
//上传文件
await this.uploadImg(file)
this.strokeRect(widthScal)
},

描框代码,这里的序号字体自适应使用字符串拼接形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
strokeRect(widthScal) {
if (this.ocr_comm.length !== 0) {
for (let i = 0; i < this.ocr_comm.length; i++) {
let show = this.ocr_comm[i];
// 原图坐标
let X1 = show.pos.left_top.x;
let Y1 = show.pos.left_top.y;
let X2 = show.pos.right_bottom.x;
let Y2 = show.pos.right_bottom.y;
let startPoint = [X1, Y1],
endPoint = [X2, Y2];
let Num_x = startPoint[0] * widthScal + this.IL;
let Num_y = startPoint[1] * widthScal + this.IT;
let Num_width = (endPoint[0] - startPoint[0]) * widthScal;
let Num_height = (endPoint[1] - startPoint[1]) * widthScal;
this.CTX.strokeStyle = 'blue';
this.CTX.strokeRect(Num_x, Num_y, Num_width, Num_height);
if (Num_width < Num_height) {
let top_x = Num_x;
let top_y = (startPoint[1] - (endPoint[0] - startPoint[0])) * widthScal + this.IT
let w_h = Num_width;
let fontsize=w_h+'px';
let fontFamily="宋体";
let Num_font=fontsize+' '+fontFamily;
this.CTX.font=Num_font;
this.CTX.strokeStyle = '#006bff';
this.CTX.fillStyle = '#006bff';
this.CTX.fillText(i + 1, Num_x + (w_h / 2), Num_y, w_h);
this.CTX.strokeRect(top_x, top_y, w_h, w_h);
this.CTX.textAlign = 'center';
} else {
let left_X = (startPoint[0] - (endPoint[1] - startPoint[1])) * widthScal + this.IL
let left_Y = Num_y;
let W_H = Num_height;
let fontsize=W_H+'px';
let fontFamily="宋体";
let Num_font=fontsize+' '+fontFamily;
this.CTX.font=Num_font;
this.CTX.strokeStyle = '#006bff';
this.CTX.fillStyle = '#006bff';
this.CTX.fillText(i + 1, left_X + W_H / 2, left_Y + W_H, W_H);
this.CTX.strokeRect(left_X, left_Y, W_H, W_H);
this.CTX.textAlign = 'center';
}
}
} else {
console.log("数据未取到");
}
},

上传代码到后台服务器,前端使用api代理解决跨域问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
async uploadImg(file) {
var forms = new FormData();
forms.append('file', file);
let config = {
headers: {'Content-Type': 'multipart/form-data'}
};
await this.$axios.post('/apidebug_imagequery?action=ocr_comm', forms, config)
.then(res => {
this.loading = false;
if (res.data.ocrcomm_res.items.length !== 0) {
this.ocr_comm = res.data.ocrcomm_res.items;
this.err = false;
} else {
this.ocr_comm = [];
this.err = true;
}
}).catch(err => {
//补充异常处理代码
console.log(err);
})
},

至此!canvas的相关实现就是这些。canvas远比自己想的强大,现在所知也是九牛一毛,以后再会~!

文章目录
  1. 1. canvas绘制图片和画识别框问题
    1. 1.1. canvas知识
      1. 1.1.1. 前言
      2. 1.1.2. 分别附带小程序及vue的完整代码
        1. 1.1.2.1. 小程序:
      3. 1.1.3. vue中实现:
,