Riconoscimento dell'inchiostro digitale con ML Kit su Android

Con il riconoscimento a inchiostro digitale di ML Kit, puoi riconoscere il testo scritto a mano su un superficie digitale in centinaia di lingue, oltre a classificare gli schizzi.

Prova

  • Prova l'app di esempio per per vedere un esempio di utilizzo di questa API.

Prima di iniziare

  1. Nel file build.gradle a livello di progetto, assicurati di includere il Repository Maven di Google in entrambe le sezioni buildscript e allprojects.
  2. Aggiungi le dipendenze per le librerie Android di ML Kit al file Gradle a livello di app del tuo modulo, che in genere è app/build.gradle:
dependencies {
  // ...
  implementation 'com.google.mlkit:digital-ink-recognition:18.1.0'
}

Ora puoi iniziare a riconoscere il testo negli oggetti Ink.

Creare un oggetto Ink

Il modo principale per creare un oggetto Ink è disegnarlo su un touchscreen. Su Android, puoi utilizzare un Canvas per questo scopo. Il tuo Gestori di eventi touch deve chiamare addNewTouchEvent() mostrato il seguente snippet di codice per memorizzare i punti nei tratti che l'utente disegna nell'oggetto Ink.

Questo pattern generale viene dimostrato nel seguente snippet di codice. Consulta le Esempio di guida rapida di ML Kit per un esempio più completo.

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();

Ottieni un'istanza di DigitalInkRecognizer

Per eseguire il riconoscimento, invia l'istanza Ink a un DigitalInkRecognizer oggetto. Il codice seguente mostra come creare un'istanza di questo riconoscitore da 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());

Elaborare un oggetto 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));

Il codice campione sopra riportato presuppone che il modello di riconoscimento sia già stato scaricato, come descritto nella sezione successiva.

Gestione dei download dei modelli

Sebbene l'API per il riconoscimento dell'inchiostro digitale supporti centinaia di lingue, ogni richiede il download di alcuni dati prima di qualsiasi riconoscimento. Vicino Sono necessari 20 MB di spazio di archiviazione per lingua. Ciò viene gestito dal RemoteModelManager oggetto.

Scaricare un nuovo modello

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));

Controllare se un modello è già stato scaricato

Kotlin

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

Java

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

Eliminare un modello scaricato

La rimozione di un modello dallo spazio di archiviazione del dispositivo libera spazio.

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));

Suggerimenti per migliorare l'accuratezza del riconoscimento del testo

La precisione del riconoscimento del testo può variare in base alle lingue. L'accuratezza dipende anche dallo stile di scrittura. La funzione di riconoscimento inchiostro digitale è addestrata per gestire molti tipi di stili di scrittura, i risultati possono variare da utente a utente.

Ecco alcuni modi per migliorare la precisione di un riconoscitore di testo. Tieni presente che queste tecniche non si applica ai classificatori dei disegni per emoji, autoDraw e forme.

Area di scrittura

Molte applicazioni dispongono di un'area di scrittura ben definita per l'input dell'utente. Il significato di un simbolo è parzialmente determinato dalle sue dimensioni rispetto alle dimensioni dell'area di scrittura che lo contiene. Ad esempio, la differenza tra la lettera "o" e quella minuscola o "c" e una virgola rispetto a barra obliqua.

Indicare al riconoscitore la larghezza e l'altezza dell'area di scrittura può migliorare la precisione. Tuttavia, il riconoscimento presuppone che l'area di scrittura contenga una sola riga di testo. Se l'ordine fisico che l'area di scrittura sia abbastanza grande da consentire all'utente di scrivere due o più righe, la visualizzazione risultati passando in un'area di scrittura con un'altezza che rappresenta la stima migliore dell'altezza di riga di testo singola. L'oggetto WritingArea che passi al riconoscitore non deve corrispondere esattamente all'area di scrittura fisica sullo schermo. La modifica dell'altezza di WritingArea in questo modo funziona meglio in alcune lingue rispetto ad altre.

Quando specifichi l'area di scrittura, specifica la larghezza e l'altezza utilizzando le stesse unità del tratto coordinate. Gli argomenti delle coordinate x,y non hanno requisiti relativi alle unità. L'API normalizza tutte le unità, quindi l'unica cosa che conta è la dimensione e la posizione relative dei tratti. Puoi scegliere la scala più adatta al tuo sistema per inserire le coordinate.

Pre-contesto

Il contesto precedente è il testo che precede immediatamente i tratti della Ink che stai cercando di riconoscere. Puoi aiutare il sistema di riconoscimento fornendogli il contesto precedente.

Ad esempio, le lettere corsie "n" e "u" spesso scambiati l'uno per l'altro. Se l'utente ha già inserito la parola parziale "arg", può continuare con tratti che possono essere riconosciuti come "ument" o "nment". Specificare l'"arg" del pre-contesto risolve l'ambiguità, poiché la parola "argument" è più probabile di "argnment".

Il contesto precedente può anche aiutare il riconoscitore a identificare gli a capo, gli spazi tra le parole. Puoi digitare un carattere di spazio, ma non puoi disegnarlo, quindi come può un sistema di riconoscimento determinare quando termina una parola e inizia la successiva? Se l'utente ha già scritto "ciao" e continua con la parola scritta "mondo", senza pre-contesto il riconoscitore restituisce la stringa "mondo". Tuttavia, se specifichi pre-contesto "hello", il modello restituisce la stringa " mondiale", con uno spazio iniziale, poiché "hello mondo" ha più senso di "helloword".

Devi fornire la stringa di pre-contesto più lunga possibile, fino a 20 caratteri, tra cui: spazi di archiviazione. Se la stringa è più lunga, il riconoscimento utilizza solo gli ultimi 20 caratteri.

L'esempio di codice seguente mostra come definire un'area di scrittura e utilizzare un RecognitionContext oggetto per specificare il pre-contesto.

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);

Ordinamento tratti

L'accuratezza del riconoscimento è sensibile all'ordine dei tratti. I responsabili del riconoscimento si aspettano che avvengono nell'ordine in cui le persone scrivono in modo naturale; ad esempio da sinistra a destra per l'inglese. Qualsiasi caso che si discosti da questo schema, ad esempio scrivere una frase in inglese che inizia con l'ultima parola, dà risultati meno accurati.

Un altro esempio è quando una parola al centro di un Ink viene rimossa e sostituita un'altra parola. La revisione è probabilmente nel mezzo di una frase, ma i tratti per la revisione si trovano alla fine della sequenza di tratti. In questo caso, ti consigliamo di inviare la parola appena scritta separatamente all'API e di unire il risultato con i riconoscimenti precedenti utilizzando la tua logica.

Gestire forme ambigue

In alcuni casi il significato della forma fornita al riconoscitore è ambiguo. Per Ad esempio, un rettangolo con bordi molto arrotondati può essere visto come un rettangolo o un'ellisse.

Questi casi non chiari possono essere gestiti utilizzando i punteggi di riconoscimento, se disponibili. Solo classificatori di forme forniscono punteggi. Se il modello è molto sicuro, il punteggio del miglior risultato sarà molto meglio del secondo migliore. In caso di incertezza, i punteggi dei primi due risultati essere vicini. Inoltre, tieni presente che i classificatori delle forme interpretano l'intero Ink come una singola forma. Ad esempio, se Ink contiene un rettangolo e un'ellisse uno accanto all'altro, il sistema di riconoscimento potrebbe restituire uno o l'altro (o qualcosa di completamente diverso) come risultato, poiché un singolo candidato al riconoscimento non può rappresentare due forme.