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

от автора

Такие приложения, как Snapchat, предлагают удивительное разнообразие фильтров для лиц и объективов, которые позволяют накладывать интересные эффекты на фотографии и видео. Если вы когда-либо дарили себе виртуальные собачьи уши или праздничную шляпу, вы знаете, насколько это может быть весело!

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


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

Первый шаг к созданию фильтра для лиц с нуля – это обнаружение и локализация лиц на изображениях. Поэтому мы можем начать отсюда.

Лица можно отслеживать с помощью библиотеки TensorFlow.js и модели обнаружения ориентиров лиц, которая за пару миллисекунд может дать нам 486 различных ключевых точек в пространстве для каждого лица на изображении или в видеокадре. Особенно замечательно то, что эта модель может работать в рамках веб-страницы, поэтому вы также сможете отслеживать лица на мобильных устройствах, используя тот же код.

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

Отправная точка

Вот начальный шаблон веб-страницы, который мы будем использовать для отслеживания лиц.

Этот шаблон содержит:

  • библиотеки TensorFlow.js, необходимые для этого проекта;

  • индекс эталонной сетки для лица, заданный в файле triangles.js (включён в код проекта);

  • элемент canvas для визуализированных выходных данных;

  • скрытый элемент video для веб-камеры;

  • текстовый элемент статуса и служебная функция setText;

  • служебные функции drawLine и drawTriangle для элементов canvas.

<html>     <head>         <title>Real-Time Face Tracking 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="web/triangles.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();         }          function drawTriangle( ctx, x1, y1, x2, y2, x3, y3 ) {             ctx.beginPath();             ctx.moveTo( x1, y1 );             ctx.lineTo( x2, y2 );             ctx.lineTo( x3, y3 );             ctx.lineTo( x1, y1 );             ctx.stroke();         }          (async () => {             // TODO: Add code here         })();         </script>     </body> </html>

Использование API веб-камеры в HTML5 с TensorFlow.js

В JavaScript довольно просто запустить веб-камеру, если у вас есть для неё фрагмент кода. Вот служебная функция запуска веб-камеры и запроса доступа у пользователя:

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

Мы можем вызвать функцию setupWebcam в блоке async в нижней части нашего кода и заставить её воспроизводить видео веб-камеры после загрузки.

(async () => {     await setupWebcam();     const video = document.getElementById( "webcam" );     video.play(); })();

Затем давайте настроим выходной элемент canvas и подготовимся к рисованию линий и треугольников для ограничивающего прямоугольника и каркаса лица.

Контекст canvas будет использоваться для вывода результатов отслеживания лиц, поэтому мы можем сохранить их глобально за пределами блока async. Обратите внимание, что мы зеркально отразили изображение веб-камеры по горизонтали, чтобы поведение было более естественным, как в настоящем зеркале.

let output = null;  (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; })();

Давайте отследим некоторые лица

Теперь мы готовы! Всё, что нам нужно, это загрузить модель обнаружения ориентиров лица TensorFlow и применить её к кадрам нашей веб-камеры, чтобы показать результаты.

Во-первых, нам нужна глобальная переменная model для сохранения загруженной модели:

let model = null;

Затем мы можем загрузить модель в конец блока async и задать текст статуса, чтобы указать на готовность нашего приложения для отслеживания лиц:

// Load Face Landmarks Detection model = await faceLandmarksDetection.load(     faceLandmarksDetection.SupportedPackages.mediapipeFacemesh );  setText( "Loaded!" );

Теперь давайте создадим функцию trackFace, которая принимает видеокадры веб-камеры, применяет модель отслеживания лиц, копирует изображение веб-камеры в выходной элемент canvas, а затем рисует ограничивающий прямоугольник вокруг лица и треугольники сетки каркаса поверх лица.

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     );      faces.forEach( face => {         setText( `Face Tracking Confidence: ${face.faceInViewConfidence.toFixed( 3 )}` );          // 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 );          // Draw the face mesh         const keypoints = face.scaledMesh;         for( let i = 0; i < FaceTriangles.length / 3; i++ ) {             let pointA = keypoints[ FaceTriangles[ i * 3 ] ];             let pointB = keypoints[ FaceTriangles[ i * 3 + 1 ] ];             let pointC = keypoints[ FaceTriangles[ i * 3 + 2 ] ];             drawTriangle( output, pointA[ 0 ], pointA[ 1 ], pointB[ 0 ], pointB[ 1 ], pointC[ 0 ], pointC[ 1 ] );         }     });      requestAnimationFrame( trackFace ); }

Наконец, мы можем запустить первый кадр для отслеживания, вызвав эту функцию в конце нашего блока async:

(async () => {     ...      trackFace(); })();

Финишная прямая

Полный код должен выглядеть так:

<html>     <head>         <title>Real-Time Face Tracking 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="web/triangles.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();         }          function drawTriangle( ctx, x1, y1, x2, y2, x3, y3 ) {             ctx.beginPath();             ctx.moveTo( x1, y1 );             ctx.lineTo( x2, y2 );             ctx.lineTo( x3, y3 );             ctx.lineTo( x1, y1 );             ctx.stroke();         }          let output = null;         let model = null;          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();                 }             });         }          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             );              faces.forEach( face => {                 setText( `Face Tracking Confidence: ${face.faceInViewConfidence.toFixed( 3 )}` );                  // 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 );                  // Draw the face mesh                 const keypoints = face.scaledMesh;                 for( let i = 0; i < FaceTriangles.length / 3; i++ ) {                     let pointA = keypoints[ FaceTriangles[ i * 3 ] ];                     let pointB = keypoints[ FaceTriangles[ i * 3 + 1 ] ];                     let pointC = keypoints[ FaceTriangles[ i * 3 + 2 ] ];                     drawTriangle( output, pointA[ 0 ], pointA[ 1 ], pointB[ 0 ], pointB[ 1 ], pointC[ 0 ], pointC[ 1 ] );                 }             });              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 с видео с веб-камеры, мы смогли отслеживать лица в режиме реального времени прямо в браузере. Наш код отслеживания лиц также работает с изображениями, а ключевые моменты могли бы сказать нам больше, чем мы могли бы ожидать. Может быть, нам следует попробовать применить этот код к набору данных лиц, например FER+ Facial Expression Recognition (распознавание выражений лиц)?

В следующей статье этой серии мы используем глубокое обучение на отслеженных лицах из набора данных FER+ и попытаемся точно определить эмоции человека по точкам лица в браузере с помощью TensorFlow.js. До встречи завтра, в это же время.

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

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

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


Комментарии

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

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