Благодаря функции распознавания цифровых чернил 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()
Ява
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())
Ява
// 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") }
Ява
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") }
Ява
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)
Ява
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));
Советы по повышению точности распознавания текста
Точность распознавания текста может различаться в зависимости от языка. Точность также зависит от стиля письма. Хотя Digital Ink Recognition обучен обрабатывать множество стилей письма, результаты могут различаться от пользователя к пользователю.
Вот несколько способов улучшить точность распознавателя текста. Обратите внимание, что эти методы не применяются к классификаторам рисунков для эмодзи, авторисунков и фигур.
Письменная зона
Во многих приложениях есть четко определенная область ввода для пользователя. Значение символа частично определяется его размером относительно размера области ввода, которая его содержит. Например, разница между строчной или заглавной буквой "o" или "c", а также запятой и косой чертой.
Сообщив распознавателю ширину и высоту области письма, можно повысить точность. Однако распознаватель предполагает, что область письма содержит только одну строку текста. Если физическая область письма достаточно велика, чтобы позволить пользователю написать две или более строк, вы можете получить лучшие результаты, передав 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)
Ява
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
содержит прямоугольник и эллипс рядом друг с другом, распознаватель может вернуть один или другой (или что-то совершенно другое) в качестве результата, поскольку один кандидат на распознавание не может представлять две формы.