8. Demonstração de Deteção de Objetos AR + IA

8.1 Visão Geral do Projeto

Esta é uma aplicação de Realidade Aumentada WebXR para o Meta Quest 3 que combina:

  • AR passthrough — a sala real é visível através das câmaras dos auscultadores
  • Interação hit-test — um cursor virtual que se encaixa em superfícies do mundo real
  • Deteção de objetos por IA — TensorFlow.js reconhece objetos no feed da câmara e exibe etiquetas no espaço 3D
  • Compreensão de Cena — A deteção de planos WebXR visualiza superfícies detetadas (chão, paredes, mesa)

URL em direto: http://92.113.18.92/

Em fila indiana:

index.html

 

 

8.2 Arquitetura do Projeto

📄 index.html (tudo-em-um)

── HTML canvas + botão UI + elemento de vídeo oculto

── CSS → fundo transparente, interface sobreposta

└── JavaScript

── Three.js motor de renderização 3D (cena, câmara, materiais)

── WebXR API sessão AR, hit-test, deteção de plano

└── TF.js  → Inferência de IA (modelo coco-ssd)

Porquê Three.js pura (sem A-Frame)?

Durante o desenvolvimento, descobriu-se que o A-Frame tem o seu próprio loop interno de renderização que entra em conflito com uma sessão WebXR explícita de ar imersiva. O A-Frame inicia uma  sessão de immersive VR (opaca, sem passthrough) em vez de immersive-ar. Mudar para Three.js puro  deu-nos controlo direto tanto sobre o tipo de sessão como sobre o loop de renderização.

8.3 Stack de Tecnologia

Tecnologia

Versão

Função

Three.js

0.157.0

Renderização 3D, geometria, materiais

WebXR Device API

Nativo do navegador

Sessão AR, teste de acerto, deteção de avião

TensorFlow.js

4.15.0

Motor de inferência ML no navegador

COCO-SSD

2.2.3

Modelo de deteção de objetos pré-treinado

getUserMedia API

Nativo do navegador

Stream de câmara para análise TF.js

 

 

8.4 Fluxo de Aplicação

Carregamento da página

modelo TF.js (coco-ssd) carrega de forma assíncrona (~6MB)

Verificar: navigator.xr.isSessionSupported('immersive-ar')

botão "Enter AR" torna-se ativo

O utilizador clica em "Enter AR"

Pedido: navigator.xr.requestSession('immersive-ar', {...})

Quest ativa o Passthrough (câmara visível através de auscultadores)

Simultaneamente: getUserMedia() stream de câmara para TF.js

renderer.setAnimationLoop() inicia (ciclo de renderização XR)

Cada frame (~72fps no Quest 3)

o   scene.background = null (garante transparência)

o   Teste de acerto atualiza a posição do cursor

o   A deteção de planos atualiza a visualização da superfície

o   A cada 2 segundos runDetection() inferência de IA

o   renderer.render(cena, câmara)

 

8.5 Explicação detalhada do código

1. HTML Estrutura (linhas 1–78)

Um elemento oculto <vídeo> que recebe o fluxo getUserMedia(). TensorFlow.js usa-o como entrada para inferência — nunca é apresentado ao utilizador, apenas analisado.

 

Uma camada de interface sobreposta a flutuar acima da Three.js tela. Eventos de ponteiro: nenhum no contentor mas eventos de ponteiro: todos apenas no botão — para que a sobreposição não bloqueie interações 3D.

2. Three.js Inicialização (linhas 84–109)

alpha: true cria uma tela WebGL com um canal alfa transparente — isto é um pré-requisito para passthrough. xr.enabled = true ativa a integração integrada com WebXR da Three.js.

A câmara é adicionada à cena. Isto é importante porque os objetos ligados à câmara (entidades filhas) precisam de estar na hierarquia da cena.
A geometria do anel é rodada -90° no eixo X, ficando horizontalmente plana nas superfícies detetadas. Sem esta rotação, ficaria na vertical.

3. makeLabel() — Texto Sprite (linhas 113–138)

TRÊS. Sprite é um objeto que está sempre virado para a câmara (outdoors) — ideal para rótulos no espaço 3D. É desenhado numa tela HTML, convertido numa CanvasTexture e aplicado a um SpriteMaterial.

depthTest: false garante que a etiqueta é sempre visível mesmo que esteja geometricamente "atrás" de outro objeto 3D.

4. detecçãoTo3D() —  Projeção 2D → 3D (linhas 140163)

Este é o núcleo matemático da integração IA-AR.

Exemplo: Se a IA detetar uma cadeira no terço esquerdo do vídeo, nx ≈ -0,17. Isto converte-se num ângulo negativo (à esquerda do eixo do olhar). A etiqueta é colocada 1,8m à frente da câmara nessa direção.

5. makeBox3D() e bbox2dSize3D() — Caixas Delimitadoras 3D (linhas 165–182)

EdgesGeometry + LineSegments renderiza apenas as arestas de uma caixa — o efeito de uma borda fina em wireframe sem superfície preenchida.
A  fórmula totalW é a projeção de perspetiva padrão: à distância d, a largura visível depende do FOV. A partir disto, deduzimos quantos metros correspondem aos píxeis da caixa delimitadora.

6. runDetection() — Pipeline de Inferência de IA (linhas 205–247)

cocoModel.detect(tfVideo) aceita diretamente um elemento <vídeo> — TF.js lê internamente dados de píxeis do quadro de vídeo atual.

Formato Pred.bbox : [X, Y, Largura, Altura] em pixels.

Cada deteção cria um par JS (sprite, caixa) que dura 4 segundos.

7. Sessão WebXR — Detalhes Chave (linhas 274–285)

 

Porquê a realidade aumentada imersiva e não a realidade virtual imersiva?

  • immersive-VR = fundo preto opaco (experiência de capacete VR)
  • immersive-ar = câmara passthrough + conteúdo virtual sobreposto

o espaço de referência do piso local fixa o sistema de coordenadas ao piso da sala — y=0 está ao nível do piso.

O teste de acerto e  a deteção de aviões são opcionais porque não são suportados em todos os dispositivos e versões do navegador — declará-los como opcionais impede que a sessão falhe se não estiverem disponíveis

8. Loop de Renderização (linhas 335–395)

Porque setAnimationLoop e não requestAnimationFrame?

O  loop de renderização Three.js XR deve estar integrado com o callback de frames WebXR. renderer.setAnimationLoop() liga-se automaticamente à sessão XR quando renderer.xr.enabled = true. A alternativa (chamar manualmente session.requestAnimationFrame()) requer chamar o renderer.render() manualmente, mas corre o risco de perder as matrizes corretas de visualização XR que Three.js se aplicam internamente.

scene.background = null deve ser chamado em cada frame porque Three.js pode redefinir este valor internamente durante certas operações.

9. Deteção de Planos (linhas 355–378)

plane.planeSpace é um XRSpace que rastreia a superfície física. frame.getPose() devolve a sua pose no espaço de referência escolhido.

O mapa DetectedPlanes (Map<XRPlane, TRÊS. Mesh>) impede a criação de malhas duplicadas para a mesma superfície entre frames.

8.6 Limitações Conhecidas

Limitação

Razão

As etiquetas de IA não estão alinhadas perfeitamente com o objeto       

As câmaras passthrough do Quest e o getUserMedia entregam diferentes streams com diferentes FOV e calibração ótica

A deteção de planos requer que a Configuração da Sala de Missões seja concluída

O headset deve ter escaneado a sala anteriormente

O getUserMedia pode ser recusado em alguns dispositivos

Depende das permissões do navegador

A inferência de IA não é em tempo real (executa-se a cada 2 segundos)

O Coco-SSD é um modelo relativamente pesado; Alternativas mais leves (SSD MobileNet) dariam maior débito

 

 

8.7 Código-fonte

https://github.com/bciric1/ARVR

http://vr.aiforvet.eu/

 

 

 

 

Last modified: Thursday, 18 June 2026, 4:57 AM