查看原文
其他

前端机器学习:识别人脸,并在脸颊上画草莓

前端开发劝退师 CSDN 2020-10-16

作者 | 前端开发劝退师
责编 | 夕颜
出品 | CSDN(ID:CSDNnews)


最终结果



在线体验:http://www.caomage.com/index


急性子想直接食用的点这个:源码地址 https://github.com/usecodelee/mldrawberry


原理


要在用户上传的人脸上画草莓,会面临几个问题:


  1. 在哪儿画?

  2. 画多大?

  3. 草莓的角度和脸的角度是否一致?


基于这些问题,就不得不使用机器学习来解决了!


1. 前端的机器学习


提起前端的机器学习,首先想到的就是Google的TensorFlow:


TensorFlow 是一个端到端开源机器学习平台。它拥有一个包含各种工具、库和社区资源的全面灵活生态系统,可以让研究人员推动机器学习领域的先进技术的发展,并让开发者轻松地构建和部署由机器学习提供支持的应用。


有兴趣的可以去TensorFlow官网看看,有很多好玩的东西。


当然,我们仅仅是使用人脸识别,那有人已经在TensorFlow的基础上封装了专门针对人脸识别的库 face-api.js


2. 基本原理


我只是大体说一下,我只是一个感兴趣的可以去看看具体的内容。


第一步:找出所有的面孔


解决方案HOG(Histogram of Oriented Gradients)方向梯度直方图,一种能够检测物体轮廓的算法。


第二步:脸部的不同姿势


使用由瓦希德·卡奇米(Vahid Kazemi)和约瑟菲娜·沙利文(Josephine Sullivan)在 2014 年发明的一种面部特征点估计(face landmark estimation)算法。


这一算法的基本思路是找到68个人脸上普遍存在的点(称为特征点, landmark):



其中:


下巴轮廓17个点 [0-16]

左眉毛5个点 [17-21]

右眉毛5个点 [22-26]

鼻梁4个点 [27-30]

鼻尖5个点 [31-35]

左眼6个点 [36-41]

右眼6个点 [42-47]

外嘴唇12个点 [48-59]

内嘴唇8个点 [60-67]


有了这68个点,我们就可以轻松的知道眉毛、眼睛、鼻子和嘴巴的位置信息了,根据这些位置信息,我们经过一些简单的计算就可以得出开始提到的这几个问题的答案:


  1. 在哪儿画?

  2. 画多大?

  3. 草莓的角度和脸的角度是否一致?


基于vue-cli搞一个


有了face-api.js我们就可以动手开始搞了。大体上分为4步:


  1. 使用vue-cli脚手架搭建项目

  2. 使用face-api.js检测人脸图片,获取检测结果

  3. 计算(草莓大小、位置、旋转角度等)

  4. 画草莓


1. 使用vue-cli脚手架搭建项目


使用vue-cli快速创建一个项目


2. 使用face-api.js检测人脸图片,获取检测结果


(1)安装face-api.js


npm i face-api.js


(2)加载模型数据


使用face-api.js是不需要我们自己慢慢训练的,可以直接使用models。

你可以根据应用程序的要求加载你需要的特定模型。但是如果要运行一个完整的端到端的示例,我们还需要加载人脸检测、人脸特征点检测和人脸识别模型。相关的模型文件可以在代码仓库中找到。我们将它们放在public/models文件夹下并导入:


const MODEL_URL = "/models";await faceapi.loadSsdMobilenetv1Model(MODEL_URL);await faceapi.loadFaceLandmarkModel(MODEL_URL);await faceapi.loadFaceRecognitionModel(MODEL_URL);


将face-api.js导入我们的组件:


import * as faceapi from "face-api.js";


(3)使用face-api.js检测人脸图片,获取检测结果


let input = this.$refs.inputImg; // 待检测的图片let canvas = this.$refs.overlay; // 等下画检测结果的画布const minConfidence = 0.8;let fullFaceDescriptions = await faceapi .detectAllFaces(input) .withFaceLandmarks() .withFaceDescriptors(); // 检测图片faceapi.matchDimensions(canvas, input); // 让画布和图片一样大const displaySize = { width: input.width, height: input.height };const resizedDetections = faceapi.resizeResults( fullFaceDescriptions, displaySize); // 调整检测结果和输入图片像素


3. 计算(草莓位置、大小、倾斜弧度等)


(1)获取脸上元素的特征点数组


// 脸颊是从左往右 17个点const jawOutline = resizedDetections[0].landmarks.getJawOutline(); // 鼻子是从上往下画的 9个点const nose = resizedDetections[0].landmarks.getNose(); //嘴巴分 20个点const mouth = resizedDetections[0].landmarks.getMouth(); //左眼 6个点 从左往右const leftEye = resizedDetections[0].landmarks.getLeftEye();//右眼 6个点 从左往右 const rightEye = resizedDetections[0].landmarks.getRightEye(); //左眉毛 5个点 从左往右const leftEyeBbrow = resizedDetections[0].landmarks.getLeftEyeBrow(); //右眉毛 5个点 从左往右const rightEyeBrow = resizedDetections[0].landmarks.getRightEyeBrow(); faceapi.draw.drawDetections(canvas, resizedDetections); // 画框faceapi.draw.drawFaceLandmarks(canvas, resizedDetections); // 画点



画出来的结果如图:



(2)获取草莓位置


草莓的位置获取,先看下面这张图:



先比较左右黄色和绿色两条线的长度,哪边长就画在哪边(考虑可能照片是侧着脸的),具体的位置左右不一样,右边绿色的线直接从线的中点开始画,而左边黄色的线则是在线的中点再往左边偏移草莓宽度的一半开始画。


大概如图:



所以是先获取点32和点16的距离,再获取中点:


// 获取两点之间距离const getDistance = (a, b) => { return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2))}// 获取两点之间的中点const getMidPoint = (pa, pb) => ({ x: (pa.x + pb.x) / 2, y: (pa.y + pb.y) / 2})



(3)获取草莓大小


草莓的大小可以估计一下,大概是整个脸宽的1/6:


const size = { width: faceWidth / 6, height: faceWidth / 6 }


(4)获取草莓倾斜弧度


我们可以根据眉毛的中点和下颚的最低点两个点计算出脸的弧度,即草莓的弧度:


// 获取脸的倾斜弧度const getFaceRadian = (jawPos, midPointOfEyebrows) => Math.PI - Math.atan2(jawPos.x - midPointOfEyebrows.x, jawPos.y - midPointOfEyebrows.y)



4. 画草莓


有了前面的内容,画草莓就简单了。先将原图画上去,再在原图的基础上画上草莓:


// 画草莓function drawBerry(ctx, config, berryUrl) { const { angle, faceWidth, facePos } = config getImg(berryUrl, (img) => { ctx.save() // 移动画布原点到画草莓的位置 ctx.translate(facePos.point.x, facePos.point.y) // 旋转草莓的角度和脸的角度一致 ctx.rotate(angle) // 调整草莓的位置 const { x, y, width, height } = transBerry(faceWidth, 0, 0, facePos.dir) ctx.drawImage(img, x, y, width, height) ctx.restore() })}// 获取图片function getImg(src, cb) { const img = new Image() img.setAttribute('crossOrigin', 'anonymous') img.src = src img.onload = () => cb(img)}


限于篇幅,文中只提及了部分重点代码,需要看完整代码的可以点这里:https://github.com/usecodelee/mldrawberry


原文链接:

https://blog.csdn.net/caomage/article/details/103751111



作为“百万人学AI”的重要组成部分,2020 AIProCon 开发者万人大会将于6月26日通过线上直播形式,让开发者们一站式学习了解当下 AI 的前沿技术研究、核心技术与应用以及企业案例的实践经验,同时还可以在线参加精彩多样的开发者沙龙与编程项目。参与前瞻系列活动、在线直播互动,不仅可以与上万名开发者们一起交流,还有机会赢取直播专属好礼,与技术大咖连麦。

 

评论区留言入选,可获得价值299元的「2020 AI开发者万人大会」在线直播门票一张。  快来动动手指,写下你想说的话吧!


热 文 推 荐

编程基础知识真的是九阳神功吗?

在家办公的我,砍需求砍得更狠了

科技公司都是如何应对 COVID-19?

瑞幸咖啡自曝虚假交易 22 亿,App 反冲 TOP 1

在Kubernetes上部署一个简单的、类PaaS的平台,原来这么容易!

旷视提双边分支网络BBN:攻坚长尾分布的现实世界任务 | CVPR 2020 Oral

2020年,这20个大家都认识的加密交易所过得怎么样?



你点的每个“在看”,我都认真当成了喜欢

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存