Reconnaître l'écriture manuscrite des utilisateurs

L'API Handwriting Recognition vous permet de reconnaître du texte à partir d'une saisie manuscrite 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 utiliser cette fonctionnalité. La conversion a lieu directement sur l'appareil de l'utilisateur, 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 l'entrée 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 Handwriting Recognition

Voici quelques exemples d'utilisation :

  • Applications de prise de notes dans lesquelles les utilisateurs souhaitent capturer des notes manuscrites et les faire traduire en texte.
  • Applications de formulaires dans lesquelles les utilisateurs peuvent utiliser un stylet ou leurs doigts 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 Handwriting Recognition

Détection de caractéristiques

Pour détecter la compatibilité du navigateur, vérifiez l'existence de la méthode createHandwritingRecognizer() sur l'objet navigator :

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

Concepts fondamentaux

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

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

Ces concepts sont implémentés sous forme d'interfaces et de dictionnaires spécifiques, que je vais aborder dans un instant.

Entités principales de l'API Handwriting Recognition : un ou plusieurs points composent un trait, un ou plusieurs traits composent un dessin que le moteur de reconnaissance crée. La reconnaissance proprement dite a lieu au niveau du dessin.

Créer un outil de reconnaissance

Pour reconnaître le texte d'une saisie manuscrite, vous devez obtenir une instance de 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 qui se résout avec une instance de HandwritingRecognizer lorsque le navigateur peut répondre à votre demande. Sinon, elle rejettera la promesse avec une erreur et la reconnaissance de l'écriture manuscrite ne sera pas disponible. Pour cette raison, vous pouvez d'abord interroger l'outil de reconnaissance pour savoir s'il prend en charge des fonctionnalités de reconnaissance spécifiques.

Interroger la compatibilité du module de reconnaissance

En appelant navigator.queryHandwritingRecognizer(), vous pouvez vérifier si la plate-forme cible est compatible avec les fonctionnalités de reconnaissance de l'écriture manuscrite que vous souhaitez utiliser. Cette méthode utilise le même objet de contrainte que la méthode navigator.createHandwritingRecognizer(), qui contient la liste des langues demandées. La méthode renvoie une promesse qui se résout avec un objet de résultat si un module de reconnaissance compatible est trouvé. Sinon, la promesse renvoie null. Dans l'exemple suivant, le développeur :

  • veut détecter des textes en anglais
  • obtenir des prédictions alternatives moins probables, le cas échéant ;
  • 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 result =
  await navigator.queryHandwritingRecognizerSupport({
    languages: ['en']
  });

console.log(result?.textAlternatives); // true if alternatives are supported
console.log(result?.textSegmentation); // true if segmentation is supported

Si le navigateur est compatible avec la fonctionnalité dont le développeur a besoin, sa valeur sera définie sur true dans l'objet de résultat. Sinon, la valeur sera définie sur false. Vous pouvez utiliser ces informations pour activer ou désactiver certaines fonctionnalités de votre application, ou pour envoyer une nouvelle requête pour un autre ensemble de langues.

Commencer un dessin

Dans votre application, vous devez proposer une zone de saisie dans laquelle l'utilisateur peut saisir ses entrées manuscrites. Pour des raisons de performances, nous vous recommandons d'implémenter cette fonctionnalité à l'aide d'un objet Canvas. L'implémentation exacte de cette partie ne fait pas l'objet de cet article, mais vous pouvez consulter la démonstration pour voir comment procéder.

Pour démarrer un nouveau dessin, appelez la méthode startDrawing() sur le module de reconnaissance. Cette méthode utilise un objet contenant différents indices pour affiner l'algorithme de reconnaissance. Tous les indices sont facultatifs :

  • Type de texte saisi : texte, adresses e-mail, nombres 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 de prédictions alternatives moins probables à renvoyer (alternatives)
  • Liste des caractères identifiables par l'utilisateur ("graphèmes") qu'il est le plus susceptible de saisir (graphemeSet)

L'API Handwriting Recognition fonctionne bien avec les événements de pointeur, qui fournissent une interface abstraite pour consommer les entrées de n'importe quel dispositif de pointage. Les arguments de l'é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éé lors de 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 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', 'stylus'].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 le bon endroit pour commencer un nouveau tracé. 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 seront ajoutés :

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

Ajouter un point

Après avoir créé le tracé, vous devez y ajouter directement le premier point. Comme vous ajouterez d'autres points plus tard, il est judicieux 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é depuis le code temporel de référence. Les informations temporelles sont facultatives, mais 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 est déplacé sur l'écran. Ces points doivent également être ajoutés au tracé. L'événement peut également être déclenché si le pointeur n'est pas dans un état "vers le bas", par exemple lorsque le curseur se déplace sur l'écran sans que le bouton de la souris soit enfoncé. Le gestionnaire d'événements de l'exemple suivant vérifie s'il existe un tracé actif et y ajoute le nouveau point.

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

Reconnaître du texte

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

Ensuite, il est temps de reconnaître l'entrée 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 classé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 présenter à l'utilisateur un choix de correspondances possibles et lui demander de sélectionner une option. Vous pouvez également 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 j'aborderai dans la section suivante.

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

Si la plate-forme cible le permet, 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. Il combine le caractère identifiable par l'utilisateur (grapheme) avec sa position dans le texte reconnu (beginIndex, endIndex), ainsi que les traits et les 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 graphème reconnu.

Reconnaissance complète

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

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

Démo

Le composant Web <handwriting-textarea> implémente un contrôle d'édition amélioré progressivement, capable de reconnaître l'écriture manuscrite. Cliquez sur le bouton en bas à droite du contrôle d'édition pour activer le mode dessin. Une fois le dessin terminé, le composant Web lance automatiquement la reconnaissance et ajoute le texte reconnu au contrôle d'édition. Si l'API Handwriting Recognition n'est pas du tout prise en charge ou si la plate-forme ne prend pas en charge les fonctionnalités demandées, le bouton "Modifier" sera masqué. Toutefois, la commande d'édition de base reste utilisable en tant que <textarea>.

Le composant Web propose des propriétés et des attributs permettant de définir le comportement de reconnaissance depuis l'extérieur, y compris languages et recognitiontype. Vous pouvez définir le contenu du contrôle à l'aide de 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 essayer le composant à l'aide de cette démonstration sur GitHub. N'oubliez pas de consulter le code source. Pour utiliser le contrôle dans votre application, obtenez-le depuis npm.

Sécurité et autorisations

L'équipe Chromium a conçu et implémenté l'API Handwriting Recognition en utilisant les principes de base définis dans Contrôler l'accès aux fonctionnalités puissantes de la plate-forme Web, y compris le contrôle par l'utilisateur, la transparence et l'ergonomie.

Contrôle des utilisateurs

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

Transparence

Il n'y a aucune indication permettant de savoir si la reconnaissance de l'écriture manuscrite est active. Pour éviter l'empreinte numérique, le navigateur met en œuvre des mesures de protection, comme l'affichage d'une invite d'autorisation à l'utilisateur lorsqu'il détecte une éventuelle utilisation abusive.

Persistance des autorisations

L'API Handwriting Recognition 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 Handwriting Recognition.

Parlez-nous de la conception de l'API

Y a-t-il un élément de l'API qui ne fonctionne pas comme prévu ? Ou bien manque-t-il des méthodes ou des propriétés dont vous avez besoin pour mettre en œuvre votre idée ? Vous avez une question ou un commentaire sur le modèle de sécurité ? Signalez un problème lié aux spécifications 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 la zone Composants.

Soutenir l'API

Comptez-vous 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.

Expliquez comment vous prévoyez de l'utiliser dans le fil de discussion WICG Discourse. Envoyez un tweet à @ChromiumDev avec le hashtag #HandwritingRecognition pour nous indiquer où et comment vous l'utilisez.

Remerciements

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