O Glimt transforma um segundo de cada dia em um filme. A parte interessante é que você não precisa filmar esse segundo: o Glimt o encontra nos vídeos que você já gravou e escolhe o segundo em que o seu filho aparece, mesmo num vídeo de aniversário lotado com outras cinco crianças. Tudo roda no iPhone: sem servidor, sem conta, sem upload. É assim que funciona, com os modelos, a matemática e tempos reais.
O pipeline
Por que não usar simplesmente o Vision da Apple?
O Vision faz muita coisa aqui: encontra os rostos (VNDetectFaceCaptureQualityRequest, que também avalia o quanto cada rosto está nítido e de frente), seus landmarks (VNDetectFaceLandmarksRequest) e, no iOS 18, o quanto um quadro é bonito (CalculateImageAestheticsScoresRequest, que também sinaliza recibos e capturas de tela). O que ele não oferece é de quem é um rosto. O álbum Pessoas do Photos sabe, mas esse índice não é aberto para apps.
A primeira versão do Glimt usava VNGenerateImageFeaturePrintRequest num recorte do rosto. É uma impressão digital genérica de imagem: compara cabelo, luz, cores e fundo tanto quanto o próprio rosto. Na prática, confundia tranquilamente irmãos, e qualquer criança fotografada no mesmo cômodo no mesmo horário do dia. Era a ferramenta errada.
O modelo de reconhecimento facial
Agora o Glimt traz o seu próprio: SFace (Zhong et al., 2021, “Sigmoid-Constrained Hypersphere Loss for Robust Face Recognition”), com um backbone MobileFaceNet, publicado no OpenCV Zoo sob a licença Apache 2.0. Ele reporta 99,40 % no LFW. Recebe um rosto de 112 × 112 e devolve 128 números, um embedding; duas fotos da mesma pessoa geram embeddings que apontam na mesma direção.
A conversão foi ONNX → PyTorch (onnx2torch) → Core ML (coremltools, ML Program, float16), seguida de quantização linear dos pesos em 8 bits. O resultado tem 9,8 MB, carrega em cerca de 0,5 s na primeira vez e sua saída bate com o modelo ONNX original com cosseno de 0,998 numa entrada de teste. A normalização da entrada está dentro do grafo, então o app passa simples pixels RGB de 0 a 255.
O alinhamento importa mais do que você imagina
O SFace foi treinado com rostos alinhados a um template fixo: olhos, ponta do nariz e cantos da boca em posições conhecidas no quadrado de 112 × 112. Um rosto inclinado ou fora do centro gera um embedding visivelmente pior. Os landmarks do Vision fornecem os cinco pontos (as pupilas, o centro da região do nariz, os pontos mais externos dos lábios); o Glimt então encontra a rotação, a escala uniforme e o deslocamento que melhor os levam ao template. Em duas dimensões, esse problema de mínimos quadrados tem solução fechada se você tratar os pontos como números complexos:
a = Σ conj(zᵢ − z̄) · (wᵢ − w̄) / Σ |zᵢ − z̄|² // rotation + scale, one complex number
w = a · z + (w̄ − a · z̄) // the transform for any point z
O Core Image a aplica (uma única transformação afim, renderizador por software, saída de 112 pixels) e o recorte vai para o modelo. As fotos de referência passam pelo modelo duas vezes, como estão e espelhadas, e as duas são combinadas pela média: uma referência um pouco mais estável, de graça.
É a mesma criança? Calibrando o limiar
O app compara o embedding de cada rosto com as fotos de referência por similaridade de cosseno e fica com a melhor correspondência. Onde traçar a linha foi medido, não chutado, com retratos da Casa Branca em domínio público (retratos oficiais com anos de diferença e um retrato de família com pai, mãe e as duas filhas):
O mesmo homem em retratos tirados com quatro anos de diferença marca 0,83; as mesmas pessoas recortadas da foto de família, 0,70–0,85. Estranhos ficam abaixo de 0,25. O caso difícil é a família: duas irmãs na mesma foto marcam 0,43 entre si, mãe e filhas até 0,41 (mesma luz e mesma pose deixam parentes mais parecidos), enquanto a mãe comparada às filhas numa foto diferente cai para 0,26–0,30. Por isso a linha fica em 0,45: acima das irmãs, bem abaixo da mesma pessoa. Cinco fotos são um teste de sanidade, não uma avaliação, e a linha será ajustada com vídeos reais de família.
Mais duas regras deixam os sósias de fora: num vídeo, um único quadro de sorte não basta (são precisos dois quadros a 0,45, ou um claramente acima, a 0,57), e quando há vários rostos num quadro, só o que melhor corresponde é considerado.
Várias fotos de referência
Uma foto de referência é um ângulo, uma luz e uma idade. Crianças mudam rápido. O Glimt permite adicionar até cinco fotos, e o rosto num quadro só precisa se parecer com uma delas, então uma foto de perfil ou o rosto do inverno passado ainda podem ser reconhecidos. Quando uma foto tem várias pessoas, você toca no rosto certo; o Glimt nunca adivinha “o maior”.
Escolhendo o segundo e enquadrando
Cada quadro recebe uma pontuação: 35 % pela estética, 20 % pela nitidez (variância de um laplaciano numa cópia em tons de cinza de 96 × 96: em quadros borrados os valores mudam pouco de um pixel para outro) e 45 % por haver um rosto bem visível. Com fotos de referência, essa pontuação inteira vale 40 % e “é a criança?” vale 60 %: um quadro comum do seu filho ganha de um quadro lindo de outra pessoa. O segundo escolhido é a janela com a melhor média, para que um bom quadro ao lado de um borrado não vença.
O enquadramento para um filme na vertical teve um bug próprio que vale a pena contar. Cada quadro apontava o rosto mais nítido e o app tirava a média das posições ao longo do segundo. Com duas crianças, o rosto mais nítido alternava entre elas, e a média caía na parede no meio. Agora cada quadro aponta a criança (ou o rosto mais proeminente: tamanho × qualidade, que é mais estável), e o app pega o maior grupo de posições próximas umas das outras, não a média. Quando o rosto é pequeno, o recorte dá zoom até ele ocupar cerca de um quinto da altura do quadro, no máximo 1,6× para vídeo.
Quão rápido é?
A surpresa: reconhecer um rosto é a parte barata, cerca de 2 ms no Neural Engine. Decodificar o quadro do vídeo custa quinze vezes mais. Na prática, o Glimt passa por um dia cheio de vídeos em poucos segundos, e por um mês em poucos minutos.
Rodando em segundo plano
Preencher um ano de dias faltando leva um tempo, então o processo precisa continuar quando você sai do app. O BGContinuedProcessingTask do iOS 26 foi feito para isso: o iOS mostra o progresso na Dynamic Island e na Tela Bloqueada. Três coisas que aprendemos do jeito difícil, todas a partir de um pequeno log no aparelho:
- Sem GPU em segundo plano. O Vision e o Core ML escolhem a GPU por conta própria, falham em silêncio quando o app não está em primeiro plano, e todo quadro parece vazio. O acesso à GPU exige um entitlement especial e hardware compatível; o iPhone 15 Pro não informou nenhum dos dois.
- Mas nem tudo tem fallback para a CPU. Forçar o Vision na CPU quebrou a detecção de rostos: a rede de qualidade facial dele vem sem pesos de CPU no iPhone (“Missing weights path … nonane”). Agora o Glimt pede primeiro o Neural Engine, depois a CPU, e por fim recorre a simples caixas de rostos.
- O progresso precisa andar. Um dia com um vídeo longo do iCloud ficou meio minuto sem progresso, e o iOS encerrou a tarefa. Agora o Glimt informa o progresso a cada quadro analisado e a cada etapa de download, e pede ao Photos uma versão de qualidade média de cada vídeo para a análise (os filmes continuam usando o original).
Privacidade
O Glimt não tem código de rede. Rostos, embeddings e escolhas ficam no armazenamento do próprio app no iPhone. A única coisa que pode tocar a rede é o próprio Photos, buscando os seus vídeos no seu iCloud Photos quando eles não estão no telefone.
Créditos: SFace por Yaoyao Zhong e colegas; conversão ONNX por Chengrui Wang; OpenCV Zoo (Apache 2.0). Dúvidas ou ideias: glimt@theill.com.