AR口红试色|如何挑到一只适合女朋友的口红?

2,077 阅读3分钟

我正在参加「码上掘金月赛第1期」,点击此处查看详情

//掘金由于安全原因没有在 iframe标签上设置allow="microphone *;camera *"导致摄像头打开失败!
//请点击右上角“查看详情”查看!或复制下方链接查看
https://code.juejin.cn/pen/7197979265554448442

马上就要情人节了 相信大家都在为不知道送对象什么礼物而苦恼,不管送什么礼物加上一支口红总是没有错的,但是口红明明是“送你一支口红,每天还我一点”的浪漫,结果成了死亡芭比粉甚至口黄口绿口蓝的大型车祸现场。

如果说,女生眼中的口红色号是这样的:那男生眼中的口红色号则是这样的:
image.pngimage.png

为了避免这种尴尬,我们可以在网页中实现一个口红试色功能,接下来让我们一起开始吧!

架构和概念

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方法接受多种格式的图像和视频,包括 :HTMLVideoElementHTMLImageElement和。如果你想要更多的选项,你可以传入第二个参数。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);
    }
}
image.png

绘制唇部颜色

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();
    }
},

文档

google.github.io/mediapipe/s…

github.com/tensorflow/…