Мы уже научились использовать искусственный интеллект (ИИ) в веб-браузере для отслеживания лиц в реальном времени и применять глубокое обучение для обнаружения и классификации эмоций на лице. Итак, мы собрали эти два компонента вместе и хотим узнать, сможем ли мы в реальном времени обнаруживать эмоции с помощью веб-камеры. В этой статье мы, используя транслируемое с веб-камеры видео нашего лица, узнаем, сможет ли модель реагировать на выражение лица в реальном времени.
Вы можете загрузить демоверсию этого проекта. Для обеспечения необходимой производительности может потребоваться включить в веб-браузере поддержку интерфейса WebGL. Вы также можете загрузить код и файлы для этой серии. Предполагается, что вы знакомы с JavaScript и HTML и имеете хотя бы базовое представление о нейронных сетях.
Добавление обнаружения эмоций на лице
В этом проекте мы протестируем нашу обученную модель обнаружения эмоций на лице на видео, транслируемом с веб-камеры. Мы начнём со стартового шаблона с окончательным кодом из проекта отслеживания лиц и внесём в него части кода для обнаружения эмоций на лице.
Давайте загрузим и применим нашу предварительно обученную модель выражений на лице. Начала мы определим некоторые глобальные переменные для обнаружения эмоций, как мы делали раньше:
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="http://personeltest.ru/aways/cdn.jsdelivr.net/npm/@tensorflow/tfjs@2.4.0/dist/tf.min.js"></script> <script src="http://personeltest.ru/aways/cdn.jsdelivr.net/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! В следующей статье мы вернёмся к нашей цели создать фильтр для лица в стиле Snapchat, используя то, что мы уже узнали об отслеживании лиц и добавлении 3D-визуализации посредством ThreeJS. Оставайтесь с нами! До встречи завтра, в это же время!
Отслеживание лиц в реальном времени в браузере с
использованием TensorFlow.js. Часть 2
Узнайте подробности, как получить Level Up по навыкам и зарплате или востребованную профессию с нуля, пройдя онлайн-курсы SkillFactory со скидкой 40% и промокодомHABR, который даст еще +10% скидки на обучение.
Другие профессии и курсы
ПРОФЕССИИ
КУРС