Com o reconhecimento de tinta digital do Kit de ML, é possível reconhecer texto escrito à mão em uma superfície digital em centenas de idiomas e classificar esboços.
Testar
- Teste o app de exemplo para ver um exemplo de uso da API.
Antes de começar
- No arquivo
build.gradle
no nível do projeto, inclua o repositório Maven do Google nas seçõesbuildscript
eallprojects
. - Adicione as dependências das bibliotecas do Android do Kit de ML ao arquivo Gradle no nível do app do módulo, que geralmente é
app/build.gradle
:
dependencies {
// ...
implementation 'com.google.mlkit:digital-ink-recognition:18.1.0'
}
Agora está tudo pronto para você reconhecer texto em objetos Ink
.
Criar um objeto Ink
A principal maneira de criar um objeto Ink
é desenhá-lo em uma tela sensível ao toque. No
Android, você pode usar um
Canvas para
essa finalidade. Os manipuladores de eventos de toque precisam chamar o método addNewTouchEvent()
mostrado no snippet de código a seguir para armazenar os pontos nos traços que o usuário desenha no objeto Ink
.
Esse padrão geral é demonstrado no snippet de código a seguir. Consulte a amostra do guia de início rápido do Kit de ML para um exemplo mais 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();
Acessar uma instância do DigitalInk Reconhecedor
Para realizar o reconhecimento, envie a instância Ink
para um
objeto DigitalInkRecognizer
. O código abaixo mostra como instanciar esse reconhecedor de uma 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());
Processar um 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));
O exemplo de código acima pressupõe que o modelo de reconhecimento já foi baixado, conforme descrito na próxima seção.
Como gerenciar downloads de modelos
Embora a API de reconhecimento de tinta digital ofereça suporte a centenas de idiomas, é necessário fazer o download de alguns dados antes de qualquer reconhecimento. São necessários cerca de
20 MB de armazenamento por idioma. Isso é processado pelo
objeto RemoteModelManager
.
Fazer o download de um novo modelo
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));
Verificar se um modelo já foi baixado
Kotlin
var model: DigitalInkRecognitionModel = ... remoteModelManager.isModelDownloaded(model)
Java
DigitalInkRecognitionModel model = ...; remoteModelManager.isModelDownloaded(model);
Excluir um modelo baixado
Remover um modelo do armazenamento do dispositivo libera espaço.
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));
Dicas para melhorar a precisão do reconhecimento de texto
A precisão do reconhecimento de texto pode variar de acordo com o idioma. A precisão também depende do estilo de escrita. Embora o reconhecimento de tinta digital seja treinado para lidar com muitos tipos de estilos de escrita, os resultados podem variar de usuário para usuário.
Aqui estão algumas maneiras de melhorar a precisão de um reconhecedor de texto. Essas técnicas não se aplicam aos classificadores de desenho de emojis, desenho automático e formas.
Área de escrita
Muitos aplicativos têm uma área de gravação bem definida para entradas do usuário. O significado de um símbolo é parcialmente determinado pelo tamanho dele em relação ao tamanho da área de escrita que o contém. Por exemplo, a diferença entre uma letra minúscula ou "o" ou "c" e uma vírgula ou uma barra.
Dizer ao reconhecedor a largura e a altura da área de escrita pode melhorar a precisão. No entanto, o reconhecedor supõe que a área de escrita contém apenas uma linha de texto. Se a área de gravação física for grande o suficiente para permitir que o usuário escreva duas ou mais linhas, você poderá conseguir melhores resultados transmitindo uma área de escrita com uma altura que seja a melhor estimativa da altura de uma única linha de texto. O objeto WritingArea que você transmite ao reconhecedor não precisa corresponder exatamente à área de gravação física na tela. Alterar a altura da WritingArea dessa maneira funciona melhor em alguns idiomas do que em outros.
Ao especificar a área de escrita, especifique a largura e a altura nas mesmas unidades que as coordenadas do traço. Os argumentos de coordenadas x,y não têm requisito de unidade. A API normaliza todas as unidades, então o que importa é o tamanho relativo e a posição dos traços. Você pode passar as coordenadas em qualquer escala que faça sentido para seu sistema.
Pré-contexto
Pré-contexto é o texto que precede imediatamente os traços no Ink
que você
está tentando reconhecer. Você pode ajudar o reconhecedor contando sobre o pré-contexto.
Por exemplo, as letras cursivas "n" e "u" são frequentemente confundidas uma com a outra. Se o usuário já tiver inserido a palavra parcial "arg", ele poderá continuar com traços que possam ser reconhecidos como "ument" ou "nment". Especificar o "arg" de pré-contexto resolve a ambiguidade, já que a palavra "argumento" é mais provável do que "argnment".
O pré-contexto também pode ajudar o reconhecedor a identificar quebras de palavras, os espaços entre palavras. Você pode digitar um caractere de espaço, mas não pode desenhar um, então como um reconhecedor pode determinar quando uma palavra termina e a próxima começa? Se o usuário já tiver escrito "hello" e continuar com a palavra escrita "world", sem pré-contexto, o reconhecedor retornará a string "world". No entanto, se você especificar o pré-contexto "hello", o modelo retornará a string "world", com um espaço inicial, já que "hello world" faz mais sentido do que "helloword".
Forneça a string de pré-contexto mais longa possível, com até 20 caracteres, incluindo espaços. Se a string for maior, o reconhecedor usará apenas os últimos 20 caracteres.
O exemplo de código abaixo mostra como definir uma área de escrita e usar um objeto
RecognitionContext
para especificar o pré-contexto.
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);
Ordenação dos traços
A precisão do reconhecimento depende da ordem dos traços. Os reconhecedores esperam que os traços ocorram na ordem em que as pessoas escreveriam naturalmente, por exemplo, da esquerda para a direita no inglês. Qualquer caso que se desvie desse padrão, como escrever uma frase em inglês começando com a última palavra, oferece resultados menos precisos.
Outro exemplo é quando uma palavra no meio de uma Ink
é removida e substituída por
outra palavra. A revisão provavelmente está no meio de uma frase, mas os traços da revisão estão no final da sequência do traço.
Nesse caso, recomendamos enviar a palavra recém-escrita separadamente para a API e mesclar o
resultado com os reconhecimentos anteriores usando sua própria lógica.
Como lidar com formas ambíguas
Há casos em que o significado da forma fornecida ao reconhecedor é ambíguo. Por exemplo, um retângulo com bordas muito arredondadas pode ser visto como um retângulo ou uma elipse.
Esses casos pouco claros podem ser tratados com o uso de pontuações de reconhecimento quando disponíveis. Somente os classificadores de formas fornecem pontuações. Se o modelo estiver muito confiante, a pontuação do primeiro resultado será
muito melhor do que o segundo melhor. Se houver incerteza, as pontuações dos dois primeiros resultados serão próximas. Além disso, lembre-se de que os classificadores de formas interpretam a Ink
inteira como uma
única forma. Por exemplo, se a Ink
contiver um retângulo e uma elipse lado a lado, o reconhecedor poderá retornar um ou outro (ou algo completamente diferente) como
resultado, já que um único candidato de reconhecimento não pode representar duas formas.