Благодаря распознаванию цифровых чернил ML Kit вы можете распознавать текст, написанный от руки на цифровой поверхности, на сотнях языков, а также классифицировать эскизы.
Попробуйте это
- Поэкспериментируйте с примером приложения , чтобы увидеть пример использования этого API.
Прежде чем начать
- В файле
build.gradle
на уровне проекта обязательно включите репозиторий Google Maven как в разделыbuildscript
, так и в разделыallprojects
. - Добавьте зависимости для библиотек Android ML Kit в файл Gradle уровня приложения вашего модуля, который обычно имеет
app/build.gradle
:
dependencies {
// ...
implementation 'com.google.mlkit:digital-ink-recognition:18.1.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();
Получите экземпляр DigitalInkRecouncer.
Чтобы выполнить распознавание, отправьте экземпляр 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» и запятой и косой чертой.
Сообщив распознавателю ширину и высоту области письма, можно повысить точность. Однако распознаватель предполагает, что область письма содержит только одну строку текста. Если физическая область письма достаточно велика, чтобы позволить пользователю написать две или более строк, вы можете получить лучшие результаты, передав WriteArea с высотой, которая является наилучшей оценкой высоты одной строки текста. Объект WriteArea, который вы передаете распознавателю, не обязательно точно соответствует физической области письма на экране. Изменение высоты WriteArea таким образом работает лучше на некоторых языках, чем на других.
При указании области письма укажите ее ширину и высоту в тех же единицах, что и координаты штриха. Аргументы координат x,y не имеют требований к единицам измерения — API нормализует все единицы измерения, поэтому единственное, что имеет значение, — это относительный размер и положение штрихов. Вы можете передавать координаты в любом масштабе, подходящем для вашей системы.
Предварительный контекст
Предварительный контекст — это текст, который непосредственно предшествует штрихам в Ink
, которые вы пытаетесь распознать. Вы можете помочь распознавателю, рассказав ему о предконтексте.
Например, курсивные буквы «н» и «у» часто путают друг с другом. Если пользователь уже ввел часть слова «arg», он может продолжить штрихами, которые можно распознать как «ument» или «nment». Указание предконтекста «arg» устраняет двусмысленность, поскольку слово «аргумент» встречается чаще, чем «аргумент».
Предварительный контекст также может помочь распознавателю идентифицировать разрывы слов, пробелы между словами. Вы можете ввести пробел, но не можете его нарисовать, так как же распознавателю определить, когда заканчивается одно слово и начинается следующее? Если пользователь уже написал «привет» и продолжает писать слово «мир», без предварительного контекста распознаватель возвращает строку «мир». Однако если вы укажете предконтекст «привет», модель вернет строку «мир» с пробелом в начале, поскольку «привет, мир» имеет больше смысла, чем «привет, слово».
Вы должны предоставить максимально длинную строку предварительного контекста, до 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
содержит прямоугольник и эллипс рядом друг с другом, распознаватель может вернуть в результате один или другой (или что-то совершенно другое), поскольку один кандидат на распознавание не может представлять две фигуры.