Glimt transforme une seconde de chaque journée en film. Le plus intéressant, c'est que vous n'avez pas à filmer cette seconde : Glimt la trouve dans les vidéos que vous avez déjà prises, et choisit la seconde où votre enfant est à l'image, même dans une vidéo d'anniversaire bondée avec cinq autres enfants. Tout tourne sur l'iPhone : pas de serveur, pas de compte, pas d'envoi. Voici comment ça marche, avec les modèles, les maths et de vrais temps mesurés.
Le pipeline
Pourquoi ne pas simplement utiliser Vision d'Apple ?
Vision fait déjà beaucoup ici : il trouve les visages (VNDetectFaceCaptureQualityRequest, qui note aussi à quel point chaque visage est net et de face), leurs repères (VNDetectFaceLandmarksRequest) et, sous iOS 18, l'esthétique d'une image (CalculateImageAestheticsScoresRequest, qui signale aussi les tickets de caisse et les captures d'écran). Ce qu'il ne dit pas, c'est qui est un visage. L'album Personnes de Photos le sait, mais cet index n'est pas accessible aux apps.
La première version de Glimt utilisait VNGenerateImageFeaturePrintRequest sur un recadrage du visage. C'est une empreinte d'image générique : elle compare les cheveux, la lumière, les couleurs et l'arrière-plan autant que le visage lui-même. En pratique, elle confondait volontiers frères et sœurs, et n'importe quel enfant photographié dans la même pièce à la même heure. Ce n'était pas le bon outil.
Le modèle de reconnaissance faciale
Glimt embarque désormais le sien : SFace (Zhong et al., 2021, « Sigmoid-Constrained Hypersphere Loss for Robust Face Recognition »), avec un backbone MobileFaceNet, publié dans l'OpenCV Zoo sous licence Apache 2.0. Il affiche 99,40 % sur LFW. Il prend un visage de 112 × 112 et renvoie 128 nombres, un embedding ; deux photos de la même personne donnent des embeddings qui pointent dans la même direction.
La conversion passe par ONNX → PyTorch (onnx2torch) → Core ML (coremltools, ML Program, float16), puis une quantification linéaire des poids sur 8 bits. Le résultat pèse 9,8 MB, se charge en 0,5 s environ la première fois, et sa sortie correspond au modèle ONNX d'origine avec un cosinus de 0,998 sur une entrée de test. La normalisation de l'entrée est intégrée au graphe : l'app lui passe de simples pixels RGB de 0 à 255.
L'alignement compte plus qu'on ne le croit
SFace a été entraîné sur des visages alignés sur un gabarit fixe : yeux, bout du nez et coins de la bouche à des positions connues dans le carré de 112 × 112. Un visage incliné ou décentré donne un embedding nettement moins bon. Les repères de Vision fournissent les cinq points (les pupilles, le centre de la région du nez, les points extrêmes des lèvres) ; Glimt cherche ensuite la rotation, l'échelle uniforme et la translation qui les amènent au mieux sur le gabarit. En deux dimensions, ce problème des moindres carrés a une solution analytique si l'on traite les points comme des nombres complexes :
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 l'applique (une seule transformation affine, rendu logiciel, une sortie de 112 pixels), et le recadrage part vers le modèle. Les photos de référence sont encodées deux fois, telles quelles et en miroir, puis on fait la moyenne des deux : une référence un peu plus stable, gratuitement.
Est-ce le même enfant ? Calibrer le seuil
L'app compare l'embedding de chaque visage aux photos de référence par similarité cosinus et garde la meilleure correspondance. La position du seuil a été mesurée, pas devinée, sur des portraits de la Maison-Blanche du domaine public (des portraits officiels pris à des années d'écart, et un portrait de famille avec les deux parents et leurs deux filles) :
Le même homme sur des portraits pris à quatre ans d'écart obtient 0,83 ; les mêmes personnes extraites de la photo de famille, 0,70–0,85. Les inconnus restent sous 0,25. Le cas difficile, c'est la famille : deux sœurs sur le même cliché obtiennent 0,43 entre elles, la mère et ses filles jusqu'à 0,41 (la même lumière et la même pose rendent les proches plus ressemblants), alors que la mère comparée à ses filles sur une autre photo tombe à 0,26–0,30. Le seuil est donc placé à 0,45 : au-dessus des sœurs, bien en dessous de la même personne. Cinq photos, c'est un contrôle de cohérence, pas une évaluation, et le seuil sera ajusté sur de vraies vidéos de famille.
Deux règles supplémentaires écartent les sosies : dans une vidéo, une seule image chanceuse ne suffit pas (il en faut deux à 0,45, ou une nettement au-dessus, à 0,57), et quand plusieurs visages sont dans l'image, seul celui qui correspond le mieux est pris en compte.
Plusieurs photos de référence
Une photo de référence, c'est un angle, une lumière et un âge. Or les enfants changent vite. Glimt vous permet d'ajouter jusqu'à cinq photos, et le visage dans une image n'a besoin de ressembler qu'à une d'entre elles : un profil ou le visage de l'hiver dernier peut ainsi encore être reconnu. Quand une photo contient plusieurs personnes, vous touchez le bon visage ; Glimt ne devine jamais « le plus grand ».
Choisir la seconde, et la cadrer
Chaque image reçoit un score : 35 % pour l'esthétique, 20 % pour la netteté (variance d'un laplacien sur une copie en niveaux de gris de 96 × 96 : dans une image floue, les valeurs changent peu d'un pixel à l'autre), 45 % pour la présence d'un visage bien visible. Avec des photos de référence, tout ce score compte pour 40 % et « est-ce l'enfant ? » pour 60 % : une image ordinaire de votre enfant l'emporte sur une belle image de quelqu'un d'autre. La seconde retenue est la fenêtre qui a la meilleure moyenne, pour qu'une bonne image entourée de flou ne gagne pas.
Le cadrage pour un film en portrait a eu son propre bug, qui mérite d'être raconté. Chaque image désignait le visage le plus net, et l'app faisait la moyenne des positions sur la seconde. Avec deux enfants, le visage le plus net alternait entre eux, et la moyenne tombait sur le mur au milieu. Désormais, chaque image désigne l'enfant (ou le visage le plus en évidence : taille × qualité, plus stable), et l'app prend le plus grand groupe de positions proches les unes des autres, pas la moyenne. Quand le visage est petit, le recadrage zoome jusqu'à ce qu'il occupe environ un cinquième de la hauteur de l'image, 1,6× au maximum pour la vidéo.
À quelle vitesse ?
La surprise : reconnaître un visage est la partie la moins coûteuse, environ 2 ms sur le Neural Engine. Décoder l'image de la vidéo coûte quinze fois plus. En pratique, Glimt traite une journée chargée en vidéos en quelques secondes, et un mois en quelques minutes.
Tourner en arrière-plan
Remplir une année de jours manquants prend un moment, le travail doit donc continuer quand vous quittez l'app. BGContinuedProcessingTask, arrivé avec iOS 26, est fait pour ça : iOS affiche la progression dans la Dynamic Island et sur l'écran verrouillé. Trois leçons apprises à nos dépens, toutes grâce à un petit journal enregistré sur l'appareil :
- Pas de GPU en arrière-plan. Vision et Core ML choisissent le GPU d'eux-mêmes, échouent en silence quand l'app n'est pas au premier plan, et chaque image paraît vide. L'accès au GPU exige un entitlement spécial et un matériel compatible ; l'iPhone 15 Pro n'en signalait aucun.
- Mais tout n'a pas de repli sur le CPU. Forcer Vision sur le CPU a cassé la détection des visages : son réseau de qualité des visages est livré sans poids CPU sur l'iPhone (« Missing weights path … nonane »). Glimt demande désormais d'abord le Neural Engine, puis le CPU, et se rabat sinon sur de simples cadres de visages.
- La progression doit avancer. Une journée avec une longue vidéo stockée dans iCloud n'a pas progressé pendant une demi-minute, et iOS a mis fin à la tâche. Glimt signale maintenant la progression après chaque image analysée et chaque étape de téléchargement, et demande à Photos une version de qualité moyenne de chaque vidéo pour l'analyse (les films utilisent toujours l'original).
Confidentialité
Glimt ne contient aucun code réseau. Visages, embeddings et choix restent dans le stockage propre de l'app, sur l'iPhone. La seule chose qui peut toucher au réseau, c'est Photos lui-même, quand il récupère vos propres vidéos depuis votre propre iCloud Photos parce qu'elles ne sont pas sur le téléphone.
Crédits : SFace par Yaoyao Zhong et ses collègues ; conversion ONNX par Chengrui Wang ; OpenCV Zoo (Apache 2.0). Questions ou idées : glimt@theill.com.