Inchiostro digitale riconosciuto con ML Kit su Android

Mantieni tutto organizzato con le raccolte Salva e classifica i contenuti in base alle tue preferenze.

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

Prima di iniziare

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

Ora è tutto pronto per iniziare a riconoscere il testo negli oggetti Ink.

Crea un oggetto Ink

Il modo principale per creare un oggetto Ink è tracciarlo su un touchscreen. Su Android puoi usare una Canvas per questo scopo. I gestori dei tuoi eventi touch dovrebbero chiamare il metodo addNewTouchEvent() mostrato nel seguente snippet di codice per archiviare i punti nelle bracciate che l'utente disegna nell'oggetto Ink.

Questo pattern generale è descritto nel seguente snippet di codice. Per un esempio più completo, consulta l'esempio di guida rapida di ML Kit.

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

Recupero di un'istanza di DigitalInkRecognizer

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

Elabora 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 precedente presuppone che il modello di riconoscimento sia già stato scaricato, come descritto nella sezione successiva.

Gestione dei download dei modelli

Anche se l'API di inchiostro digitale supporta centinaia di lingue, ogni lingua richiede il download di alcuni dati prima di essere riconosciuto. Sono richiesti circa 20 MB di spazio di archiviazione per lingua. Ciò viene gestito dall'oggetto RemoteModelManager.

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

Controlla se un modello è già stato scaricato

Kotlin

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

Java

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

Elimina 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 la precisione del riconoscimento del testo

La precisione del riconoscimento del testo può variare in base alla lingua. La precisione dipende anche dallo stile di scrittura. Il riconoscimento dell'inchiostro digitale è studiato per gestire molti tipi di stili di scrittura, ma i risultati possono variare a seconda dell'utente.

Ecco alcuni modi per migliorare la precisione di un riconoscimento testo. Tieni presente che queste tecniche non si applicano ai classificatori di disegno per emoji, disegno automatico e forme.

Area di scrittura

Molte applicazioni hanno un'area di scrittura ben definita per l'input dell'utente. Il significato di un simbolo è in parte determinato dalle sue dimensioni rispetto alla dimensione dell'area di scrittura che lo contiene. Ad esempio, la differenza tra una lettera minuscola o maiuscola "o" o "c" e una virgola rispetto a una barra.

Indicare alla persona che ha riconosciuto la larghezza e l'altezza dell'area di scrittura può migliorare la precisione. Tuttavia, il riconoscimento presume che l'area di scrittura contenga solo una singola riga di testo. Se l'area di scrittura fisica è abbastanza grande da consentire all'utente di scrivere due o più righe, potresti ottenere risultati migliori passando un'area di scrittura con un'altezza che sia la tua stima migliore dell'altezza di una singola riga di testo. L'oggetto WriteArea che trasmetti al riconoscimento non deve corrispondere esattamente all'area di scrittura fisica sullo schermo. In questo modo, modificare l'altezza dell'area di scrittura funziona meglio in alcune lingue rispetto ad altre.

Quando specifichi l'area di scrittura, specifica la larghezza e l'altezza nelle stesse unità delle coordinate di tratto. Gli argomenti delle coordinate x,y non hanno requisiti di unità: l'API normalizza tutte le unità, quindi l'unica cosa importante è la dimensione e la posizione relative dei tratti. Sei libero di passare in coordinate nella scala più adatta al tuo sistema.

Pre-contesto

Il pre-contesto è il testo che precede immediatamente i tratti in Ink che stai cercando di riconoscere. Puoi aiutare il responsabile fornendo informazioni sul pre-contesto.

Ad esempio, le lettere corsive "n" e "u" vengono spesso scambiate per un altro. Se l'utente ha già inserito la parola parziale "arg", può continuare con tratti che possono essere riconosciuti come "ument" o "nment". Specificare il pre-contesto "arg" risolve l'ambiguità, poiché la parola "argomento" ha più probabilità di "argnment".

Il pre-contesto può anche aiutare il sistema a riconoscere le interruzioni di parola, ovvero gli spazi tra le parole. Puoi digitare uno spazio, ma non ne puoi disegnare uno. In che modo un riconoscimento può determinare quando termina una parola e la successiva inizia? Se l'utente ha già scritto "ciao" e continua con la parola scritta "mondo", senza il pre-contesto il riconoscimento restituisce la stringa "mondo". Tuttavia, se specifichi il pre-contesto "hello", il modello restituirà la stringa "mondo", con uno spazio all'inizio, poiché "hello world" ha più senso di "helloword".

Fornisci la stringa di pre-contesto più lunga possibile, fino a 20 caratteri, spazi inclusi. 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 oggetto RecognitionContext 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);

Ordine di bracciate

La precisione del riconoscimento è sensibile all'ordine dei tratti. I riconosciuti si aspettano che i colpi si verifichino nell'ordine in cui le persone scrivono normalmente, ad esempio da sinistra a destra per l'inglese. Ogni caso che si discosta da questo pattern, ad esempio la scrittura di una frase inglese che inizia con l'ultima parola, fornisce risultati meno accurati.

Un altro esempio è quando una parola al centro dell'elemento Ink viene rimossa e sostituita con un'altra parola. Probabilmente la revisione si trova a metà di una frase, ma le bracciate della revisione si trovano alla fine della sequenza di tratti. In questo caso consigliamo di inviare separatamente la parola appena scritta 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 che gli viene assegnato è ambiguo. Ad esempio, un rettangolo con bordi molto arrotondati può essere visto come un rettangolo o un'ellissi.

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