Reconnaître l'écriture manuscrite des utilisateurs

L'API de reconnaissance d'écriture manuscrite vous permet de reconnaître en temps réel du texte issu d'une entrée manuscrite.

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

L'API Handwrite Recognition vous permet de convertir en texte l'écriture manuscrite de vos utilisateurs. Certains systèmes d'exploitation incluent depuis longtemps ces API. Grâce à cette nouvelle fonctionnalité, vos applications Web peuvent enfin l'utiliser. La conversion a lieu directement sur l'appareil de l'utilisateur et fonctionne même en mode hors connexion, sans ajouter de bibliothèques ou de services tiers.

Cette API implémente une reconnaissance dite "en ligne" ou quasiment en temps réel. Cela signifie que l'entrée manuscrite est reconnue pendant que l'utilisateur la dessine, en capturant et en analysant les traits uniques. Contrairement aux procédures hors connexion, telles que la reconnaissance optique des caractères (OCR), pour lesquelles 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 Handwrite 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 Forms où les utilisateurs peuvent utiliser la saisie au stylet ou au doigt en raison de contraintes de temps.
  • Jeux nécessitant la saisie de lettres ou de chiffres, comme les mots croisés, le pendu ou le sudoku.

État actuel

L'API de reconnaissance d'écriture manuscrite est disponible depuis (Chromium 99).

Utiliser l'API Handwrite Recognition

Détection de fonctionnalités

Détectez la compatibilité avec les navigateurs 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 de reconnaissance d'écriture manuscrite convertit les entrées manuscrites en texte, quelle que soit la méthode de saisie (souris, tactile, stylo). L'API possède quatre entités principales:

  1. Un point représente l'endroit où se trouvait le 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 vers le bas (c'est-à-dire lorsqu'il clique sur le bouton principal de la souris ou touche l'écran avec son stylo ou son doigt) et se termine lorsqu'il lève à nouveau le pointeur.
  3. Un dessin se compose d'un ou de plusieurs traits. C'est à ce niveau que la reconnaissance a lieu.
  4. Le reconnaissance est configuré avec la langue d'entrée attendue. Il permet de créer une instance d'un dessin avec la configuration de reconnaissance appliquée.

Ces concepts sont implémentés sous la forme d'interfaces et de dictionnaires spécifiques, que je présenterai bientôt.

Entités principales de l'API de reconnaissance d'écriture manuscrite: un ou plusieurs points composent un trait, un ou plusieurs traits composent un dessin, créé par l'outil de reconnaissance. La reconnaissance s'effectue au niveau du dessin.

Créer un programme de reconnaissance

Pour reconnaître du texte à partir d'une entrée 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 d'é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 de HandwritingRecognizer lorsque le navigateur peut répondre à votre requête. Sinon, la promesse sera rejetée avec une erreur, et la reconnaissance de l'écriture manuscrite ne sera pas disponible. Pour cette raison, vous pouvez d'abord interroger la compatibilité du programme de reconnaissance pour certaines fonctionnalités de reconnaissance.

Interroger la prise en charge du programme de reconnaissance

En appelant navigator.queryHandwritingRecognizerSupport(), vous pouvez vérifier si la plate-forme cible prend en charge 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 aux points et 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 sera définie sur true. Sinon, il sera défini sur false. Vous pouvez utiliser ces informations pour activer ou désactiver certaines fonctionnalités de 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 effectue ses entrées manuscrites. Pour des raisons de performances, nous vous recommandons d'effectuer cette opération à l'aide d'un objet Canvas. L'implémentation exacte de cette partie n'entre pas dans le cadre de cet article, mais vous pouvez consulter la démonstration pour savoir comment procéder.

Pour commencer un nouveau dessin, appelez la méthode startDrawing() sur l'outil de reconnaissance. Cette méthode utilise un objet contenant différentes indications pour affiner l'algorithme de reconnaissance. Toutes les indications sont facultatives:

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

L'API de reconnaissance d'écriture manuscrite fonctionne bien avec les événements de pointeur, qui fournissent une interface abstraite permettant d'utiliser les entrées de n'importe quel périphérique 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 de 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', '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 contour

L'événement pointerdown est également l'endroit idéal pour commencer un nouveau trait. Pour ce faire, créez une instance de HandwritingStroke. En outre, vous devez stocker l'heure actuelle comme point de référence pour les points suivants qui lui 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 par la suite, 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é à partir de l'horodatage de référence. Les informations temporelles sont facultatives, mais peuvent améliorer la qualité de la reconnaissance. Ensuite, il lit les coordonnées X et Y à partir 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 trait. L'événement peut également être déclenché si le pointeur n'est pas à l'é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, de sorte que le gestionnaire pointermove n'ajoute pas de points au trait terminé.

Il est maintenant temps de reconnaître l'entrée 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 en fonction de leur 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 différentes correspondances possibles et lui demander de sélectionner une option. Sinon, vous pouvez simplement opter pour la prédiction la plus probable, ce que je fais dans cet exemple.

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

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

S'il est compatible avec la plate-forme cible, 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 identifiable (grapheme) ainsi que 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 la toile à nouveau.

Des boîtes sont dessinées 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émonstration

Le composant Web <handwriting-textarea> implémente une commande de modification progressivement améliorée capable de reconnaître l'écriture manuscrite. Cliquez sur le bouton en bas à droite de la commande d'édition pour activer le mode dessin. Une fois le dessin terminé, le composant Web lance automatiquement la reconnaissance et ajoute de nouveau le texte reconnu à la commande d'édition. Si l'API de reconnaissance d'é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é. Cependant, la commande de modification 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 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é des modifications apportées à la valeur, vous pouvez écouter l'événement input.

Vous pouvez essayer le composant en suivant cette démonstration sur Glitch. Veillez également à consulter le code source. Pour utiliser la commande dans votre application, obtenez-la auprès de npm.

Sécurité et autorisations

L'équipe Chromium a conçu et mis en œuvre l'API Handwrite Recognition en suivant les principes fondamentaux définis dans la section 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 Handwrite Recognition. Il n'est disponible que pour les sites Web diffusés via HTTPS et ne peut être appelé qu'à partir du contexte de navigation de premier niveau.

Transparence

Aucune indication ne vous indique si la reconnaissance de l'écriture manuscrite est activée. Pour éviter le fingerprinting, le navigateur met en œuvre des contre-mesures, par exemple en affichant une invite d'autorisation à l'utilisateur lorsqu'il détecte un possible abus.

Persistance des autorisations

L'API Handwrite Recognition n'affiche actuellement aucune invite d'autorisation. Ainsi, 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 avis sur l'utilisation de l'API Reconnaissance d'écriture manuscrite.

Présentez-nous la conception de l'API

Y a-t-il quelque chose qui ne fonctionne pas comme prévu dans l'API ? Ou manque-t-il des méthodes ou des propriétés nécessaires à la mise en œuvre de 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 réflexions à un problème existant.

Signaler un problème d'implémentation

Avez-vous détecté un bug dans l'implémentation de Chromium ? Ou la mise en œuvre est-elle différente des spécifications ? Signalez un bug sur new.crbug.com. Veillez à inclure autant de détails que possible et des instructions simples pour reproduire le bug, puis saisissez Blink>Handwriting dans la zone Composants. Glitch est idéal pour partager des représentations rapides et faciles.

Montrer votre soutien à l'API

Comptez-vous utiliser l'API Handwrite Recognition ? Votre assistance publique 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 du WiCG. Envoyez un tweet à @ChromiumDev en utilisant le hashtag #HandwritingRecognition, et indiquez-nous où et comment vous l'utilisez.

Remerciements

Cet article a été lu par Joe Medley, Honglin Yu et Jiewei Qian. Image principale de Samir Bouaked sur Unsplash.