我正在参加「码上掘金月赛第1期」,点击此处查看详情
//掘金由于安全原因没有在 iframe标签上设置allow="microphone *;camera *"导致摄像头打开失败!
//请点击右上角“查看详情”查看!或复制下方链接查看
https://code.juejin.cn/pen/7197979265554448442
马上就要情人节了 相信大家都在为不知道送对象什么礼物而苦恼,不管送什么礼物加上一支口红总是没有错的,但是口红明明是“送你一支口红,每天还我一点”的浪漫,结果成了死亡芭比粉甚至口黄口绿口蓝的大型车祸现场。
如果说,女生眼中的口红色号是这样的: | 那男生眼中的口红色号则是这样的: |
---|---|
为了避免这种尴尬,我们可以在网页中实现一个口红试色
功能,接下来让我们一起开始吧!
架构和概念
graph TD
A[调取Camera获得相机画面] --> B[使用MediaPipeFaceMesh模型识别用户唇部的位置] --> C[根据唇部的位置绘制颜色]
安装MediaPipeFaceMesh
通过脚本标签:
<!-- Require the peer dependencies of face-landmarks-detection. -->
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/face_mesh"></script>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-core"></script>
<!-- You must explicitly require a TF.js backend if you're not using the TF.js union bundle. -->
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-backend-webgl"></script>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/face-landmarks-detection"></script>
通过 npm:
yarn add @mediapipe/face_mesh
yarn add @tensorflow/tfjs-core, @tensorflow/tfjs-backend-webgl
yarn add @tensorflow-models/face-landmarks-detection
使用MediaPipeFaceMesh
如果您通过 npm 使用 face-landmarks-detection API,则需要先导入库。
导入库
import '@mediapipe/face_mesh';
import '@tensorflow/tfjs-core';
// Register WebGL backend.
import '@tensorflow/tfjs-backend-webgl';
import * as faceLandmarksDetection from '@tensorflow-models/face-landmarks-detection';
调取Camera获得相机画面
通过navigator.mediaDevices.getUserMedia
获取stream,放到video
查看。
async function setupWebcam() {
return new Promise( ( resolve, reject ) => {
const webcamElement = document.getElementById( "webcam" );
const navigatorAny = navigator;
navigator.getUserMedia = navigator.getUserMedia ||
navigatorAny.webkitGetUserMedia || navigatorAny.mozGetUserMedia ||
navigatorAny.msGetUserMedia;
if( navigator.getUserMedia ) {
navigator.getUserMedia( { video: true },
stream => {
webcamElement.srcObject = stream;
webcamElement.addEventListener( "loadeddata", resolve, false );
},
error => reject());
}
else {
reject();
}
});
}
创建检测器
detectorConfig
是定义 MediaPipeFaceMesh 特定配置的对象
maxFaces
默认为 1。模型将检测到的最大面孔数。返回的人脸数量可以小于最大值(例如,当输入中不存在人脸时)。强烈建议将此值设置为预期的最大面数,否则模型将继续搜索可能会降低性能的缺失面。refineLandmarks
默认为 false。如果设置为 true,则细化眼睛和嘴唇周围的界标坐标,并输出虹膜周围的其他界标。
const model = faceLandmarksDetection.SupportedModels.MediaPipeFaceMesh;
const detectorConfig = {
runtime: 'mediapipe',
maxFaces:1,
refineLandmarks:true, //必须要开启,否则无法识别嘴唇周围的界标坐标
solutionPath: 'https://cdn.jsdelivr.net/npm/@mediapipe/face_mesh'
};
detector = await faceLandmarksDetection.createDetector(model, detectorConfig);
识别人脸模型
现在您可以使用检测器检测人脸。该estimateFaces
方法接受多种格式的图像和视频,包括 :HTMLVideoElement
、HTMLImageElement
和。如果你想要更多的选项,你可以传入第二个参数。HTMLCanvasElement``Tensor3D``estimationConfig
estimationConfig
是定义 MediaPipeFaceMesh 特定配置的对象MediaPipeFaceMeshMediaPipeEstimationConfig
:
flipHorizontal
:镜像
async recognition() {
try {
const video = this.$refs.video;
const faces = await this.model.estimateFaces(video, {
flipHorizontal: false, //镜像
});
faces.forEach(faceItem => {
const keypoints = (faceItem.keypoints || []);
this.setLipsColor(keypoints);
})
} catch (error) {
console.log(error);
}
}
绘制唇部颜色
setLipsColor(keypoints) {
const { lipsColor, lipsOpacity } = this.controls;
const bottom_lips = [61, 146, 91, 181, 84, 17, 314, 405, 321, 375, 291, 292, 308, 324, 318, 402, 317, 14, 87, 178, 88, 95, 78, 62, 61];//下
const top_lips = [61, 185, 40, 39, 37, 0, 267, 269, 270, 409, 291, 292, 308, 415, 310, 311, 312, 13, 82, 81, 80, 191, 78, 62, 61];//上
const color = this.toRGBA(lipsColor, lipsOpacity)
const bottom_points = bottom_lips.map(idx => keypoints[idx]);
this.drawLine(bottom_points, 1, color, true);
const top_points = top_lips.map(idx => keypoints[idx]);
this.drawLine(top_points, 1, color, true);
},
//画线
drawLine(lineArr = [], lineWidth = 2, color = "lime", isFill = false) {
this.ctx.beginPath();
this.ctx.lineCap = "round";
this.ctx.lineJoin = "round";
this.ctx.strokeStyle = color;
this.ctx.lineWidth = lineWidth;
lineArr.forEach((e, index) => {
if (index > 0) {
this.ctx.lineTo(e.x, e.y);
} else {
this.ctx.moveTo(e.x, e.y);
}
});
this.ctx.stroke();
this.ctx.closePath();
if (isFill) {
this.ctx.fillStyle = color;
this.ctx.fill();
}
},