Reconnaissance de l'encre numérique avec ML Kit sur Android

La reconnaissance d'encre numérique de ML Kit vous permet de reconnaître du texte manuscrit à la main sur une surface numérique dans des centaines de langues et de classer des croquis.

Essayer

Avant de commencer

  1. Dans le fichier build.gradle au niveau du projet, veillez à inclure le dépôt Maven de Google dans vos sections buildscript et allprojects.
  2. Ajoutez les dépendances des bibliothèques Android de ML Kit au fichier Gradle au niveau de l'application de votre module, qui est généralement app/build.gradle:
dependencies {
  // ...
  implementation 'com.google.mlkit:digital-ink-recognition:18.1.0'
}

Vous êtes maintenant prêt à reconnaître du texte dans les objets Ink.

Créer un objet Ink

La principale méthode pour créer un objet Ink consiste à le dessiner sur un écran tactile. Sur Android, vous pouvez utiliser un canvas à cette fin. Vos gestionnaires d'événements tactiles doivent appeler la méthode addNewTouchEvent() illustrée dans l'extrait de code suivant pour stocker les points des traits que l'utilisateur dessine dans l'objet Ink.

Ce schéma général est illustré dans l'extrait de code suivant. Consultez l'exemple de démarrage rapide de ML Kit pour obtenir un exemple plus complet.

Kotlin

var inkBuilder = Ink.builder()
lateinit var strokeBuilder: Ink.Stroke.Builder

// Call this each time there is a new event.
fun addNewTouchEvent(event: MotionEvent) {
  val action = event.actionMasked
  val x = event.x
  val y = event.y
  var t = System.currentTimeMillis()

  // If your setup does not provide timing information, you can omit the
  // third paramater (t) in the calls to Ink.Point.create
  when (action) {
    MotionEvent.ACTION_DOWN -> {
      strokeBuilder = Ink.Stroke.builder()
      strokeBuilder.addPoint(Ink.Point.create(x, y, t))
    }
    MotionEvent.ACTION_MOVE -> strokeBuilder!!.addPoint(Ink.Point.create(x, y, t))
    MotionEvent.ACTION_UP -> {
      strokeBuilder.addPoint(Ink.Point.create(x, y, t))
      inkBuilder.addStroke(strokeBuilder.build())
    }
    else -> {
      // Action not relevant for ink construction
    }
  }
}

...

// This is what to send to the recognizer.
val ink = inkBuilder.build()

Java

Ink.Builder inkBuilder = Ink.builder();
Ink.Stroke.Builder strokeBuilder;

// Call this each time there is a new event.
public void addNewTouchEvent(MotionEvent event) {
  float x = event.getX();
  float y = event.getY();
  long t = System.currentTimeMillis();

  // If your setup does not provide timing information, you can omit the
  // third paramater (t) in the calls to Ink.Point.create
  int action = event.getActionMasked();
  switch (action) {
    case MotionEvent.ACTION_DOWN:
      strokeBuilder = Ink.Stroke.builder();
      strokeBuilder.addPoint(Ink.Point.create(x, y, t));
      break;
    case MotionEvent.ACTION_MOVE:
      strokeBuilder.addPoint(Ink.Point.create(x, y, t));
      break;
    case MotionEvent.ACTION_UP:
      strokeBuilder.addPoint(Ink.Point.create(x, y, t));
      inkBuilder.addStroke(strokeBuilder.build());
      strokeBuilder = null;
      break;
  }
}

...

// This is what to send to the recognizer.
Ink ink = inkBuilder.build();

Obtenir une instance de DigitalInkRecognizer

Pour effectuer la reconnaissance, envoyez l'instance Ink à un objet DigitalInkRecognizer. Le code ci-dessous montre comment instancier un tel outil de reconnaissance à partir d'un tag BCP-47.

Kotlin

// Specify the recognition model for a language
var modelIdentifier: DigitalInkRecognitionModelIdentifier
try {
  modelIdentifier = DigitalInkRecognitionModelIdentifier.fromLanguageTag("en-US")
} catch (e: MlKitException) {
  // language tag failed to parse, handle error.
}
if (modelIdentifier == null) {
  // no model was found, handle error.
}
var model: DigitalInkRecognitionModel =
    DigitalInkRecognitionModel.builder(modelIdentifier).build()


// Get a recognizer for the language
var recognizer: DigitalInkRecognizer =
    DigitalInkRecognition.getClient(
        DigitalInkRecognizerOptions.builder(model).build())

Java

// Specify the recognition model for a language
DigitalInkRecognitionModelIdentifier modelIdentifier;
try {
  modelIdentifier =
    DigitalInkRecognitionModelIdentifier.fromLanguageTag("en-US");
} catch (MlKitException e) {
  // language tag failed to parse, handle error.
}
if (modelIdentifier == null) {
  // no model was found, handle error.
}

DigitalInkRecognitionModel model =
    DigitalInkRecognitionModel.builder(modelIdentifier).build();

// Get a recognizer for the language
DigitalInkRecognizer recognizer =
    DigitalInkRecognition.getClient(
        DigitalInkRecognizerOptions.builder(model).build());

Traiter un objet Ink

Kotlin

recognizer.recognize(ink)
    .addOnSuccessListener { result: RecognitionResult ->
      // `result` contains the recognizer's answers as a RecognitionResult.
      // Logs the text from the top candidate.
      Log.i(TAG, result.candidates[0].text)
    }
    .addOnFailureListener { e: Exception ->
      Log.e(TAG, "Error during recognition: $e")
    }

Java

recognizer.recognize(ink)
    .addOnSuccessListener(
        // `result` contains the recognizer's answers as a RecognitionResult.
        // Logs the text from the top candidate.
        result -> Log.i(TAG, result.getCandidates().get(0).getText()))
    .addOnFailureListener(
        e -> Log.e(TAG, "Error during recognition: " + e));

L'exemple de code ci-dessus suppose que le modèle de reconnaissance a déjà été téléchargé, comme décrit dans la section suivante.

Gérer les téléchargements de modèles

Bien que l'API de reconnaissance de l'encre numérique soit compatible avec des centaines de langues, chaque langue nécessite le téléchargement de certaines données avant toute reconnaissance. Un espace de stockage d'environ 20 Mo est nécessaire par langue. Cette opération est gérée par l'objet RemoteModelManager.

Télécharger un nouveau modèle

Kotlin

import com.google.mlkit.common.model.DownloadConditions
import com.google.mlkit.common.model.RemoteModelManager

var model: DigitalInkRecognitionModel =  ...
val remoteModelManager = RemoteModelManager.getInstance()

remoteModelManager.download(model, DownloadConditions.Builder().build())
    .addOnSuccessListener {
      Log.i(TAG, "Model downloaded")
    }
    .addOnFailureListener { e: Exception ->
      Log.e(TAG, "Error while downloading a model: $e")
    }

Java

import com.google.mlkit.common.model.DownloadConditions;
import com.google.mlkit.common.model.RemoteModelManager;

DigitalInkRecognitionModel model = ...;
RemoteModelManager remoteModelManager = RemoteModelManager.getInstance();

remoteModelManager
    .download(model, new DownloadConditions.Builder().build())
    .addOnSuccessListener(aVoid -> Log.i(TAG, "Model downloaded"))
    .addOnFailureListener(
        e -> Log.e(TAG, "Error while downloading a model: " + e));

Vérifier si un modèle a déjà été téléchargé

Kotlin

var model: DigitalInkRecognitionModel =  ...
remoteModelManager.isModelDownloaded(model)

Java

DigitalInkRecognitionModel model = ...;
remoteModelManager.isModelDownloaded(model);

Supprimer un modèle téléchargé

Supprimer un modèle de l'espace de stockage de l'appareil libère de l'espace.

Kotlin

var model: DigitalInkRecognitionModel =  ...
remoteModelManager.deleteDownloadedModel(model)
    .addOnSuccessListener {
      Log.i(TAG, "Model successfully deleted")
    }
    .addOnFailureListener { e: Exception ->
      Log.e(TAG, "Error while deleting a model: $e")
    }

Java

DigitalInkRecognitionModel model = ...;
remoteModelManager.deleteDownloadedModel(model)
                  .addOnSuccessListener(
                      aVoid -> Log.i(TAG, "Model successfully deleted"))
                  .addOnFailureListener(
                      e -> Log.e(TAG, "Error while deleting a model: " + e));

Conseils pour améliorer la précision de la reconnaissance de texte

La précision de la reconnaissance de texte peut varier selon les langues. La précision dépend également du style d'écriture. Bien que la reconnaissance d'encre numérique soit entraînée à gérer de nombreux types de styles d'écriture, les résultats peuvent varier d'un utilisateur à l'autre.

Voici quelques conseils pour améliorer la précision d'un outil de reconnaissance de texte. Notez que ces techniques ne s'appliquent pas aux classificateurs de dessin pour les emoji, les dessins automatiques et les formes.

Zone d'écriture

De nombreuses applications disposent d'une zone d'écriture bien définie pour les entrées utilisateur. La signification d'un symbole est partiellement déterminée par sa taille par rapport à la taille de la zone d'écriture qui le contient. Par exemple, la différence entre une lettre majuscule ou minuscule "o" ou "c", et une virgule par rapport à une barre oblique.

Indiquer à l'outil de reconnaissance la largeur et la hauteur de la zone d'écriture peut améliorer la précision. Toutefois, l'outil de reconnaissance suppose que la zone d'écriture ne contient qu'une seule ligne de texte. Si la zone d'écriture physique est suffisamment grande pour permettre à l'utilisateur d'écrire deux lignes ou plus, vous pouvez obtenir de meilleurs résultats en transmettant une zone d'écriture avec une hauteur qui correspond à votre meilleure estimation de la hauteur d'une seule ligne de texte. L'objet WriteArea que vous transmettez à l'outil de reconnaissance ne doit pas nécessairement correspondre exactement à la zone d'écriture physique à l'écran. Modifier la hauteur d'WriteArea de cette manière fonctionne mieux dans certaines langues que dans d'autres.

Lorsque vous spécifiez la zone d'écriture, indiquez sa largeur et sa hauteur dans les mêmes unités que les coordonnées du trait. Les arguments de coordonnées x et y n'ont aucune exigence d'unité : l'API normalise toutes les unités. La seule chose qui importe est donc la taille et la position relatives des traits. Vous êtes libre de transmettre les coordonnées à l'échelle qui convient à votre système.

Pré-contexte

Le pré-contexte correspond au texte qui précède immédiatement les traits dans l'élément Ink que vous essayez de reconnaître. Vous pouvez aider l'outil de reconnaissance en lui donnant des informations sur le pré-contexte.

Par exemple, les lettres cursives "n" et "u" sont souvent confondues. Si l'utilisateur a déjà saisi le mot partiel "arg", il peut continuer avec des traits pouvant être reconnus comme "ument" ou "nment". La spécification de l'argument de pré-contexte "arg" résout l'ambiguïté, car le mot "argument" est plus probable que "argnment".

Le pré-contexte peut également aider l'outil de reconnaissance à identifier les coupures entre les mots, c'est-à-dire les espaces entre les mots. Vous pouvez saisir un espace, mais vous ne pouvez pas en dessiner un. Comment un outil de reconnaissance peut-il déterminer quand un mot se termine et quand le suivant commence ? Si l'utilisateur a déjà écrit "hello" et poursuit avec le mot "world", sans contexte préalable, l'outil de reconnaissance renvoie la chaîne "world". Toutefois, si vous spécifiez le pré-contexte "hello", le modèle renvoie la chaîne "world", avec un espace de début, car "hello world" est plus logique que "helloword".

Vous devez fournir la chaîne de pré-contexte la plus longue possible, jusqu'à 20 caractères, espaces compris. Si la chaîne est plus longue, le programme de reconnaissance n'utilise que les 20 derniers caractères.

L'exemple de code ci-dessous montre comment définir une zone d'écriture et utiliser un objet RecognitionContext pour spécifier le pré-contexte.

Kotlin

var preContext : String = ...;
var width : Float = ...;
var height : Float = ...;
val recognitionContext : RecognitionContext =
    RecognitionContext.builder()
        .setPreContext(preContext)
        .setWritingArea(WritingArea(width, height))
        .build()

recognizer.recognize(ink, recognitionContext)

Java

String preContext = ...;
float width = ...;
float height = ...;
RecognitionContext recognitionContext =
    RecognitionContext.builder()
                      .setPreContext(preContext)
                      .setWritingArea(new WritingArea(width, height))
                      .build();

recognizer.recognize(ink, recognitionContext);

Ordre des traits

La précision de la reconnaissance dépend de l'ordre des traits. Les outils de reconnaissance s'attendent à ce que les traits se produisent dans l'ordre dans lequel les utilisateurs écrivent naturellement, par exemple de gauche à droite pour l'anglais. Les résultats sont moins précis si les cas diffèrent de ce modèle, par exemple lorsque vous écrivez une phrase en anglais en commençant par le dernier mot.

Autre exemple : un mot situé au milieu d'une Ink est supprimé et remplacé par un autre mot. La révision se trouve probablement au milieu d'une phrase, mais les traits de la révision se trouvent à la fin de la séquence des traits. Dans ce cas, nous vous recommandons d'envoyer séparément le mot nouvellement écrit à l'API et de fusionner le résultat avec les reconnaissances précédentes à l'aide de votre propre logique.

Gérer les formes ambiguës

Dans certains cas, la signification de la forme fournie à l'outil de reconnaissance est ambiguë. Par exemple, un rectangle aux bords très arrondis peut être considéré comme un rectangle ou comme une ellipse.

Ces cas peu clairs peuvent être traités à l'aide des scores de reconnaissance, lorsqu'ils sont disponibles. Seuls les classificateurs de formes fournissent des scores. Si le modèle est très confiant, le score du meilleur résultat sera nettement supérieur à celui du deuxième résultat. En cas d'incertitude, les scores des deux premiers résultats seront proches. Gardez également à l'esprit que les classificateurs de formes interprètent l'ensemble de la Ink comme une forme unique. Par exemple, si Ink contient un rectangle et une ellipse côte à côte, le programme de reconnaissance peut renvoyer l'un ou l'autre (ou quelque chose de complètement différent), car un candidat à la reconnaissance unique ne peut pas représenter deux formes.