Con el reconocimiento de tinta digital del ML Kit, puedes reconocer texto escrito a mano en una superficie digital en cientos de idiomas, así como clasificar bocetos.
Probar
- Experimenta con la app de ejemplo para ver un ejemplo del uso de esta API.
Antes de comenzar
- En tu archivo
build.gradle
de nivel de proyecto, asegúrate de incluir el repositorio Maven de Google en las seccionesbuildscript
yallprojects
. - Agrega las dependencias para las bibliotecas de Android del ML Kit al archivo Gradle a nivel de la app de tu módulo, que suele ser
app/build.gradle
:
dependencies {
// ...
implementation 'com.google.mlkit:digital-ink-recognition:19.0.0'
}
Ya puedes comenzar a reconocer texto en objetos Ink
.
Compila un objeto Ink
La principal forma de compilar un objeto Ink
es dibujarlo en una pantalla táctil. En Android, puedes usar un objeto Canvas para este propósito. Los controladores de eventos táctiles deben llamar al método addNewTouchEvent()
que se muestra en el siguiente fragmento de código para almacenar los puntos en los trazos que el usuario dibuja en el objeto Ink
.
Este patrón general se demuestra en el siguiente fragmento de código. Consulta la muestra de inicio rápido del Kit de AA para ver un ejemplo más 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();
Obtén una instancia de DigitalInkRecognizer
Para realizar el reconocimiento, envía la instancia de Ink
a un objeto DigitalInkRecognizer
. El siguiente código muestra cómo crear una instancia de un reconocedor de este tipo a partir de una etiqueta 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());
Procesa un objeto 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));
En el ejemplo de código anterior, se supone que el modelo de reconocimiento ya se descargó, como se describe en la siguiente sección.
Administra las descargas de modelos
Si bien la API de reconocimiento de escritura a mano alzada admite cientos de idiomas, cada uno requiere que se descarguen algunos datos antes de cualquier reconocimiento. Se requieren alrededor de 20 MB de almacenamiento por idioma. Esto se controla con el objeto RemoteModelManager
.
Descarga un modelo nuevo
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));
Cómo verificar si ya se descargó un modelo
Kotlin
var model: DigitalInkRecognitionModel = ... remoteModelManager.isModelDownloaded(model)
Java
DigitalInkRecognitionModel model = ...; remoteModelManager.isModelDownloaded(model);
Cómo borrar un modelo descargado
Si quitas un modelo del almacenamiento del dispositivo, se libera espacio.
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));
Sugerencias para mejorar la precisión del reconocimiento de texto
La precisión del reconocimiento de texto puede variar según los idiomas. La precisión también depende del estilo de escritura. Si bien el reconocimiento de escritura a mano alzada se entrena para controlar muchos tipos de estilos de escritura, los resultados pueden variar de un usuario a otro.
Estas son algunas formas de mejorar la precisión de un reconocedor de texto. Ten en cuenta que estas técnicas no se aplican a los clasificadores de dibujo para emojis, dibujo automático y formas.
Área de escritura
Muchas aplicaciones tienen un área de escritura bien definida para la entrada del usuario. El significado de un símbolo se determina parcialmente por su tamaño en relación con el tamaño del área de escritura que lo contiene. Por ejemplo, la diferencia entre una letra "o" o "c" en mayúscula o minúscula, y una coma en comparación con una barra diagonal.
Indicarle al reconocedor el ancho y el alto del área de escritura puede mejorar la precisión. Sin embargo, el reconocedor supone que el área de escritura solo contiene una sola línea de texto. Si el área de escritura física es lo suficientemente grande como para permitir que el usuario escriba dos o más líneas, es posible que obtengas mejores resultados si pasas un WritingArea con una altura que sea tu mejor estimación de la altura de una sola línea de texto. El objeto WritingArea que pasas al reconocedor no tiene que corresponder exactamente con el área de escritura física en la pantalla. Cambiar la altura de WritingArea de esta manera funciona mejor en algunos idiomas que en otros.
Cuando especifiques el área de escritura, indica su ancho y alto en las mismas unidades que las coordenadas del trazo. Los argumentos de coordenadas X e Y no tienen requisitos de unidades. La API normaliza todas las unidades, por lo que lo único que importa es el tamaño y la posición relativos de los trazos. Puedes pasar coordenadas en la escala que tenga sentido para tu sistema.
Contexto previo
El precontexto es el texto que precede inmediatamente a los trazos en el Ink
que intentas reconocer. Puedes ayudar al reconocedor diciéndole sobre el contexto previo.
Por ejemplo, las letras cursivas “n” y “u” a menudo se confunden entre sí. Si el usuario ya ingresó la palabra parcial "arg", podría continuar con trazos que se pueden reconocer como "umento" o "mento". Especificar el "arg" de precontexto resuelve la ambigüedad, ya que es más probable que la palabra sea "argument" que "argnment".
El precontexto también puede ayudar al reconocedor a identificar las pausas entre palabras, es decir, los espacios entre ellas. Puedes escribir un espacio, pero no puedes dibujarlo. Entonces, ¿cómo puede un reconocedor determinar cuándo termina una palabra y comienza la siguiente? Si el usuario ya escribió "hola" y continúa con la palabra escrita "mundo", sin contexto previo, el reconocedor devuelve la cadena "mundo". Sin embargo, si especificas el precontexto "hola", el modelo devolverá la cadena " mundo", con un espacio inicial, ya que "hola mundo" tiene más sentido que "holamundo".
Debes proporcionar la cadena de precontexto más larga posible, de hasta 20 caracteres, incluidos los espacios. Si la cadena es más larga, el reconocedor solo usa los últimos 20 caracteres.
En la siguiente muestra de código, se muestra cómo definir un área de escritura y usar un objeto RecognitionContext
para especificar el contexto previo.
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);
Orden de trazo
La precisión del reconocimiento depende del orden de los trazos. Los reconocedores esperan que los trazos se produzcan en el orden en que las personas escribirían de forma natural; por ejemplo, de izquierda a derecha para el inglés. Cualquier caso que se desvíe de este patrón, como escribir una oración en inglés que comience con la última palabra, arroja resultados menos precisos.
Otro ejemplo es cuando se quita una palabra del medio de un Ink
y se reemplaza por otra. Es probable que la revisión esté en medio de una oración, pero los trazos de la revisión se encuentran al final de la secuencia de trazos.
En este caso, te recomendamos que envíes la palabra recién escrita por separado a la API y que combines el resultado con los reconocimientos anteriores usando tu propia lógica.
Cómo abordar formas ambiguas
Hay casos en los que el significado de la forma proporcionada al reconocedor es ambiguo. Por ejemplo, un rectángulo con bordes muy redondeados podría considerarse un rectángulo o una elipse.
Estos casos poco claros se pueden controlar con las puntuaciones de reconocimiento cuando estén disponibles. Solo los clasificadores de formas proporcionan puntuaciones. Si el modelo tiene mucha confianza, la puntuación del primer resultado será mucho mejor que la del segundo. Si hay incertidumbre, las puntuaciones de los dos primeros resultados serán similares. Además, ten en cuenta que los clasificadores de formas interpretan todo el Ink
como una sola forma. Por ejemplo, si el Ink
contiene un rectángulo y una elipse uno al lado del otro, es posible que el reconocedor devuelva uno u otro (o algo completamente diferente) como resultado, ya que un solo candidato de reconocimiento no puede representar dos formas.