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, além de classificar esboços.
Faça um teste
- Teste o app de exemplo para um exemplo de uso dessa 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 você já pode começar a reconhecer texto em objetos Ink
.
Criar um objeto Ink
A principal maneira de criar um objeto Ink
é desenhá-lo em uma tela touchscreen. Ativado
no Android, é possível usar
Canvas para
para esse propósito. Seu
manipuladores de evento de toque
precisa chamar addNewTouchEvent()
mostrou o seguinte snippet de código para armazenar os pontos nos traços que
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 conferir 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();
Criar uma instância de DigitalInkRecognizer
Para realizar o reconhecimento, envie a instância Ink
para um
objeto DigitalInkRecognizer
. O código abaixo mostra como instanciar esse
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, cada
idioma exige que alguns dados sejam transferidos antes de qualquer reconhecimento. Por volta de
São necessários 20 MB de armazenamento por idioma. Isso é gerenciado pela
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 transferido por download
Kotlin
var model: DigitalInkRecognitionModel = ... remoteModelManager.isModelDownloaded(model)
Java
DigitalInkRecognitionModel model = ...; remoteModelManager.isModelDownloaded(model);
Excluir um modelo salvo
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.
Confira algumas maneiras de melhorar a precisão de um reconhecedor de texto. Essas técnicas não se aplicam aos classificadores de desenho para emojis, AutoDraw 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 é determinado parcialmente pelo tamanho dele em relação ao tamanho da área de escrita que o contém. Por exemplo, a diferença entre uma letra maiúscula ou minúscula "o" ou "c" e uma vírgula em comparação com um barra inclinada.
Informar ao reconhecedor a largura e a altura da área de escrita pode melhorar a precisão. No entanto, o reconhecedor presume que a área de escrita contém apenas uma linha de texto. Se o ponto físico área de escrita for grande o suficiente para permitir que o usuário escreva duas ou mais linhas, talvez seja melhor resultados passando um WritingArea com uma altura que seja a melhor estimativa da altura de um uma única linha de texto. O objeto WritingArea transmitido ao reconhecedor não precisa corresponder exatamente à área de escrita física na tela. Como alterar a altura da área de escrita dessa forma funciona melhor em alguns idiomas do que em outros.
Ao especificar a área de escrita, especifique a largura e a altura nas mesmas unidades das coordenadas de traço. Os argumentos de coordenadas x,y não têm requisito de unidade. A API normaliza todos o que importa é o tamanho relativo e a posição dos traços. Você é livre para e passar coordenadas em qualquer escala que faça sentido para o seu sistema.
Pré-contexto
Pré-contexto é o texto que precede imediatamente os traços no Ink
que você
estão tentando reconhecer. Você pode ajudar o reconhecedor contando sobre o pré-contexto.
Por exemplo, as letras cursivas "n" e "u" muitas vezes são confundidos uns com os outros. Se o usuário tiver já inseriram a palavra parcial "arg", eles podem continuar com traços que podem ser reconhecidos como “umentar” ou "nment". Como especificar o "argumento" de pré-contexto resolve a ambiguidade, já que a palavra "argument" é mais provável que "argnamento".
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 o pré-contexto, o reconhecedor vai retornar a string "world". No entanto, se você especificar o pré-contexto "hello", o modelo vai 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 gravação 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 escrevem naturalmente. Por exemplo, da esquerda para a direita no inglês. Qualquer caso que se desvie desse padrão, como escrever uma frase começando com a última palavra, fornece 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 de traços.
Nesse caso, recomendamos enviar a palavra recém-gravada 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 podem ser resolvidos usando as pontuações de reconhecimento quando elas estiverem disponíveis. Somente
classificadores de forma fornecem pontuações. Se o modelo tiver muita confiança, a pontuação do resultado principal será
muito melhor do que a segunda melhor. Se houver incerteza, as pontuações dos dois primeiros resultados serão
estar perto. Além disso, lembre-se de que os classificadores de formas interpretam toda a Ink
como uma
forma única. Por exemplo, se o Ink
contiver um retângulo e uma elipse ao lado um do
outro, 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.