Благодаря функции распознавания цифровых чернил 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));
Советы по повышению точности распознавания текста
Точность распознавания текста может различаться в зависимости от языка. Точность также зависит от стиля письма. Хотя система распознавания рукописного текста обучена обрабатывать множество стилей письма, результаты могут различаться у разных пользователей.
Вот несколько способов повысить точность распознавания текста. Обратите внимание, что эти методы не применимы к классификаторам рисунков эмодзи, авторисунков и фигур.
Зона для письма
Во многих приложениях есть чётко определённая область ввода для пользователя. Значение символа частично определяется его размером относительно размера области ввода, в которой он находится. Например, разница между строчной и прописной буквой «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
содержит прямоугольник и эллипс, расположенные рядом друг с другом, распознаватель может вернуть один из них (или что-то совершенно другое) в качестве результата, поскольку один кандидат на распознавание не может представлять две фигуры.