В этой статье будет рассмотрен код реализации распознавания символов Google Translate (вместе с переводом текста) в Jetpack Compose с использованием MLKit DigitalInk и перевода на устройстве.

Вот исходный код моей реализации.



Рисунок на холсте

Давайте настроим @Composable Canvas, где пользователи будут рисовать символы на экране.

Это стандартный вариант использования, когда мы перехватываем события касания на View и переводим их как Path на холст. Расширение его до Jetpack Compose,

  • С помощью Modifier.pointerInteropFilter получаем MotionEvent аналогично полученному в android.view.View OnTouchListener.
  • Для удобства я создал sealed class DrawEvent, который будет сопоставлен с соответствующим MotionEvent.
  • remember объект Path, так как он не будет меняться при рекомпозиции. Установка значения mutableStateOf<DrawEvent?> в обратном вызове вызовет рекомпозицию, когда либо создается новый подпуть, либо изменяется текущий.
  • path.lineTo() тоже подойдет вместо path.quadraticBezierTo(). Я использовал квадратичный подконтур только для того, чтобы сгладить острые углы.
  • Наконец, в конце каждой композиции calldrawPath()

Цифровые чернила MLKit

MLKit от Google предоставляет Digital Ink, готовый к использованию пакет для распознавания символов для более чем 300 языков. Он также обеспечивает распознавание рукописного ввода для таких сервисов, как Gboard, Google Translate.

Основными компонентами, с которыми мы будем иметь дело в первую очередь, являются Ink.Stroke, DigitalInkRecognitionModel и DigitalInkRecognizer.

Давайте инициализируем их, когда мы обсудим их назначение.

  • Ink.Stroke : этот объект данных содержит список координат (Ink.Point) информации о нарисованной фигуре, которая является входом для нашего RecognitionModel. Затем к этому добавляются MotionEvent координаты, полученные в обратном вызове.
  • DigitalInkRecognitionModel : это модель машинного обучения, соответствующая конкретному языку, символы которого должны быть распознаны. Построитель принимает «modelIndentifier», чтобы указать язык распознавания символов. В этом примере мы будем использовать модель японского языка, поэтому мы передаем DigitalInkRecognitionModelIdentifier.JA
  • DigitalInkRecognizer : это принимает сгенерированное Ink.Stroke в качестве входных данных для модели ML и выводит результат символов, предсказанных моделью.

Проверка доступности модели

Прежде чем использовать Digital Ink, для поддержки распознавания символов для конкретного языка необходимо загрузить данные модели машинного обучения для конкретного языка (примерно 20 МБ на каждый язык) и сделать их доступными локально.

Вы можете оформить заказ Digital Ink | Управление загрузкой моделей для более подробной информации. Вот мой код для справки.

Создание Ink.Stroke и распознавание

Имея готовую модель Canvas и MLKit, давайте добавим обратные вызовы для обработки построения Ink.Stroke и запуска Recognizer.recognize() по завершении.

Для удобства я добавил sealed class DrawEvent, сопоставленный с соответствующим MotionEvent.

Внутри обратного вызова onDrawEvent

  • MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE: указывает на то, что пользователь касается холста для рисования. Добавляем полученные координаты события в Ink.Stroke.Builder
  • MotionEvent.ACTION_UP: Это указывает на то, что пользователь перестал касаться холста, тем самым сигнализируя об окончании рисования. Теперь мы можем построить сгенерированный Ink.Stroke и передать его Recognizer.recognize().
    В onSuccess мы возвращаем результаты прогноза (RecognitionResult.candidates) для отображения. В onComplete мы сбрасываем Ink.Stroke.Builder, чтобы начать принимать координаты следующего символа, который нужно отрисовать.

На данный момент приведенный выше фрагмент кода работает нормально. Однако в приведенном ниже примере результаты прогнозов совсем неверны.

Хотя некоторые символы можно написать одним штрихом, такие символы, как i, t, и сложные символы, такие как , даже курсивом, нельзя написать одним штрихом. Каждый MotionEvent.ACTION_UP вызывает Recognizer.recognize(), где мы сбрасываем Ink.Stroke после получения результатов. Таким образом, вместо того, чтобы передавать координаты всех вместе взятых подпутей, мы вызываем .recognize() для каждого отдельного подпути.

Эту проблему можно смягчить, добавив поведение, похожее на debounce, которое будет предотвращать последовательные вызовы .recognize() в течение определенного периода времени после MotionEvent.ACTION_UP.

Добавление устранения дребезга

Чтобы реализовать debounce, мы должны отменить все последовательные вызовы Recognizer.recognize() в течение определенного периода времени (смещение debounce) после MotionEvent.ACTION_UP. Могут быть лучшие способы реализовать это, однако это решение, которое я придумал.

Мы добавляем delay(offset) в сопрограмму перед вызовом Recognizer.recognize() в MotionEvent.ACTION_UP. Затем мы можем отменить запущенную сопрограмму в следующем MotionEvent.ACTION_DOWN.

Если пользователь касается холста в течение указанной задержки, запущенная сопрограмма отменяется, что позволяет избежать Recognizer.recognize(). Это позволит пользователям добавлять несколько штрихов, пока они начинают рисовать следующий штрих (вызывать следующий MotionEvent.ACTION_DOWN в пределах этого конкретного смещения.

Перевод MLKit на устройстве

Реализация перевода текста очень похожа на описанную выше, вместо этого мы будем использовать Перевод на устройстве MLKit. Как следует из названия, модель перевода работает на вашем устройстве без подключения к Интернету, поэтому иногда она может быть немного неточной.

Как и DigitalInkRecognizer в Digital Ink, здесь Translator. Это обрабатывает как загрузку перевода, так и перевод. При инициализации Translator мы должны указать исходный и целевой языки. Остальная часть реализации довольно понятна.

Наконец .close() все

Даже в документации четко указано, что .close() объекты Translator & Recognizer больше не используются. Мы можем добавить наблюдателя жизненного цикла и закрыть эти объекты в LifeCycle.Event.ON_STOP.

Спасибо, что выдержали это довольно длинное чтение! 😅

Эта статья является частью Адвент-календаря Goodpatch на 2021 год🎄 У нас есть серия замечательных статей для вас в этот праздничный сезон. Хотя большинство статей будет на японском языке, если вам интересно ознакомиться с содержанием, вы можете использовать переводчик DeepL. Он дает довольно точные переводы технических статей на японский язык ♨️