Отслеживание лиц в реальном времени в браузере с использованием TensorFlow.js. Часть 6

от автора

Активация экранной магии вашим лицом в браузере
Активация экранной магии вашим лицом в браузере

Вот и финал этой серии статей (ссылки на предыдущие части — в конце этого материала), в которой мы создавали в браузере фильтры в стиле Snapchat, обучая модель ИИ понимать выражения лиц и добились ещё большего, используя библиотеку Tensorflow.js и отслеживание лиц.

Было бы здорово закончить, реализовав обнаружение движения на лицах? Позвольте показать, как по ключевым точкам лица определять, когда мы открываем рот и моргаем глазами, чтобы активировать события, происходящие на экране.


Вы можете загрузить демоверсию этого проекта. Для обеспечения необходимой производительности может потребоваться включить в веб-браузере поддержку интерфейса WebGL. Вы также можете загрузить код и файлы для этой серии. Предполагается, что вы знакомы с JavaScript и HTML и имеете хотя бы базовое представление о нейронных сетях.

Обнаружение моргания глаз и открывания рта

Мы собираемся использовать ключевые точки лица, предоставляемые кодом отслеживания лиц, который мы разработали в первой статье этой серии, а также отслеживание лица в реальном времени, чтобы обнаружить моргание глаз и открывание рта.

Аннотированные точки лица дают достаточно информации, чтобы определить, когда глаза закрыты и когда открыт рот. Хитрость заключается в том, чтобы масштабировать положения с учетом относительного размера в анфас.

Для этого мы можем обратиться к удобному расстоянию между глазами, чтобы аппроксимировать относительную шкалу в функции 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 ); }

Теперь мы готовы к обнаружению некоторых движений на лицах.

Время вечеринки с конфетти

На каждом празднике требуется конфетти, верно? Мы собираемся соединить виртуальное конфетти с моргающими глазами и открывающимся ртом, чтобы получилась настоящая вечеринка.

Для этого мы будем использовать библиотеку JavaScript с открытым исходным кодом, которая называется Party-JS. Включите её в верхней части своей страницы следующим образом:

<script src="https://cdn.jsdelivr.net/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/npm/@tensorflow/tfjs@2.4.0/dist/tf.min.js"></script>         <script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/face-landmarks-detection@0.0.1/dist/face-landmarks-detection.js"></script>         <script src="https://cdn.jsdelivr.net/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>

Что дальше?

Собственно, на этом пока всё. В этой серии статей мы научились применять ИИ к лицам, чтобы отслеживать их в режиме реального времени, а также определять эмоции на лице и движения рта и глаз. Мы даже создали с нуля собственную игру с дополненной реальностью и виртуальными очками, и всё это работает в веб-браузере.

Хотя мы выбрали для применения забавные примеры, для этой технологии также существует множество приложений в бизнесе. Представьте продавца очков, который хочет позволить посетителям своего веб-сайта выбирать очки, примеряя их. Нетрудно представить, как вы будете использовать знания, приобретённые в этой серии статей, для создания нужных функциональных возможностей. Надеюсь, теперь у вас есть инструменты для создания более полезных решений с использованием ИИ и TensorFlow.js.

Попробуйте реализовать конфетти в проекте виртуальных очков. Проверьте, сможете ли вы применить обнаружение эмоций к фотоальбому.

И если эти серии статей вдохновят вас на создание ещё более крутых проектов, поделитесь ими в комментариях! Мы будем рады узнать о ваших проектах.

Удачи и удовольствия от программирования!

Узнайте подробности, как получить Level Up по навыкам и зарплате или востребованную профессию с нуля, пройдя онлайн-курсы SkillFactory со скидкой 40% и промокодом HABR, который даст еще +10% скидки на обучение.

Другие профессии и курсы

ссылка на оригинал статьи https://habr.com/ru/company/skillfactory/blog/545338/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *