https://a.storyblok.com/f/270183/21916/7dc65b6541/blog_snapchat-style-filters_1200x600.png

Filtros no estilo do Snapchat com Tracking.js e Vonage

Publicado em November 16, 2020

Tempo de leitura: 20 minutos

Seja o Batman ou simplesmente assuste algumas pessoas. Neste guia, você vai adicionar o rastreamento facial usando o Tracking.js e criar alguns filtros legais no estilo do Snapchat para uma transmissão de Video. Legal!

Video API da Vonage

Video API da Vonage oferece uma plataforma baseada em WebRTC para adicionar vídeo ao vivo, voz e mensagens a aplicativos da web, móveis e de desktop. Usaremos a Video API da Vonage para conectar nosso aplicativo a uma sessão de streaming de vídeo. Ao trabalhar com a Video API da Vonage, você poderá encontrar o nome OpenTok, seu nome anterior, usado no código ou na documentação.

Tracking.js

Tracking.js é uma biblioteca leve e poderosa de detecção de rostos e rastreamento de cores em tempo real, que traz diversos algoritmos e técnicas para o ambiente do navegador. Usaremos o tracking.js para rastrear rostos, a fim de podermos aplicar máscaras.

Pré-requisitos

É isso que você vai precisar para seguir este tutorial.

Node e NPM

Para começar, você precisará ter o Node e o gerenciador de pacotes do Node (npm) instalados. Vamos verificar se eles estão instalados executando o seguinte comando:

node --version
npm --version

Se você ainda não tiver o Node e o npm instalados, acesse nodejs.org e instale a versão correta para o seu sistema operacional. Escrevi este artigo usando o Node 11.8 e o npm 6.8.

Account da Vonage

Acesse Vonage para se inscrever para um teste gratuito.

Git

Veja se você tem o Git instalado.

git --version

Caso contrário, a Atlassian disponibiliza um ótimo guia sobre como instalar o Git. Escrevi este artigo com o Git 2.17 instalado.

Comece agora

Como base, preparei uma versão do aplicativo a partir de exemplos da web do OpenTok para filtros de stream, que você pode editar.

Confira minha versão dos exemplos de filtro da web do OpenTok aqui executando o seguinte comando na ferramenta de linha de comando de sua preferência ou usando sua interface do Git para acessar esta URL: https://github.com/nexmo-community/snapchat-filters-with-opentok-and-face-tracking.git

git clone git@github.com:nexmo-community/snapchat-filters-with-opentok-and-face-tracking.git

Observação: O master ramificação está no ponto inicial, mas se você for impaciente (e gostar de pular direto para o final, como eu), pode git checkout demo-end e executar o aplicativo a partir daí.

Configurar o aplicativo

Portanto, antes de começar a programar, você precisa configurar o aplicativo tal como está e ter uma ideia do ponto de partida. Para isso, será necessária a Account da Vonage mencionada nos pré-requisitos. Se você não tiver uma, acesse Vonage para se cadastrar agora.

Criar um projeto

Depois de confirmar seu endereço de e-mail e fazer login, você poderá clicar em “Criar projeto” na visão geral da sua conta de Account. Crie um novo projeto da Video API da Vonage clicando em “Criar projeto personalizado”. Dê um nome ao seu projeto e clique em “Criar”.

Agora, anote sua chave de API e SECRETO para a etapa de configuração.

Criar uma sessão

Depois de criar seu projeto, você será redirecionado para o painel do projeto. Role a página para baixo até Ferramentas do projeto e clique em “Criar ID de sessão”. Você também pode escolher entre Reencaminhado e Roteado. Basicamente, o modo “Relayed” tenta uma conexão ponto a ponto, mas usará servidores de retransmissão quando os firewalls bloquearem a conexão ponto a ponto. “Routed” é a configuração padrão, na qual os streams são direcionados para o Media Router. Você pode ler mais sobre sessões na documentação.

Anote o ID DA SESSÃO que for gerado.

Gerar um token

Antes de configurarmos o aplicativo, precisamos gerar um token. No painel do seu projeto, na seção Ferramentas do Projeto (a mesma seção em que você criou seu ID de sessão), insira o ID DE SESSÃO que você acabou de gerar e clique em Gerar token.

Anote o TOKEN GERADO para a próxima etapa.

Criar um arquivo de configuração

Faça uma cópia de js/config.js.example e salve-a como js/config.js (no diretório js), certificando-se de modificar os valores para os da sua conta da Vonage.

CHAVE DA API, ID DA SESSÃOe TOKEN foram criados acima. Se você estiver executando o aplicativo localmente usando npm start, você precisará de uma URL BASE de http://127.0.0.1:8080. Para plataformas como o Heroku, é mais parecido com https://app-name.herokuapp.com.

Dica rápida: Se você estiver executando o aplicativo após ter executado npm start você pode pará-lo novamente com CTRL+C.

O seu js/config.js ficará mais ou menos assim.

// js/config.js
(function closure(exports) {
  exports.BASE_URL = 'base url';
  exports.API_KEY = 'api key';
  exports.SESSION_ID = 'session id';
  exports.TOKEN = 'token';
})(exports);

Instalar dependências

Agora use o npm para instalar suas dependências. Execute este comando dentro do diretório do seu projeto.

npm install

Executar o aplicativo

Você pode iniciar o aplicativo usando npm start. Essa é uma função bem prática. Se você não configurar um script de inicialização personalizado em package.json, o programa presumirá que você se refere a node index.js onde index.js é o arquivo principal configurado em package.json. No nosso caso, ele executará ./node_modules/bin/http-server para iniciar nosso servidor web baseado em nós.

npm start

Depois que estiver tudo funcionando, você poderá visualizar a sessão localmente abrindo localhost:8080 no seu navegador preferido. Deve ficar mais ou menos assim, mas com você no lugar de mim.

Basic Vonage Video example up and runningBasic Vonage Video example up and running

Agora, abra a mesma URL em outra janela do navegador para que você possa conversar consigo mesmo!

Observação importante: Nem sempre estou com essa aparência desleixada :)

Basic Vonage Video conversation occuringBasic Vonage Video conversation occuring

Apresentando a tecnologia

A plataforma Vonage Video API facilita a integração de vídeo interativo em tempo real e de alta qualidade, mensagens, compartilhamento de tela e muito mais em aplicativos web e móveis. Neste caso específico, utilizaremos os SDKs de cliente em JavaScript.

SDK de Video da Vonage

O Client SDK JavaScript está fazendo o trabalho pesado. Ele vai usar credenciais codificadas para se conectar a uma sessão por meio de um token que geramos no portal do desenvolvedor. Em seguida, ele vai assinar e publicar fluxos de áudio e Video no nosso navegador.

Tracking.js

O Tracking.js é uma biblioteca leve de JavaScript para rastrear posições faciais em imagens e Video. Vamos recuperar as coordenadas na tela.

O aplicativo de demonstração

O aplicativo que você clonou e iniciou já vem com alguns filtros integrados. Esses filtros funcionam utilizando dados de imagem da API do Canvas e os modificam. Os dados da imagem estão em um objeto que contém largura, altura e dados de pixels. Os dados de pixels estão contidos em uma matriz. A cada 4 itens representam os pixels R, G, B e A, respectivamente.

Portanto, a aplicação de efeitos de imagem pode ser feita por meio da modificação dos dados RGBA.

Aqui está um exemplo um tanto improvisado. O código é explicado por meio de comentários, mas, na verdade, ele não vai funcionar.

// The existing image data.
const imgData = new ImageData(existingData, width, height);

// An empty typed data array for our new modified image data
const dataArray = new Uint8ClampedArray(imgData.data.length);

// Loop over the data, skipping to every 4th item.
for (let i = 0; i < imgData.data.length; i += 4) {
  // i is R / red
  // i+1 is G / green
  // i+2 is B / blue
  // i+3 is alpha - which specifies the opacity of the color.

  // here, we're addoing 80 to the relative channel value, effectively lighting this channel
  dataArray[i] = imgData.data[i] + 80;
  dataArray[i + 1] = imgData.data[i + 1] + 80;
  dataArray[i + 2] = imgData.data[i + 2] + 80;
  // lightening every channel will result in a whitewashed/brightened effect

  dataArray[i + 3] = imgData.data[i + 3];
}

return new ImageData(dataArray, imgData.width, imgData.height);

Você pode ver os filtros existentes modificando os valores RGB em js/filters.js.

Criando alguns novos filtros

A seguir, estão as etapas para editar o aplicativo com o objetivo de criar dois novos filtros: um para ajustar o brilho e limite do nosso fluxo.

Brilho

Ajustar o brilho de uma imagem é uma das operações de processamento de imagem mais simples que se pode realizar. Conforme mostrado no exemplo de código anterior, basta aplicar a alteração desejada no brilho a cada um dos canais vermelho, verde e azul.

Abra js/filters.js e encontre a linha com o comentário // Brighten e substitua o comentário por uma nova função para o nosso filtro.

Dica rápida: Comentários como // ... e <!-- // ... --> mostram que há mais código, mas que foi removido, para maior clareza, aqui no blog.

// js/filters.js
(function closure(exports) {
  var Filters = {

    // ...

    brighten: function brighten(imgData) {
      // New data

      // Loop 

      // Return
    }
  };

  // ...
})(exports);

Os dados da imagem estão em uma matriz de 8 bits sem sinal e com valores limitados. Isso significa que ela tem comprimento fixo e os valores são limitados a valores sem sinal entre 0 e 255. Perfeito para valores RGB. Portanto, precisamos criar uma nova matriz vazia de 8 bits na qual armazenaremos nossos dados de imagem modificados. Substitua // New data conforme mostrado aqui, onde você define o comprimento fixo com o comprimento dos dados existentes.

// js/filters.js
    // ...

    brighten: function brighten(imgData) {
      const res = new Uint8ClampedArray(imgData.data.length);

      // Loop 

      // Return
    }

    // ...

Agora, percorra os dados existentes e desloque os valores para aplicar um efeito de clareamento. Substitua // Loop conforme mostrado aqui. E, conforme explicado anteriormente neste artigo, você está percorrendo os dados, pulando para cada quarto índice na matriz.

// js/filters.js
    // ...

    brighten: function brighten(imgData) {
      const res = new Uint8ClampedArray(imgData.data.length);

      for (let i = 0; i < imgData.data.length; i += 4) {
        var inputRed = imgData.data[i];
        var inputGreen = imgData.data[i + 1];
        var inputBlue = imgData.data[i + 2];
        res[i] = inputRed + 80;
        res[i + 1] = inputGreen + 80;
        res[i + 2] = inputBlue + 80;
        res[i + 3] = imgData.data[i + 3];
      }

      // Return
    }

    // ...

Por fim, nessa função, você retornará a nova matriz de dados. Substitua // Return conforme mostrado.

// js/filters.js
    // ...

    brighten: function brighten(imgData) {
      const res = new Uint8ClampedArray(imgData.data.length);

      for (let i = 0; i < imgData.data.length; i += 4) {
        var inputRed = imgData.data[i];
        var inputGreen = imgData.data[i + 1];
        var inputBlue = imgData.data[i + 2];
        res[i] = inputRed + 80;
        res[i + 1] = inputGreen + 80;
        res[i + 2] = inputBlue + 80;
        res[i + 3] = imgData.data[i + 3];
      }

      return new ImageData(res, imgData.width, imgData.height);
    }

    // ...

Com a função configurada, agora você precisa poder selecioná-la. Então, abra index.html e substitua <!-- Brighten --> por uma opção para selecionar nossa nova função.

<!-- index.html -->
<!-- // ... -->
          <select id="filter">
            <option value="none">Filter: None</option>
            <option value="grayscale">Filter: Grayscale</option>
            <option value="sepia">Filter: Sepia</option>
            <option value="invert">Filter: Invert</option>
            <option value="brighten">Filter: Brightening</option>
            <!-- Threshold -->
          </select>
<!-- // ... -->

Agora, reinicie o aplicativo abrindo localhost:8080 e confira o novo filtro de clareamento. O option valor é o mesmo que o nome da função.

npm start

Vonage Video stream with a brightening filterVonage Video stream with a brightening filter

Determinação do limiar

O próximo passo é aplicar o limiar à imagem.

Abra js/filters.js e encontre a linha com o comentário // Threshold e substitua-a pela função a seguir. Desta vez, você tem a função completa.

O funcionamento do limiar consiste em substituir a cor por branco ou preto com base em um algoritmo que envolve a soma dos valores dos canais vermelho, verde e azul. Uma soma calculada acima de 100 (&gt;= 100), que representa as cores mais claras, é transformada em branco. Todo o restante é transformado em preto.

// js/filters.js
(function closure(exports) {
  var Filters = {

    // ...

    threshold: function threshold(imgData) {
      const res = new Uint8ClampedArray(imgData.data.length);
      for (let i = 0; i < imgData.data.length; i += 4) {
        var inputRed = imgData.data[i];
        var inputGreen = imgData.data[i+1];
        var inputBlue = imgData.data[i+2];
        var v = (0.2126 * inputRed + 0.7152 * inputGreen + 0.0722 * inputBlue >= 100) ? 255 : 0;
        res[i] = res[i+1] = res[i+2] = v;
        res[i + 3] = imgData.data[i + 3];
      }

      return new ImageData(res, imgData.width, imgData.height);
    }

  };

  // ...
})(exports);

Agora, vamos adicionar uma opção para selecionar o limiar. Então, abra index.html e substitua <!-- Threshold --> por uma opção para selecionar nossa nova função.

<!-- index.html -->
<!-- // ... -->
          <select id="filter">
            <option value="none">Filter: None</option>
            <option value="grayscale">Filter: Grayscale</option>
            <option value="sepia">Filter: Sepia</option>
            <option value="invert">Filter: Invert</option>
            <option value="brighten">Filter: Brightening</option>
            <option value="threshold">Filter: Thresholding</option>
          </select>
<!-- // ... -->

Agora, reinicie o aplicativo abrindo localhost:8080 e confira o novo filtro de limiar.

npm start

Vonage Video stream with a thresholding filter!Vonage Video stream with a thresholding filter!

Rastreamento facial com o Tracking.js

A seguir, você vai adicionar o Tracking.js e exibir uma imagem sobre o seu rosto, como se fosse uma máscara.

Instalar o Tracking.js

Use o npm para instalar o Tracking.js.

npm i tracking

Adicione a biblioteca ao seu aplicativo usando a abordagem tradicional. Edite index.html e localize o comentário onde está escrito <!-- Scripts -->. Substitua-o pelas novas tags de script mostradas abaixo. O primeiro arquivo tracking-min.js é a biblioteca Tracking.js minimizada. O segundo arquivo face-min.js contém os dados utilizados pelo Tracking.js para o rastreamento de rostos em mídias. Outros conjuntos de dados, como características faciais — incluindo olhos, nariz e boca —, também estão disponíveis.

<!-- index.html -->
<!-- // ... -->
    
      var exports = {};
    
    <a href="http://node_modules/tracking/build/tracking-min.js">http://node_modules/tracking/build/tracking-min.js</a>
    <a href="http://node_modules/tracking/build/data/face-min.js">http://node_modules/tracking/build/data/face-min.js</a>
    <a href="http://js/config.js">http://js/config.js</a>
<!-- // ... -->

Como os filtros e as máscaras funcionam de maneira um pouco diferente, eu os mantive separados. Isso tem a ver com os objetos disponíveis dentro do arquivo js/filters.js arquivo.

Crie um novo arquivo js/masks.js e insira o seguinte conteúdo para começar.

// js/masks.js
(function closure(exports) {
  var Masks = {
    none: function none(tracker, canvas, ctx) {}

    // Guy Fawkes

    // Batman
  };

  // Set the initial mask to none
  Masks.selectedMask = Masks.none;

  // When the mask selector changes we update the selectedMask
  var maskSelector = document.querySelector('#mask');
  maskSelector.addEventListener('change', function change() {
    Masks.selectedMask = Masks[maskSelector.value];
  });

  exports.Masks = Masks;
})(exports);

Abra e edite js/publish.js e localize o // Tracker.js comentário. Substitua-o, conforme mostrado aqui.

Este rastreador de objetos é o que irá analisar o material de mídia e reconhecer rostos. O rastreamento funciona, em geral, movendo uma “forma” ao redor do material de mídia até encontrar uma correspondência próxima. A configuração define a escala inicial da “forma” e o tamanho do passo. Esses parâmetros ajudarão a ajustar com precisão (e a melhorar a velocidade) do rastreador.

Observação: Com minhas webcams integrada e de 720p, essa configuração costumava funcionar muito bem.

// js/publish.js

    // ...
    var reqId;

    // Draw a box around face
    var tracker = new tracking.ObjectTracker('face');
    tracker.setInitialScale(10);
    tracker.setStepSize(2);
    tracker.setEdgesDensity(0.01);

    // ...

E, nesse mesmo arquivo, encontre o // apply Mask comentário e substitua-o desta forma.

// js/publish.js

      // ...
      ctx.putImageData(imgData, 0, 0);

      exports.Masks.selectedMask(tracker, canvas, ctx);

      // ...

Editar index.html e encontre os dois <!-- Masks --> comentários.

Para manter as máscaras separadas, você também criará uma caixa de seleção separada. Vamos deixar as opções em branco, por enquanto.

Você também precisa incluir o novo js/masks.js arquivo aqui.

<!-- index.html -->
<!-- // ... -->
    <div id="videos">
        <div id="subscriber"></div>
        <div id="publisher">
          <!-- // ... -->
          
            Mask: None
            <!-- Guy Fawkes -->
            <!-- Batman -->
          
        </div>
    </div>
    <!-- // ... -->
    <a href="http://js/filters.js">http://js/filters.js</a>
    <a href="http://js/masks.js">http://js/masks.js</a>
<!-- // ... -->

Antes de executá-lo, é preciso aplicar alguns estilos à caixa de seleção das máscaras. Edite css/app.css e localize o comentário /* Masks menu css */ e substitua-o pelo seguinte CSS.

/* css/app.css */
/* // ... */
#mask {
  position: absolute;
  bottom: 0;
  left: 0;
  z-index: 102;
}

Você ainda não criou nenhuma máscara, mas reinicie o aplicativo abrindo localhost:8080 para verificar se não cometeu nenhum erro. Se o stream funcionar, você está no caminho certo.

npm start

A máscara de Guy Fawkes

A primeira máscara será a do Guy. A máscara de Guy Fawkes se tornou um símbolo do anonimato, que é justamente o objetivo de usar uma máscara!

Abrir js/masks.js e encontre o comentário // Guy Fawkes. Substitua o comentário pela nova função.

// js/masks.js
// ...
(function closure(exports) {
  var Masks = {
    none: function none(tracker, canvas, ctx) {},

    guy: function guy(tracker, canvas, ctx) {
      // Load an image

      // Start tracking

      // Apply our image as a mask
    }

    // Batman
  };
// ...

Carregue a imagem que você deseja sobrepor a qualquer rosto identificado pelo rastreador, substituindo o // Load an image comentário conforme mostrado aqui. ../images/guy.png deve estar disponível no repositório.

// js/masks.js

    // ...
    guy: function guy(tracker, canvas, ctx) {
      var mask = document.createElement("img");
      mask.src = '../images/guy.png';  

      // Start tracking

      // Apply our image as a mask
    }
    // ...

Agora, habilite o rastreamento em nossa mídia. A mídia que você está usando é o fluxo do canvas. Você precisa habilitar a câmera e o áudio aqui. Substitua o // Start tracking comentário da seguinte maneira.

// js/masks.js

    // ...
    guy: function guy(tracker, canvas, ctx) {
      var mask = document.createElement("img");
      mask.src = '../images/guy.png';

      tracking.track(canvas, tracker, { camera: true, audio: true });

      // Apply our image as a mask
    }
    // ...

Você precisa registrar um ouvinte de evento no track evento do rastreador para que, quando ele detectar um rosto, possa desenhar a máscara sobre o rosto detectado.

// js/masks.js

    // ...
    guy: function guy(tracker, canvas, ctx) {
      var mask = document.createElement("img");
      mask.src = '../images/guy.png';  

      tracking.track(canvas, tracker, { camera: true, audio: true });

      tracker.on('track', function(event) {
        event.data.forEach(function(rect) {
          ctx.drawImage(mask, rect.x - 100, rect.y - 100, rect.width * 1.5, rect.height * 1.5);
        });
      });
    }
    // ...

Agora, você precisa adicionar a opção da máscara do Guy ao aplicativo. Edite index.html, procure o <!-- Guy Fawkes --> comentário e adicione a opção conforme mostrado abaixo.

<!-- index.html -->
<!-- // ... -->
    <div id="videos">
        <div id="subscriber"></div>
        <div id="publisher">
          <!-- // ... -->
          
            Mask: None
            Mask: Guy Fawkes
            <!-- Batman -->
          
        </div>
    </div>
<!-- // ... -->

Vamos torcer para que tudo funcione! Reinicie o aplicativo abrindo localhost:8080. Selecione sua primeira máscara e dê uma olhada nela.

Vonage Video stream with a Guy Fawkes mask!Vonage Video stream with a Guy Fawkes mask!

Seja o Batman

A máscara final e a edição do nosso código serão o Batman. Abra js/masks.js e localize o comentário // Batman. Substitua o comentário por esta nova função.

// js/masks.js

    // ...

    batman: function batman(tracker, canvas, ctx) {
      var mask = document.createElement("img");
      mask.src = '../images/batman.png';  

      tracking.track(canvas, tracker, { camera: true, audio: true });
      tracker.on('track', function(event) {
        event.data.forEach(function(rect) {
          ctx.drawImage(mask, rect.x - 60, rect.y - 170, rect.width * 1.4, rect.height * 1.5);
        });
      });
    }
    // ...

Agora adicione a opção da máscara do Batman ao aplicativo. Edite index.html, procure o <!-- Batman --> comentário e adicione a opção conforme mostrado abaixo.

<!-- index.html -->
<!-- // ... -->
    <div id="videos">
        <div id="subscriber"></div>
        <div id="publisher">
          <!-- // ... -->
          
            Mask: None
            Mask: Guy Fawkes
            Mask: Batman
          
        </div>
    </div>
<!-- // ... -->

Posso ser o Batman?? Reinicie o aplicativo abrindo localhost:8080. Selecione a máscara e experimente.

Vonage Video stream with a Batman maskVonage Video stream with a Batman mask

Em resumo

A criação de filtros do Snapchat e o uso de máscaras em transmissões de vídeo ficam mais fáceis com os SDKs do Vonage Video. O Vonage Video também permite que você transmita simultaneamente para plataformas como o YouTube Live, o Twitch e o Facebook. Assim, além de poder criar seu próprio site de transmissões ao vivo, você ainda pode alcançar o público em outros serviços.

Compartilhar:

https://a.storyblok.com/f/270183/250x250/451101b4f0/lukeoliff.png
Luke OliffEx-funcionários da Vonage

Educador de tecnologia com um jeito amigável, pai de família, defensor da diversidade, provavelmente discuto um pouco demais. Ex-engenheiro de backend. Fale comigo sobre JavaScript (front-end ou back-end), o incrível Vue.js, DevOps, DevSecOps e tudo relacionado ao JamStack. Escritor no DEV.to