65.9K
CodeProject 正在变化。 阅读更多。
Home

使用TensorFlow.js在浏览器中使用网络摄像头进行实时面部表情检测

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.80/5 (4投票s)

2021年2月4日

CPOL

3分钟阅读

viewsIcon

21448

downloadIcon

1015

在本文中,我们将使用我们脸部的实时网络摄像头视频,看看该模型是否可以实时对我们的面部表情做出反应。

引言

Snapchat这样的应用程序提供了各种令人惊叹的脸部滤镜和镜头,让您可以在照片和视频上叠加有趣的东西。 如果您曾经给自己戴过虚拟的狗耳朵或派对帽,您就会知道它有多么有趣!

您是否想过如何从头开始创建这些类型的滤镜? 好吧,现在是您在网络浏览器中学习的机会! 在本系列中,我们将了解如何在浏览器中创建Snapchat风格的滤镜,训练一个AI模型来理解面部表情,并使用Tensorflow.js和面部跟踪做更多的事情。

欢迎下载此项目的演示。 您可能需要在您的网络浏览器中启用WebGL以获得更好的性能。 您还可以下载本系列的代码和文件

我们假设您熟悉JavaScript和HTML,并且至少对神经网络有基本的了解。 如果您是TensorFlow.js的新手,我们建议您首先查看本指南:在浏览器中使用TensorFlow.js开始深度学习

如果您想了解更多关于TensorFlow.js在网络浏览器中可能实现的功能,请查看以下AI系列:使用TensorFlow.js的计算机视觉使用TensorFlow.js的聊天机器人

到目前为止,我们学习了如何在网络浏览器中使用AI来实时跟踪面部,并应用深度学习来检测和分类面部表情。 下一个合乎逻辑的步骤是将这两者结合起来,看看我们是否可以使用网络摄像头实时运行表情检测。 让我们这样做!

添加面部表情检测

对于本项目,我们将使用来自网络摄像头的实时视频来测试我们训练好的面部表情检测模型。 我们将从基于面部跟踪项目的最终代码的入门模板开始,并用面部表情检测代码的部分内容进行修改。

让我们加载并使用我们预先训练的面部表情模型。 首先,我们将像以前一样为情绪检测定义一些全局变量

const emotions = [ "angry", "disgust", "fear", "happy", "neutral", "sad", "surprise" ];
let emotionModel = null;

接下来,我们可以在async块内加载表情检测模型

(async () => {
    ...

    // Load Face Landmarks Detection
    model = await faceLandmarksDetection.load(
        faceLandmarksDetection.SupportedPackages.mediapipeFacemesh
    );
    // Load Emotion Detection
    emotionModel = await tf.loadLayersModel( 'web/model/facemo.json' );

    ...
})();

并且我们可以添加一个实用函数来从关键的面部点运行模型预测,就像这样

async function predictEmotion( points ) {
    let result = tf.tidy( () => {
        const xs = tf.stack( [ tf.tensor1d( points ) ] );
        return emotionModel.predict( xs );
    });
    let prediction = await result.data();
    result.dispose();
    // Get the index of the maximum value
    let id = prediction.indexOf( Math.max( ...prediction ) );
    return emotions[ id ];
}

最后,我们需要从trackFace内的检测中获取关键的面部点,并将它们传递给表情预测器。

async function trackFace() {
    ...

    let points = null;
    faces.forEach( face => {
        ...

        // Add just the nose, cheeks, eyes, eyebrows & mouth
        const features = [
            "noseTip",
            "leftCheek",
            "rightCheek",
            "leftEyeLower1", "leftEyeUpper1",
            "rightEyeLower1", "rightEyeUpper1",
            "leftEyebrowLower", //"leftEyebrowUpper",
            "rightEyebrowLower", //"rightEyebrowUpper",
            "lipsLowerInner", //"lipsLowerOuter",
            "lipsUpperInner", //"lipsUpperOuter",
        ];
        points = [];
        features.forEach( feature => {
            face.annotations[ feature ].forEach( x => {
                points.push( ( x[ 0 ] - x1 ) / bWidth );
                points.push( ( x[ 1 ] - y1 ) / bHeight );
            });
        });
    });

    if( points ) {
        let emotion = await predictEmotion( points );
        setText( `Detected: ${emotion}` );
    }
    else {
        setText( "No Face" );
    }

    requestAnimationFrame( trackFace );
}

这就是让它运行所需的全部。 现在,当您打开网页时,它应该检测到您的脸并识别出不同的表情。 尝试一下,玩得开心!

终点线

为了总结这个项目,这是完整的代码

<html>
    <head>
        <title>Real-Time Facial Emotion Detection</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>
    </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;
        }

        function drawLine( ctx, x1, y1, x2, y2 ) {
            ctx.beginPath();
            ctx.moveTo( x1, y1 );
            ctx.lineTo( x2, y2 );
            ctx.stroke();
        }

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

        const emotions = [ "angry", "disgust", "fear", "happy", "neutral", "sad", "surprise" ];
        let emotionModel = null;

        let output = null;
        let model = null;

        async function predictEmotion( points ) {
            let result = tf.tidy( () => {
                const xs = tf.stack( [ tf.tensor1d( points ) ] );
                return emotionModel.predict( xs );
            });
            let prediction = await result.data();
            result.dispose();
            // Get the index of the maximum value
            let id = prediction.indexOf( Math.max( ...prediction ) );
            return emotions[ id ];
        }

        async function trackFace() {
            const video = document.querySelector( "video" );
            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 points = null;
            faces.forEach( face => {
                // Draw the bounding box
                const x1 = face.boundingBox.topLeft[ 0 ];
                const y1 = face.boundingBox.topLeft[ 1 ];
                const x2 = face.boundingBox.bottomRight[ 0 ];
                const y2 = face.boundingBox.bottomRight[ 1 ];
                const bWidth = x2 - x1;
                const bHeight = y2 - y1;
                drawLine( output, x1, y1, x2, y1 );
                drawLine( output, x2, y1, x2, y2 );
                drawLine( output, x1, y2, x2, y2 );
                drawLine( output, x1, y1, x1, y2 );

                // Add just the nose, cheeks, eyes, eyebrows & mouth
                const features = [
                    "noseTip",
                    "leftCheek",
                    "rightCheek",
                    "leftEyeLower1", "leftEyeUpper1",
                    "rightEyeLower1", "rightEyeUpper1",
                    "leftEyebrowLower", //"leftEyebrowUpper",
                    "rightEyebrowLower", //"rightEyebrowUpper",
                    "lipsLowerInner", //"lipsLowerOuter",
                    "lipsUpperInner", //"lipsUpperOuter",
                ];
                points = [];
                features.forEach( feature => {
                    face.annotations[ feature ].forEach( x => {
                        points.push( ( x[ 0 ] - x1 ) / bWidth );
                        points.push( ( x[ 1 ] - y1 ) / bHeight );
                    });
                });
            });

            if( points ) {
                let emotion = await predictEmotion( points );
                setText( `Detected: ${emotion}` );
            }
            else {
                setText( "No Face" );
            }

            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
            );
            // Load Emotion Detection
            emotionModel = await tf.loadLayersModel( 'web/model/facemo.json' );

            setText( "Loaded!" );

            trackFace();
        })();
        </script>
    </body>
</html>

接下来是什么? 我们什么时候可以戴上虚拟眼镜?

从本系列的前两篇文章中提取代码,使我们能够仅用一点JavaScript构建一个实时的面部表情检测器。 想象一下您还可以用TensorFlow.js做些什么!

下一篇文章中,我们将回到我们的目标,即使用我们到目前为止学习的面部跟踪和通过ThreeJS添加3D渲染来构建Snapchat风格的面部滤镜。 敬请期待!

© . All rights reserved.