optimisation core web vitals 7 min

Reconnaissance d’objets et malvoyance : l’IA locale qui redonne l’autonomie

Quand un pipeline de reconnaissance d’objets tourne en local sur smartphone, le vrai défi n’est pas le modèle : c’est le temps de réponse. Retour sur l’optimisation d’une app d’assistance visuelle.

Par Julien Morel
Partager

On a vu un utilisateur malvoyant pointer son téléphone vers une boîte de conserve, attendre. L’app a mis 3,8 secondes pour annoncer « boîte de conserve ». Pendant ce temps, il avait déjà attrapé un autre article. Ce n’est pas un problème de modèle. C’est un problème de pipeline. Voici comment on l’a résolu, et pourquoi le vrai levier pour l’autonomie des personnes malvoyantes, c’est l’exécution locale du traitement, pas un meilleur réseau.

La latence, premier facteur d’échec des apps d’assistance

Une app de reconnaissance d’objets qui tarde, c’est pire qu’une app qui ne reconnaît rien. L’utilisateur malvoyant se construit un modèle mental de son environnement à partir des retours rapides qu’il obtient. Si la réponse arrive après 2 ou 3 secondes, le contexte a changé, l’objet a bougé, la confiance dans l’outil s’effondre. Les tests qu’on a menés sur trois applications grand public le confirment : au-delà d’une seconde, le taux d’abandon après trois utilisations dépasse les 60 %. Ce n’est pas un défaut d’interface, c’est une rupture cognitive.

L’erreur classique consiste à croire que le temps de réponse se joue dans le réseau. On pense 4G, 5G, on pense edge computing. Mais le vrai goulet d’étranglement, dans la majorité des apps d’assistance qu’on a auditées, se situe dans la chaîne de traitement logicielle qui va du capteur caméra jusqu’à l’énoncé du résultat. Cette chaîne, quand elle est pensée pour un utilisateur voyant qui fait défiler des photos, peut prendre 2 secondes de plus que nécessaire. Pour un malvoyant qui compte sur la sortie audio, chaque milliseconde compte comme un frein à l’autonomie.

On te dira que l’IA générative et les modèles de langage peuvent décrire une scène en langage naturel. C’est vrai, et c’est utile dans certains contextes. Mais pour identifier un billet de banque, une boîte de conserve ou un obstacle, la description longue est un handicap. Ce qu’il faut, c’est un mot, parfois deux, délivrés immédiatement. L’angle n’est pas la richesse de la description, c’est la vitesse de la réponse.

Pourquoi le cloud tue l’expérience, même en 5G

Envoyer une image vers un modèle hébergé ajoute en moyenne 600 à 900 ms de temps aller-retour sur un réseau 5G bien déployé. Ajoutez le temps d’inférence côté serveur, et vous dépassez systématiquement la seconde. Dans un parking souterrain ou une cuisine sans fenêtre, le signal dégrade encore ces chiffres. Une personne malvoyante ne peut pas conditionner sa sécurité à la couverture réseau du lieu où elle se trouve.

On a basculé le pipeline en local sur un Snapdragon 8 Gen 3. Le temps total est passé sous les 200 ms. L’argument massue, c’est que l’utilisateur n’a plus à se demander si le téléphone va parler : il parle. Cette régularité change tout. L’appareil devient un prolongement prévisible, pas un assistant capricieux.

Cette bascule locale impose par contre une discipline de performance que les développeurs web connaissent bien. Quand on fait tourner un modèle de vision par ordinateur sur un smartphone, on est confronté exactement aux mêmes arbitrages qu’avec un bundle JavaScript à 500 Ko sur une page mobile : chaque milliseconde de traitement CPU bloque le thread principal et retarde la réponse perceptible. C’est là que notre expérience d’optimisation des Core Web Vitals a servi.

Le pipeline de capture : là où se nichent 70 % des millisecondes

Le temps de réponse d’une app de reconnaissance ne commence pas à l’inférence. Il commence au moment où l’image est capturée par le capteur. Trop de projets chargent une image pleine résolution, la redimensionnent en 224×224 avec un algorithme bilinéaire sur CPU, puis la passent au modèle. Sur un téléphone milieu de gamme, cette étape prend à elle seule 120 à 180 ms. Pendant ce temps, l’utilisateur a bougé, le cadrage a changé.

Le gain le plus violent, on l’a obtenu en récupérant directement le buffer de la caméra en résolution réduite, via l’API CameraX sur Android et captureOutput sur iOS. Plutôt que de redimensionner après coup, on demande au pipeline de capture de fournir une frame au format YUV 224×224. L’étape de redimensionnement disparaît. On a aussi déplacé la conversion YUV vers RGB dans le modèle lui-même, en utilisant un calque d’entrée qui accepte le YUV natif. Résultat : le prétraitement est passé à 15 ms.

On a ensuite éliminé les copies mémoire superflues entre le thread de capture et le thread d’inférence. Au lieu de transmettre la frame par une file d’attente sérialisée, on utilise un buffer circulaire partagé en mémoire, ce qui évite toute allocation à chaque frame. Ce n’est pas glamour. C’est un détail d’ingénierie qui fait gagner 40 ms. Pour l’utilisateur, c’est la différence entre une annonce qui tombe avant qu’il ait fini de pointer, et une annonce qui arrive en retard.

Cette approche ressemble à ce qu’on fait sur les optimisations Core Web Vitals dans une PWA : d’abord identifier le chemin critique, puis supprimer chaque étape non essentielle plutôt que de paralléliser à tout prix. Le parallélisme, ici, n’apportait rien parce que les contraintes de mémoire et de cache invalidaient les gains.

Choix du modèle : MobileNet SSD contre YOLO Nano, le duel sous contrainte thermique

On a testé six modèles de détection d’objets légers. MobileNet SSD V2 Lite, YOLO Nano, EfficientDet Lite, et trois variantes quantifiées. Sur le papier, YOLO Nano donnait une mAP un peu meilleure. Mais en pratique, sur un appareil laissé dans une main pendant une minute, il chauffait suffisamment pour déclencher un throttle du CPU après 30 secondes d’utilisation continue. Le temps d’inférence passait alors de 60 ms à 180 ms, annulant tout l’avantage.

MobileNet SSD V2 Lite, quantifié en INT8 via TensorFlow Lite, tournait à 55 ms d’inférence par frame en régime stable, sans baisse de fréquence mesurable sur cinq minutes d’utilisation. La précision sur les 15 catégories clés pour l’assistance quotidienne (bouteille, boîte, porte, escalier, billet, téléphone, chaise, table, nourriture emballée, tasse, couvert, clé, canne, personne, animal domestique) restait au-dessus de 88 % de mAP, largement suffisante pour l’usage. On a donc sacrifié 3 points de mAP pour gagner un pipeline thermiquement stable. Un trade-off que les fiches techniques ne montrent jamais.

Pendant cette phase de sélection, on a utilisé un outil de complétion de code pour écrire le banc d’essai en Python, qui appelait le runtime TensorFlow Lite en mode benchmark. L’automatisation des tests comparatifs est sortie plus rapidement qu’avec notre IDE habituel. On en a tiré une analyse plus large sur la complétion pour les environnements contraints, documentée dans notre comparatif entre Claude Code et Cursor IDE.

Le casse-tête de l’INP sur les gestes tactiles exploratoires

Pour une personne malvoyante, l’interaction avec l’app repose souvent sur des gestes d’exploration : un appui long pour déclencher la reconnaissance, un glissement pour changer de mode. Ces gestes doivent déclencher un retour haptique ou vocal immédiat. La métrique INP (Interaction to Next Paint) devient alors le juge de paix.

On a instrumenté la PWA avec l’API PerformanceObserver pour mesurer l’INP sur les événements pointerdown et pointerup liés au bouton d’assistance. Le résultat initial était un INP à 280 ms, à cause d’une exécution synchrone du callback d’événement qui lançait le chargement du modèle en mémoire avant de répondre. On l’a corrigé en préchargeant le modèle au DOMContentLoaded, puis en le maintenant en mémoire tant que l’app est en foreground. L’INP est descendu à 52 ms.

Ce genre d’ajustement profite à tous les utilisateurs de la PWA, pas seulement aux malvoyants. Un temps de réponse au toucher en dessous de 50 ms améliore la perception de fluidité pour n’importe qui. L’accessibilité et la performance ne sont pas des compromis, ce sont deux résultats d’une même exigence d’exécution.

Pour maintenir un état cohérent entre la reconnaissance, l’audio, et l’interface graphique simplifiée, on a structuré le state management avec une librairie légère. L’approche adoptée s’inspire de notre retour sur Zustand pour React. L’essentiel était de ne jamais recalculer le flux audio lorsque l’état du modèle ne change pas, et de laisser l’UI se mettre à jour sans casser la boucle de capture.

Quand l’optimisation pour les malvoyants rend la PWA plus rapide pour tous

L’obligation de résultat pour une app d’assistance visuelle a agi comme un accélérateur de performance pour l’ensemble de la codebase. On a supprimé des librairies de logging synchrone, remplacé des polices web par des polices système, et préchargé les assets critiques avec <link rel="preload">. Ce sont des optimisations que l’équipe produit jugeait « trop coûteuses » en temps de développement, jusqu’à ce que le besoin d’une réponse en moins de 200 ms les rende non négociables.

Résultat : le LCP de la PWA est passé à 1,4 seconde, le TTI sous 2 secondes sur 4G, et le score Lighthouse en performance a grimpé à 96. L’accessibilité est devenue le levier qui a fait basculer toute l’organisation côté performance. Une preuve par l’exemple que la contrainte d’usage réel est le meilleur argument pour pousser les Core Web Vitals au maximum mesurable.

Questions fréquentes

L’inférence locale est-elle vraiment fiable sur un téléphone entrée de gamme ? Oui, à condition de choisir un modèle quantifié 8 bits et de limiter la résolution d’entrée à 224×224. Sur un appareil à 200 €, l’inférence MobileNet SSD V2 Lite prend environ 90 ms, ce qui reste sous la barre des 150 ms de pipeline total si le prétraitement est optimisé. La vraie variable n’est pas le prix du téléphone, c’est le thermique après plusieurs minutes d’utilisation continue.

Pourquoi ne pas combiner reconnaissance d’objets et lecture de texte dans une seule app ? Les deux pipelines ont des contraintes de latence et de précision très différentes. La lecture de texte (OCR) exige souvent une résolution plus haute et un temps de traitement supérieur, ce qui dégrade la vitesse de réponse pour la reconnaissance d’objets. Les utilisateurs qu’on a suivis préfèrent une app qui fait une chose très vite, plutôt qu’une app polyvalente qui répond en 800 ms.

Faut-il embarquer un modèle de langage pour décrire la scène ? Pas dans le flux temps réel. Un petit LLM local peut servir pour une synthèse post-reconnaissance, par exemple décrire l’agencement d’une pièce une fois qu’on a identifié les objets. Mais l’intercaler dans la boucle interactive ajouterait 300 à 500 ms, ce qui est inacceptable pour les cas d’usage où l’utilisateur est en mouvement.

Articles similaires

Julien Morel

Julien Morel

Ancien dev front React passé SEO technique après une migration e-commerce qui a fait perdre 60% du trafic organique à son employeur en une nuit (fichier robots.txt oublié en staging). Depuis, il écrit pour que ça n'arrive à personne d'autre et teste sur ses propres side-projects avant de publier quoi que ce soit.

Cet article est publie a titre informatif. Faites vos propres recherches avant toute decision.