Voltar ao blog

Construindo um classificador de imagens no navegador com TensorFlow.js

Construindo um classificador de imagens no navegador com TensorFlow.js

TensorFlow.js é uma poderosa biblioteca JavaScript que traz machine learning para o browser e outras plataformas web (codelabs.developers.google.com). Neste post, vamos explorar como você pode criar, treinar e executar um modelo de classificação de imagens inteiramente no navegador, sem depender de um servidor externo. Isso permite aplicações low latency, com privacidade maior (os dados não saem do dispositivo) e funcionam até mesmo offline. Aprenderemos também como aproveitar o backend WebGL para acelerar a computação no GPU do usuário, evitando travamentos na interface e otimizando o desempenho do nosso modelo (dev.to) (www.tensorflow.org). Ao final, você terá uma visão clara dos passos necessários para preparar dados de imagem, definir uma rede neural, treiná-la no cliente e utilizá-la em tempo real.

O que é TensorFlow.js e por que usar no navegador

TensorFlow.js é, como descreve um tutorial da Google, “uma biblioteca de machine learning avançada e flexível para JavaScript” (codelabs.developers.google.com). Diferentemente de frameworks tradicionais (que normalmente rodam em Python), o TensorFlow.js permite definir, treinar e executar modelos de ML direto no navegador. Ou seja, todo o processamento de treino e inferência acontece no lado do cliente, usando recursos do próprio dispositivo, sem necessidade de comunicação com servidor. Isso traz várias vantagens:

  • Baixa latência e privacidade: as imagens ou dados usados nunca precisam ser enviados para servidores remotos, garantindo respostas mais rápidas e privacidade do usuário.
  • Execução em múltiplas plataformas: qualquer navegador moderno (desktop ou mobile) pode rodar TensorFlow.js. O mesmo código JS funciona em Chrome, Firefox, Safari, etc.
  • Acesso a hardware disponível: quando possível, TensorFlow.js utiliza a GPU do dispositivo via WebGL para acelerar cálculos de álgebra linear de forma paralela (dev.to) (www.tensorflow.org). Em GPUs comuns, algumas operações podem ser até 100x mais rápidas que no backend de CPU puros (www.tensorflow.org). Caso a GPU não esteja disponível, ele cai para um backend de WASM (WebAssembly) ou mesmo JavaScript puro (menos comum) para garantir compatibilidade (wild.codes).

Em termos práticos, você pode incluir o TensorFlow.js na sua página usando uma tag <script> que aponta para o CDN oficial. Por exemplo, para trabalhar com classificação de imagens usando o modelo pré-treinado MobileNet, incluiríamos no <head>:

<!-- Carrega o TensorFlow.js --> <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs"></script> <!-- Carrega o modelo pré-treinado MobileNet --> <script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/mobilenet"></script>

Esses trechos de código são baseados em um codelab do Google que mostra como carregar um modelo em browser (codelabs.developers.google.com). Após isso, podemos usar a API mobilenet.load() para obter o modelo pré-treinado em JavaScript.

Ambientes e Backends

TensorFlow.js possui diferentes backends para realizar cálculos:

  • WebGL (padrão no navegador): usa a API gráfica do navegador para computação paralela na GPU.
  • WASM (WebAssembly): executa no processador (CPU) mas com otimizações modernas, incluindo SIMD e multithreading (wild.codes). Bom para dispositivos sem GPU estável.
  • cpu (JS puro): fallback simples.

Em geral o TF.js escolhe automaticamente o melhor backend disponível (www.tensorflow.org), mas você pode explicitamente definir o backend no código, por exemplo:

await tf.setBackend('webgl'); // força uso do backend WebGL console.log(`Backend atual: ${tf.getBackend()}`);

Segundo a documentação do TensorFlow.js, o backend WebGL é “atualmente o mais poderoso para o navegador”, oferecendo aceleração massiva (www.tensorflow.org). Para ilustrar com uma analogia: pense na GPU como uma cozinha industrial com muitos chefs trabalhando em paralelo (pixels, matrizes) versus a CPU como um único chef fazendo tarefas uma a uma. O WebGL permite triturar pedaços de imagem em paralelo, como vários sous-chefs em uma cozinha, acelerando a operação.

No entanto, vale lembrar que usar o WebGL exige alguns cuidados adicionais, especialmente com memória. Ao contrário dos objetos JavaScript comuns, as texturas WebGL (onde os tensores são armazenados) não são coletadas automaticamente pelo navegador. Portanto, precisamos liberar manualmente esses recursos para evitar vazamentos de memória (www.tensorflow.org). Usaremos métodos como tensor.dispose() ou tf.tidy() para isso (veremos mais detalhes na seção de otimização).

Preparando os dados de imagem

Antes de construir o modelo, precisamos definir como obter e preparar as imagens que ele irá classificar. Uma classificação de imagens típica envolve estes passos:

  1. Coleta de imagens: você pode usar um conjunto de imagens existentes (dataset público), ou permitir que o usuário faça upload de imagens via um <input type="file">, ou ainda capturar diretamente da webcam com <video>.
  2. Pré-processamento: ajustar o tamanho (largura e altura), normalizar valores de pixel, e transformar o formato da imagem em um tensor que o TensorFlow.js consiga entender.
  3. Criação de lotes (batches): o treino do modelo costuma ser feito em lotes de várias imagens por vez para maior eficiência.

Carregando imagens HTML

Suponha que temos um elemento de imagem no HTML:

<img id="minhaImagem" src="foto-exemplo.jpg" width="150">

Podemos converter isso em um tensor utilizando tf.browser.fromPixels(). Por exemplo:

const imgEl = document.getElementById('minhaImagem'); // Lê os pixels diretamente do elemento <img> do DOM: let tensorImg = tf.browser.fromPixels(imgEl) .resizeNearestNeighbor([150, 150]) // redimensiona para 150x150 (caso seja necessário) .toFloat() // converte para float32 .div(255) // normaliza valores de 0-255 para 0-1 (opcional) .expandDims(); // adiciona dimensão de batch (1) no início

Agora tensorImg é uma variável tf.Tensor4D com forma [1, 150, 150, 3] (um batch de uma imagem RGB). Podemos passar esse tensor diretamente para modelos de classificação pré-treinados ou para nossos próprios modelos.

Usando webcam em tempo real

Outra forma interessante é usar a câmera do usuário para coletar imagens. O TensorFlow.js fornece uma API conveniente: tf.data.webcam(). Com ela, você aponta para um elemento HTML <video> e pode capturar quadros (frames) como tensores. Exemplo:

const video = document.getElementById('webcam'); const webcam = await tf.data.webcam(video); // No loop de inferência: const imgTensor = await webcam.capture(); // ... usa imgTensor em predição ... imgTensor.dispose();

Esse método retorna cada quadro em um tensor tf.Tensor3D com gama de pixels [0,255]. Novamente, podemos chamar resize e toFloat().div(255) conforme o modelo exigir. Recomenda-se descartar (dispose()) o tensor após usá-lo para liberar memória GPU.

Construindo e treinando o modelo de classificação

Agora que sabemos lidar com dados de imagem, vamos criar nossa rede neural de classificação direto no navegador. Existem duas abordagens principais:

  • Construir uma rede do zero: definir camadas (convolucionais, densas, etc) com tf.layers.
  • Transferência de aprendizado: aproveitar um modelo pré-treinado (como MobileNet) e treinar apenas as camadas finais com nossas próprias imagens.

Nesta seção focaremos na construção do modelo do zero para fins didáticos. Suponha que queremos classificar imagens pequenas (por exemplo, 2 ou 3 classes simples). Usaremos um modelo sequencial básico:

const model = tf.sequential(); // Primeira camada convolucional que extrai recursos (features) da imagem: model.add(tf.layers.conv2d({ inputShape: [150, 150, 3], filters: 16, kernelSize: 3, activation: 'relu' })); model.add(tf.layers.maxPooling2d({poolSize: 2, strides: 2})); // Segunda camada convolucional: model.add(tf.layers.conv2d({filters: 32, kernelSize: 3, activation: 'relu'})); model.add(tf.layers.maxPooling2d({poolSize: 2, strides: 2})); // Achata as saídas para alimentar camadas densas: model.add(tf.layers.flatten()); // Camada oculta densa: model.add(tf.layers.dense({units: 64, activation: 'relu'})); // Camada de saída com 'numClasses' neurônios: const numClasses = 3; model.add(tf.layers.dense({units: numClasses, activation: 'softmax'}));

Nesse exemplo, criamos uma rede convolucional simples com duas camadas Conv2D (seguida de pooling) para extrair padrões visuais, seguidas de camadas densas para classificação. A camada final usa a ativação softmax, adequada para classificação multiclasse, gerando probabilidades para cada classe. Você deve ajustar inputShape (tamanho da imagem) e numClasses de acordo com seu problema.

Compilando o modelo

Antes de treinar, é preciso compilar o modelo definindo o otimizador, função de perda e métricas de avaliação:

model.compile({ optimizer: tf.train.adam(), loss: 'categoricalCrossentropy', metrics: ['accuracy'] });

Usamos o otimizador Adam, a perda categoricalCrossentropy (assumindo que as labels serão codificadas em one-hot) e queremos acompanhar a acurácia. Esses são bons valores padrão para classificação.

Preparando os dados de treino

Para treinar, precisamos preparar tensores de entrada (xs) e saída (ys). Suponha que temos duas classes (0 e 1) e algumas imagens para cada classe. Em um exemplo simples, podemos vetorizar as imagens e labels assim:

// Suponha que trainImages é uma matriz JavaScript contendo tensores de imagens // e trainLabels é um array de inteiros (0 ou 1) representando a classe. const xs = tf.stack(trainImages); // trainImages: array de tf.Tensor4D (cada com formato [1, 150,150,3]). 'stack' cria uma Tensor4D batch. const labelsTensor = tf.tensor1d(trainLabels, 'int32'); // Converte inteiros em vetor one-hot: const ys = tf.oneHot(labelsTensor, numClasses); // Exemplo: trainImages.length = 100, numClasses = 2 // xs.shape = [100, 150, 150, 3]; ys.shape = [100, 2].

Em projetos reais, você carregaria essas imagens de arquivos, URLs ou webcam e converteria cada uma do jeito ilustrado acima. Há também APIs de dados (tf.data) que podem montar datasets e alimentar o modelo em batches, o que é útil para grandes quantidades de dados.

Treinando no navegador

Com xs e ys prontos, treinamos o modelo chamando model.fit():

await model.fit(xs, ys, { epochs: 10, batchSize: 32, validationSplit: 0.1, callbacks: { onEpochEnd: (epoch, logs) => { console.log(`Época ${epoch+1}: perda = ${logs.loss.toFixed(3)}, acurácia = ${logs.acc.toFixed(3)}`); } } });

Esse código executa 10 épocas de treinamento, em lotes de 32 imagens, e separa 10% dos dados para validação. A cada época, mostraremos no console o progresso. Note que o treinamento pode ser demorado no navegador, especialmente em CPUs mobile, mas ao menos ele não bloqueia a interface graças ao backend WebGL.

Um ponto importante: libere a memória ao trabalhar com muitos tensores. TensorFlow.js não recupera automaticamente a memória GPU de tensores não utilizados (www.tensorflow.org). Podemos usar tf.dispose(tensor) ou colocar operações dentro de tf.tidy() para descartar automaticamente intermediate tensors após a execução:

tf.tidy(() => { // Esse bloco libera todos os tensores criados dentro dele, exceto o que retornamos. const logits = model.predict(tf.browser.fromPixels(imgEl).toFloat().expandDims()); // ... faz coisas com logits ... });

Isso evita vazamentos como described in [21].

Exemplos práticos de inferência (classificando imagens)

Com o modelo treinado, podemos usá-lo para inferência em novas imagens. Vamos ver dois cenários comuns: classificar uma imagem estática e usar a webcam em tempo real.

Classificação em imagens estáticas

Imagine termos outra imagem no HTML, e queremos prever sua classe. Considerando que nosso modelo espera entradas 150x150, faríamos:

<img id="imgTeste" src="foto-teste.jpg" width="150">
// Obtém o elemento de imagem const imgEl = document.getElementById('imgTeste'); // Prepara o tensor (mesma pré-processamento do treinamento) const imgTensor = tf.browser.fromPixels(imgEl) .resizeNearestNeighbor([150, 150]) .toFloat() .div(255) .expandDims(); // Executa a inferência: const preds = model.predict(imgTensor); // 'preds' é um tensor com shape [1, numClasses] const probabilities = preds.dataSync(); // Converte para array no CPU (infere assincrona, mas aqui usamos dataSync para simplificar) console.log(`Probabilidades: ${probabilities}`); imgTensor.dispose(); preds.dispose();

Após a predição, probabilities será algo como [0.12, 0.88] em um caso de 2 classes. Podemos escolher a classe com maior valor. No exemplo acima usamos dataSync() para esperar o resultado (síncrono), porém em uma aplicação real pode ser melhor usar await preds.data() (assíncrono) para não bloquear a thread de UI (www.tensorflow.org).

Inferência em tempo real com webcam

Para aplicações dinâmicas, podemos classificar cada quadro capturado da webcam. Usando tf.data.webcam, podemos criar um loop de detecção contínua:

const webcamElement = document.getElementById('webcam'); const webcam = await tf.data.webcam(webcamElement); while (true) { const img = await webcam.capture(); // Pré-processamento idêntico: const imgResized = img.resizeNearestNeighbor([150, 150]).toFloat().div(255).expandDims(); const prediction = model.predict(imgResized); const probs = prediction.dataSync(); console.log(`Classe prevista: ${probs.indexOf(Math.max(...probs))}, prob: ${Math.max(...probs).toFixed(2)}`); img.dispose(); imgResized.dispose(); prediction.dispose(); // Dá um tempinho antes de capturar o próximo frame: await tf.nextFrame(); }

Dessa forma, classificamos cada novo frame da câmera em tempo real. Notamos o uso de await tf.nextFrame(), que cede o controle de volta ao navegador permitindo atualizar a página e manter a UI responsiva (codelabs.developers.google.com) (web.dev). Cada tensor é descartado logo após o uso (dispose()), o que evita esgotar a memória GPU.

Otimizações de desempenho com WebGL e boas práticas

Para que nosso classificador funcione rápido e suave, aproveitaremos ao máximo o backend WebGL. Além de simplesmente chamar tf.setBackend('webgl'), veja algumas dicas avançadas:

  • Backend WebGL vs WASM: Por padrão, TensorFlow.js usa WebGL no front-end. Como vimos, nesse backend os cálculos são transferidos para a GPU, paralelizando heavy tasks (dev.to) (www.tensorflow.org). Em GPUs sem suporte completo ou em navegadores antigos, o fallback é o backend WASM, que roda no CPU porém com otimizações. Para maximizar o desempenho, é importante escolher ou confirmar o backend. Podemos verificar tf.getBackend() e, se necessário, alternar:
    if (tf.getBackend() !== 'webgl') { await tf.setBackend('webgl'); await tf.ready(); }
  • Prefazer/“warm up” o modelo: O primeiro cálculo em um modelo WebGL faz com que o TF.js compile shaders para a GPU, o que demora um pouco. Uma técnica comum é rodar uma inferência "de aquecimento" assim que o modelo carrega, para “pré-compilar” o pipeline. Exemplo:
    // Dummy input compatível com o modelo (zeros serve apenas para warm-up) const warmupInput = tf.zeros([1, 150, 150, 3]); model.predict(warmupInput).dispose(); warmupInput.dispose();
    Assim, cálculos futuros reais ficam mais rápidos. Como notou um desenvolvedor, esse warm-up ajuda a evitar o lag inicial de 2-3 segundos no primeiro uso do modelo (dev.to).
  • Evite bloqueio da UI: Conforme a documentação do TensorFlow.js, operações intensas (como tensor.dataSync()) podem bloquear a thread principal, congelando a interface (www.tensorflow.org). Sempre que possível, use métodos assíncronos (await tensor.data(), await tensor.array()) e realoque cálculos para web workers. No nosso loop de webcam, por exemplo, usamos await tf.nextFrame() para que o navegador possa desenhar cada quadro.
  • Gerenciamento de memória: Já mencionamos dispose(). Em adição, use tf.tidy() para envolver funções e descartar automaticamente tensores temporários ao final da execução (www.tensorflow.org). Por exemplo:
    tf.tidy(() => { const output = model.predict(tf.browser.fromPixels(imgEl).toFloat().expandDims()); // O tensor 'output' deve ser retornado ou copiado antes do tidy });
    O TensorFlow.js alerta que esquecer de liberar tensores causa vazamento de memória, que é um dos erros mais comuns em apps TF.js (wild.codes) (www.tensorflow.org). Sempre monitore tf.memory().numTensors durante o desenvolvimento.
  • Quantização e tamanhos de modelo: Para aplicações reais, é interessante usar modelos menores. Técnicas como quantização (por exemplo, converter pesos de float32 para int8) reduzem drasticamente o tamanho do modelo (2-4x menor) com perda mínima de precisão (wild.codes). Isso acelera o carregamento e a inferência. No contexto do navegador, modelos pré-treinados podem ser carregados em formatos quantizados ou sharded.
  • Servir de forma eficiente: Em aplicações em produção, armazene modelos em CDN e use chunking. O TensorFlow.js permite dividir os pesos em vários arquivos e até armazenar em IndexedDB após o download, evitando rebaixá-los a cada visita do usuário (wild.codes).

De forma geral, seguir boas práticas de performance em aplicações de ML no browser faz a diferença entre um modelo lento e algo próximo de tempo real. Pode-se por exemplo, medir o tempo de inferência (FPS) em diferentes dispositivos e ainda decidir trocar para o backend WASM se a GPU falhar. Mas em muitos casos de classificação de imagem clássica, o WebGL provê aceleração suficiente para obter várias dezenas de FPS em laptops modernos.

Conclusão

Neste artigo vimos como construir um classificador de imagens no navegador usando o TensorFlow.js, cobrindo desde o carregamento de dados até o treino e inferência em tempo real. Aprendemos que é possível aproveitar modelos pré-treinados (como MobileNet) ou criar nossas próprias redes no front-end. Além disso, ressaltamos a importância do backend WebGL, que usa a GPU do cliente e pode acelerar os cálculos em até centenas de vezes (www.tensorflow.org), desde que o uso de memória seja bem gerenciado (com dispose() e tf.tidy()).

Para avançar além deste tutorial, você pode explorar:

  • Transferência de aprendizado: adaptar modelos complexos pré-treinados ao seu caso, treino apenas das camadas finais.
  • Web Workers e WebGPU: usar workers para não bloquear a UI, e acompanhar o desenvolvimento da WebGPU, que no futuro poderá superar o WebGL (web.dev) (web.dev).
  • Aplicações reais: integrar a classificação de imagens em apps web, como filtros de foto, detecção embarcada ou AR.

O futuro do ML no navegador é promissor. Com otimizações constantes na biblioteca e com o aumento de suporte a padrões gráficos (WebGL 2.0, WebGPU), logo veremos modelos ainda mais complexos rodando suavemente em qualquer navegador. Seja criando protótipos rápidos ou liberando aplicações de AI ao vivo para usuários finais, o TensorFlow.js oferece as ferramentas para democratizar a IA diretamente no cliente (dev.to) (www.tensorflow.org).

Boa experimentação!

Quer aprender mais sobre Programação?

Você acabou de ganhar 1 hora com a minha equipe para uma call exclusiva! Vamos entender o seu momento e te mostrar o caminho para se tornar um programador de sucesso. Clique no botão abaixo e agende agora mesmo!