Благодаря функции распознавания цифровых чернил ML Kit вы можете распознавать рукописный текст на цифровой поверхности на сотнях языков, а также классифицировать эскизы.
Попробуйте!
- Поэкспериментируйте с примером приложения , чтобы увидеть, как используется этот API.
Прежде чем начать
- В файле
build.gradleна уровне проекта обязательно укажите репозиторий Maven от Google в разделахbuildscriptиallprojects. - Добавьте зависимости для библиотек ML Kit Android в файл Gradle на уровне приложения вашего модуля, который обычно находится по
app/build.gradle:
dependencies {
// ...
implementation 'com.google.mlkit:digital-ink-recognition:19.0.0'
}
Теперь вы готовы начать распознавать текст в объектах Ink .
Создать объект Ink
Основной способ создания объекта Ink — это рисование его на сенсорном экране. На Android для этой цели можно использовать Canvas . В обработчиках событий касания следует вызывать метод addNewTouchEvent() показанный в следующем фрагменте кода, для сохранения точек в штрихах, которые пользователь рисует в объекте Ink .
Этот общий принцип демонстрируется в следующем фрагменте кода. Более полный пример можно найти в кратком руководстве по ML Kit .
Котлин
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();
Получите экземпляр DigitalInkRecognizer
Для выполнения распознавания отправьте экземпляр Ink объекту DigitalInkRecognizer . Приведенный ниже код показывает, как создать экземпляр такого распознавателя из метки BCP-47 .
Котлин
// 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());
Обработка объекта, Ink .
Котлин
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));
Приведённый выше пример кода предполагает, что модель распознавания уже загружена, как описано в следующем разделе.
Управление загрузкой моделей
Хотя API распознавания цифровых чернил поддерживает сотни языков, для распознавания каждого языка требуется предварительная загрузка некоторых данных. На каждый язык требуется около 20 МБ памяти. Эта задача решается объектом RemoteModelManager .
Скачать новую модель
Котлин
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));
Проверьте, была ли модель уже загружена.
Котлин
var model: DigitalInkRecognitionModel = ... remoteModelManager.isModelDownloaded(model)
Java
DigitalInkRecognitionModel model = ...; remoteModelManager.isModelDownloaded(model);
Удалить загруженную модель
Удаление модели из памяти устройства освобождает место.
Котлин
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));
Советы по повышению точности распознавания текста
Точность распознавания текста может различаться в зависимости от языка. Точность также зависит от стиля письма. Хотя система распознавания цифровых чернил обучена обрабатывать множество различных стилей письма, результаты могут отличаться от пользователя к пользователю.
Вот несколько способов повысить точность распознавания текста. Обратите внимание, что эти методы не применимы к классификаторам рисунков для эмодзи, авторисовки и фигур.
зона для письма
Во многих приложениях есть четко определенная область для ввода данных пользователем. Значение символа частично определяется его размером относительно размера области ввода, в которой он находится. Например, разница между строчной и заглавной буквой «о» или «с», а также между запятой и косой чертой.
Указание распознавателю ширины и высоты области для письма может повысить точность. Однако распознаватель предполагает, что область для письма содержит только одну строку текста. Если физическая область для письма достаточно велика, чтобы пользователь мог написать две или более строк, вы можете получить лучшие результаты, передав объект WritingArea с высотой, которая является вашей наилучшей оценкой высоты одной строки текста. Объект WritingArea, передаваемый распознавателю, не обязательно должен точно соответствовать физической области для письма на экране. Изменение высоты WritingArea таким образом работает лучше в одних языках, чем в других.
При указании области для письма задавайте её ширину и высоту в тех же единицах, что и координаты штрихов. Для аргументов координат x,y единицы измерения не требуются — API нормализует все единицы, поэтому важны только относительные размеры и положение штрихов. Вы можете передавать координаты в любом масштабе, который подходит для вашей системы.
Предконтекст
Предконтекст — это текст, непосредственно предшествующий штрихам Ink , которые вы пытаетесь распознать. Вы можете помочь распознавателю, сообщив ему о предконтексте.
Например, рукописные буквы «n» и «u» часто путают друг с другом. Если пользователь уже ввел часть слова «arg», он может продолжить ввод, используя символы, которые можно распознать как «ument» или «nment». Указание контекста «arg» устраняет неоднозначность, поскольку слово «argument» более вероятно, чем «argnment».
Предварительный контекст также может помочь распознавателю определить разрывы слов, пробелы между словами. Вы можете ввести пробел, но не можете его нарисовать, так как же распознаватель может определить, когда одно слово заканчивается и начинается следующее? Если пользователь уже написал «hello» и продолжает писать слово «world», без предварительного контекста распознаватель вернет строку «world». Однако, если вы укажете предварительный контекст «hello», модель вернет строку «world» с пробелом в начале, поскольку «hello world» звучит логичнее, чем «helloword».
Необходимо указать максимально длинную строку преконтекста, не более 20 символов, включая пробелы. Если строка длиннее, распознаватель использует только последние 20 символов.
Приведённый ниже пример кода показывает, как определить область для письма и использовать объект RecognitionContext для указания предварительного контекста.
Котлин
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);
Порядок выполнения игл
Точность распознавания зависит от порядка написания штрихов. Системы распознавания ожидают, что штрихи будут располагаться в том порядке, в котором люди пишут в естественном порядке; например, слева направо для английского языка. Любой случай, отклоняющийся от этого шаблона, например, написание английского предложения, начинающегося с последнего слова, дает менее точные результаты.
Другой пример: когда слово в середине Ink удаляется и заменяется другим словом. Исправление, вероятно, происходит в середине предложения, но штрихи для исправления находятся в конце последовательности штрихов. В этом случае мы рекомендуем отправлять новое написанное слово отдельно в API и объединять результат с предыдущими распознаваниями, используя собственную логику.
Работа с неоднозначными формами
В некоторых случаях значение формы, предоставленной распознавателю, может быть неоднозначным. Например, прямоугольник с очень закругленными краями может восприниматься либо как прямоугольник, либо как эллипс.
Эти неясные случаи можно обработать, используя оценки распознавания, если они доступны. Оценки предоставляют только классификаторы форм. Если модель очень уверена, оценка лучшего результата будет намного выше, чем у второго лучшего. Если есть неопределенность, оценки двух лучших результатов будут близки. Также следует помнить, что классификаторы форм интерпретируют все Ink как единую фигуру. Например, если Ink содержат прямоугольник и эллипс, расположенные рядом, распознаватель может вернуть один из них (или что-то совершенно другое) в качестве результата, поскольку один кандидат на распознавание не может представлять две фигуры.