[JavaScript, Программирование, HTML, TensorFlow] Отслеживание лиц в реальном времени в браузере с использованием TensorFlow.js. Часть 4 (перевод)
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
В 4 части (вы же прочли первую, вторую и третью, да?) мы возвращаемся к нашей цели – создание фильтра для лица в стиле Snapchat, используя то, что мы уже узнали об отслеживании лиц и добавлении 3D-визуализации посредством ThreeJS. В этой статье мы собираемся использовать ключевые точки лица для виртуальной визуализации 3D-модели поверх видео с веб-камеры, чтобы немного развлечься с дополненной реальностью.Вы можете загрузить демоверсию этого проекта. Для обеспечения необходимой производительности может потребоваться включить в веб-браузере поддержку интерфейса WebGL. Вы также можете загрузить код и файлы для этой серии. Предполагается, что вы знакомы с JavaScript и HTML и имеете хотя бы базовое представление о нейронных сетях. Добавление 3D-графики с помощью ThreeJSЭтот проект будет основан на коде проекта отслеживания лиц, который мы создали в начале этой серии. Мы добавим наложение 3D-сцены на исходное полотно.ThreeJS позволяет относительно легко работать с 3D-графикой, поэтому мы собираемся с помощью этой библиотеки визуализировать виртуальные очки поверх наших лиц.В верхней части страницы нам нужно включить два файла скриптов, чтобы добавить ThreeJS и загрузчик файлов в формате GLTF для модели виртуальных очков, которую мы будем использовать:
<script src="https://cdn.jsdelivr.net/npm/three@0.123.0/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.123.0/examples/js/loaders/GLTFLoader.js"></script>
Чтобы упростить задачу и не беспокоиться о том, как поместить текстуру веб-камеры на сцену, мы можем наложить дополнительное прозрачное полотно (canvas) и нарисовать виртуальные очки на нём. Мы используем CSS-код, приведённый ниже над тегом body, поместив выходное полотно (output) в контейнер и добавив полотно наложения (overlay).
<style>
.canvas-container {
position: relative;
width: auto;
height: auto;
}
.canvas-container canvas {
position: absolute;
left: 0;
width: auto;
height: auto;
}
</style>
<body>
<div class="canvas-container">
<canvas id="output"></canvas>
<canvas id="overlay"></canvas>
</div>
...
</body>
Для 3D-сцены требуется несколько переменных, и мы можем добавить служебную функцию загрузки 3D-модели для файлов GLTF:
<style>
.canvas-container {
position: relative;
width: auto;
height: auto;
}
.canvas-container canvas {
position: absolute;
left: 0;
width: auto;
height: auto;
}
</style>
<body>
<div class="canvas-container">
<canvas id="output"></canvas>
<canvas id="overlay"></canvas>
</div>
...
</body>
Теперь мы можем инициализировать все компоненты нашего блока async, начиная с размера полотна наложения, как это было сделано с выходным полотном:
(async () => {
...
let canvas = document.getElementById( "output" );
canvas.width = video.width;
canvas.height = video.height;
let overlay = document.getElementById( "overlay" );
overlay.width = video.width;
overlay.height = video.height;
...
})();
Также необходимо задать переменные renderer, scene и camera. Даже если вы не знакомы с трёхмерной перспективой и математикой камеры, вам не надо волноваться. Этот код просто располагает камеру сцены так, чтобы ширина и высота видео веб-камеры соответствовали координатам трёхмерного пространства:
(async () => {
...
// Load Face Landmarks Detection
model = await faceLandmarksDetection.load(
faceLandmarksDetection.SupportedPackages.mediapipeFacemesh
);
renderer = new THREE.WebGLRenderer({
canvas: document.getElementById( "overlay" ),
alpha: true
});
camera = new THREE.PerspectiveCamera( 45, 1, 0.1, 2000 );
camera.position.x = videoWidth / 2;
camera.position.y = -videoHeight / 2;
camera.position.z = -( videoHeight / 2 ) / Math.tan( 45 / 2 ); // distance to z should be tan( fov / 2 )
scene = new THREE.Scene();
scene.add( new THREE.AmbientLight( 0xcccccc, 0.4 ) );
camera.add( new THREE.PointLight( 0xffffff, 0.8 ) );
scene.add( camera );
camera.lookAt( { x: videoWidth / 2, y: -videoHeight / 2, z: 0, isVector3: true } );
...
})();
Нам нужно добавить в функцию trackFace всего лишь одну строку кода для визуализации сцены поверх выходных данных отслеживания лица:
async function trackFace() {
const video = document.querySelector( "video" );
output.drawImage(
video,
0, 0, video.width, video.height,
0, 0, video.width, video.height
);
renderer.render( scene, camera );
const faces = await model.estimateFaces( {
input: video,
returnTensors: false,
flipHorizontal: false,
});
...
}
Последний этап этого ребуса перед отображением виртуальных объектов на нашем лице – загрузка 3D-модели виртуальных очков. Мы нашли паруочков в форме сердца от Maximkuzlin на SketchFab. При желании вы можете загрузить и использовать другой объект.Здесь показано, как загрузить объект и добавить его в сцену до вызова функции trackFace:Размещение виртуальных очков на отслеживаемом лицеТеперь начинается самое интересное – наденем наши виртуальные очки.Помеченные аннотации, предоставляемые моделью отслеживания лиц TensorFlow, включают массив координат MidwayBetweenEyes, в котором координаты X и Y соответствуют экрану, а координата Z добавляет экрану глубины. Это делает размещение очков на наших глазах довольно простой задачей.Необходимо сделать координату Y отрицательной, так как в системе координат двумерного экрана положительная ось Y направлена вниз, но в пространственной системе координат указывает вверх. Мы также вычтем из значения координаты Z расстояние или глубину камеры, чтобы получить правильное расстояния в сцене.
glasses.position.x = face.annotations.midwayBetweenEyes[ 0 ][ 0 ];
glasses.position.y = -face.annotations.midwayBetweenEyes[ 0 ][ 1 ];
glasses.position.z = -camera.position.z + face.annotations.midwayBetweenEyes[ 0 ][ 2 ];
Теперь нужно рассчитать ориентацию и масштаб очков. Это возможно, если мы определим направление «вверх» относительно нашего лица, которое указывает на макушку нашей головы, и расстояние между глазами.Оценить направление «вверх» можно с помощью вектора из массива midwayBetweenEyes, использованного для очков, вместе с отслеживаемой точкой для нижней части носа. Затем нормируем его длину следующим образом:
glasses.up.x = face.annotations.midwayBetweenEyes[ 0 ][ 0 ] - face.annotations.noseBottom[ 0 ][ 0 ];
glasses.up.y = -( face.annotations.midwayBetweenEyes[ 0 ][ 1 ] - face.annotations.noseBottom[ 0 ][ 1 ] );
glasses.up.z = face.annotations.midwayBetweenEyes[ 0 ][ 2 ] - face.annotations.noseBottom[ 0 ][ 2 ];
const length = Math.sqrt( glasses.up.x ** 2 + glasses.up.y ** 2 + glasses.up.z ** 2 );
glasses.up.x /= length;
glasses.up.y /= length;
glasses.up.z /= length;
Чтобы получить относительный размер головы, можно вычислить расстояние между глазами:
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
);
Наконец, мы масштабируем очки на основе значения eyeDist и ориентируем очки по оси Z, используя угол между вектором «вверх» и осью Y. И вуаля!Выполните свой код и проверьте результат.
Прежде чем перейти к следующей части этой серии, давайте посмотрим на полный код, собранный вместе:Простыня с кодом
<html>
<head>
<title>Creating a Snapchat-Style Virtual Glasses Face Filter</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/three@0.123.0/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.123.0/examples/js/loaders/GLTFLoader.js"></script>
</head>
<style>
.canvas-container {
position: relative;
width: auto;
height: auto;
}
.canvas-container canvas {
position: absolute;
left: 0;
width: auto;
height: auto;
}
</style>
<body>
<div class="canvas-container">
<canvas id="output"></canvas>
<canvas id="overlay"></canvas>
</div>
<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();
}
});
}
let output = null;
let model = null;
let renderer = null;
let scene = null;
let camera = null;
let glasses = null;
function loadModel( file ) {
return new Promise( ( res, rej ) => {
const loader = new THREE.GLTFLoader();
loader.load( file, function ( gltf ) {
res( gltf.scene );
}, undefined, function ( error ) {
rej( error );
} );
});
}
async function trackFace() {
const video = document.querySelector( "video" );
output.drawImage(
video,
0, 0, video.width, video.height,
0, 0, video.width, video.height
);
renderer.render( scene, camera );
const faces = await model.estimateFaces( {
input: video,
returnTensors: false,
flipHorizontal: false,
});
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 );
glasses.position.x = face.annotations.midwayBetweenEyes[ 0 ][ 0 ];
glasses.position.y = -face.annotations.midwayBetweenEyes[ 0 ][ 1 ];
glasses.position.z = -camera.position.z + face.annotations.midwayBetweenEyes[ 0 ][ 2 ];
// Calculate an Up-Vector using the eyes position and the bottom of the nose
glasses.up.x = face.annotations.midwayBetweenEyes[ 0 ][ 0 ] - face.annotations.noseBottom[ 0 ][ 0 ];
glasses.up.y = -( face.annotations.midwayBetweenEyes[ 0 ][ 1 ] - face.annotations.noseBottom[ 0 ][ 1 ] );
glasses.up.z = face.annotations.midwayBetweenEyes[ 0 ][ 2 ] - face.annotations.noseBottom[ 0 ][ 2 ];
const length = Math.sqrt( glasses.up.x ** 2 + glasses.up.y ** 2 + glasses.up.z ** 2 );
glasses.up.x /= length;
glasses.up.y /= length;
glasses.up.z /= length;
// Scale to the size of the head
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
);
glasses.scale.x = eyeDist / 6;
glasses.scale.y = eyeDist / 6;
glasses.scale.z = eyeDist / 6;
glasses.rotation.y = Math.PI;
glasses.rotation.z = Math.PI / 2 - Math.acos( glasses.up.x );
});
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;
let overlay = document.getElementById( "overlay" );
overlay.width = video.width;
overlay.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
);
renderer = new THREE.WebGLRenderer({
canvas: document.getElementById( "overlay" ),
alpha: true
});
camera = new THREE.PerspectiveCamera( 45, 1, 0.1, 2000 );
camera.position.x = videoWidth / 2;
camera.position.y = -videoHeight / 2;
camera.position.z = -( videoHeight / 2 ) / Math.tan( 45 / 2 ); // distance to z should be tan( fov / 2 )
scene = new THREE.Scene();
scene.add( new THREE.AmbientLight( 0xcccccc, 0.4 ) );
camera.add( new THREE.PointLight( 0xffffff, 0.8 ) );
scene.add( camera );
camera.lookAt( { x: videoWidth / 2, y: -videoHeight / 2, z: 0, isVector3: true } );
// Glasses from https://sketchfab.com/3d-models/heart-glasses-ef812c7e7dc14f6b8783ccb516b3495c
glasses = await loadModel( "web/3d/heart_glasses.gltf" );
scene.add( glasses );
setText( "Loaded!" );
trackFace();
})();
</script>
</body>
</html>
Чт дальше? Что если также добавить обнаружение эмоций на лице?Поверите ли, что всё это возможно на одной веб-странице? Добавив 3D-объекты к функции отслеживания лиц в реальном времени, мы сотворили волшебство с помощью камеры прямо в веб-браузере. Вы можете подумать: «Но очки в форме сердца существуют в реальной жизни…» И это правда! А что, если мы создадим что-то действительно волшебное, например шляпу… которая знает, что мы чувствуем?Давайте в следующей статье создадим волшебную шляпу (как в Хогвартсе!) для обнаружения эмоций и посмотрим, сможем ли мы сделать невозможное возможным, ещё больше используя библиотеку TensorFlow.js! До встречи завтра, в это же время.
- Отслеживание лиц в реальном времени в браузере. Часть 1
- Отслеживание лиц в реальном времени в браузере. Часть 2
- Отслеживание лиц в реальном времени в браузере. Часть 3
Узнайте подробности, как получить Level Up по навыкам и зарплате или востребованную профессию с нуля, пройдя онлайн-курсы SkillFactory со скидкой 40% и промокодом HABR, который даст еще +10% скидки на обучение.
Другие профессии и курсыПРОФЕССИИ
- Профессия Java-разработчик
- Профессия QA-инженер на JAVA
- Профессия Frontend-разработчик
- Профессия Этичный хакер
- Профессия C++ разработчик
- Профессия Разработчик игр на Unity
- Профессия Веб-разработчик
- Профессия iOS-разработчик с нуля
- Профессия Android-разработчик с нуля
КУРСЫ
- Курс по Machine Learning
- Курс "Математика и Machine Learning для Data Science"
- Курс "Machine Learning и Deep Learning"
- Курс "Python для веб-разработки"
- Курс "Алгоритмы и структуры данных"
- Курс по аналитике данных
- Курс по DevOps
===========
Источник:
habr.com
===========
===========
Автор оригинала: Raphael Mun
===========Похожие новости:
- [Программирование, Разработка мобильных приложений, Разработка под Android, Kotlin] Влияние data-классов на вес приложения
- [Программирование, Git, GitHub, Лайфхаки для гиков] Продвинутые функции гита, о которых вы, возможно, не знали (перевод)
- [Программирование, Хакатоны, Управление разработкой, Учебный процесс в IT, Развитие стартапа] Фонд «Сколково» и IQпарк Уфы запускают онлайн-хакатон UFA SuperHero с призовым фондом 300 000 рублей
- [Программирование] Некоторые программистские заблуждения о времени
- [Карьера в IT-индустрии, Финансы в IT] Dice и Hired опубликовали ежегодную статистику самых высоких зарплат программистов в США
- [Python, Программирование, Машинное обучение] Как ML помогает при аудите качества клиентского сервиса
- [Разработка веб-сайтов, JavaScript, Лайфхаки для гиков] Как превратить Google-таблицы в REST API и использовать их с React-приложением (перевод)
- [Разработка систем связи, Программирование микроконтроллеров] Составное устройство USB на STM32. Часть 2: USB Audio Speaker
- [Программирование] Трудности перевода
- [DIY или Сделай сам, Лайфхаки для гиков, Электроника для начинающих] Как сделать волоконно-оптическую светодиодную лампу (перевод)
Теги для поиска: #_javascript, #_programmirovanie (Программирование), #_html, #_tensorflow, #_skillfactory, #_programmirovanie (программирование), #_glubokoe_obuchenie (глубокое обучение), #_html, #_tensorflowjs, #_tensorflow, #_javascript, #_raspoznavanie_lits (распознавание лиц), #_lajfhaki (лайфхаки), #_blog_kompanii_skillfactory (
Блог компании SkillFactory
), #_javascript, #_programmirovanie (
Программирование
), #_html, #_tensorflow
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 13:15
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
В 4 части (вы же прочли первую, вторую и третью, да?) мы возвращаемся к нашей цели – создание фильтра для лица в стиле Snapchat, используя то, что мы уже узнали об отслеживании лиц и добавлении 3D-визуализации посредством ThreeJS. В этой статье мы собираемся использовать ключевые точки лица для виртуальной визуализации 3D-модели поверх видео с веб-камеры, чтобы немного развлечься с дополненной реальностью.Вы можете загрузить демоверсию этого проекта. Для обеспечения необходимой производительности может потребоваться включить в веб-браузере поддержку интерфейса WebGL. Вы также можете загрузить код и файлы для этой серии. Предполагается, что вы знакомы с JavaScript и HTML и имеете хотя бы базовое представление о нейронных сетях. Добавление 3D-графики с помощью ThreeJSЭтот проект будет основан на коде проекта отслеживания лиц, который мы создали в начале этой серии. Мы добавим наложение 3D-сцены на исходное полотно.ThreeJS позволяет относительно легко работать с 3D-графикой, поэтому мы собираемся с помощью этой библиотеки визуализировать виртуальные очки поверх наших лиц.В верхней части страницы нам нужно включить два файла скриптов, чтобы добавить ThreeJS и загрузчик файлов в формате GLTF для модели виртуальных очков, которую мы будем использовать: <script src="https://cdn.jsdelivr.net/npm/three@0.123.0/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.123.0/examples/js/loaders/GLTFLoader.js"></script> <style>
.canvas-container { position: relative; width: auto; height: auto; } .canvas-container canvas { position: absolute; left: 0; width: auto; height: auto; } </style> <body> <div class="canvas-container"> <canvas id="output"></canvas> <canvas id="overlay"></canvas> </div> ... </body> <style>
.canvas-container { position: relative; width: auto; height: auto; } .canvas-container canvas { position: absolute; left: 0; width: auto; height: auto; } </style> <body> <div class="canvas-container"> <canvas id="output"></canvas> <canvas id="overlay"></canvas> </div> ... </body> (async () => {
... let canvas = document.getElementById( "output" ); canvas.width = video.width; canvas.height = video.height; let overlay = document.getElementById( "overlay" ); overlay.width = video.width; overlay.height = video.height; ... })(); (async () => {
... // Load Face Landmarks Detection model = await faceLandmarksDetection.load( faceLandmarksDetection.SupportedPackages.mediapipeFacemesh ); renderer = new THREE.WebGLRenderer({ canvas: document.getElementById( "overlay" ), alpha: true }); camera = new THREE.PerspectiveCamera( 45, 1, 0.1, 2000 ); camera.position.x = videoWidth / 2; camera.position.y = -videoHeight / 2; camera.position.z = -( videoHeight / 2 ) / Math.tan( 45 / 2 ); // distance to z should be tan( fov / 2 ) scene = new THREE.Scene(); scene.add( new THREE.AmbientLight( 0xcccccc, 0.4 ) ); camera.add( new THREE.PointLight( 0xffffff, 0.8 ) ); scene.add( camera ); camera.lookAt( { x: videoWidth / 2, y: -videoHeight / 2, z: 0, isVector3: true } ); ... })(); async function trackFace() {
const video = document.querySelector( "video" ); output.drawImage( video, 0, 0, video.width, video.height, 0, 0, video.width, video.height ); renderer.render( scene, camera ); const faces = await model.estimateFaces( { input: video, returnTensors: false, flipHorizontal: false, }); ... } glasses.position.x = face.annotations.midwayBetweenEyes[ 0 ][ 0 ];
glasses.position.y = -face.annotations.midwayBetweenEyes[ 0 ][ 1 ]; glasses.position.z = -camera.position.z + face.annotations.midwayBetweenEyes[ 0 ][ 2 ]; glasses.up.x = face.annotations.midwayBetweenEyes[ 0 ][ 0 ] - face.annotations.noseBottom[ 0 ][ 0 ];
glasses.up.y = -( face.annotations.midwayBetweenEyes[ 0 ][ 1 ] - face.annotations.noseBottom[ 0 ][ 1 ] ); glasses.up.z = face.annotations.midwayBetweenEyes[ 0 ][ 2 ] - face.annotations.noseBottom[ 0 ][ 2 ]; const length = Math.sqrt( glasses.up.x ** 2 + glasses.up.y ** 2 + glasses.up.z ** 2 ); glasses.up.x /= length; glasses.up.y /= length; glasses.up.z /= length; 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 ); Прежде чем перейти к следующей части этой серии, давайте посмотрим на полный код, собранный вместе:Простыня с кодом <html>
<head> <title>Creating a Snapchat-Style Virtual Glasses Face Filter</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/three@0.123.0/build/three.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/three@0.123.0/examples/js/loaders/GLTFLoader.js"></script> </head> <style> .canvas-container { position: relative; width: auto; height: auto; } .canvas-container canvas { position: absolute; left: 0; width: auto; height: auto; } </style> <body> <div class="canvas-container"> <canvas id="output"></canvas> <canvas id="overlay"></canvas> </div> <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(); } }); } let output = null; let model = null; let renderer = null; let scene = null; let camera = null; let glasses = null; function loadModel( file ) { return new Promise( ( res, rej ) => { const loader = new THREE.GLTFLoader(); loader.load( file, function ( gltf ) { res( gltf.scene ); }, undefined, function ( error ) { rej( error ); } ); }); } async function trackFace() { const video = document.querySelector( "video" ); output.drawImage( video, 0, 0, video.width, video.height, 0, 0, video.width, video.height ); renderer.render( scene, camera ); const faces = await model.estimateFaces( { input: video, returnTensors: false, flipHorizontal: false, }); 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 ); glasses.position.x = face.annotations.midwayBetweenEyes[ 0 ][ 0 ]; glasses.position.y = -face.annotations.midwayBetweenEyes[ 0 ][ 1 ]; glasses.position.z = -camera.position.z + face.annotations.midwayBetweenEyes[ 0 ][ 2 ]; // Calculate an Up-Vector using the eyes position and the bottom of the nose glasses.up.x = face.annotations.midwayBetweenEyes[ 0 ][ 0 ] - face.annotations.noseBottom[ 0 ][ 0 ]; glasses.up.y = -( face.annotations.midwayBetweenEyes[ 0 ][ 1 ] - face.annotations.noseBottom[ 0 ][ 1 ] ); glasses.up.z = face.annotations.midwayBetweenEyes[ 0 ][ 2 ] - face.annotations.noseBottom[ 0 ][ 2 ]; const length = Math.sqrt( glasses.up.x ** 2 + glasses.up.y ** 2 + glasses.up.z ** 2 ); glasses.up.x /= length; glasses.up.y /= length; glasses.up.z /= length; // Scale to the size of the head 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 ); glasses.scale.x = eyeDist / 6; glasses.scale.y = eyeDist / 6; glasses.scale.z = eyeDist / 6; glasses.rotation.y = Math.PI; glasses.rotation.z = Math.PI / 2 - Math.acos( glasses.up.x ); }); 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; let overlay = document.getElementById( "overlay" ); overlay.width = video.width; overlay.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 ); renderer = new THREE.WebGLRenderer({ canvas: document.getElementById( "overlay" ), alpha: true }); camera = new THREE.PerspectiveCamera( 45, 1, 0.1, 2000 ); camera.position.x = videoWidth / 2; camera.position.y = -videoHeight / 2; camera.position.z = -( videoHeight / 2 ) / Math.tan( 45 / 2 ); // distance to z should be tan( fov / 2 ) scene = new THREE.Scene(); scene.add( new THREE.AmbientLight( 0xcccccc, 0.4 ) ); camera.add( new THREE.PointLight( 0xffffff, 0.8 ) ); scene.add( camera ); camera.lookAt( { x: videoWidth / 2, y: -videoHeight / 2, z: 0, isVector3: true } ); // Glasses from https://sketchfab.com/3d-models/heart-glasses-ef812c7e7dc14f6b8783ccb516b3495c glasses = await loadModel( "web/3d/heart_glasses.gltf" ); scene.add( glasses ); setText( "Loaded!" ); trackFace(); })(); </script> </body> </html>
Узнайте подробности, как получить Level Up по навыкам и зарплате или востребованную профессию с нуля, пройдя онлайн-курсы SkillFactory со скидкой 40% и промокодом HABR, который даст еще +10% скидки на обучение. Другие профессии и курсыПРОФЕССИИ
=========== Источник: habr.com =========== =========== Автор оригинала: Raphael Mun ===========Похожие новости:
Блог компании SkillFactory ), #_javascript, #_programmirovanie ( Программирование ), #_html, #_tensorflow |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 13:15
Часовой пояс: UTC + 5