Vite + Vue3!使用人脸识别技术制作专属拜年表情包

PK创意闹新春,我正在参加「春节创意投稿大赛」,详情请看:春节创意投稿大赛

前言

春节即将到来,过年的时候要给亲戚朋友拜年,疫情期间不允许聚集走动,所以通过手机拜年发红包就变成了过年的日常。说一句“新年快乐,恭喜发财”难以表达我们的祝福,所以很多人都会发一些可爱有趣的表情包。为了表达自己的真切祝福,我就想用人脸识别技术替换表情包头像,合成一张新的表情包。以下是效果图,是不是还有点意思!线上体验地址和源码地址在文章结尾处。

54A2E637-1E06-4C50-BB91-46429CC46868.gif

face-api.js

虽然人脸识别技术已经出现很久,但是我一直没有接触过,所以做这个东西之前我去查找了一些技术方案。作为前端开发人员,我只会写一点简单的后端,所以我希望可以尽可能的使用前端的技术去做。后面找到了 face-api.js 这个库。看了提供的功能。可以满足我的需求。于是下载官方demo学习。克隆下来项目后,我们进入example\examples-browser目录,执行yarn 或者 npm install安装依赖包,安装完成后执行npm tun start启动项目,注意这里要使用本机IP加端口号在浏览器访问,不能使用localhost。在这些例子中我们找到faceExtraction这个例子就是我们想要的人脸识别并提取。接下来就是看例子学习就行。

微信截图_20220112145902.png

实现上传图片获取人脸图

例子中用的是jquery + node的方式来做的,我自己觉定使用了vite + cue3 + TS来构建项目,开发环境都没有什么问题,但是在打包的时候报错了,看到一些关于webgl的错误,因为暂时不知道怎么解决,所以就舍弃了TS。

微信截图_20220112151845.png

创建好项目后添加一个上传图片的控件,可以使用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上面有具体的说明。

微信截图_20220112153244.png

所以在项目加载的时候我们要加载人脸识别模型,根据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()方法把人脸图放到画布,调整一下位置和大小就可以遮住背景图的头部。

微信截图_20220112172058.png

实现图片下载

图片已经合成在画布上面,我们又知道画布怎么转成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格式图片。

总结

这个小项目虽然简单,但是从技术选型到完成项目还是花了不少的时间。人脸识别技术现在已经很成熟了,在我们的生活中都有应用到。也是我们需要学习的技术。通过这个项目我也算是入了门,虽然是站在巨人的肩膀上做的,但是也获得了经验,拓宽了视野。

源码和体验地址

因为人脸识别模型文件比较大,并且服务器性能一般,所以人脸识别模型下载的很慢,出现页面后需要等待一段时间才能使用人脸识别功能。线上体验地址

源码地址

zan.png

猜你喜欢

转载自juejin.im/post/7052292816134897695