Создайте Android-приложение для рукописного классификатора цифр с помощью MediaPipe Tasks.

1. Введение

Что такое МедиаПайп?

MediaPipe Solutions позволяет применять решения машинного обучения (ML) к вашим приложениям. Он предоставляет основу для настройки предварительно созданных конвейеров обработки, которые обеспечивают немедленный, привлекательный и полезный вывод для пользователей. Вы даже можете настроить эти решения с помощью MediaPipe Model Maker для обновления моделей по умолчанию.

Классификация изображений — одна из нескольких задач машинного обучения, которые может предложить MediaPipe Solutions. MediaPipe Tasks доступен для Android, iOS, Python (включая Raspberry Pi!) и в Интернете.

В этой лаборатории кода вы начнете с приложения для Android, которое позволяет рисовать числовые цифры на экране, затем вы добавите функциональность, которая классифицирует эти нарисованные цифры как одно значение от 0 до 9.

Что вы узнаете

  • Как включить задачу классификации изображений в приложение Android с помощью MediaPipe Tasks .

Что вам понадобится

  • Установленная версия Android Studio (эта кодовая лаборатория была написана и протестирована с помощью Android Studio Giraffe).
  • Устройство Android или эмулятор для запуска приложения.
  • Базовые знания Android-разработки (это не «Hello World», но не за горами!).

2. Добавьте задачи MediaPipe в приложение Android.

Загрузите стартовое приложение для Android

Эта лаборатория кода начнется с готового примера, позволяющего рисовать на экране. Вы можете найти это стартовое приложение в официальном репозитории MediaPipe Samples здесь . Клонируйте репозиторий или загрузите zip-файл, нажав «Код» > «Загрузить ZIP».

Импортируйте приложение в Android Studio.

  1. Откройте Android-студию.
  2. На экране «Добро пожаловать в Android Studio» выберите «Открыть» в правом верхнем углу.

a0b5b070b802e4ea.png

  1. Перейдите туда, где вы клонировали или загрузили репозиторий, и откройте каталог codelabs/digitclassifier/android/start .
  2. Убедитесь, что все открылось правильно, нажав на зеленую стрелку запуска ( 7e15a9c9e1620fe7.png ) в правом верхнем углу Android Studio.
  3. Вы должны увидеть открытое приложение с черным экраном, на котором можно рисовать, а также кнопку «Очистить» для сброса этого экрана. Хотя на этом экране можно рисовать, он больше ничего не делает, поэтому сейчас мы начнем это исправлять.

11a0f6fe021fdc92.jpeg

Модель

При первом запуске приложения вы можете заметить, что файл с именем mnist.tflite загружается и сохраняется в каталоге ресурсов вашего приложения. Для простоты мы уже взяли известную модель MNIST, которая классифицирует цифры, и добавили ее в приложение с помощью скрипта download_models.gradle в проекте. Если вы решите обучить свою собственную модель, например, для рукописных букв, вам следует удалить файл download_models.gradle , удалить ссылку на него в файле build.gradle уровня приложения и изменить имя модели позже в код (в частности, в файле DigitClassifierHelper.kt ).

Обновить build.gradle

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

  1. Откройте файл build.gradle , расположенный в модуле вашего приложения , затем прокрутите вниз до блока зависимостей .
  2. Внизу этого блока вы должны увидеть комментарий с надписью // ШАГ 1 Импорт зависимостей .
  3. Замените эту строку следующей реализацией
implementation("com.google.mediapipe:tasks-vision:latest.release")
  1. Нажмите кнопку «Синхронизировать сейчас» , которая появляется на баннере в верхней части Android Studio, чтобы загрузить эту зависимость.

3. Создайте помощник классификатора цифр MediaPipe Tasks.

На следующем шаге вы создадите класс, который будет выполнять тяжелую работу по вашей классификации машинного обучения. Откройте DigitClassifierHelper.kt и приступим!

  1. Найдите комментарий вверху класса, в котором говорится: // ШАГ 2. Создание прослушивателя.
  2. Замените эту строку следующим кодом. Это создаст прослушиватель, который будет использоваться для передачи результатов из класса DigitClassifierHelper обратно туда, где прослушиваются эти результаты (в данном случае это будет ваш класс DigitCanvasFragment , но мы скоро доберемся до этого).
// STEP 2 Create listener

interface DigitClassifierListener {
    fun onError(error: String)
    fun onResults(
        results: ImageClassifierResult,
        inferenceTime: Long
    )
}
  1. Вам также необходимо будет принять DigitClassifierListener в качестве необязательного параметра класса:
class DigitClassifierHelper(
    val context: Context,
    val digitClassifierListener: DigitClassifierListener?
) {
  1. Перейдя к строке с надписью // ШАГ 3 определите классификатор , добавьте следующую строку, чтобы создать заполнитель для ImageClassifier, который будет использоваться для этого приложения:

// ШАГ 3 определяем классификатор

private var digitClassifier: ImageClassifier? = null
  1. Добавьте следующую функцию, где вы увидите комментарий // ШАГ 4. Настройка классификатора :
// STEP 4 set up classifier
private fun setupDigitClassifier() {

    val baseOptionsBuilder = BaseOptions.builder()
        .setModelAssetPath("mnist.tflite")

    // Describe additional options
    val optionsBuilder = ImageClassifierOptions.builder()
        .setRunningMode(RunningMode.IMAGE)
        .setBaseOptions(baseOptionsBuilder.build())

    try {
        digitClassifier =
            ImageClassifier.createFromOptions(
                context,
                optionsBuilder.build()
            )
    } catch (e: IllegalStateException) {
        digitClassifierListener?.onError(
            "Image classifier failed to initialize. See error logs for " +
                    "details"
        )
        Log.e(TAG, "MediaPipe failed to load model with error: " + e.message)
    }
}

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

val baseOptionsBuilder = BaseOptions.builder()
    .setModelAssetPath("mnist.tflite")

// Describe additional options
val optionsBuilder = ImageClassifierOptions.builder()
    .setRunningMode(RunningMode.IMAGE)
    .setBaseOptions(baseOptionsBuilder.build())

Этот блок будет определять параметры, используемые ImageClassifier. Сюда входит модель, хранящаяся в вашем приложении ( mnist.tflite ) в разделе BaseOptions, и RunningMode в разделе ImageClassifierOptions, которым в данном случае является IMAGE, но VIDEO и LIVE_STREAM являются дополнительными доступными параметрами. Другими доступными параметрами являются MaxResults, который ограничивает модель возвратом максимального количества результатов, и ScoreThreshold, который устанавливает минимальную достоверность, которую модель должна иметь в результате перед его возвратом.

try {
    digitClassifier =
        ImageClassifier.createFromOptions(
            context,
            optionsBuilder.build()
        )
} catch (e: IllegalStateException) {
    digitClassifierListener?.onError(
        "Image classifier failed to initialize. See error logs for " +
                "details"
    )
    Log.e(TAG, "MediaPipe failed to load model with error: " + e.message)
}

После создания параметров конфигурации вы можете создать новый ImageClassifier, передав контекст и параметры. Если что-то пойдет не так в процессе инициализации, через DigitClassifierListener будет возвращена ошибка.

  1. Поскольку нам нужно инициализировать ImageClassifier перед его использованием, вы можете добавить блок инициализации для вызова setupDigitClassifier().
init {
    setupDigitClassifier()
}
  1. Наконец, прокрутите вниз до комментария с надписью // ШАГ 5 создайте функцию классификации и добавьте следующий код. Эта функция принимает Bitmap , который в данном случае является нарисованной цифрой, преобразует его в объект изображения MediaPipe (MPImage), а затем классифицирует это изображение с помощью ImageClassifier, а также записывает, сколько времени занимает вывод, прежде чем возвращать эти результаты. прослушиватель DigitClassifierListener.
// STEP 5 create classify function
fun classify(image: Bitmap) {
    if (digitClassifier == null) {
        setupDigitClassifier()
    }

    // Convert the input Bitmap object to an MPImage object to run inference.
    // Rotating shouldn't be necessary because the text is being extracted from
    // a view that should always be correctly positioned.
    val mpImage = BitmapImageBuilder(image).build()

    // Inference time is the difference between the system time at the start and finish of the
    // process
    val startTime = SystemClock.uptimeMillis()

    // Run image classification using MediaPipe Image Classifier API
    digitClassifier?.classify(mpImage)?.also { classificationResults ->
        val inferenceTimeMs = SystemClock.uptimeMillis() - startTime
        digitClassifierListener?.onResults(classificationResults, inferenceTimeMs)
    }
}

И это все, что касается вспомогательного файла! В следующем разделе вы выполните последние шаги, чтобы начать классифицировать выпавшие номера.

4. Запустите вывод с помощью задач MediaPipe.

Вы можете начать этот раздел, открыв класс DigitCanvasFragment в Android Studio, где и будет происходить вся работа.

  1. В самом низу этого файла вы должны увидеть комментарий: // ШАГ 6. Настройка прослушивателя . Здесь вы добавите функции onResults() и onError(), связанные с прослушивателем.
// STEP 6 Set up listener
override fun onError(error: String) {
    activity?.runOnUiThread {
        Toast.makeText(requireActivity(), error, Toast.LENGTH_SHORT).show()
        fragmentDigitCanvasBinding.tvResults.text = ""
    }
}

override fun onResults(
    results: ImageClassifierResult,
    inferenceTime: Long
) {
    activity?.runOnUiThread {
        fragmentDigitCanvasBinding.tvResults.text = results
            .classificationResult()
            .classifications().get(0)
            .categories().get(0)
            .categoryName()

        fragmentDigitCanvasBinding.tvInferenceTime.text = requireActivity()
            .getString(R.string.inference_time, inferenceTime.toString())
    }
}

onResults() особенно важен, поскольку он отображает результаты, полученные от ImageClassifier. Поскольку этот обратный вызов запускается из фонового потока, вам также потребуется запускать обновления пользовательского интерфейса в потоке пользовательского интерфейса Android.

  1. Поскольку на предыдущем шаге вы добавляете новые функции из интерфейса, вам также необходимо добавить объявление реализации в верхней части класса.
class DigitCanvasFragment : Fragment(), DigitClassifierHelper.DigitClassifierListener
  1. В верхней части класса вы должны увидеть комментарий: // ШАГ 7a Инициализация классификатора . Здесь вы поместите объявление DigitClassifierHelper.
// STEP 7a Initialize classifier.
private lateinit var digitClassifierHelper: DigitClassifierHelper
  1. Переходя к // STEP 7b Инициализация классификатора, вы можете инициализировать digitClassifierHelper внутри функции onViewCreated().
// STEP 7b Initialize classifier
// Initialize the digit classifier helper, which does all of the
// ML work. This uses the default values for the classifier.
digitClassifierHelper = DigitClassifierHelper(
    context = requireContext(), digitClassifierListener = this
)
  1. Для последних шагов найдите комментарий // STEP 8a *: classify* и добавьте следующий код для вызова новой функции, которую вы добавите через мгновение. Этот блок кода активирует классификацию, когда вы уберете палец с области рисования в приложении.
// STEP 8a: classify
classifyDrawing()
  1. Наконец, найдите комментарий // STEP 8b classify, чтобы добавить новую функцию classifyDrawing(). Это позволит извлечь растровое изображение из холста, а затем передать его в DigitClassifierHelper для выполнения классификации и получения результатов в интерфейсной функции onResults().
// STEP 8b classify
private fun classifyDrawing() {
    val bitmap = fragmentDigitCanvasBinding.digitCanvas.getBitmap()
    digitClassifierHelper.classify(bitmap)
}

5. Разверните и протестируйте приложение.

После всего этого у вас должно быть работающее приложение, которое сможет классифицировать нарисованные цифры на экране! Разверните приложение на эмуляторе Android или на физическом устройстве Android, чтобы протестировать его.

  1. Нажмите «Выполнить» ( 7e15a9c9e1620fe7.png ) на панели инструментов Android Studio, чтобы запустить приложение.
  2. Нарисуйте любую цифру на планшете для рисования и посмотрите, сможет ли приложение ее распознать. Он должен отображать цифру, которая, по мнению модели, была нарисована, а также время, которое потребовалось для предсказания этой цифры.

7f37187f8f919638.gif

6. Поздравляем!

Вы сделали это! В этой лабораторной работе вы узнали, как добавить классификацию изображений в приложение Android и, в частности, как классифицировать нарисованные от руки цифры с помощью модели MNIST.

Следующие шаги

  • Теперь, когда вы умеете классифицировать цифры, вы можете научить свою собственную модель классифицировать нарисованные буквы, или классифицировать животных, или бесконечное количество других предметов. Вы можете найти документацию по обучению новой модели классификации изображений с помощью MediaPipe Model Maker на страницеdevelopers.google.com/ mediapipe.
  • Узнайте о других задачах MediaPipe , доступных для Android, включая обнаружение ориентиров лица, распознавание жестов и классификацию звука.

Мы с нетерпением ждем всех крутых вещей, которые вы сделаете!