Glimt convierte un segundo de cada día en una película. Lo interesante es que no tienes que grabar ese segundo: Glimt lo encuentra en los vídeos que ya hiciste y elige el segundo en el que sale tu hijo, incluso en un vídeo de cumpleaños abarrotado con otros cinco niños. Todo se ejecuta en el iPhone: sin servidor, sin cuenta, sin subir nada. Así es como funciona, con los modelos, las matemáticas y tiempos reales.
El pipeline
¿Por qué no usar simplemente Vision de Apple?
Vision hace mucho aquí: encuentra las caras (VNDetectFaceCaptureQualityRequest, que además puntúa lo nítida y frontal que es cada cara), sus landmarks (VNDetectFaceLandmarksRequest) y, en iOS 18, lo bien que se ve un fotograma (CalculateImageAestheticsScoresRequest, que también marca tiques y capturas de pantalla). Lo que no ofrece es de quién es una cara. El álbum Personas de Photos lo sabe, pero ese índice no está abierto a las apps.
La primera versión de Glimt usaba VNGenerateImageFeaturePrintRequest sobre un recorte de la cara. Es una huella de imagen genérica: compara el pelo, la luz, los colores y el fondo tanto como la propia cara. En la práctica confundía alegremente a hermanos, y a cualquier niño fotografiado en la misma habitación a la misma hora del día. Era la herramienta equivocada.
El modelo de reconocimiento facial
Ahora Glimt lleva el suyo propio: SFace (Zhong et al., 2021, «Sigmoid-Constrained Hypersphere Loss for Robust Face Recognition»), con un backbone MobileFaceNet, publicado en el OpenCV Zoo bajo licencia Apache 2.0. Declara un 99,40 % en LFW. Recibe una cara de 112 × 112 y devuelve 128 números, un embedding; dos fotos de la misma persona dan embeddings que apuntan en la misma dirección.
La conversión fue ONNX → PyTorch (onnx2torch) → Core ML (coremltools, ML Program, float16), seguida de una cuantización lineal de pesos a 8 bits. El resultado ocupa 9,8 MB, carga en unos 0,5 s la primera vez y su salida coincide con el modelo ONNX original con un coseno de 0,998 en una entrada de prueba. La normalización de la entrada está dentro del grafo, así que la app le pasa píxeles RGB de 0 a 255 sin más.
La alineación importa más de lo que crees
SFace se entrenó con caras alineadas a una plantilla fija: ojos, punta de la nariz y comisuras de la boca en posiciones conocidas dentro del cuadrado de 112 × 112. Una cara inclinada o descentrada da un embedding claramente peor. Los landmarks de Vision proporcionan los cinco puntos (las pupilas, el centro de la región de la nariz, los puntos extremos de los labios); después Glimt busca la rotación, la escala uniforme y el desplazamiento que mejor los llevan a la plantilla. En dos dimensiones, ese problema de mínimos cuadrados tiene solución cerrada si tratas los puntos como números complejos:
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
Core Image la aplica (una sola transformación afín, renderizador por software, una salida de 112 píxeles) y el recorte va al modelo. Las fotos de referencia se procesan dos veces, tal cual y en espejo, y se promedian ambas: una referencia algo más estable sin coste extra.
¿Es el mismo niño? Calibrar el umbral
La app compara el embedding de cada cara con las fotos de referencia mediante similitud coseno y se queda con la mejor coincidencia. Dónde poner el límite se midió, no se adivinó, con retratos de la Casa Blanca de dominio público (retratos oficiales separados por años y un retrato familiar con ambos padres y sus dos hijas):
El mismo hombre en retratos tomados con cuatro años de diferencia obtiene 0,83; las mismas personas extraídas de la foto familiar, 0,70–0,85. Los desconocidos se quedan por debajo de 0,25. El caso difícil es la familia: dos hermanas en la misma toma obtienen 0,43 entre ellas, la madre y las hijas hasta 0,41 (la misma luz y la misma pose hacen que los parientes se parezcan más), mientras que la madre frente a sus hijas en una foto distinta baja a 0,26–0,30. Así que el límite está en 0,45: por encima de las hermanas y muy por debajo de la misma persona. Cinco fotos son una comprobación de cordura, no una evaluación, y el límite se ajustará con vídeos familiares reales.
Dos reglas más dejan fuera a los parecidos: en un vídeo, un solo fotograma afortunado no basta (hacen falta dos a 0,45, o uno claramente por encima, a 0,57), y cuando hay varias caras en un fotograma solo se tiene en cuenta la que mejor coincide.
Varias fotos de referencia
Una foto de referencia es un ángulo, una luz y una edad. Y los niños cambian rápido. Glimt te deja añadir hasta cinco fotos, y la cara de un fotograma solo tiene que parecerse a una de ellas, así que una foto de perfil o la cara del invierno pasado todavía pueden reconocerse. Cuando una foto tiene varias personas, tocas la cara correcta; Glimt nunca adivina «la más grande».
Elegir el segundo y encuadrarlo
Cada fotograma recibe una puntuación: un 35 % por lo bien que se ve, un 20 % por la nitidez (varianza de un laplaciano sobre una copia en grises de 96 × 96: en los fotogramas borrosos los valores cambian poco de un píxel a otro) y un 45 % por si hay una cara clara. Con fotos de referencia, toda esa puntuación pesa un 40 % y «¿es el niño?» un 60 %: un fotograma normal de tu hijo gana a uno precioso de otra persona. El segundo elegido es la ventana con la mejor media, para que un buen fotograma junto a uno borroso no gane.
El encuadre para una película en vertical tuvo su propio bug, que vale la pena contar. Cada fotograma señalaba la cara más clara y la app promediaba las posiciones a lo largo del segundo. Con dos niños, la cara más clara alternaba entre ellos y la media acababa en la pared del medio. Ahora cada fotograma señala al niño (o la cara más destacada: tamaño × calidad, que es más estable), y la app toma el mayor grupo de posiciones cercanas entre sí, no la media. Cuando la cara es pequeña, el recorte hace zoom hasta que ocupa alrededor de una quinta parte de la altura del fotograma, como mucho 1,6× en vídeo.
¿Qué tan rápido es?
La sorpresa: reconocer una cara es la parte barata, unos 2 ms en el Neural Engine. Decodificar el fotograma del vídeo cuesta quince veces más. En la práctica, Glimt repasa un día con muchos vídeos en unos segundos, y un mes en unos minutos.
Ejecución en segundo plano
Rellenar un año de días que faltan lleva un rato, así que debería seguir cuando sales de la app. BGContinuedProcessingTask, de iOS 26, está hecho para eso: iOS muestra el progreso en la Dynamic Island y en la pantalla bloqueada. Tres cosas que aprendimos por las malas, todas gracias a un pequeño log en el dispositivo:
- No hay GPU en segundo plano. Vision y Core ML eligen la GPU por su cuenta, fallan en silencio cuando la app no está en primer plano y todos los fotogramas parecen vacíos. El acceso a la GPU requiere un entitlement especial y hardware compatible; el iPhone 15 Pro no informó de ninguno.
- Pero no todo tiene alternativa en CPU. Forzar Vision a la CPU rompió la detección de caras: su red de calidad facial viene sin pesos para CPU en el iPhone («Missing weights path … nonane»). Ahora Glimt pide primero el Neural Engine, luego la CPU, y si no, recurre a simples recuadros de caras.
- El progreso tiene que avanzar. Un día con un vídeo largo desde iCloud pasó medio minuto sin avanzar, e iOS terminó la tarea. Ahora Glimt informa del progreso tras cada fotograma analizado y cada paso de descarga, y pide a Photos una versión de calidad media de cada vídeo para el análisis (las películas siguen usando el original).
Privacidad
Glimt no tiene código de red. Las caras, los embeddings y las elecciones se quedan en el almacenamiento propio de la app en el iPhone. Lo único que puede tocar la red es Photos, cuando descarga tus propios vídeos de tu propio iCloud Photos si no están en el teléfono.
Créditos: SFace de Yaoyao Zhong y colaboradores; conversión a ONNX de Chengrui Wang; OpenCV Zoo (Apache 2.0). Preguntas o ideas: glimt@theill.com.