Используйте делегат GPU для машинного обучения на периферии

TensorFlow Lite - облегченная версия TensorFlow Mobile. Он предназначен для того, чтобы раскрыть возможности машинного обучения на ваших смартфонах, обеспечивая при этом не слишком большой размер двоичного файла модели и низкую задержку. Кроме того, он также поддерживает аппаратное ускорение с помощью API нейронных сетей и предназначен для работы в 4 раза быстрее с поддержкой графического процессора.

CameraX - это последняя версия API камеры, выпущенная с библиотекой поддержки Jetpack. Он создан для того, чтобы упростить разработку с помощью камеры, а с помощью автоматизированного лабораторного тестирования Google стремится обеспечить единообразие на всех устройствах Android, которых много. CameraX представляет собой огромное улучшение по сравнению с Camera 2 API с точки зрения простоты использования и простоты.

Цель этой статьи - объединить миры камеры и машинного обучения путем обработки кадров CameraX для классификации изображений с использованием модели TensorFlow Lite. Мы создадим приложение для Android с использованием Kotlin, которое будет использовать возможности графических процессоров ваших смартфонов.

CameraX: краткий обзор

CameraX учитывает жизненный цикл. Таким образом, отпадает необходимость в обработке состояний в методах onResume и onPause.

API основан на сценариях использования. В настоящее время поддерживаются три основных варианта использования:

  • Предварительный просмотр - отображает канал с камеры.
  • Анализ - для обработки изображений для компьютерного зрения или других задач, связанных с машинным обучением.
  • Захват - для сохранения изображений высокого качества.

Кроме того, CameraX предоставляет расширения для легкого доступа к таким функциям, как HDR, портретный и ночной режим на поддерживаемых устройствах.

Конвертер Tensor Flow Lite

Конвертер TensorFlow Lite использует модель TensorFlow и генерирует файл TensorFlow Lite FlatBuffer. Затем модель .tflite можно развернуть на мобильных или встроенных устройствах для локального запуска с помощью интерпретатора Tensor Flow.

В следующем фрагменте кода показан один из таких способов преобразования модели Keras в файл .tflite, совместимый с мобильными устройствами:

from tensorflow import lite
converter = lite.TFLiteConverter.from_keras_model_file( 'model.h5')
tfmodel = converter.convert()
open ("model.tflite" , "wb") .write(tfmodel)

В следующих разделах мы продемонстрируем практическую реализацию CameraX с моделью MobileNet TensorFlow Lite с использованием Kotlin. Вы можете создавать свои собственные обученные модели или выбирать среди размещенных, предварительно обученных.

Реализация

Под капотом

Процесс действительно простой. Мы передаем растровые изображения из варианта использования Analyze в CameraX в интерпретатор TensorFlow, который выполняет вывод изображения с использованием модели MobileNet и классов меток. Вот иллюстрация того, как CameraX и TensorFlow Lite взаимодействуют друг с другом.

Настраивать

Запустите новый проект Android Studio Kotlin и добавьте следующие зависимости в build.gradle файл вашего приложения.

//CameraX
implementation 'androidx.camera:camera-core:1.0.0-alpha02'
implementation 'androidx.camera:camera-camera2:1.0.0-alpha02'
// Task API
implementation "com.google.android.gms:play-services-tasks:17.0.0"

implementation 'org.tensorflow:tensorflow-lite:0.0.0-nightly'
implementation 'org.tensorflow:tensorflow-lite-gpu:0.0.0-nightly'

Ночная сборка TensorFlow Lite обеспечивает экспериментальную поддержку графических процессоров. API задач Google Play Services используется для обработки вызовов асинхронных методов.

Затем добавьте файлы MVP, метки и файл модели .tflite в каталог ресурсов. Кроме того, вам необходимо убедиться, что модель не сжата, установив следующее aaptOptions в build.gradle файле:

android{
aaptOptions {
    noCompress "tflite"
    noCompress "lite"
}

Добавьте необходимые разрешения для камеры в ваш AndroidManifest.xml файл:

<uses-permission android:name="android.permission.CAMERA" />

Теперь, когда настройка завершена, пора создать макет!

Макет

Макет определяется внутри файла activity_main.xml и состоит из TextureView для отображения предварительного просмотра камеры и TextView, который показывает прогнозируемые выходные данные вашей модели классификации изображений.

Запросить разрешения камеры

Перед доступом к камере вам нужно будет запросить разрешения на выполнение. Следующий код из класса MainActivity.kt показывает, как это делается.

Как только разрешение будет получено, мы запустим камеру!

Настройка сценариев использования камеры

Как видно из кода предыдущего раздела, startCamera вызывается из метода post в TextureView. Это гарантирует, что камера запустится только после того, как TextureView будет положен на экран. В методе updateTransform мы фиксируем ориентацию вида относительно ориентации устройства.

В приведенном выше коде мы делаем довольно много вещей. Пройдемся по каждому из них:

  • Настройка нашего варианта использования Preview с использованием PreviewConfig.Builder.
  • setOnPreviewOutputUpdateListener - здесь мы добавляем текстуру поверхности предварительного просмотра камеры в TextureView.
  • Внутри варианта использования Analyzer мы конвертируем прокси изображения в Bitmap и передаем его методу TFClassifier classify. Если это выглядит неуместным, пропустите его, так как мы подробно обсудим класс TFClassifier в следующем разделе.

Следующий фрагмент кода используется для преобразования ImageProxy в Bitmap:

fun ImageProxy.toBitmap(): Bitmap {
    val yBuffer = planes[0].buffer // Y
    val uBuffer = planes[1].buffer // U
    val vBuffer = planes[2].buffer // V

    val ySize = yBuffer.remaining()
    val uSize = uBuffer.remaining()
    val vSize = vBuffer.remaining()

    val nv21 = ByteArray(ySize + uSize + vSize)
    
    yBuffer.get(nv21, 0, ySize)
    vBuffer.get(nv21, ySize, vSize)
    uBuffer.get(nv21, ySize + vSize, uSize)

    val yuvImage = YuvImage(nv21, ImageFormat.NV21, this.width, this.height, null)
    val out = ByteArrayOutputStream()
    yuvImage.compressToJpeg(Rect(0, 0, yuvImage.width, yuvImage.height), 100, out)
    val imageBytes = out.toByteArray()
    return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
}

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

Интерпретатор Tensor Flow Lite

Интерпретатор TensorFlow Lite выполняет следующие шаги, чтобы вернуть прогнозы на основе входных данных.

1. Преобразование модели в ByteBuffer

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

@Throws(IOException::class)
private fun loadModelFile(assetManager: AssetManager, filename: String): ByteBuffer {
    val fileDescriptor = assetManager.openFd(filename)
    val inputStream = FileInputStream(fileDescriptor.fileDescriptor)
    val fileChannel = inputStream.channel
    val startOffset = fileDescriptor.startOffset
    val declaredLength = fileDescriptor.declaredLength
    return fileChannel.map(FileChannel.MapMode.READ_ONLY, startOffset, declaredLength)
}

2. Загрузка классов этикеток в структуру данных.

Файл меток состоит из тысяч различных классов из ImageNet. Мы загрузим эти метки в массив. В конце концов, интерпретатор вернет прогнозы на основе этих строк меток.

@Throws(IOException::class)
fun loadLines(context: Context, filename: String): ArrayList<String> {
    val s = Scanner(InputStreamReader(context.assets.open(filename)))
    val labels = ArrayList<String>()
    while (s.hasNextLine()) {
        labels.add(s.nextLine())
    }
    s.close()
    return labels
}
var labels = loadLines(context, "labels.txt")

3. Инициализация нашего переводчика

Теперь, когда у нас есть ByteBuffer и список меток, пора инициализировать наш интерпретатор. В следующем коде мы добавили GPUDelegate в наш Interpreter.Options() метод:

В приведенном выше коде после завершения настройки модели в интерпретаторе мы получаем форму входного тензора модели. Это делается для предварительной обработки растрового изображения в ту же форму, которую принимает модель.

Callable интерфейс похож на Runnable, но позволяет нам возвращать результат. ExecutorService используется для управления несколькими потоками из ThreadPool.

Метод initialize вызывается в методе onCreate нашего MainActivity, как показано ниже:

private var tfLiteClassifier: TFLiteClassifier = TFLiteClassifier(this@MainActivity)
tfLiteClassifier
    .initialize()
    .addOnSuccessListener { }
    .addOnFailureListener { e -> Log.e(TAG, "Error in setting up the classifier.", e) }

4. Предварительная обработка ввода и выполнение вывода

Теперь мы можем изменить размер нашего растрового изображения, чтобы оно соответствовало форме ввода модели. Затем мы преобразуем новый Bitmap в ByteBuffer для выполнения модели:

В приведенном выше коде convertBitmapToByteBuffer маскирует 8 младших битов каждого пикселя, чтобы игнорировать альфа-канал.

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

5. Вычисление максимального аргумента

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

private fun getMaxResult(result: FloatArray): Int {
    var probability = result[0]
    var index = 0
    for (i in result.indices) {
        if (probability < result[i]) {
            probability = result[i]
            index = i
        }
    }
    return index
}

Метод classifyAsync, который выполняется в варианте использования Analyzer, получает строку, состоящую из прогноза и времени вывода, через onSuccessListener благодаря интерфейсу Callable.

tfLiteClassifier
    .classifyAsync(bitmap)
    .addOnSuccessListener { resultText -> predictedTextView?.text = resultText }

В свою очередь, мы отображаем прогнозируемую метку и время вывода на экране, как показано ниже:

Заключение

Итак, это подводит итог этой статьи. Мы использовали TensorFlow Lite и CameraX для создания Android-приложения для классификации изображений с помощью MobileNet, используя делегат графического процессора, и довольно быстро получили довольно точный результат. Двигаясь дальше, вы можете попробовать создать свои собственные пользовательские модели TFLite и посмотреть, как они работают с CameraX. CameraX все еще находится на стадии альфа-тестирования, но вы уже можете многое с ней сделать.

Полный исходный код этого руководства доступен здесь.

Вот и все. Надеюсь, вам понравилось читать.

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

Независимо от редакции, Heartbeat спонсируется и публикуется Comet, платформой MLOps, которая позволяет специалистам по данным и группам машинного обучения отслеживать, сравнивать, объяснять и оптимизировать свои эксперименты. Мы платим участникам и не продаем рекламу.

Если вы хотите внести свой вклад, отправляйтесь на наш призыв к участникам. Вы также можете подписаться на наши еженедельные информационные бюллетени (Deep Learning Weekly и Comet Newsletter), присоединиться к нам в » «Slack и подписаться на Comet в Twitter и LinkedIn для получения ресурсов, событий и гораздо больше, что поможет вам быстрее и лучше строить лучшие модели машинного обучения.