Canvas识别图片内容并使用用户头像拼接

今天 12 月了,本来应该开始做外包的项目,但是好歹不好的长智齿了,
下午去拔了牙之后看着电脑发呆了一个下午,看来真的麻药影响 🧠 脑子。
所以,先写篇文章醒醒脑….

前天初步实现了下使用 canvas 来识别图片内容,今天就把它记录下来。
毕竟 canvas 这部分的内容是真的不懂,现学现卖,欢迎吐槽。📧 MailTo

前几天,领导和我说要制作一个年会的活动页面,需要有用户签到展示、企业形象展示、抽奖小游戏。

其中一个需求如下:

Logo 签到墙

年会的签到墙,使用微信扫码签到,然后后台拿到微信授权之后传给我用户的头像昵称,然后我就在前台展示并且完成一些特效。

需求图

需要用头像图片拼接组成图形以及文字内容。
这个图形和文字是用户上传的一个纯色内容+透明/白色底的图片。👇
会上传的图


我就直接想到用 canvas 来识别图片内容,

前天晚上初步实现了需求,
直接暴力的按照设置的 size 大小
从左上角不断循环识别到右下角,然后保存有内容的坐标点,
再按照坐标来绘制矩形和图片填充。

这边是我实现的效果 👇

识别成栅格
识别后的栅格图
用户头像填充
用户头像填充

识别内容:

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
const MaxHeight = document.body.clientWidth; // 获取视窗宽度
const MaxHeight = document.body.clientHeight; // 获取视窗高度
const size = 10; // 设置栅格大小
const points = []; // 坐标数组
const canvas = document.querySelector("canvas"); // 拿到canvas实例
canvas.width = MaxHeight; // 设置canvas宽度 不能使用canvas.style.width来设置,会造成内容拉伸
canvas.height = MaxHeight; // 设置canvas高度
const ctx = canvas.getContext("2d"); // 获取canvas上下文,这边获取的是二维绘图,还有一个3D内容 "webgl"
let img = new Image(); // 创建图片实例
img.src = require("assets/img/text.jpg"); // 获取设置图片url
let w = maxWidth; // 设置临时宽度,后边给绘制图片的时候会用到
let h = 0; // 设置临时高度
img.onload = () => {
// img 设置 url 后会立即加载,加载完成后触发 onload 事件
// 图片加载完成
h = (w / img.width) * img.height; // 计算图片高度
ctx.drawImage(img, 0, 0, w, h); // 绘制图片从(0,0)坐标,w为绘制的图片高度,h为绘制的图片高度
// 开始识别
for (let x = 0; x <= w - 10; x += size + 1) {
// x轴开始循环 因为我需要有1像素的间隙所以是 size+1 ,如果不需要间隙则 +=size 即可
for (let y = 0; y <= h - 10; y += size + 1) {
// y轴开始循环
let color = ctx.getImageData(x, y, size, size).data; // 识别区块内容会 一个像素内返回RGBA四个参数
let count = 0; // 设置计数器
// 以4个一组开始循环
for (let i = 0; i < color.length; i += 4) {
// 如果区块中有颜色内容则 count++,我这边识别的是黑色内容
if (color[i] <= 100 || color[i + 1] <= 100 || color[i + 2] <= 100) {
count++;
}
}
// 颜色比例超过10%则记录,其实50%也可以,但是会出现 特殊情况1 的问题,下边会提到,但是内容会相对粗一些
if (count >= size * size * 0.1) {
points.push({ x: x, y: y }); // 添加到坐标数组
}
}
}
ctx.clearRect(0, 0, maxWidth, maxHeight); // 清楚绘制的图片
};

绘制栅格图

1
2
3
4
5
ctx.fillStyle = "rgba(255,0,0,.55)"; // 设置填充色
// 遍历坐标数组内的所有数据
points.forEach(piont => {
ctx.fillRect(piont.x, piont.y, this.size, this.size); // 绘制矩形
});

填充图片

填充图片和绘制矩形的原理相似所以就不举例了。

考虑到签到的人数有可能达不到坐标的数量,所以在最后可以重新循环用户头像列表来填充满整个栅格区

用户头像填充

以上是简单的使用 canvas 试别图片内容,并且栅格化且使用图片填充。

  • 特殊情况 1:如果识别超过 50% 时,笔画交汇时折角会超过 50%,但是没有处在交汇处的内容可能并没有超过 50%
    笔画交汇