Перейти к содержанию

ML Kit на Android

ML Kit предоставляет высокоуровневые API машинного обучения для мобильных приложений. Это хороший выбор, когда готовый API уже решает задачу: приложение работает с привычными Android-объектами и не управляет напрямую тензорами, операторами модели и аппаратными ускорителями.

Обработка ML Kit выполняется на устройстве. Это позволяет работать без сети, уменьшает задержку и поддерживает сценарии реального времени с камерой, оставляя чувствительные входные данные локально. При этом способ доставки модели, поддерживаемые устройства, языки и стабильность API зависят от конкретной возможности.

Доступные возможности

В ML Kit есть несколько групп API:

  • компьютерное зрение: распознавание текста и штрихкодов, обнаружение лиц, классификация изображений, обнаружение и отслеживание объектов, определение позы, сегментация и сканирование документов;
  • обработка естественного языка: определение языка, перевод, умные ответы и извлечение сущностей;
  • генеративный ИИ: суммаризация, корректура, переформулирование, описание изображений, распознавание речи и запросы на поддерживаемых устройствах.

Обычные API компьютерного зрения и обработки языка используют специализированные модели. GenAI API ML Kit - отдельное семейство на базе AICore и общей модели Gemini Nano на устройстве. Их поддержка устройств и языков уже, поэтому проверяйте доступность во время выполнения и заранее проектируйте запасной сценарий.

ML Kit или LiteRT?

Выбирайте ML Kit, если:

  • поддерживаемый API соответствует задаче;
  • нужны готовая модель и полный конвейер обработки;
  • команда должна сосредоточиться на поведении функции, а не интеграции модели;
  • достаточно удобных Android-объектов результата.

Выбирайте LiteRT, если:

  • приложение должно запускать собственную .tflite-модель;
  • предобработка и постобработка входят в контракт модели;
  • нужен прямой контроль над тензорами или ускорением;
  • модель, результат или поддерживаемые сценарии ML Kit не соответствуют требованиям.

Некоторые API ML Kit также принимают пользовательские модели. Оцените этот вариант до реализации всего конвейера на более низком уровне.

Зависимость и доставка модели

Артефакт и способы доставки зависят от API. Для некоторых возможностей ML Kit доступны оба варианта:

  • встроенная модель входит в приложение, работает сразу и увеличивает его размер;
  • модель из Google Play services уменьшает размер приложения, но перед первым успешным запросом может потребовать загрузку.

Например, для распознавания текста на латинице используются разные артефакты:

dependencies {
    // Модель включена в приложение
    implementation("com.google.mlkit:text-recognition:16.0.1")

    // Или модель доставляется через Google Play services
    // implementation("com.google.android.gms:play-services-mlkit-text-recognition:19.0.1")
}

Эти версии были актуальны на момент ревью. Проверяйте их в официальном руководстве для выбранного API. Не подключайте оба варианта одного распознавателя.

Если при первом запуске нельзя ждать загрузку, выберите встроенную модель или запросите доставку при установке там, где API это поддерживает. Иначе показывайте состояние подготовки и обрабатывайте ошибку загрузки. Модель из Play services может не вернуть результат, пока загрузка не завершена.

Пример распознавания текста

Создавайте и переиспользуйте распознаватель, а не конструируйте его для каждого кадра:

class TextRecognitionDataSource : Closeable {
    private val recognizer = TextRecognition.getClient(
        TextRecognizerOptions.DEFAULT_OPTIONS
    )

    suspend fun recognize(bitmap: Bitmap): String {
        val image = InputImage.fromBitmap(bitmap, 0)
        return recognizer.process(image).await().text
    }

    override fun close() {
        recognizer.close()
    }
}

Функцию await() предоставляет kotlinx-coroutines-play-services. Отмена ожидающей корутины не обязательно отменяет уже начатую задачу ML Kit. Игнорируйте поздний результат, если владелец или входные данные уже изменились.

Привяжите источник данных к владельцу с явным временем жизни, например к компоненту экрана или области внедрения зависимостей. Закрывайте клиенты с методом close() при уничтожении владельца.

Интеграция с CameraX

При анализе кадров камеры нужно учитывать поворот, обратное давление и время жизни ресурсов:

fun analyze(
    imageProxy: ImageProxy,
    recognizer: TextRecognizer,
    onResult: (String) -> Unit,
    onError: (Throwable) -> Unit,
) {
    val mediaImage = imageProxy.image
    if (mediaImage == null) {
        imageProxy.close()
        return
    }

    val image = InputImage.fromMediaImage(
        mediaImage,
        imageProxy.imageInfo.rotationDegrees,
    )

    recognizer.process(image)
        .addOnSuccessListener { result -> onResult(result.text) }
        .addOnFailureListener(onError)
        .addOnCompleteListener { imageProxy.close() }
}

Основные правила:

  • закрывайте каждый ImageProxy, включая ошибки и ранний выход;
  • передавайте поворот кадра, полученный от CameraX;
  • для большинства сценариев реального времени используйте ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST;
  • не запускайте параллельные вызовы распознавателя, если API и функция на это не рассчитаны;
  • уменьшайте разрешение или обрезайте вход только после оценки влияния на точность.

Состояние и обработка ошибок

Представляйте функцию через явные состояния, а не допускающий null результат:

sealed interface RecognitionState {
    data object Idle : RecognitionState
    data object Preparing : RecognitionState
    data class Ready(val text: String) : RecognitionState
    data class Failed(val cause: Throwable) : RecognitionState
}

Различайте пустой, но корректный результат, отсутствующую или загружаемую модель, неподдерживаемый ввод или устройство, отмену и настоящую ошибку обработки. Не показывайте ошибку для каждого кадра, в котором нет распознаваемого объекта.

Для GenAI API проверка доступности во время выполнения входит в контракт функции. Вывод разрешён только когда приложение находится на переднем плане поверх остальных, AICore применяет квоты на приложение, а разные версии Gemini Nano могут давать разные ответы. Обрабатывайте неподдерживаемые устройства, ошибки BUSY, превышение квоты батареи и запрет фонового использования; оценивайте качество на всех поддерживаемых версиях модели.

Производительность и жизненный цикл

  • Инициализируйте клиенты распознавания один раз и переиспользуйте их.
  • Выполняйте обработку вне кода отрисовки интерфейса и не блокируйте главный поток ожиданием результата.
  • Ограничивайте частоту непрерывного ввода и отбрасывайте устаревшие результаты, если их заменил новый кадр.
  • Не храните исходные изображения в долгоживущем состоянии без необходимости.
  • Измеряйте холодный запуск отдельно от установившейся задержки: инициализация или загрузка модели может определять время первого запроса.
  • Проверяйте работу на менее производительных устройствах и с реалистичным разрешением камеры.

Приватность

Локальный вывод сохраняет входные и выходные данные API на устройстве, но не делает автоматически приватной всю функцию. Приложение всё ещё может отправлять изображения, аналитику, распознанный текст или диагностические отчёты. Зафиксируйте каждый путь данных и не записывайте пользовательский контент в журналы.

Если результат затем отправляется на сервер или в облачную модель, явно обозначьте эту границу в пользовательском сценарии и примените правила приложения для согласия, хранения и безопасности.

Тестирование

Скройте клиент ML Kit за небольшим интерфейсом, чтобы ViewModel и сценарии использования работали с детерминированными подменами:

fun interface TextRecognizerGateway {
    suspend fun recognize(bitmap: Bitmap): String
}

Для настоящего клиента используйте интеграционные тесты с отобранным набором изображений. Включите поворот, размытие, слабое освещение, частично видимый контент, поддерживаемые системы письма, пустой ввод и минимальный полезный размер изображения. Не проверяйте нестабильную полную строку OCR, если достаточно меньшего инварианта. Для вероятностного или генеративного результата оценивайте качество на репрезентативном наборе данных, а не по одному эталонному ответу.

Частые ошибки

  • Распознаватель создаётся заново для каждого кадра.
  • ImageProxy или закрываемый клиент ML Kit не закрывается.
  • Код предполагает, что модель или возможность GenAI уже доступна.
  • Каждый кадр камеры ставится в неограниченную очередь.
  • Интерфейс получает устаревший результат после смены экрана или входных данных.
  • Вероятностный результат воспринимается как гарантированный факт.
  • Изображения или распознанный пользовательский контент попадают в журналы.

См. также