PK创意闹新春,我正在参加「春节创意投稿大赛」,详情请看:春节创意投稿大赛
前言
春节即将到来,过年的时候要给亲戚朋友拜年,疫情期间不允许聚集走动,所以通过手机拜年发红包就变成了过年的日常。说一句“新年快乐,恭喜发财”难以表达我们的祝福,所以很多人都会发一些可爱有趣的表情包。为了表达自己的真切祝福,我就想用人脸识别技术替换表情包头像,合成一张新的表情包。以下是效果图,是不是还有点意思!线上体验地址和源码地址在文章结尾处。
face-api.js
虽然人脸识别技术已经出现很久,但是我一直没有接触过,所以做这个东西之前我去查找了一些技术方案。作为前端开发人员,我只会写一点简单的后端,所以我希望可以尽可能的使用前端的技术去做。后面找到了 face-api.js 这个库。看了提供的功能。可以满足我的需求。于是下载官方demo学习。克隆下来项目后,我们进入example\examples-browser
目录,执行yarn
或者 npm install
安装依赖包,安装完成后执行npm tun start
启动项目,注意这里要使用本机IP加端口号在浏览器访问,不能使用localhost。在这些例子中我们找到faceExtraction这个例子就是我们想要的人脸识别并提取。接下来就是看例子学习就行。
实现上传图片获取人脸图
例子中用的是jquery + node的方式来做的,我自己觉定使用了vite + cue3 + TS来构建项目,开发环境都没有什么问题,但是在打包的时候报错了,看到一些关于webgl的错误,因为暂时不知道怎么解决,所以就舍弃了TS。
创建好项目后添加一个上传图片的控件,可以使用UI组件库的,也可以自己写。根据例子我们需要获取到图片的file
对象。实际上图片没有上传到服务器,所以只要在change事件内获取即可。通过faceapi.bufferToImage(imgFile)
方法获取到Image对象。
// * 上传图片
const handleChange = async (file) => {
const imgFile = file.file;
isUpload.value = true; // 上传loading
const img = await faceapi.bufferToImage(imgFile);
imgUrl.value = img.src;
isUpload.value = false;
updateResults();
};
复制代码
接下来就可以调用人脸识别提取的api获取到人脸。在获取人脸图像之前要先加载人脸识别模型。根据使用的模型返回对应的模型对象。
import * as faceapi from 'face-api.js';
const SSD_MOBILENETV1 = 'ssd_mobilenetv1' // SSD 移动网络检测模型
const TINY_FACE_DETECTOR = 'tiny_face_detector' // 微型人脸检测器模型
let selectedFaceDetector = SSD_MOBILENETV1
export const getCurrentFaceDetectionNet = () => {
if (selectedFaceDetector === SSD_MOBILENETV1) {
return faceapi.nets.ssdMobilenetv1
}
if (selectedFaceDetector === TINY_FACE_DETECTOR) {
return faceapi.nets.tinyFaceDetector
}
}
export const isFaceDetectionModelLoaded = () => {
console.log(getCurrentFaceDetectionNet());
return !!getCurrentFaceDetectionNet().params
}
复制代码
根据官方的例子使用的是ssd_mobilenetv1
这个模型,在npm上面有具体的说明。
所以在项目加载的时候我们要加载人脸识别模型,根据isFaceDetectionModelLoaded()
这个方法的返回值判断是否已经加载完成。人脸模型文件放在publish即可。
onMounted(() => {
// ! 初始化,加载人脸识别模型
if (!isFaceDetectionModelLoaded()) {
getCurrentFaceDetectionNet().load();
}
});
复制代码
模型加载完成后就可以获取人脸图了。获取到的faceImages是一个数组,里面是的元素是canvas对象。
// * 获取人脸头像
const updateResults = async () => {
const isFace = isFaceDetectionModelLoaded();
console.log(isFace);
if (!isFace) {
return;
}
const inputImgEl = document.getElementById('inputImg');
const options = getFaceDetectorOptions();
const detections = await faceapi.detectAllFaces(inputImgEl, options);
// * 得到人脸数据(数组)
const faceImages = await faceapi.extractFaces(inputImgEl, detections);
if (faceImages.length > 0) {
displayExtractedFaces(faceImages);
} else {
message.warning('识别不到人脸');
facesContainer.innerHTML = '';
mergeUrl.value = '';
}
};
复制代码
获取到数据后就可以渲染人脸到页面。因为是canvas标签,所以不能使用v-html来渲染,所以这里使用dom元素append的方法来添加在页面。
// * 渲染获取的人脸图片
const displayExtractedFaces = (faceImages) => {
const facesContainer = document.getElementById('facesContainer');
facesContainer.innerHTML = '';
faceImages.forEach((canvas) => facesContainer.append(canvas));
convertCanvasToImage(faceImages[0]);
};
复制代码
使用人脸图和背景图合成图片
两张图片合起来变成一张图片有很多种方案,我这里用的是把图片放在画布上面的方式。首先要新建一张画布。把背景图放上去。这里要注意先放背景图,再放人脸图,因为人脸图比较小,先放会被背景图覆盖遮住。
// * 创建画布
const canvas = document.createElement('canvas');
// * 创建背景图
const bgImg = new Image();
bgImg.src = bg;
bgImg.crossOrigin = 'Anonymous';
bgImg.onload = () => {
const width = bgImg.width;
const height = bgImg.height;
// * 按照背景图的宽高设置画布宽高
canvas.width = width;
canvas.height = height;
// ! 把图片放入画布中, 先放背景图,再放人脸,防止覆盖
canvas.getContext('2d').drawImage(bgImg, 0, 0, width, height);
}
复制代码
接下来放人脸图,因为人脸图是canvas对象,所以需要先转成图片的base64格式的url。
const image = new Image();
const url = canvas.toDataURL('image/png');
image.src = url;
复制代码
转成Image对象后就可以使用canvas.getContext('2d').drawImage()方法把人脸图放到画布,调整一下位置和大小就可以遮住背景图的头部。
实现图片下载
图片已经合成在画布上面,我们又知道画布怎么转成url地址,所以实现下载就很简单了。
const downloadImg = () => {
console.log(downloadUrl.value);
if (!downloadUrl.value) {
message.warning('没有图片可以下载!');
return;
}
const url = downloadUrl.value; //* 获取图片地址
const a = document.createElement('a'); //* 创建一个a节点插入的document
const event = new MouseEvent('click'); //* 模拟鼠标click点击事件
a.download = 'happy-newyear'; //* 设置a节点的download属性值
a.href = url; //* 将图片的src赋值给a节点的href
a.dispatchEvent(event); //* 触发鼠标点击事件
};
复制代码
到此,人脸抠图合成新的表情包就实现了。发给你的朋友亲戚要红包吧!
项目改进
现在的项目只能上传单张图片,并且模板单一。后续希望新增以下功能。有好的提议也可以在评论区提出。当然有兴趣的朋友可以一起加入开发。
-
支持上传多张图片,获取所有图片后合成全家福。
-
支持背景自定义上传。
-
支持导出gif格式图片。
总结
这个小项目虽然简单,但是从技术选型到完成项目还是花了不少的时间。人脸识别技术现在已经很成熟了,在我们的生活中都有应用到。也是我们需要学习的技术。通过这个项目我也算是入了门,虽然是站在巨人的肩膀上做的,但是也获得了经验,拓宽了视野。
源码和体验地址
因为人脸识别模型文件比较大,并且服务器性能一般,所以人脸识别模型下载的很慢,出现页面后需要等待一段时间才能使用人脸识别功能。线上体验地址