RSS
Pitang Labs

13 Novembro de 2023

min para ler
Compartilhe

Intervalo Técnico: Explorando o Potencial da Visão Computacional na Web

Neste Intervalo Técnico, você aprenderá a desenvolver uma aplicação interativa de Visão Computacional no navegador, que permitirá controlar um jogo com base em gestos capturados em tempo real através da Webcam.

 

 

Introdução a Visão Computacional

 

A área de Visão Computacional tem conquistado bastante destaque nos últimos anos devido à sua capacidade de extrair e interpretar cenas em imagens e vídeos digitais. Essa capacidade permite solucionar uma ampla variedade de desafios, como detecção de objetos, reconhecimento de textos em imagens, reconhecimento facial e monitoramento de objetos em tempo real.

Atualmente têm surgido bibliotecas e frameworks que possibilitam o desenvolvimento de aplicações de Visão Computacional para rodar diretamente em nossos navegadores, sem a necessidade de instalar softwares adicionais, dado que a maioria destes browsers suportam o WebGL, que pode ser usado para acelerar as operações necessárias para a utilização de algoritmos de aprendizado de máquina e deep learning.

Um cenário interessante seria explorar sites que oferecem recursos como reconhecimento de gestos em tempo real, através da webcam, para possibilitar o controle intuitivo de aplicações e consequentemente, proporcionar inúmeras experiências interativas.

É exatamente sobre este tema que este postblog irá explorar, onde nele, você aprenderá a desenvolver uma aplicação interativa de Visão Computacional no navegador, que permitirá controlar um jogo com base em gestos capturados em tempo real através da Webcam. Para isso, utilizaremos uma biblioteca chamada
ML5.js, que simplifica o desenvolvimento de aplicações de aprendizado de máquina.

 

A biblioteca ML5.js

 

O ML5.js é uma biblioteca open-source, baseada no TensorFlow.js, que foi desenvolvida visando tornar o uso de aprendizado de máquina mais acessível, simples e intuitivo. Com esta biblioteca, é possível ter acesso imediato a algoritmos e modelos pré-treinados para diversas tarefas, como detecção de poses humanas, geração de texto, classificação de áudios, detecção de objetos, dentre outros.

Além disso, esta biblioteca oferece recursos que permitem o treinamento de nossos próprios modelos de aprendizado de máquina, ao incluir, por exemplo, algoritmos como Rede Neural Convolucional (CNN) e o K-Nearest Neighbors (KNN) para classificação de imagens. Podemos, também, extrair características de um modelo pré-treinado, como o MobileNet, e reutilizá-las em novos projetos (Transfer Learning), eliminando assim, a necessidade de treinar um novo modelo totalmente do zero. Dessa maneira, podemos personalizar e adaptar os algoritmos conforme as necessidades específicas de cada projeto.

 

O que é o MobileNet?

 

Para que a aplicação possa reconhecer os gestos realizados, será necessário treinar um modelo de aprendizado de máquina utilizando características extraídas dos frames de vídeo por meio do MobileNet, que é o modelo usado pelo ML5.js para a realização de tarefas voltadas para a classificação de imagens.

O MobileNet é uma arquitetura de deep learning projetada para funcionar em dispositivos com recursos computacionais limitados, como dispositivos móveis, web e aplicações embarcadas de visão computacional. Esse modelo foi treinado com base no ImageNet, que consiste em um conjunto de dados com mais de 14 milhões de imagens, para classificar 1000 classes diferentes.

Ao utilizar o MobileNet, podemos obter os logits, que consiste em uma camada que contém informações relevantes sobre as características das imagens. Os logits são representados como um vetor com 1000 elementos, onde cada elemento representa a pontuação ou probabilidade associada a uma classe específica.

No TensorFlow.js, os logits são armazenados como um tensor de formato [1000], onde os valores mais altos indicam maior confiança na pertinência a classe correspondente.
Para ter uma melhor compreensão de como os logits podem ser usados para representar as características semânticas de uma imagem, você pode acessar este artigo, publicado por Nikhil Thorat, que oferece diversos exemplos elucidativos.

 

K-Nearest Neighbors

 

Para classificarmos corretamente os gestos com base nos logits, precisaremos enviar esses dados extraídos para serem utilizados pelo KNN (K-vizinhos mais próximos ou K-nearest neighbors), que consiste em um algoritmo de aprendizado de máquina que classifica dados com base na classe predominante dos K vizinhos mais próximos. Em suma, ao receber um novo exemplo, o algoritmo calcula a distância entre esse exemplo e os dados existentes, identificando suas similaridades e, com base nisso, determina a classe à qual o exemplo fornecido pertence.


Para melhor compreender o conceito, podemos analisar a ilustração abaixo, onde existem exemplos representados em vermelho e azul, e desejamos determinar a classe do círculo verde.

Exemplo de KNN                                                                 Figura 1: Exemplo de KNN

Após calcular a distância entre o exemplo verde e todos os pontos existentes, podemos identificar os K vizinhos mais próximos. Se escolhermos um valor de K igual a 3, o círculo verde seria classificado como pertencente à classe vermelha, uma vez que, ao considerarmos os três vizinhos mais próximos, observamos uma predominância de exemplos vermelhos próximos a ele. No entanto, se aumentarmos o valor de K para 5, por exemplo, o circulo verde seria classificado como pertencente à classe azul, levando em consideração os cinco vizinhos mais próximos.

 

Preparando o ambiente de desenvolvimento


Agora que adquirimos um entendimento preliminar dos algoritmos que iremos empregar na aplicação, podemos avançar para a fase de implementação. Para dar início ao desenvolvimento, iremos organizar os arquivos em pastas, seguindo a estrutura ilustrada na Figura 2. O arquivo index.html consiste na página principal da aplicação, responsável por estruturar e renderizar o conteúdo da interface. Na pasta assets
encontram-se os recursos utilizados pelo site, como imagens, scripts e estilos.

A subpasta images contém todos os ícones que serão exibidos na interface. Estas imagens foram obtidas através do site Flaticon, que oferece uma grande variedade de ícones que podem ser baixados de forma gratuita. A referência para os ícones utilizados podem ser encontradas no repositório do projeto.

A subpasta scripts contém os arquivos de scripts da aplicação, onde estão os códigos Javascript responsáveis pela interatividade do site. Já a subpasta styles contém os arquivos que determinam a aparência visual dos elementos na interface.

                                                        Figura 2: Estrutura dos arquivos e pastas

 

Para incorporar a biblioteca ML5.js na aplicação, precisaremos importá-la conforme o código abaixo. Além
disso, faremos uso do Bootstrap, um framework para o desenvolvimento de aplicações responsivas e do Font Awesome para adicionar alguns ícones aos botões do aplicativo. Estas importações precisarão ser realizadas no cabeçalho do arquivo index.html, na tag <head> , juntamente com os arquivos de estilos e scripts que estão localizados na pasta assets.

<!DOCTYPE html>
<html lang="pt-br">
<head>
<title>App de reconhecimento de Gestos</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Boostrap -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha2/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-aFq/bzH65dt+w6FI2ooMVUpc+21e0SRygnTpmBvdBgSdnuTN7QbdgL+OapgHtvPp" crossorigin="anonymous">
<!-- Estilo customizado -->
<link rel="stylesheet" href="./assets/styles/index.css" />
<!-- FA Icons -->
<script src="https://kit.fontawesome.com/c27ffa2f00.js" crossorigin="anonymous"></script>
<!-- ml5 -->
<script src="https://unpkg.com/ml5@latest/dist/ml5.min.js"></script>
<!-- Defer: o script será executado após a análise completa da página HTML,
mas o download do arquivo ocorre no background de forma assíncrona -->
<script defer src="./assets/scripts/index.js"></script>
<!-- Importando o jogo a ser controlado -->
<script defer src="./assets/scripts/snake.js"></script>
<!-- CSS do jogo -->
<link rel="stylesheet" href="./assets/styles/snake.css" />
</head>
<body>
<!-- Conteúdo da página -->
</body>

 

Estruturação da página HTML
 

Toda a construção da interface da aplicação considera as estruturas visuais das Figuras 3 e 4.Nesta interface, foi criada uma div com a classe row que contem três colunas (cols). A primeira coluna é destinada à exibição do vídeo, a segunda coluna é reservada para mostrar o jogo a ser controlado, e a terceira coluna é utilizada para exibir o painel de treinamento ou os gestos detectados. O painel do jogo e o card de exibição dos gestos detectados permanecerão ocultos, a menos que a classificação seja iniciada, conforme ilustrado pela Figura 4.

 

                      Figura 3: Setup inicial da aplicação com as opções de inserção de exemplos, baixar e carregar o modelo.

 

                        Figura 4: Áreas exibidas ao iniciar o processo de classificação de imagens em tempo real.

O principal objetivo deste blog post é exemplificar a implementação e uso dos algoritmos de Aprendizado de Máquina e Deep Learning no navegador, ao invés de descrever o processo de construção da página HTML. Por esse motivo, será indicado apenas o link para o código completo do arquivo index.html e index.css, disponível no repositório do GitHub. Logo, o enfoque maior será no index.js. Além disso, os arquivos referentes a implementação do jogo a ser controlado podem ser visualizados através destes dois links: snake.js e snake.css.

 

Mas, de modo geral, para que seja possível compreender a dinâmica da página, podemos destacar as
seguintes informações inerentes a construção dos arquivos index.html e index.css:

 

  • Algumas das classes atribuídas às tags html são provenientes do Bootstrap e têm a finalidade de indicar margens, centralizar o conteúdo, aplicar estilos em botões, entre outras funcionalidades. Além disso, no arquivo index.css , foi criada a classe hide para ocultar elementos de acordo com a execução da aplicação.
  • Os ícones dos gestos reconhecidos serão exibidos em tempo real durante a classificação, no card com o ID detectedGestureIndicatorArea. No card identificado pelo ID insertNewExamplesArea, você encontrará cinco botões para inserir exemplos de cada classe: cima, baixo, esquerda, direita e para uma classe neutra, que não corresponde a nenhuma dessas direções.
  • Ainda no card insertNewExamplesArea, existem dois botões, um para carregar e outro para salvar o modelo. O botão de carregamento do modelo manipulará uma tag de input com id modelDataFile, que possibilitará a inserção de arquivos e permanecerá oculta da interface.
  • Em cada um dos botões de inserção de exemplos, existem elementos spans para exibir a quantidade de imagens adicionadas para representar cada classe.
  • Na página HTML, existem também duas linhas ( row ): uma para exibir uma mensagem de erro caso a webcam não possa ser carregada e outra para exibir feedbacks durante a aplicação como: 'Modelo carregado com sucesso' ou 'Erro ao carregar o modelo'.
  • E, por fim, haverá uma última row para exibição de dois botões: o primeiro será usado para iniciar a classificação, permitindo assim a classificação dos gestos em tempo real para o controle do jogo. O outro botão será usado para interromper a classificação e exibir novamente o painel para inserção de novos exemplos, ou seja, ocultar o card com o ID detectedGestureIndicatorArea e ocultar o jogo cujo id é gameArea.

 

Arquivo index.js

 

No arquivo index.js, todos os trechos de código serão colocados dentro do método addEventListener, que escuta o evento DOMContentLoaded. Esse evento é acionado quando o arquivo HTML for totalmente carregado e analisado.

document.addEventListener('DOMContentLoaded', async () => {}

Dentro da função assíncrona, através do método querySelector , é necessário referenciar pelo ID, todos os elementos essenciais para a inclusão de interatividades, como botões, áreas de exibição de gestos, área de inserção de exemplos, o elemento de exibição de vídeo, dentre outros.

Para facilitar a manipulação e evitar a repetição de código, as variáveis btUsedToAddTrainDatatotalExamplesAddedToTrain estão no formato de um array de objetos e um objeto, respectivamente. Isso proporcionará maior flexibilidade e organização ao lidar com esses elementos posteriormente.

// div para controlar exibição de jogo
const gameArea = document.querySelector('#gameArea');
// botão para reiniciar jogo
const btRestartGame = document.querySelector('#btRestartGame');
// div para exibir mensagem de erro caso a webcam não possa ser executada pelo browser
const errorVideoIndicatorArea = document.querySelector(
'#errorVideoIndicatorArea'
);
// div para exibir feedbacks gerais da aplicação como modelo pronto, modelo carregado
const modelFeedbackIndicatorArea = document.querySelector(
'#modelFeedbackIndicatorArea'
);
// card referente a inserção de exemplo
const insertNewExamplesArea = document.querySelector(
'#insertNewExamplesArea'
);
// card referente a exibição das classes detectadas
const detectedGestureIndicatorArea = document.querySelector(
'#detectedGestureIndicatorArea'
);
// para mostrar a imagem das classes detectadas
const recognizedClassImage = document.querySelector('#recognizedClassImage');
// referência para todos os botões de exemplos
const btUsedToaddTrainData = [
{

Explorando o Potencial da Visão Computacional na Web 7

element: document.querySelector('#btAddExampleDown'),
label: 'down',
},
{
element: document.querySelector('#btAddExampleLeft'),
label: 'left',
},
{
element: document.querySelector('#btAddExampleUp'),
label: 'up',
},
{
element: document.querySelector('#btAddExampleRight'),
label: 'right',
},
{
element: document.querySelector('#btAddExampleNegative'),
label: 'negative',
},
];
// para mostrar quantidade de exemplos inseridos no span da interface
const totalExamplesAddedToTrain = {
up: document.querySelector('#numberExamplesUp'),
left: document.querySelector('#numberExamplesLeft'),
right: document.querySelector('#numberExamplesRight'),
down: document.querySelector('#numberExamplesDown'),
negative: document.querySelector('#numberExamplesNegative'),
};
// botões para manipulação do modelo
const btSaveModel = document.querySelector('#btSaveModel');
const btLoadModel = document.querySelector('#btLoadModel')
// input referente a inserção dos arquivos do modelo baixado (model.json)
const modelDataFile = document.querySelector('#modelDataFile');
// botões para iniciar classificação caso exista dados para treinamento
const btStartClassification = document.querySelector(
'#btStartClassification'
);
// botão para parar a classificação
const btStopClassification = document.querySelector('#btStopClassification');
// div de vídeo para exibição da webcam
const video = document.querySelector('#webcam');

Em seguida, criaremos um objeto chamado keys para disparar eventos de botões pressionados, na medida em que os gestos forem reconhecidos em tempo real.

const keys = {
up: new KeyboardEvent('keydown', {
key: 'ArrowUp',
}),
down: new KeyboardEvent('keydown', {
key: 'ArrowDown',
}),
left: new KeyboardEvent('keydown', {
key: 'ArrowLeft',
}),
right: new KeyboardEvent('keydown', {
key: 'ArrowRight',
}),
};

Além disso, incluiremos uma variável booleana para permitir ou interromper a classificação (shouldClassify), outra variável booleana para indicar se o modelo MobileNet foi carregado (mobileNetLoaded) e, por fim, uma variável contendo a instância do jogo a ser controlado (snakeGame).

let shouldClassify = false;
let mobileNetLoaded = false;
const snakeGame = new SnakeGame(17, 24, 18, 230, {
boardColor: '#ededed',
snakeColor: '#573dff',
foodColor: '#ff2626',
});

O próximo passo consiste em verificar se a aplicação tem permissão para acessar a webcam. Se a webcam estiver disponível e o acesso for permitido, a aplicação irá obter o fluxo de vídeo e irá exibi-lo na tela. Para garantir que o movimento do usuário em tempo real esteja alinhado com o vídeo, a imagem será espelhada usando o comando scaleX(-1). Dessa forma, os movimentos realizados pelo usuário serão representados corretamente na visualização do vídeo. Caso ocorra algum erro durante esse processo, a aplicação exibirá
uma mensagem na tela, informando que não foi possível acessar a webcam.

if (navigator.mediaDevices.getUserMedia) {
try {
const videoStream = await navigator.mediaDevices.getUserMedia({
video: true,
});
video.srcObject = videoStream;
// deixando a imagem espelhada, através de um flip na horizontal
video.style.webkitTransform = 'scaleX(-1)';
video.style.transform = 'scaleX(-1)';
video.play();
// exibindo video e card para inserir exemplos
video.classList.remove('hide');
insertNewExamplesArea.classList.remove('hide');
} catch (error) {
errorVideoIndicatorArea.classList.remove('hide');
errorVideoIndicatorArea.innerHTML =
"<img src='./assets/images/no-video.png' alt='No video icon' />" +
"<p class='mt-3'>Não foi possível acessar a sua Webcam</p>";
// desabilitar botões de iniciar e parar classificação
btStartClassification.disabled = true;
btStopClassification.disabled = true;
console.error(error.message);
}
}

O próximo passo é criar o objeto referente ao modelo KNN, definir a quantidade mínima de vizinhos (K_NEAREST_NEIGHBORS) e estabelecer um limiar mínimo para a determinação do gesto (MIN_THRESHOLD). Além disso, deve-se criar o objeto responsável por extrair as características dos frames de vídeo usando o MobileNet. Os valores de MIN_THRESHOLD e K_NEAREST_NEIGHBORS foram definidos empiricamente e podem ser ajustados de modo a alcançar um resultado satisfatório.

const MIN_THRESHOLD = 0.90;
const K_NEAREST_NEIGHBORS = 11;
const knnModelClassifier = ml5.KNNClassifier();
const featureExtractor = ml5.featureExtractor('MobileNet', () => {
console.log('Modelo carregado!');
mobileNetLoaded = true;
});

Para adicionar exemplos de cada classe ao modelo KNN, precisamos implementar o código abaixo que utiliza o método forEach para percorrer os elementos do array de objetos chamado btUsedToAddTrainData. Cada objeto contém referências para os botões de inserção de exemplos e os nomes de cada classe. Dentro do loop forEach, são definidos dois eventos: mouseover e mouseout. Quando o mouse está sobre um dos botões, a função addNewTrainData é chamada a cada 100 milissegundos. Quando o mouse sair do botão, o temporizador será finalizado.

btUsedToaddTrainData.forEach((btn) => {
let intervalAddExamples = null;
btn.element.addEventListener('mouseover', () => {
intervalAddExamples = setInterval(() => {
addNewTrainData(btn.label);
}, 100);
});
btn.element.addEventListener('mouseout', () => {
clearInterval(intervalAddExamples);
});
});

A função addNewTrainData recebe a label do botão acionado que é importante para especificar a classe ao qual o gesto a ser executado pertence. Se o modelo MobileNet tiver sido carregado, as características relevantes para o gesto em questão serão extraídas usando o método infer . Para identificar e extrair as características específicas do gesto em tempo real, esse método (infer) analisa os frames utilizando o objeto de vídeo que foi recebido como argumento.

Essas características são adicionadas ao modelo KNN usando o método addExample, que recebe também
como argumento, o nome da classe associada a este exemplo. Em seguida, a quantidade de exemplos
adicionados para a classe correspondente é obtida através do método getCountByLabel, que retorna um objeto.

Essa quantidade é exibida no botão correspondente, usando um elemento de span que pode ser acessado
através do objeto totalExamplesAddedToTrain[label].

 

const addNewTrainData = async (label) => {
if (mobileNetLoaded) {
const features = featureExtractor.infer(video);

Explorando o Potencial da Visão Computacional na Web 10

knnModelClassifier.addExample(features, label);
const totalExamplesPerLabel = knnModelClassifier.getCountByLabel();
totalExamplesAddedToTrain[label].innerText = totalExamplesPerLabel[label];
}
};

 

Para controlar a interação com o teclado com base no gesto reconhecido, é necessário implementar a função controlArrowKeysBasedOnALabel, que recebe o nome do gesto detectado como argumento. A ação de pressionar uma tecla é disparada por meio do método dispatchEvent, que pode ser utilizado para acionar um evento associado ao objeto KeyboardEvent. Neste caso, o objeto keys possui para cada uma das teclas direcionais, uma instância do objeto KeyboardEvent que cria um evento do tipo keydown para as teclas correspondentes (ArrowUp, ArrowDown, ArrowLeft, ArrowRight). Ao especificar keys[label], estamos obtendo acesso ao objeto KeyboardEvent da tecla referente a label passada como argumento.

 

const controlArrowKeysBasedOnALabel = (label) => {
// se não for a classe negativa, pode executar porque é a arrow key
if (label !== 'negative') {
document.dispatchEvent(keys[label]);
}
};

 

O próximo passo é criar a função classifyGesture que será usada para classificar os gestos em tempo real.
Essa função utiliza o objeto featureExtractor para extrair as características dos frames de vídeo. Logo em
seguida, essas características são passadas para o método classify pertencente ao objeto knnModelClassifier, que recebe também, a quantidade mínima de vizinhos K_NEAREST_NEIGHBORS e uma função intitulada getLabelsReturnedFromModel que é executada assim que o gesto realizado no frame de vídeo tiver sido classificado.

 

const classifyGesture = () => {
const features = featureExtractor.infer(video);
return knnModelClassifier.classify(features, K_NEAREST_NEIGHBORS, getLabelsReturnedFromModel);
};

 

A função getLabelsReturnedFromModel é responsável por receber dois parâmetros: um erro e o resultado da classificação. Caso ocorra algum erro durante o processo, a função exibirá a mensagem de erro no console e encerrará sua execução.

Caso não ocorra nenhum erro, a função irá obter a confiança (probabilidade de o gesto realizado ser de uma determinada classe) para cada uma das classes através do atributo result.confidencesByLabel, que retorna um objeto no formato abaixo:

 

{
down: 0.1
left: 0
negative: 0
right: 0

up: 0.9
}

 

Podemos obter uma label com confiança acima do limiar definido por MIN_THRESHOLD usando
Object.entries(confidences).find(([, confidence]) => confidence >= MIN_THRESHOLD) . Isso ajuda a reduzir os falsos negativos e positivos durante o reconhecimento de gestos em tempo real.

O método Object.entries retorna pares de chaves e valores do objeto result.confidencesByLabel, conforme
exemplificado abaixo.

 

[
['down', 0.1],
['left', 0],
['negative', 0],
['right', 0],
['up', 0.9],
]

 

Após isso, o método find é executado com base no array retornado por Object.entries. Esta execução
retornará o primeiro elemento que atender à condição estabelecida, resultando em um array de duas
posições, como: ['up', 0.9] .

Em seguida, verificamos se algum valor foi retornado nessa busca. Se sim, exibimos a imagem correspondente ao gesto no card com o ID detectedGestureIndicatorArea, mencionado anteriormente, e
enviamos a label detectada como argumento para a função responsável pelo acionamento do evento de tecla pressionada.

Após isso, verificamos se a classificação deve continuar, ou seja, se a variável shouldClassify é true. Se for
verdadeira, a função classifyGesture será chamada repetidamente até que a variável shouldClassify seja
definida como false.

const getLabelsReturnedFromModel = (error, result) => {
if (error) {
return console.log(error.message);
}
if (result.confidencesByLabel) {
const confidences = result.confidencesByLabel;
const label =
Object.entries(confidences)
.find(([, confidence]) => confidence >= MIN_THRESHOLD);
if (label) {
recognizedClassImage.src = `./assets/images/${label[0]}.png`;
console.log(`Label: ${label[0]} - ${(label[1] * 100).toFixed(2)}%`);
controlArrowKeysBasedOnALabel(label[0]);
}
}
// se usuário não tiver apertado em parar classificação, continuar em loop
if (shouldClassify) classifyGesture();
};

O evento abaixo é responsável por iniciar a classificação, onde primeiramente, verificamos se existem dados
de exemplos no modelo KNN. Se não houver, a classificação não será realizada e será fornecido um
feedback ao usuário através do elemento com id modelFeedbackIndicatorArea. Caso existam labels inseridas no modelo, as seguintes etapas são executadas:


1. Exibir o canvas do jogo, ou seja, remover a classe hide da div com id gameArea;
2. Iniciar o jogo através do método snakeGame.restartGame() ou snakeGame.startGame() ;
3. Atribuir o valor true para a variável responsável por controlar o loop da classificação;
4. Exibir o card com id detectedGestureIndicatorArea e ocultar o card com id insertNewExamplesArea ;
5. Chamar o método de classificação de gestos, classifyGesture ;

 

btStartClassification.addEventListener('click', () => {
const qtdLabels = knnModelClassifier.getNumLabels();
if (qtdLabels > 0) {
gameArea.classList.remove('hide');
snakeGame.restartGame();
shouldClassify = true;
detectedGestureIndicatorArea.classList.remove('hide');
insertNewExamplesArea.classList.add('hide');
return classifyGesture();
}
modelFeedbackIndicatorArea.classList.remove('hide');
modelFeedbackIndicatorArea.innerText =
'Adicione imagens de exemplos antes de iniciar a classificação';
});

 

Para interromper a classificação, deve-se:

 

1. Esconder o jogo, inserindo novamente a classe hide na div com id gameArea ;
2. Exibir o card com id insertNewExamplesArea e esconder o card com id detectedGestureIndicatorArea ;
3. Atribuir o valor false para a variável shouldClassify ;

btStopClassification.addEventListener('click', () => {
gameArea.classList.add('hide');
detectedGestureIndicatorArea.classList.add('hide');
insertNewExamplesArea.classList.remove('hide');
shouldClassify = false;
});

O próximo passo é permitir salvar o modelo. Para isso, é preciso verificar se o modelo possui labels. Se
houver, basta chamar o método knnModelClassifier.save() . Ao chamar este método, um arquivo no formato JSON será salvo em sua máquina. Esse arquivo contém várias informações relacionadas ao modelo KNN, como dados de configuração, logits, labels e outras informações relacionadas.

 

btSaveModel.addEventListener('click', () => {
if (knnModelClassifier.getNumLabels() > 0) return knnModelClassifier.save();
modelFeedbackIndicatorArea.classList.remove('hide');
modelFeedbackIndicatorArea.innerText =
'Adicione imagens de exemplos antes de salvar o modelo';
});

 

Para carregar o modelo, precisaremos permitir a inserção do arquivo JSON baixado. Ao clicar no botão com
id btLoadModel , o elemento de input criado na página HTML será acionado através de  modelDataFile.click().


btLoadModel.addEventListener('click', () => {
modelDataFile.click();
});

 

Após o arquivo JSON ser inserido, ele poderá ser acessado por meio de e.target.files[0] . Em seguida, este arquivo precisa ser transformado para uma URL usando URL.createObjectURL() , para que, através de
knnModelClassifier.load() , ele possa ser carregado e usado efetivamente pela aplicação. No callback de load , está sendo obtida a quantidade de exemplos já inseridos no modelo carregado para ser feita a atualização e exibição na interface. Em seguida, uma mensagem de sucesso ao carregar modelo é exibida para o usuário na área referente aos feedbacks.

 

modelDataFile.addEventListener('change', (e) => {
modelFeedbackIndicatorArea.classList.remove('hide');
try {
const selectedFile = e.target.files[0];
const objectURL = URL.createObjectURL(selectedFile);
knnModelClassifier.load(objectURL, () => {
const totalExamplesPerLabel = knnModelClassifier.getCountByLabel();
knnModelClassifier.mapStringToIndex.forEach((label) => {
totalExamplesAddedToTrain[label].innerText = totalExamplesPerLabel[label];
});
});
modelFeedbackIndicatorArea.innerText = 'Modelo carregado com sucesso';
} catch (error) {
modelFeedbackIndicatorArea.innerText = 'Erro ao carregar o modelo';
console.log(error);
}
});

 

Ao final da implementação podemos destacar a criação de um botão para reiniciar o jogo conforme
necessário.

 

btRestartGame.addEventListener('click', () => {
snakeGame.restartGame();
});

 

Além disso, foram incluídos trechos de códigos para inserir classes de animação do Font Awesome em todos os botões, para estilizar a interface da aplicação. Através dos eventos mouseover e mouseout , é possível inserir um efeito de hover, que inclui ou remove a classe fa-beat-fade dependendo da ação do usuário.

 

// obtendo apenas os botões, para incluir hover
const btElements = btUsedToaddTrainData.map((btn) => btn.element);
// Os Códigos abaixo incluem hover effects em todos os botões
const listOfButtonsElementsToAddHover = [
btSaveModel,
btLoadModel,
btStartClassification,
btStopClassification,
...btElements,
];
listOfButtonsElementsToAddHover.forEach((element) => {
const animation = 'fa-beat-fade';
element.addEventListener('mouseover', () => {
// obtendo primeiro elemento do botão que é o <i> e colocando a animação
element.firstElementChild.classList.add(animation);
});
element.addEventListener('mouseout', () => {
// obtendo primeiro elemento do botão que é o <i> e removendo a animação
element.firstElementChild.classList.remove(animation);
});
});

 

 

Resultados

 

O código completo do projeto se encontra neste repositório e agora podemos testar a aplicação executando localmente através da extensão Live Share do VS Code.

 

 

Em seguida, a página da aplicação será aberta em seu navegador, permitindo que você forneça exemplos de imagens para classificação dos gestos (cima, baixo, esquerda e direita) em tempo real através da Webcam. Após adicionar a quantidade necessária de exemplos para a classificação de cada classe, você poderá controlar o jogo por meio destes gestos, conforme o exemplo abaixo. Quanto mais exemplos você adicionar, maior será a precisão do modelo.

 


 

Com este blog post, você adquiriu conhecimentos sobre a implementação de aplicações de visão computacional na web, considerando os seguintes pontos abordados:


1. Introdução a Visão Computacional: Foi abordado de forma resumida o conceito de visão computacional, suas possíveis aplicações e o uso de algoritmos dessa área em aplicações voltadas para Web.


2. A biblioteca ML5.js: Neste tópico, foi apresentado o ML5.js, que pode ser usado para diversas tarefas de Machine Learning e Visão Computacional no Browser, como a classificação de imagens em tempo real.

3. MobileNet e KNN: Foi apresentado o MobileNet, uma arquitetura de deep learning otimizada para dispositivos com recursos computacionais limitados como aplicações embarcadas e web, e o KNN (K-Nearest Neighbors), um algoritmo de aprendizado de máquina usado para classificação de dados.

4. Implementação da aplicação de classificação de Gestos em Tempo Real e aplicação Prática da classificação no controle de jogo: Foi detalhado o processo de implementação da aplicação de classificação de gestos utilizando a biblioteca ML5.js, e descrito como essa classificação pode ser aplicada no controle interativo de jogos, possibilitando a construção de experiências mais imersivas e intuitivas para os usuários.

 

Fontes

Pournaras, Xenofon, and Dimitrios A. Koutsomitropoulos. "Deep learning on the web: state-of-the-art object detection using web-based client-side frameworks." 2020 11th International Conference on Information, Intelligence, Systems and Applications (IISA. IEEE, 2020.

ML5.js. Disponível em: <https://learn.ml5js.org/#/>.

Nikhil Thorat. How to build a Teachable Machine with TensorFlow.js. Disponível em:
<https://observablehq.com/@nsthorat/how-to-build-a-teachable-machine-with-tensorflow-js>.