Como Construir Um Simulador de Máscaras em 3D

3d mask simulator

Como Construir Um Simulador de Máscaras em 3D

Enquanto web developer, estou sempre à procura de projetos que permitam adquirir novas competências. Numa dessas pesquisas, descobri um exemplo de detecção de movimentos através de Javascript e a API getUserMedia do HTML5, o que me deu a ideia de usar a mesma tecnologia para criar algo divertido: um simulador de máscaras 3D que capture vídeo a partir de uma webcam e coloque um modelo 3D de uma máscara na cabeça/cara do utilizador.

Nesta série de posts vamos abordar os seguintes tópicos:

:: Utilizar a API getUserMedia do HTML5
:: Detecção de movimentos e identificar feições faciais num stream de webcam
:: Utilizar o stream da webcam com WebGL
:: Mover um objecto em 3D em WebGL tendo em conta a detecção de movimentos

Já deu para perceber que há muito a aprender, por isso, sem perder mais tempo, vamos começar!
 

Parte 1: Utilizar a API getUserMedia do HTML5

Recentemente, a API WebRTC disponibilizou a interface MediaDevices que fornece o método getUserMedia(). A aplicação deste método num dispositivo/browser compatível acciona um pedido de permissão para aceder a recursos vídeo e/ou áudio do utilizador. Assim que é dada permissão, o método irá devolver uma Promise com um objecto MediaStream. Podem ler mais sobre o assunto nos links acima.

O método permite assim a utilização de um stream de webcam no dispositivo/browser e a sua manipulação com Javascript, abrindo possibilidades infinitas para tornar a interacção com os utilizadores mais rica.
 

NOTA: Caso queiram acompanhar esta série de artigos enquanto experimentam o código, vão precisar de um ambiente de desenvolvimento. No final da série, um repositório com a source completa dos exemplos será disponibilizado. Estou a servir HTML através uma stack LAMP numa máquina virtual, usando o Vagrant, com o Chrome 52.0.2743.82 (64-bit) em OS X El Capitan 10.11.6. Notem também que a não ser que o vosso servidor tenha HTTPS configurado, o Chrome lança avisos sobre questões de segurança acerca da utilização da API getUserMedia em condições de insegurança. Podem resolver isso adicionando um certificado self-signed ao vosso servidor virtual. Estes tópicos estão fora do âmbito destes tutoriais, mas caso necessário, perguntem-me que eu tentarei ajudar.


O primeiro passo para chegar ao resultado final é aceder ao stream e colocá-lo num documento HTML. Vamos fazê-lo agora!

Criem um documento HTML5 vazio com uma declaração de doctype HTML5 e façam link a um ficheiro javascript nesse documento. Também podem adicionar um ficheiro CSS, se quiserem. Vamos precisar de alguns elementos HTML5 no corpo do documento para ter um stream de vídeo da forma que nós pretendemos ver e outro à medida que for sendo tratado/capturado para efeitos de debugging. Eu fi-lo da seguinte forma:
 

index.html
 

<!DOCTYPE html> <html> <head> <title>Augmented reality mask simulator</title> <!-- STYLE --> <link rel="stylesheet" href="style/style.css" /> </head> <body> <!-- CONTENT --> <canvas id="inputCanvas" width="320" height="240"></canvas> <video id="inputVideo" autoplay loop style="display: none"></video> <canvas id="debugCanvas" width="320" height="240"></canvas> <div class="results"> face center x: <span class="cx"></span><br> face center y: <span class="cy"></span><br> face angle: <span class="ang"></span><br> FOV: <span class="fov"></span><br> </div> <!-- SCRIPTS --> <script src="https://code.jquery.com/jquery-2.2.4.min.js"></script> <script src="js/inc/headtrackr.min.js"></script> <script src="js/main.js"></script> </body> </html>


De notar que adicionei algumas coisas:

:: jQuery - poderá ser necessário numa fase posterior, pelo que decidi adicionar desde já. 
:: headtrackr.js - não vou desenvolver o script de detecção de movimentos do zero. Prefiro usar esta biblioteca, disponível publicamente, para tratar desta questão. Podem ler mais sobre a headtrackr.js aqui, já que vai ser utilizada com bastante frequência.
:: main.js - este vai ser o nosso ficheiro fonte onde a magia vai acontecer. 


Vamos passo a passo: criei elementos canvas para exibir o stream da minha webcam, o que representa a sua entrada. O elemento vídeo iria de qualquer forma fornecer uma entrada, mas optei por usar um elemento canvas para permitir a interação com o arquivo. Criei um elemento canvas adicional para servir de visão de debugging para o arquivo de detecção de movimentos que estou a utilizar. É aqui que vamos poder ver os resultados da operação do script de detecção. Existem algumas opções para tentar melhorar a performance, mas neste momento as configurações vanilla deverão ser suficientes.

No canto superior direito existe também um div results para ver as mudanças em alguns dos parâmetros utilizados para calcular o nosso comportamento.
 

main.js

var videoInput = document.getElementById("inputVideo"); var canvasInput = document.getElementById("inputCanvas"); var canvasDebug = document.getElementById("debugCanvas"); var htracker = new headtrackr.Tracker({ ui: true, debug: canvasDebug, calcAngles: true }); htracker.init(videoInput, canvasInput); htracker.start(); document.addEventListener('facetrackingEvent', function(event){ var fov = htracker.getFOV; $(".cx").text(event.x); $(".cy").text(event.y); $(".ang").text(Math.floor(event.angle*180/Math.PI)); $(".fov").text(fov); }); navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia; window.URL = window.URL || window.webkitURL; var camvideo = document.getElementById('monitor'); if (!navigator.getUserMedia) { document.getElementById('errorMessage').innerHTML = 'Sorry. <code>navigator.getUserMedia()</code> is not available.'; } else { navigator.getUserMedia({video: true}, gotStream, noStream); } function gotStream(stream) { if (window.URL) { camvideo.src = window.URL.createObjectURL(stream); } else // Opera { camvideo.src = stream; } camvideo.onerror = function(e) { stream.stop(); }; stream.onended = noStream; } function noStream(e) { var msg = 'No camera available.'; if (e.code == 1) { msg = 'User denied access to use camera.'; } document.getElementById('errorMessage').textContent = msg; }

 

No nosso javascript, vamos simplesmente seleccionar os elementos com os quais queremos interagir e populá-los com os streams, criar uma nova instância do Tracker e passar-lhe alguns parâmetros, começar a seguir o feed da camera e depois criar funções para o caso da API getUserMedia não estar disponível no browser/dispositivo ou o acesso seja negado.

Se tudo correr como planeado, quando acederem à página, deverão ver algo deste género:

 

Screenshot of the webpage in action.


Neste momento, já conseguimos algo bastante importante. Aprendemos como usar o stream da webcam, e, usando a biblioteca headtrackr.js, seguir movimento no stream.

O nosso próximo passo será descobrir como melhorar a detecção de movimento para que possamos encontrar, ou pelo menos extrapolar, a posições de feições faciais também. Isto irá depois permitir usar esses parâmetros para calcular uma medida de profundidade de campo a partir do stream, a qual será necessária para rodar objectos em 3D mais para a frente.

Espero que tenham gostado da primeira parte da nossa série, e fiquem atentos a novos posts!