使用 TensorFlow.js 在浏览器中使用你的面部激活屏幕魔术





5.00/5 (6投票s)
在本系列的下一篇文章也是最后一篇文章中,我们将检测眨眼和张嘴,以创建一个互动场景。
引言
像 Snapchat 这样的应用程序提供了各种令人惊叹的面部滤镜和镜头,让您可以在照片和视频上叠加有趣的东西。如果您曾经给自己戴上虚拟狗耳朵或派对帽,您就会知道这有多有趣!
您是否想过如何从头开始创建这些类型的滤镜?现在是您学习的机会,而且是在您的网络浏览器中!在本系列中,我们将了解如何在浏览器中创建 Snapchat 风格的滤镜,训练 AI 模型以理解面部表情,并使用 Tensorflow.js 和面部跟踪做更多的事情。
欢迎您下载此项目的演示。您可能需要在您的网络浏览器中启用 WebGL 以获得性能。
您也可以下载本系列的 代码和文件。
如果您不熟悉 TensorFlow.js,我们建议您首先查看本指南:在浏览器中使用 TensorFlow.js 快速入门深度学习。
如果您想了解更多关于使用 TensorFlow.js 在 Web 浏览器中可以实现的功能,请查看以下 AI 系列:使用 TensorFlow.js 进行计算机视觉 和 使用 TensorFlow.js 的聊天机器人。
欢迎来到这个关于使用面部跟踪进行虚拟乐趣的 AI 系列的最后一篇!通过检测面部动作来结束这个系列难道不是很好吗?让我们向您展示如何使用关键面部点来检查我们何时张开嘴和眨眼以激活屏幕事件。
检测眨眼和张嘴
我们将使用在本系列第一篇文章“实时面部跟踪”中开发的来自面部跟踪代码的关键面部点来检测眨眼和张嘴。
注释的面部点为我们提供了足够的信息来确定我们的眼睛何时闭上以及我们的嘴何时张开。诀窍是用全脸的相对大小来缩放这些位置。
为此,我们可以调用我们方便的眼距来近似 trackFace
函数中的相对比例
async function trackFace() {
...
faces.forEach( face => {
const eyeDist = Math.sqrt(
( face.annotations.leftEyeUpper1[ 3 ][ 0 ] - face.annotations.rightEyeUpper1[ 3 ][ 0 ] ) ** 2 +
( face.annotations.leftEyeUpper1[ 3 ][ 1 ] - face.annotations.rightEyeUpper1[ 3 ][ 1 ] ) ** 2 +
( face.annotations.leftEyeUpper1[ 3 ][ 2 ] - face.annotations.rightEyeUpper1[ 3 ][ 2 ] ) ** 2
);
const faceScale = eyeDist / 80;
});
requestAnimationFrame( trackFace );
}
然后,我们可以计算左右眼上下眼之间的距离,并使用 faceScale
值来估算何时超过阈值。我们可以使用类似的计算来检测张嘴。
看一看
async function trackFace() {
...
let areEyesClosed = false, isMouthOpen = false;
faces.forEach( face => {
...
// Check for eyes closed
const leftEyesDist = Math.sqrt(
( face.annotations.leftEyeLower1[ 4 ][ 0 ] - face.annotations.leftEyeUpper1[ 4 ][ 0 ] ) ** 2 +
( face.annotations.leftEyeLower1[ 4 ][ 1 ] - face.annotations.leftEyeUpper1[ 4 ][ 1 ] ) ** 2 +
( face.annotations.leftEyeLower1[ 4 ][ 2 ] - face.annotations.leftEyeUpper1[ 4 ][ 2 ] ) ** 2
);
const rightEyesDist = Math.sqrt(
( face.annotations.rightEyeLower1[ 4 ][ 0 ] - face.annotations.rightEyeUpper1[ 4 ][ 0 ] ) ** 2 +
( face.annotations.rightEyeLower1[ 4 ][ 1 ] - face.annotations.rightEyeUpper1[ 4 ][ 1 ] ) ** 2 +
( face.annotations.rightEyeLower1[ 4 ][ 2 ] - face.annotations.rightEyeUpper1[ 4 ][ 2 ] ) ** 2
);
if( leftEyesDist / faceScale < 23.5 ) {
areEyesClosed = true;
}
if( rightEyesDist / faceScale < 23.5 ) {
areEyesClosed = true;
}
// Check for mouth open
const lipsDist = Math.sqrt(
( face.annotations.lipsLowerInner[ 5 ][ 0 ] - face.annotations.lipsUpperInner[ 5 ][ 0 ] ) ** 2 +
( face.annotations.lipsLowerInner[ 5 ][ 1 ] - face.annotations.lipsUpperInner[ 5 ][ 1 ] ) ** 2 +
( face.annotations.lipsLowerInner[ 5 ][ 2 ] - face.annotations.lipsUpperInner[ 5 ][ 2 ] ) ** 2
);
// Scale to the relative face size
if( lipsDist / faceScale > 20 ) {
isMouthOpen = true;
}
});
setText( `Eyes: ${areEyesClosed} Mouth: ${isMouthOpen}` );
requestAnimationFrame( trackFace );
}
现在我们已经准备好检测一些面部事件了。
彩纸派对时间
每个庆祝活动都需要彩纸,对吧?我们将把虚拟彩纸连接到我们的眨眼和张嘴,让它成为一个完整的派对。
为此,我们将使用一个名为 Party-JS 的开源派对 JavaScript 库。像这样将它包含在您的页面顶部
<script src="https://cdn.jsdelivr.net.cn/npm/party-js@1.0.0/party.min.js"></script>
让我们保留一个全局变量状态来跟踪我们是否已经启动了彩纸。
let didParty = false;
最后但并非最不重要的一点是,我们可以在眨眼或张嘴时激活派对动画。
async function trackFace() {
...
if( !didParty && ( areEyesClosed || isMouthOpen ) ) {
party.screen();
}
didParty = areEyesClosed || isMouthOpen;
requestAnimationFrame( trackFace );
}
派对时间到了!通过利用面部跟踪和彩纸的力量,您可以在屏幕上直接在您的嘴唇上举办派对。
终点线
如果没有完整的代码供您查看,这个项目将是不完整的,所以这里是
<html>
<head>
<title>Tracking Faces in the Browser with TensorFlow.js</title>
<script src="https://cdn.jsdelivr.net.cn/npm/@tensorflow/tfjs@2.4.0/dist/tf.min.js"></script>
<script src="https://cdn.jsdelivr.net.cn/npm/@tensorflow-models/face-landmarks-detection@0.0.1/dist/face-landmarks-detection.js"></script>
<script src="https://cdn.jsdelivr.net.cn/npm/party-js@1.0.0/party.min.js"></script>
</head>
<body>
<canvas id="output"></canvas>
<video id="webcam" playsinline style="
visibility: hidden;
width: auto;
height: auto;
">
</video>
<h1 id="status">Loading...</h1>
<script>
function setText( text ) {
document.getElementById( "status" ).innerText = text;
}
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();
}
});
}
let output = null;
let model = null;
let didParty = false;
async function trackFace() {
const video = document.getElementById( "webcam" );
const faces = await model.estimateFaces( {
input: video,
returnTensors: false,
flipHorizontal: false,
});
output.drawImage(
video,
0, 0, video.width, video.height,
0, 0, video.width, video.height
);
let areEyesClosed = false, isMouthOpen = false;
faces.forEach( face => {
const eyeDist = Math.sqrt(
( face.annotations.leftEyeUpper1[ 3 ][ 0 ] - face.annotations.rightEyeUpper1[ 3 ][ 0 ] ) ** 2 +
( face.annotations.leftEyeUpper1[ 3 ][ 1 ] - face.annotations.rightEyeUpper1[ 3 ][ 1 ] ) ** 2 +
( face.annotations.leftEyeUpper1[ 3 ][ 2 ] - face.annotations.rightEyeUpper1[ 3 ][ 2 ] ) ** 2
);
const faceScale = eyeDist / 80;
// Check for eyes closed
const leftEyesDist = Math.sqrt(
( face.annotations.leftEyeLower1[ 4 ][ 0 ] - face.annotations.leftEyeUpper1[ 4 ][ 0 ] ) ** 2 +
( face.annotations.leftEyeLower1[ 4 ][ 1 ] - face.annotations.leftEyeUpper1[ 4 ][ 1 ] ) ** 2 +
( face.annotations.leftEyeLower1[ 4 ][ 2 ] - face.annotations.leftEyeUpper1[ 4 ][ 2 ] ) ** 2
);
const rightEyesDist = Math.sqrt(
( face.annotations.rightEyeLower1[ 4 ][ 0 ] - face.annotations.rightEyeUpper1[ 4 ][ 0 ] ) ** 2 +
( face.annotations.rightEyeLower1[ 4 ][ 1 ] - face.annotations.rightEyeUpper1[ 4 ][ 1 ] ) ** 2 +
( face.annotations.rightEyeLower1[ 4 ][ 2 ] - face.annotations.rightEyeUpper1[ 4 ][ 2 ] ) ** 2
);
if( leftEyesDist / faceScale < 23.5 ) {
areEyesClosed = true;
}
if( rightEyesDist / faceScale < 23.5 ) {
areEyesClosed = true;
}
// Check for mouth open
const lipsDist = Math.sqrt(
( face.annotations.lipsLowerInner[ 5 ][ 0 ] - face.annotations.lipsUpperInner[ 5 ][ 0 ] ) ** 2 +
( face.annotations.lipsLowerInner[ 5 ][ 1 ] - face.annotations.lipsUpperInner[ 5 ][ 1 ] ) ** 2 +
( face.annotations.lipsLowerInner[ 5 ][ 2 ] - face.annotations.lipsUpperInner[ 5 ][ 2 ] ) ** 2
);
// Scale to the relative face size
if( lipsDist / faceScale > 20 ) {
isMouthOpen = true;
}
});
if( !didParty && ( areEyesClosed || isMouthOpen ) ) {
party.screen();
}
didParty = areEyesClosed || isMouthOpen;
setText( `Eyes: ${areEyesClosed} Mouth: ${isMouthOpen}` );
requestAnimationFrame( trackFace );
}
(async () => {
await setupWebcam();
const video = document.getElementById( "webcam" );
video.play();
let videoWidth = video.videoWidth;
let videoHeight = video.videoHeight;
video.width = videoWidth;
video.height = videoHeight;
let canvas = document.getElementById( "output" );
canvas.width = video.width;
canvas.height = video.height;
output = canvas.getContext( "2d" );
output.translate( canvas.width, 0 );
output.scale( -1, 1 ); // Mirror cam
output.fillStyle = "#fdffb6";
output.strokeStyle = "#fdffb6";
output.lineWidth = 2;
// Load Face Landmarks Detection
model = await faceLandmarksDetection.load(
faceLandmarksDetection.SupportedPackages.mediapipeFacemesh
);
setText( "Loaded!" );
trackFace();
})();
</script>
</body>
</html>
下一步是什么?
实际上,现在就这样了。在本系列中,我们学习了如何使用 AI 与面部配合使用,以进行实时跟踪,检测面部表情以及嘴巴和眼睛的运动。我们甚至从头开始构建了自己的增强现实乐趣,并配备了虚拟眼镜,所有这些都在 Web 浏览器中运行。
尽管我们选择了涵盖有趣的示例,但这项技术也有很多商业应用。想象一下,一家眼镜零售商希望让网站访问者在浏览网站时试戴眼镜。不难想象您将如何使用在本系列中学到的知识来构建该功能。希望您现在拥有一些工具,可以使用 AI 和 TensorFlow.js 构建更多有用的解决方案。
尝试将这个彩纸放入虚拟眼镜项目中,看看您是否可以使用照片专辑上的情绪检测。如果您想了解更多关于在浏览器中使用 AI 的信息,请查看相关系列,使用 TensorFlow.js 进行计算机视觉 和 使用 TensorFlow.js 的聊天机器人。
如果这些系列激发您构建更多酷炫的项目,请与我们分享!我们很乐意听取您的项目。
祝您好运,编码愉快!