С помощью распознавания цифровых чернил ML Kit вы можете распознавать текст, написанный от руки на цифровой поверхности, на сотнях языков, а также классифицировать эскизы.
Попробовать
- Попробуйте пример приложения, чтобы увидеть, как используется этот API.
Подготовка
- В файле
build.gradleна уровне проекта убедитесь, что репозиторий Google Maven указан в разделахbuildscriptиallprojects. - Добавьте зависимости для библиотек ML Kit для Android в файл Gradle на уровне приложения, который обычно называется
app/build.gradle:
dependencies {
// ...
implementation 'com.google.mlkit:digital-ink-recognition:19.0.0'
}
Теперь вы можете распознавать текст в объектах Ink.
Как создать объект Ink
Основной способ создания объекта Ink – нарисовать его на сенсорном экране. На устройстве Android для этого можно использовать макет. Обработчики событий касания должны вызывать метод addNewTouchEvent(), показанный в следующем фрагменте кода, чтобы сохранить точки в штрихах, которые пользователь рисует в объекте Ink.
Ниже приведен пример фрагмента кода, в котором используется этот общий принцип. Более полный пример можно найти в кратком руководстве по 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();
Как получить экземпляр DigitalInkRecognizer
Чтобы выполнить распознавание, отправьте экземпляр Ink в объект DigitalInkRecognizer. В приведенном ниже коде показано, как создать экземпляр такого распознавателя из тега 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());
Как обработать объект 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));
В приведенном выше образце кода предполагается, что модель распознавания уже скачана, как описано в следующем разделе.
Как управлять скачиванием моделей
Хотя API распознавания цифровых чернил поддерживает сотни языков, для каждого языка требуется загрузить некоторые данные перед распознаванием. Для каждого языка требуется около 20 МБ свободного места. Это делается с помощью объекта RemoteModelManager.
Скачать новую модель
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));
Проверьте, скачана ли модель
Kotlin
var model: DigitalInkRecognitionModel = ... remoteModelManager.isModelDownloaded(model)
Java
DigitalInkRecognitionModel model = ...; remoteModelManager.isModelDownloaded(model);
Как удалить скачанную модель
Удаление модели из хранилища устройства освобождает место.
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));
Как повысить точность распознавания текста
Точность распознавания текста может различаться в зависимости от языка. Точность также зависит от стиля письма. Хотя функция распознавания рукописного ввода обучена распознавать разные стили письма, результаты могут отличаться в зависимости от пользователя.
Вот несколько способов повысить точность распознавания текста. Обратите внимание, что эти методы не применяются к классификаторам рисунков для эмодзи, Autodraw и фигур.
Область ввода
Во многих приложениях есть четко определенная область для ввода текста. Значение символа частично определяется его размером относительно размера области письма, в которой он находится. Например, разница между строчной и заглавной буквами "о" или "с", а также между запятой и косой чертой.
Если указать ширину и высоту области ввода, распознавание может стать точнее. Однако распознаватель предполагает, что в области ввода текста находится только одна строка. Если физическая область ввода достаточно велика, чтобы пользователь мог написать две или более строк, вы можете получить более точные результаты, передав объект 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, чтобы задать предварительный контекст.
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);
Порядок штрихов
Точность распознавания зависит от порядка штрихов. Распознаватели ожидают, что штрихи будут наноситься в том порядке, в котором люди обычно пишут, например слева направо для английского языка. Любой случай, который отклоняется от этой модели, например, написание английского предложения, начинающегося с последнего слова, дает менее точные результаты.
Другой пример – когда слово в середине Ink удаляется и заменяется другим словом. Исправление, вероятно, находится в середине предложения, но штрихи для исправления находятся в конце последовательности штрихов.
В этом случае мы рекомендуем отправить новое слово в API отдельно и объединить результат с предыдущими распознаваниями, используя собственную логику.
Работа с неоднозначными фигурами
В некоторых случаях распознаватель может неоднозначно интерпретировать форму. Например, прямоугольник с сильно скругленными краями может быть распознан как прямоугольник или как эллипс.
В таких случаях можно использовать оценки распознавания, если они доступны. Оценки предоставляют только классификаторы форм. Если модель уверена в результате, то оценка лучшего варианта будет намного выше, чем оценка второго по качеству варианта. Если результаты неоднозначные, оценки для двух лучших вариантов будут близки. Кроме того, помните, что классификаторы форм интерпретируют весь символ Ink как одну форму. Например, если на изображении Ink рядом друг с другом находятся прямоугольник и эллипс, распознаватель может вернуть в качестве результата один из них (или что-то совершенно другое), поскольку один кандидат на распознавание не может представлять две фигуры.