Desenvolvimento Web

Algoritmo de sugestão de corte por formato de rosto em Indianópolis

Descubra como desenvolver algoritmo inteligente para sugerir cortes de cabelo baseados no formato facial. Guia técnico completo com implementação prática.

O salão Beleza Natural em Indianópolis recebia diariamente clientes indecisas diante do espelho. “O que fica bem no meu rosto?” — a pergunta ecoava nas cadeiras, consumindo 15 a 20 minutos de consulta por atendimento. A proprietária, Mariana, notou que 40% das clientes saíam com corte diferente do solicitado inicialmente, muitas vezes arrependidas na semana seguinte. O problema não era falta de habilidade dos cabeleireiros, mas a ausência de ferramenta objetiva para traduzir geometria facial em recomendação técnica.

A solução nasceu de um hackathon interno: um algoritmo que analisa foto do rosto, identifica 468 landmarks faciais via MediaPipe, classifica o formato em cinco categorias principais e sugere três cortes ranqueados por compatibilidade estética. Em seis meses, o tempo de consulta caiu para 5 minutos, a taxa de retenção subiu 23% e o NPS saltou de 72 para 89. Este artigo detalha a arquitetura, decisões técnicas e lições aprendidas para quem deseja replicar ou adaptar a abordagem.

O segredo não está apenas no modelo de visão computacional, mas na integração fluida com o fluxo de trabalho do salão: o profissional valida, ajusta ou rejeita a sugestão, e cada interação retroalimenta o sistema. Vamos dissecar cada camada — da captura de imagem ao deploy em produção —, com código pronto para adaptação.

Arquitetura geral do sistema

O pipeline divide-se em três estágios independentes, comunicados via filas assíncronas RabbitMQ. O estágio de percepção roda no navegador da cliente (WebAssembly + WebGL): carrega imagem, executa MediaPipe Face Mesh, extrai 468 coordenadas normalizadas (x, y, z) e envia JSON de ~15 KB para o backend. Latência média: 180 ms em iPhone 12, 320 ms em Android intermediário.

O estágio de classificação roda em container Docker com Python/FastAPI. Recebe landmarks, calcula 12 medidas geométricas (largura testa, maxilar, pomulos, comprimento facial, razões áureas derivadas) e alimenta classificador XGBoost treinado com 3.200 amostras rotuladas por três visagistas independentes (kappa de Fleiss = 0,87). Retorna formato facial com score de confiança.

O estágio de recomendação consulta base de conhecimento PostgreSQL: tabela face_shape_cuts mapeia cada formato para cortes com pesos sazonais e popularidade local. Aplicam-se filtros: tipo de cabelo (liso, ondulado, cacheado, crespo), comprimento atual, manutenção desejada. Resultado: array de até 5 sugestões ordenadas por match score composto.

Detecção facial robusta no frontend

A escolha do MediaPipe Face Mesh sobre alternativas (Dlib, OpenCV Haar Cascades, MTCNN) baseou-se em três critérios: execução 100% client-side sem servidor GPU, latência sub-300 ms em dispositivos de 3 anos atrás e landmarks anatômicos consistentes (não apenas bounding box). O modelo face_mesh.tflite quantizado INT8 pesa 3,2 MB — cacheado via Service Worker.

// Inicialização lazy-loaded do MediaPipe Face Mesh
import { FaceMesh } from '@mediapipe/face_mesh';
import { Camera } from '@mediapipe/camera_utils';

let faceMesh = null;
async function initFaceMesh() {
  if (faceMesh) return faceMesh;
  faceMesh = new FaceMesh({
    locateFile: (file) => `https://cdn.jsdelivr.net/npm/@mediapipe/face_mesh/${file}`
  });
  faceMesh.setOptions({
    maxNumFaces: 1,
    refineLandmarks: true, // iris, lips, contours
    minDetectionConfidence: 0.7,
    minTrackingConfidence: 0.7
  });
  return faceMesh;
}

// Extração de landmarks normalizados [0,1] x [0,1] x [-1,1]
async function extractLandmarks(videoElement) {
  const fm = await initFaceMesh();
  return new Promise((resolve) => {
    fm.onResults((results) => {
      if (!results.multiFaceLandmarks?.[0]) {
        resolve(null);
        return;
      }
      const landmarks = results.multiFaceLandmarks[0].map(lm => [lm.x, lm.y, lm.z]);
      resolve(landmarks); // 468 pontos [[x,y,z], ...]
    });
    fm.send({ image: videoElement });
  });
}

Pontos críticos: refineLandmarks: true habilita contorno de íris, lábios e sobrancelhas — essenciais para medir abertura ocular e arco superciliar, discriminadores fortes entre formatos coração e diamante. O minDetectionConfidence: 0.7 filtra falsos positivos em iluminação ruim; abaixo disso, a UX exibe “Posicione o rosto na oval” com overlay SVG.

Para garantir diversidade de iluminação e pose no dataset de treino, implementamos test-time augmentation no frontend: capturamos 3 frames (frontal, +15° yaw esquerda, +15° yaw direita) e enviamos todos. O backend faz ensemble votando pelo formato majoritário — reduziu erro de classificação em 18% nos testes de validação cruzada.

Classificação geométrica de formato facial

Doze features engenheiradas a partir dos 468 landmarks alimentam o classificador. As mais discriminantes (importância XGBoost > 0,08):

Feature Fórmula Interpretação visagista
face_ratio face_height / face_width Alongamento vertical
forehead_jaw_ratio forehead_width / jaw_width Dominância testa vs. maxilar
cheekbone_ratio cheekbone_width / face_width Proeminência malar
chin_sharpness angle(chin_tip, jaw_left, jaw_right) Angularidade do queixo
temple_width_ratio temple_width / face_width Largura temporária

O classificador XGBoost (100 estimadores, max_depth=4, learning_rate=0,1) atinge 94,2% acurácia no holdout estratificado (20% dos dados). Matriz de confusão revela confusão residual entre oval e redondo (6% dos casos) — mitigado no estágio de recomendação permitindo cortes compatíveis com ambos.

O modelo é versionado com MLflow, retreinado mensalmente com dados novos (feedback loop). Deploy via docker build --target production gera imagem 247 MB. Healthcheck expõe /metrics Prometheus: latência p95 < 120 ms, throughput 85 req/s em 2 vCPU / 4 GB RAM.

Base de conhecimento: mapeamento formato → corte

A tabela face_shape_cuts não é estática — incorpora sazonalidade, tendências locais de Indianópolis e restrições físicas do cabelo. Exemplo de linhas ativas:

face_shape cut_id base_weight seasonal_boost hair_type_filter maintenance_level
square bob_angulado 0.92 +0.15 (outono) liso, ondulado medio
square long_layers 0.87 +0.08 (verão) todos baixo
round pixie_texturizado 0.91 +0.12 (primavera) liso, ondulado alto
round long_bob_assimetrico 0.89 +0.10 (inverno) todos medio
heart curtain_bangs 0.94 +0.18 (ano todo) liso, ondulado medio
diamond side_swept_fringe 0.90 +0.05 (outono) todos baixo
oval blunt_cut 0.85 0.00 todos baixo

O match_score final combina: base_weight * 0.6 + seasonal_boost * 0.2 + popularity_local * 0.15 + professional_affinity * 0.05. popularity_local vem de analytics do agendamento (cortes mais pedidos em Indianópolis nos últimos 90 dias). professional_affinity é preferência declarada do cabeleireiro logado (cada profissional cadastra seus 5 cortes “assinatura”).

Essa abordagem híbrida — dados + expertise — evita recomendações tecnicamente corretas mas comercialmente inviáveis. Um corte para rosto quadrado em Indianópolis bem executado requer texturização específica que nem todo profissional domina; o sistema respeita isso.

Integração com fluxo de agendamento

O endpoint POST /api/v1/recommendations recebe:

{
  "landmarks": [[0.42, 0.31, -0.02], ...],
  "client_id": "cli_7x9k2",
  "professional_id": "pro_3m8n1",
  "hair_type": "wavy",
  "current_length": "shoulder",
  "maintenance_preference": "low"
}

Resposta em < 200 ms:

{
  "face_shape": "square",
  "confidence": 0.91,
  "recommendations": [
    {
      "cut_id": "bob_angulado",
      "name": "Bob Angulado",
      "match_score": 0.94,
      "reason": "Suaviza maxilar angular, alonga rosto",
      "estimated_time_min": 45,
      "price_tier": 2
    },
    {
      "cut_id": "long_layers",
      "name": "Camadas Longas",
      "match_score": 0.89,
      "reason": "Movimento nas laterais reduz largura aparente",
      "estimated_time_min": 60,
      "price_tier": 3
    }
  ],
  "model_version": "v2.3.1-2024-01-15"
}

O frontend do agendamento (desenvolvido com React + TanStack Query) exibe cards visuais com foto do corte, razão técnica e botão “Agendar este”. Ao clicar, pré-preenche service_id no formulário de agendamento. O profissional vê badge “Sugerido por IA” e pode confirmar, substituir ou adicionar observação — tudo auditado.

Para evitar acoplamento forte, a recomendação é opcional: se a API falhar (timeout 2s, circuit breaker), o agendamento prossegue normalmente. Implementamos fallback estático no frontend: JSON embutido com top-3 cortes por formato, atualizado a cada deploy. Isso garante resiliência — lição aprendida com estratégias de versionamento de código aplicadas a modelos ML.

Feedback loop e melhoria contínua

Cada agendamento concluído gera evento cut_completed no Kafka com payload enriquecido:

{
  "recommendation_id": "rec_a1b2c3",
  "cut_performed_id": "bob_angulado",
  "professional_rating": 5, // 1-5
  "client_satisfaction": 4, // pesquisa pós-atendimento
  "return_visit_30d": true,
  "photos_before_after": ["s3://...", "s3://..."]
}

Três pipelines consomem esse tópico:

  1. Retreino mensal: acumula 500+ amostras, roda hyperparameter tuning Optuna (20 trials), valida em backtesting temporal (últimos 3 meses). Só promove se lift > 1,5% em match_score médio.
  2. Detecção de drift: monitora distribuição de face_shape predito vs. histórico. Alerta se KS-test p-value < 0,01 — indica mudança demográfica da clientela ou degradação de câmera.
  3. Painel de gestão: Metabase dashboard com cohort analysis por formato facial, profissional, estação. Identificou que coração + curtain bangs tinha 92% satisfação mas só 34% retorno — investigação revelou manutenção de franja como barreira.

A governança inclui model card documentado (limitações: não avalia textura capilar, densidade, couro cabeludo) e data card do dataset de treino (composição demográfica, consentimento LGPD). Revisão trimestral com comitê ético interno (2 devs, 2 cabeleireiros, 1 cliente frequente).

Lições práticas para sua implementação

Comece com MVP estreito: um formato facial (ex.: quadrado), um corte alvo, 50 fotos rotuladas manualmente. Valide hipótese de valor antes de investir em pipeline completo. Use boas práticas de programação para evitar bugs desde o dia zero — type hints no Python, Zod schemas no TypeScript, testes de contrato entre frontend/backend.

Invista em UX de captura: guia visual oval, contagem regressiva 3-2-1, feedback háptico (Vibration API) quando landmarks estabilizam. Clientes leigas não sabem “olhar para a câmera” — o app deve ensinar. Acessibilidade: suporte a VoiceOver/TalkBack descrevendo posicionamento.

Não subestime o fator humano. O algoritmo sugere, o profissional decide, a cliente escolhe. Projete para aumentar autonomia, não substituir expertise. O maior ganho do Beleza Natural não foi acurácia do modelo, foi transformar “O que faço?” em “Olha, essa opção combina com seu rosto — quer ver como fica?” — conversa diferente, resultado melhor.

Pronto para começar? Clone o repositório base, rode docker-compose up, substitua dataset pelo seu, ajuste pesos na tabela face_shape_cuts. Em duas semanas você tem recomendação funcional. Em dois meses, tem sistema que aprende com cada corte. A tecnologia é commodity — o diferencial está em como você a insere no ritual da cadeira do salão.

Checklist prático

0 de 9 concluídos

Perguntas frequentes

Quais tecnologias são mais indicadas para detectar formato de rosto em aplicações web?

As tecnologias mais indicadas incluem MediaPipe Face Mesh para detecção de landmarks faciais em tempo real no navegador, TensorFlow.js para modelos de classificação de formato facial e OpenCV.js para processamento de imagem client-side. A escolha depende da precisão necessária e dos recursos computacionais disponíveis no dispositivo do usuário.

Como mapear medidas faciais para categorias de formato de rosto padronizadas?

O mapeamento utiliza proporções entre largura da testa, maxilares, comprimento facial e largura dos pomulos. Calcula-se índices como razão largura/comprimento e diferença entre medidas transversais. Esses valores alimentam um classificador baseado em regras ou modelo ML treinado com dataset rotulado por especialistas em visagismo.

É viável rodar detecção facial complexa apenas no frontend sem backend?

Sim, é viável com WebAssembly e WebGL acceleration. MediaPipe Face Mesh roda a 30+ FPS em dispositivos móveis modernos. Para aplicações críticas, recomenda-se arquitetura híbrida: detecção leve no frontend e classificação pesada via API, garantindo fallback offline com modelo quantizado TensorFlow Lite.

Como integrar sugestão de corte com sistema de agendamento de salão existente?

A integração ocorre via API REST ou GraphQL expondo endpoint de recomendação. O frontend envia landmarks faciais normalizados, recebe IDs de cortes sugeridos com scores de confiança. O agendamento pré-preenche serviço recomendado, permitindo override do profissional. Webhooks notificam aceite/rejeição para retreino contínuo do modelo.

Quais métricas acompanhar para validar eficácia do algoritmo em produção?

Métricas-chave: taxa de aceite da sugestão pelo cliente, taxa de conversão agendamento após recomendação, NPS pós-corte, taxa de retorno para mesmo estilo, acurácia percebida pelo profissional (feedback explícito). A/B test entre algoritmo vs. escolha livre mede lift real. Monitorar drift de distribuição facial da clientela local.

Como lidar com viés de dataset em algoritmos de sugestão estética?

Mitiga-se viés coletando dados representativos da população local de Indianópolis, balanceando etnias, idades e gêneros. Auditoria periódica com matriz de confusão por subgrupo demétrico. Técnicas de data augmentation para sub-representados. Comitê de revisão com profissionais diversos valida recomendações antes de deploy. Documentação de limitações conhecida.