Reconnaître l'écriture manuscrite des utilisateurs

L'API Handwriting Recognition vous permet de reconnaître le texte saisi à la main en temps réel.

Qu'est-ce que l'API Handwriting Recognition ?

L'API Handwriting Recognition vous permet de convertir l'écriture manuscrite (encre) de vos utilisateurs en texte. Certains systèmes d'exploitation incluent depuis longtemps de telles API. Grâce à cette nouvelle fonctionnalité, vos applications Web peuvent enfin les utiliser. La conversion se produit directement sur l'appareil de l'utilisateur et fonctionne même en mode hors connexion, le tout sans ajouter de bibliothèques ni de services tiers.

Cette API implémente la reconnaissance dite "en ligne" ou en temps quasi réel. Cela signifie que la saisie manuscrite est reconnue pendant que l'utilisateur la dessine en capturant et en analysant les traits individuels. Contrairement aux procédures "hors connexion" telles que la reconnaissance optique des caractères (OCR), où seul le produit final est connu, les algorithmes en ligne peuvent fournir un niveau de précision plus élevé grâce à des signaux supplémentaires tels que la séquence temporelle et la pression des traits d'encre individuels.

Cas d'utilisation suggérés pour l'API de reconnaissance de l'écriture manuscrite

Voici quelques exemples d'utilisation:

  • Applications de prise de notes permettant aux utilisateurs de saisir des notes manuscrites et de les traduire en texte.
  • Applications de formulaires dans lesquelles les utilisateurs peuvent utiliser un stylet ou un doigt en raison de contraintes de temps.
  • Jeux qui nécessitent de remplir des lettres ou des chiffres, comme les mots croisés, le pendu ou le sudoku

État actuel

L'API Handwriting Recognition est disponible à partir de (Chromium 99).

Utiliser l'API de reconnaissance de l'écriture manuscrite

Détection de fonctionnalités

Détectez la compatibilité du navigateur en vérifiant l'existence de la méthode createHandwritingRecognizer() sur l'objet navigateur:

if ('createHandwritingRecognizer' in navigator) {
  // 🎉 The Handwriting Recognition API is supported!
}

Concepts fondamentaux

L'API Handwriting Recognition convertit l'entrée manuscrite en texte, quelle que soit la méthode d'entrée (souris, écran tactile, stylet). L'API comporte quatre entités principales:

  1. Un point représente l'emplacement du pointeur à un moment donné.
  2. Un trait se compose d'un ou de plusieurs points. L'enregistrement d'un trait commence lorsque l'utilisateur place le pointeur (c'est-à-dire clique sur le bouton principal de la souris ou touche l'écran avec son stylet ou son doigt) et se termine lorsqu'il le relève.
  3. Un dessin se compose d'un ou de plusieurs traits. La reconnaissance réelle a lieu à ce niveau.
  4. Le recogneur est configuré avec la langue de saisie attendue. Il permet de créer une instance d'un dessin avec la configuration du lecteur appliquée.

Ces concepts sont implémentés en tant qu'interfaces et dictionnaires spécifiques, que je vais aborder prochainement.

Entités principales de l'API de reconnaissance de l'écriture manuscrite: un ou plusieurs points composent un trait, un ou plusieurs traits composent un dessin, que le système de reconnaissance crée. La reconnaissance se fait au niveau du dessin.

Créer un outil de reconnaissance

Pour reconnaître le texte à partir d'une saisie manuscrite, vous devez obtenir une instance d'un HandwritingRecognizer en appelant navigator.createHandwritingRecognizer() et en lui transmettant des contraintes. Les contraintes déterminent le modèle de reconnaissance de l'écriture manuscrite à utiliser. Actuellement, vous pouvez spécifier une liste de langues par ordre de préférence:

const recognizer = await navigator.createHandwritingRecognizer({
  languages: ['en'],
});

La méthode renvoie une promesse résolue avec une instance d'un HandwritingRecognizer lorsque le navigateur peut répondre à votre requête. Sinon, il rejette la promesse avec une erreur et la reconnaissance de l'écriture manuscrite n'est pas disponible. Pour cette raison, vous pouvez d'abord interroger la compatibilité du détecteur avec des fonctionnalités de reconnaissance spécifiques.

Compatibilité des requêtes avec le lecteur

En appelant navigator.queryHandwritingRecognizerSupport(), vous pouvez vérifier si la plate-forme cible est compatible avec les fonctionnalités de reconnaissance de l'écriture manuscrite que vous prévoyez d'utiliser. Dans l'exemple suivant, le développeur:

  • souhaite détecter des textes en anglais
  • obtenir des prédictions alternatives moins probables lorsqu'elles sont disponibles ;
  • accéder au résultat de la segmentation, c'est-à-dire aux caractères reconnus, y compris les points et les traits qui les composent ;
const { languages, alternatives, segmentationResults } =
  await navigator.queryHandwritingRecognizerSupport({
    languages: ['en'],
    alternatives: true,
    segmentationResult: true,
  });

console.log(languages); // true or false
console.log(alternatives); // true or false
console.log(segmentationResult); // true or false

La méthode renvoie une promesse résolue avec un objet de résultat. Si le navigateur est compatible avec la fonctionnalité spécifiée par le développeur, sa valeur est définie sur true. Sinon, la valeur est égale à false. Vous pouvez utiliser ces informations pour activer ou désactiver certaines fonctionnalités dans votre application, ou pour ajuster votre requête et en envoyer une nouvelle.

Commencer un dessin

Dans votre application, vous devez proposer une zone de saisie dans laquelle l'utilisateur saisit ses entrées manuscrites. Pour des raisons de performances, nous vous recommandons de l'implémenter à l'aide d'un objet canvas. L'implémentation exacte de cette partie n'entre pas dans le champ d'application de cet article, mais vous pouvez consulter la démo pour voir comment procéder.

Pour commencer un nouveau dessin, appelez la méthode startDrawing() sur le détecteur. Cette méthode reçoit un objet contenant différentes indications pour affiner l'algorithme de reconnaissance. Tous les indices sont facultatifs:

  • Type de texte saisi: texte, adresses e-mail, chiffres ou caractère individuel (recognitionType)
  • Type de périphérique d'entrée: souris, saisie tactile ou stylet (inputType)
  • Le texte précédent (textContext)
  • Nombre d'alternatives de prédiction moins probables à renvoyer (alternatives)
  • Liste des caractères identifiables par l'utilisateur ("graphemes") qu'il est le plus susceptible de saisir (graphemeSet)

L'API de reconnaissance de l'écriture manuscrite fonctionne bien avec les événements de pointeur, qui fournissent une interface abstraite pour consommer l'entrée de n'importe quel dispositif de pointage. Les arguments d'événement de pointeur contiennent le type de pointeur utilisé. Cela signifie que vous pouvez utiliser des événements de pointeur pour déterminer automatiquement le type d'entrée. Dans l'exemple suivant, le dessin pour la reconnaissance de l'écriture manuscrite est automatiquement créé à la première occurrence d'un événement pointerdown dans la zone d'écriture manuscrite. Comme pointerType peut être vide ou défini sur une valeur propriétaire, j'ai introduit une vérification de la cohérence pour m'assurer que seules les valeurs acceptées sont définies pour le type d'entrée du dessin.

let drawing;
let activeStroke;

canvas.addEventListener('pointerdown', (event) => {
  if (!drawing) {
    drawing = recognizer.startDrawing({
      recognitionType: 'text', // email, number, per-character
      inputType: ['mouse', 'touch', 'pen'].find((type) => type === event.pointerType),
      textContext: 'Hello, ',
      alternatives: 2,
      graphemeSet: ['f', 'i', 'z', 'b', 'u'], // for a fizz buzz entry form
    });
  }
  startStroke(event);
});

Ajouter un trait

L'événement pointerdown est également l'endroit idéal pour commencer un nouveau trait. Pour ce faire, créez une instance de HandwritingStroke. Vous devez également stocker l'heure actuelle comme point de référence pour les points suivants qui y sont ajoutés:

function startStroke(event) {
  activeStroke = {
    stroke: new HandwritingStroke(),
    startTime: Date.now(),
  };
  addPoint(event);
}

Ajouter un point

Après avoir créé le trait, vous devez y ajouter directement le premier point. Comme vous ajouterez d'autres points plus tard, il est logique d'implémenter la logique de création de points dans une méthode distincte. Dans l'exemple suivant, la méthode addPoint() calcule le temps écoulé à partir du code temporel de référence. Les informations temporelles sont facultatives, mais elles peuvent améliorer la qualité de la reconnaissance. Il lit ensuite les coordonnées X et Y de l'événement de pointeur et ajoute le point au trait actuel.

function addPoint(event) {
  const timeElapsed = Date.now() - activeStroke.startTime;
  activeStroke.stroke.addPoint({
    x: event.offsetX,
    y: event.offsetY,
    t: timeElapsed,
  });
}

Le gestionnaire d'événements pointermove est appelé lorsque le pointeur se déplace à l'écran. Ces points doivent également être ajoutés au trait. L'événement peut également être généré si le pointeur n'est pas dans un état "bas", par exemple lorsque vous déplacez le curseur sur l'écran sans appuyer sur le bouton de la souris. Le gestionnaire d'événements de l'exemple suivant vérifie si un trait actif existe et y ajoute le nouveau point.

canvas.addEventListener('pointermove', (event) => {
  if (activeStroke) {
    addPoint(event);
  }
});

Reconnaître du texte

Lorsque l'utilisateur soulève à nouveau le pointeur, vous pouvez ajouter le trait à votre dessin en appelant sa méthode addStroke(). L'exemple suivant réinitialise également activeStroke. Le gestionnaire pointermove n'ajoutera donc pas de points au trait terminé.

Ensuite, il est temps de reconnaître la saisie de l'utilisateur en appelant la méthode getPrediction() sur le dessin. La reconnaissance prend généralement moins de quelques centaines de millisecondes. Vous pouvez donc exécuter des prédictions à plusieurs reprises si nécessaire. L'exemple suivant exécute une nouvelle prédiction après chaque trait terminé.

canvas.addEventListener('pointerup', async (event) => {
  drawing.addStroke(activeStroke.stroke);
  activeStroke = null;

  const [mostLikelyPrediction, ...lessLikelyAlternatives] = await drawing.getPrediction();
  if (mostLikelyPrediction) {
    console.log(mostLikelyPrediction.text);
  }
  lessLikelyAlternatives?.forEach((alternative) => console.log(alternative.text));
});

Cette méthode renvoie une promesse qui se résout avec un tableau de prédictions triées par probabilité. Le nombre d'éléments dépend de la valeur que vous avez transmise à l'indice alternatives. Vous pouvez utiliser ce tableau pour proposer à l'utilisateur un choix de correspondances possibles et lui demander de sélectionner une option. Vous pouvez également simplement choisir la prédiction la plus probable, ce que je fais dans l'exemple.

L'objet de prédiction contient le texte reconnu et un résultat de segmentation facultatif, que je décrirai dans la section suivante.

Insights détaillés avec les résultats de segmentation

Si la plate-forme cible est compatible, l'objet de prédiction peut également contenir un résultat de segmentation. Il s'agit d'un tableau contenant tous les segments d'écriture manuscrite reconnus, une combinaison du caractère reconnu identifiable par l'utilisateur (grapheme) ainsi que de sa position dans le texte reconnu (beginIndex, endIndex), ainsi que des traits et des points qui l'ont créé.

if (mostLikelyPrediction.segmentationResult) {
  mostLikelyPrediction.segmentationResult.forEach(
    ({ grapheme, beginIndex, endIndex, drawingSegments }) => {
      console.log(grapheme, beginIndex, endIndex);
      drawingSegments.forEach(({ strokeIndex, beginPointIndex, endPointIndex }) => {
        console.log(strokeIndex, beginPointIndex, endPointIndex);
      });
    },
  );
}

Vous pouvez utiliser ces informations pour retrouver les graphèmes reconnus sur le canevas.

Des cadres sont dessinés autour de chaque graphe reconnu.

Reconnaissance complète

Une fois la reconnaissance terminée, vous pouvez libérer des ressources en appelant la méthode clear() sur le HandwritingDrawing et la méthode finish() sur le HandwritingRecognizer:

drawing.clear();
recognizer.finish();

Démo

Le composant Web <handwriting-textarea> implémente un contrôle de modification progressivement amélioré capable de reconnaître l'écriture manuscrite. En cliquant sur le bouton en bas à droite du contrôle de modification, vous activez le mode dessin. Lorsque vous avez terminé le dessin, le composant Web lance automatiquement la reconnaissance et ajoute le texte reconnu à nouveau au contrôle de modification. Si l'API de reconnaissance de l'écriture manuscrite n'est pas du tout compatible ou si la plate-forme n'est pas compatible avec les fonctionnalités demandées, le bouton de modification est masqué. Toutefois, le contrôle de base de la saisie reste utilisable en tant que <textarea>.

Le composant Web propose des propriétés et des attributs pour définir le comportement de reconnaissance de l'extérieur, y compris languages et recognitiontype. Vous pouvez définir le contenu de la commande via l'attribut value:

<handwriting-textarea languages="en" recognitiontype="text" value="Hello"></handwriting-textarea>

Pour être informé de toute modification de la valeur, vous pouvez écouter l'événement input.

Vous pouvez tester le composant à l'aide de cette démonstration sur Glitch. N'oubliez pas de consulter le code source. Pour utiliser le contrôle dans votre application, obtenez-le sur npm.

Sécurité et autorisations

L'équipe Chromium a conçu et implémenté l'API de reconnaissance de l'écriture manuscrite en suivant les principes de base définis dans Controlling Access to Powerful Web Platform Features (Contrôler l'accès aux fonctionnalités puissantes de la plate-forme Web), y compris le contrôle utilisateur, la transparence et l'ergonomie.

Contrôle des utilisateurs

L'utilisateur ne peut pas désactiver l'API de reconnaissance de l'écriture manuscrite. Il n'est disponible que pour les sites Web diffusés via HTTPS et ne peut être appelé que depuis le contexte de navigation de premier niveau.

Transparence

Il n'est pas indiqué si la reconnaissance de l'écriture manuscrite est active. Pour éviter l'empreinte digitale, le navigateur met en œuvre des contre-mesures, telles que l'affichage d'une invite d'autorisation à l'utilisateur lorsqu'il détecte une utilisation abusive potentielle.

Persistance des autorisations

L'API de reconnaissance de l'écriture manuscrite n'affiche actuellement aucune invite d'autorisation. Par conséquent, l'autorisation n'a pas besoin d'être conservée de quelque manière que ce soit.

Commentaires

L'équipe Chromium souhaite connaître votre expérience avec l'API de reconnaissance de l'écriture manuscrite.

Parlez-nous de la conception de l'API

L'API ne fonctionne-t-elle pas comme prévu ? Ou manque-t-il des méthodes ou des propriétés dont vous avez besoin pour implémenter votre idée ? Vous avez une question ou un commentaire sur le modèle de sécurité ? Signalez un problème de spécification dans le dépôt GitHub correspondant ou ajoutez vos commentaires à un problème existant.

Signaler un problème d'implémentation

Avez-vous trouvé un bug dans l'implémentation de Chromium ? Ou l'implémentation est-elle différente de la spécification ? Signalez un bug sur new.crbug.com. Veillez à inclure autant de détails que possible, des instructions simples pour reproduire le problème et saisissez Blink>Handwriting dans le champ Composants. Glitch est idéal pour partager des reproductions rapidement et facilement.

Afficher la compatibilité avec l'API

Prévoyez-vous d'utiliser l'API Handwriting Recognition ? Votre soutien public aide l'équipe Chromium à hiérarchiser les fonctionnalités et montre aux autres fournisseurs de navigateurs à quel point il est essentiel de les prendre en charge.

Indiquez comment vous prévoyez de l'utiliser dans le fil de discussion Discourse de la WICM. Envoyez un tweet à @ChromiumDev avec le hashtag #HandwritingRecognition et indiquez-nous où et comment vous l'utilisez.

Remerciements

Ce document a été examiné par Joe Medley, Honglin Yu et Jiewei Qian.