1. Прежде чем начать
Написание кода на клавиатуре — отличный способ развить мышечную память и углубить понимание материала. Хотя копирование и вставка могут сэкономить время, вложение средств в эту практику в долгосрочной перспективе приведет к большей эффективности и более сильным навыкам программирования.
В этом практическом занятии вы научитесь создавать Android-приложение, выполняющее сегментацию изображений в реальном времени на основе видеопотока с камеры, используя новую среду выполнения Google для TensorFlow Lite — LiteRT. Вы возьмете базовое Android-приложение и добавите в него возможности сегментации изображений. Мы также рассмотрим этапы предварительной обработки, вывода и постобработки. Вы научитесь:
- Разработайте Android-приложение, которое сегментирует изображения в режиме реального времени.
- Интегрируйте предварительно обученную модель сегментации изображений LiteRT.
- Предварительно обработайте входное изображение для модели.
- Используйте среду выполнения LiteRT для ускорения работы процессора и графического процессора.
- Разберитесь, как обрабатывать выходные данные модели для отображения маски сегментации.
- Разберитесь, как настроить фронтальную камеру.
В итоге у вас получится что-то похожее на изображение ниже:

Предварительные требования
Этот практический семинар разработан для опытных разработчиков мобильных приложений, желающих получить опыт работы с машинным обучением. Вам необходимо знать следующее:
- Разработка приложений для Android с использованием Kotlin и Android Studio.
- Основные понятия обработки изображений
Что вы узнаете
- Как интегрировать и использовать среду выполнения LiteRT в Android-приложении.
- Как выполнить сегментацию изображений с использованием предварительно обученной модели LiteRT.
- Как выполнить предварительную обработку входного изображения для модели.
- Как выполнить вывод результатов для модели.
- Как обработать выходные данные модели сегментации для визуализации результатов.
- Как использовать CameraX для обработки видеопотока с камеры в реальном времени.
Что вам понадобится
- Последняя версия Android Studio (протестировано на v2025.1.1).
- Физическое устройство Android. Наилучшее тестирование проводилось на устройствах Galaxy и Pixel.
- Пример кода (с GitHub).
- Базовые знания разработки под Android на Kotlin.
2. Сегментация изображений
Сегментация изображений — это задача компьютерного зрения, которая включает в себя разделение изображения на несколько сегментов или областей. В отличие от обнаружения объектов, которое обводит объект ограничивающей рамкой, сегментация изображений присваивает каждому пикселю изображения определенный класс или метку. Это обеспечивает гораздо более детальное и детальное понимание содержимого изображения, позволяя точно определить форму и границы каждого объекта.
Например, вместо того, чтобы просто знать, что «человек» находится в рамке, вы можете точно определить, какие пиксели принадлежат этому человеку. В этом руководстве показано, как выполнять сегментацию изображений в реальном времени на устройстве Android с использованием предварительно обученной модели машинного обучения.

LiteRT: Расширяя границы машинного обучения на устройствах.
Ключевой технологией, обеспечивающей сегментацию в реальном времени с высокой точностью на мобильных устройствах, является LiteRT . LiteRT, высокопроизводительная среда выполнения следующего поколения от Google для TensorFlow Lite, разработана для достижения максимальной производительности от используемого оборудования.
Это достигается за счет интеллектуального и оптимизированного использования аппаратных ускорителей, таких как GPU (графический процессор) и NPU (нейронный процессор). Перенося интенсивную вычислительную нагрузку модели сегментации с универсального центрального процессора на эти специализированные процессоры, LiteRT значительно сокращает время выполнения. Именно это ускорение позволяет плавно запускать сложные модели на видеопотоке с камеры в реальном времени, расширяя границы возможностей машинного обучения непосредственно на вашем телефоне. Без такого уровня производительности сегментация в реальном времени была бы слишком медленной и прерывистой для комфортного пользовательского опыта.
3. Подготовка к работе
Клонируйте репозиторий
Сначала клонируйте репозиторий LiteRT:
git clone https://github.com/google-ai-edge/litert-samples.git
litert-samples/compiled_model_api/image_segmentation — это каталог со всеми необходимыми ресурсами. Для этого практического задания вам понадобится только проект kotlin_cpu_gpu/android_starter . Если у вас возникнут трудности, вы можете посмотреть готовый проект: kotlin_cpu_gpu/android
Примечание о путях к файлам.
В этом руководстве пути к файлам указаны в формате Linux/macOS. Если вы используете Windows, вам потребуется соответствующим образом скорректировать пути.
Важно также отметить различие между представлением проекта в Android Studio и стандартным представлением файловой системы. Представление проекта в Android Studio — это структурированное отображение файлов вашего проекта, организованное для разработки под Android. Пути к файлам в этом руководстве относятся к путям в файловой системе, а не к путям в представлении проекта Android Studio.
Импортируйте стартовое приложение
Начнём с импорта стартового приложения в Android Studio.
- Откройте Android Studio и выберите «Открыть» .

- Перейдите в каталог
kotlin_cpu_gpu/android_starterи откройте его.

Чтобы убедиться, что все зависимости доступны вашему приложению, после завершения процесса импорта следует синхронизировать проект с файлами Gradle.
- Выберите пункт «Синхронизировать проект с файлами Gradle» на панели инструментов Android Studio.

- Пожалуйста, не пропускайте этот шаг — если он не сработает, остальная часть руководства будет непонятна.
Запустите стартовое приложение
Теперь, когда вы импортировали проект в Android Studio, вы готовы запустить приложение в первый раз.
Подключите ваше Android-устройство к компьютеру через USB и нажмите кнопку «Запустить» на панели инструментов Android Studio.

Приложение должно запуститься на вашем устройстве. Вы увидите прямую трансляцию с камеры, но сегментация пока не будет происходить. Все изменения файлов, которые вы будете вносить в этом руководстве, будут находиться в каталоге litert-samples/compiled_model_api/image_segmentation/kotlin_cpu_gpu/android_starter/app/src/main/java/com/google/ai/edge/examples/image_segmentation (теперь вы понимаете, почему Android Studio изменяет структуру этого каталога 😃).

Вы также увидите комментарии TODO в файлах ImageSegmentationHelper.kt , MainViewModel.kt и view/SegmentationOverlay.kt . На следующих шагах вы реализуете функциональность сегментации изображений, заполнив эти комментарии TODO .
4. Разберитесь в работе стартового приложения.
В стартовом приложении уже есть базовый пользовательский интерфейс и логика обработки изображения с камеры. Вот краткий обзор основных файлов:
-
app/src/main/java/com/google/ai/edge/examples/image_segmentation/MainActivity.kt: Это основная точка входа в приложение. Она настраивает пользовательский интерфейс с помощью Jetpack Compose и обрабатывает разрешения для камеры. -
app/src/main/java/com/google/ai/edge/examples/image_segmentation/MainViewModel.kt: Эта ViewModel управляет состоянием пользовательского интерфейса и координирует процесс сегментации изображений. -
app/src/main/java/com/google/ai/edge/examples/image_segmentation/ImageSegmentationHelper.kt: Здесь мы добавим основную логику сегментации изображений. Она будет отвечать за загрузку модели, обработку кадров с камеры и выполнение вывода. -
app/src/main/java/com/google/ai/edge/examples/image_segmentation/view/CameraScreen.kt: Эта составная функция отображает предварительный просмотр с камеры и наложение сегментации. -
app/download_model.gradle: Этот скрипт загружаетselfie_multiclass.tflite. Это предварительно обученная модель сегментации изображений TensorFlow Lite, которую мы будем использовать.
5. Понимание LiteRT и добавление зависимостей
Теперь давайте добавим функциональность сегментации изображений в стартовое приложение.
1. Добавьте зависимость LiteRT.
Во-первых, необходимо добавить библиотеку LiteRT в ваш проект. Это важнейший первый шаг для включения машинного обучения на устройстве с использованием оптимизированной среды выполнения Google.
Откройте файл app/build.gradle.kts и добавьте следующую строку в блок dependencies :
// LiteRT for on-device ML
implementation(libs.litert)
После добавления зависимости синхронизируйте свой проект с файлами Gradle, нажав кнопку « Синхронизировать сейчас» , которая появится в правом верхнем углу Android Studio.

2. Понимание ключевых API LiteRT
Откройте ImageSegmentationHelper.kt
Прежде чем писать код реализации, важно понять основные компоненты API LiteRT, которые вы будете использовать. Убедитесь, что вы импортируете компоненты из пакета com.google.ai.edge.litert , добавив следующие импорты в начало файла ImageSegmentationHelper.kt :
import com.google.ai.edge.litert.Accelerator
import com.google.ai.edge.litert.CompiledModel
-
CompiledModel: Это центральный класс для взаимодействия с вашей моделью TFLite. Он представляет собой модель, предварительно скомпилированную и оптимизированную для конкретного аппаратного ускорителя (например, ЦП или ГП). Эта предварительная компиляция является ключевой особенностью LiteRT, которая приводит к более быстрой и эффективной обработке результатов. -
CompiledModel.Options: Этот класс-конструктор используется для настройкиCompiledModel. Наиболее важная настройка — указание аппаратного ускорителя, который вы хотите использовать для запуска вашей модели. -
Accelerator: Этот перечислимый параметр позволяет выбрать оборудование для выполнения инференции. Стартовый проект уже настроен для обработки этих параметров:-
Accelerator.CPU: Для запуска модели на процессоре устройства. Это наиболее универсальный и совместимый вариант. -
Accelerator.GPU: Используется для запуска модели на графическом процессоре устройства. Зачастую это значительно быстрее, чем центральный процессор, для моделей, работающих с изображениями.
-
- Входные и выходные буферы (
TensorBuffer) : LiteRT используетTensorBufferдля ввода и вывода данных модели. Это обеспечивает точный контроль над памятью и позволяет избежать ненужного копирования данных. Вы получаете эти буферы непосредственно из экземпляраCompiledModel, используяmodel.createInputBuffers()иmodel.createOutputBuffers(), а затем записываете в них входные данные и считываете результаты. -
model.run(): Эта функция выполняет вывод результатов. Вы передаете ей входные и выходные буферы, а LiteRT берет на себя сложную задачу запуска модели на выбранном аппаратном ускорителе.
6. Завершите первоначальную реализацию вспомогательной функции сегментации изображений (ImageSegmentationHelper).
Теперь пришло время написать код. Вам предстоит завершить начальную реализацию ImageSegmentationHelper.kt . Это включает в себя настройку приватного класса Segmenter для хранения модели LiteRT и реализацию функции cleanup() для её корректного освобождения.
- Завершите класс
Segmenterи функциюcleanup(): В файлеImageSegmentationHelper.ktвы найдете основу для приватного класса с именемSegmenterи функцииcleanup(). Сначала завершите классSegmenter, определив его конструктор для хранения модели, создав свойства для входных/выходных буферов и добавив методclose()для освобождения модели. Затем реализуйте функциюcleanup(), чтобы вызывать этот новый методclose()Замените существующий классSegmenterи функциюcleanup()следующим кодом: (~строка 83)private class Segmenter( // Add this argument private val model: CompiledModel, private val coloredLabels: List<ColoredLabel>, ) { // Add these private vals private val inputBuffers = model.createInputBuffers() private val outputBuffers = model.createOutputBuffers() fun cleanup() { // cleanup buffers inputBuffers.forEach { it.close() } outputBuffers.forEach { it.close() } // cleanup model model.close() } } - Определите метод toAccelerator : Этот метод сопоставляет определенные перечисления-акселераторы из меню акселераторов с перечислениями-акселераторами, специфичными для импортированных модулей LiteRT (~строка 225):
fun toAccelerator(acceleratorEnum: AcceleratorEnum): Accelerator { return when (acceleratorEnum) { AcceleratorEnum.CPU -> Accelerator.CPU AcceleratorEnum.GPU -> Accelerator.GPU } } - Инициализация
CompiledModel: Теперь найдите функциюinitSegmenter. Здесь вы создадите экземплярCompiledModelи используете его для создания экземпляра вашего теперь определенного классаSegmenter. Этот код настраивает модель с указанным ускорителем (CPU или GPU) и подготавливает ее к инференсу. ЗаменитеTODOвinitSegmenterследующей реализацией (Cmd/Ctrl+f 'initSegmenter' или ~строка 62):cleanup() try { withContext(singleThreadDispatcher) { val model = CompiledModel.create( context.assets, "selfie_multiclass.tflite", CompiledModel.Options(toAccelerator(acceleratorEnum)), null, ) segmenter = Segmenter(model, coloredLabels) Log.d(TAG, "Created an image segmenter") } } catch (e: Exception) { Log.i(TAG, "Create LiteRT from selfie_multiclass is failed: ${e.message}") _error.emit(e) }
7. Начало сегментации и предварительной обработки.
Теперь, когда у нас есть модель, нам нужно запустить процесс сегментации и подготовить входные данные для модели.
Триггерная сегментация
Процесс сегментации начинается в MainViewModel.kt , который получает кадры с камеры.
Откройте файл MainViewModel.kt
- Запуск сегментации по кадрам с камеры : Функции
segmentвMainViewModelявляются точкой входа для нашей задачи сегментации. Они вызываются всякий раз, когда появляется новое изображение с камеры или оно выбрано из галереи. Затем эти функции вызывают методsegmentв нашемImageSegmentationHelper. ЗаменитеTODOв обеих функцияхsegmentследующим кодом (строка ~107):// For ImageProxy (from CameraX) fun segment(imageProxy: ImageProxy) { segmentJob = viewModelScope.launch { imageSegmentationHelper.segment(imageProxy.toBitmap(), imageProxy.imageInfo.rotationDegrees) imageProxy.close() } } // For Bitmaps (from gallery) fun segment(bitmap: Bitmap, rotationDegrees: Int) { segmentJob = viewModelScope.launch { val argbBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true) imageSegmentationHelper.segment(argbBitmap, rotationDegrees) } }
Предварительная обработка изображения
Теперь вернемся к ImageSegmentationHelper.kt , чтобы заняться предварительной обработкой изображений.
Откройте ImageSegmentationHelper.kt
- Реализуйте публичную функцию
segment: эта функция служит оберткой, которая вызывает приватную функциюsegmentвнутри классаSegmenter. ЗаменитеTODOна (~строка 95):try { withContext(singleThreadDispatcher) { segmenter?.segment(bitmap, rotationDegrees)?.let { if (isActive) _segmentation.emit(it) } } } catch (e: Exception) { Log.i(TAG, "Image segment error occurred: ${e.message}") _error.emit(e) } - Реализация предварительной обработки : Приватная функция
segmentвнутри классаSegmenter— это место, где мы будем выполнять необходимые преобразования входного изображения, чтобы подготовить его для модели. Это включает масштабирование, вращение и нормализацию изображения. Затем эта функция вызовет другую приватную функциюsegmentдля выполнения вывода. ЗаменитеTODOв функцииsegment(bitmap: Bitmap, ...)на (~строка 121):val totalStartTime = SystemClock.uptimeMillis() val rotation = -rotationDegrees / 90 val (h, w) = Pair(256, 256) // Preprocessing val preprocessStartTime = SystemClock.uptimeMillis() var image = bitmap.scale(w, h, true) image = rot90Clockwise(image, rotation) val inputFloatArray = normalize(image, 127.5f, 127.5f) Log.d(TAG, "Preprocessing time: ${SystemClock.uptimeMillis() - preprocessStartTime} ms") // Inference val inferenceStartTime = SystemClock.uptimeMillis() val segmentResult = segment(inputFloatArray) Log.d(TAG, "Inference time: ${SystemClock.uptimeMillis() - inferenceStartTime} ms") Log.d(TAG, "Total segmentation time: ${SystemClock.uptimeMillis() - totalStartTime} ms") return SegmentationResult(segmentResult, SystemClock.uptimeMillis() - inferenceStartTime)
8. Первичный вывод с помощью LiteRT
После предварительной обработки входных данных мы можем запустить основной процесс вывода с помощью LiteRT.
Откройте ImageSegmentationHelper.kt
- Реализация выполнения модели : функция private
segment(inputFloatArray: FloatArray)— это место, где мы напрямую взаимодействуем с методомrun()LiteRT. Мы записываем предварительно обработанные данные во входной буфер, запускаем модель и считываем результаты из выходного буфера. ЗаменитеTODOв этой функции на (~строка 188):val (h, w, c) = Triple(256, 256, 6) // MODEL EXECUTION PHASE val modelExecStartTime = SystemClock.uptimeMillis() // Write input data - measure time val bufferWriteStartTime = SystemClock.uptimeMillis() inputBuffers[0].writeFloat(inputFloatArray) val bufferWriteTime = SystemClock.uptimeMillis() - bufferWriteStartTime Log.d(TAG, "Buffer write time: $bufferWriteTime ms") // Optional tensor inspection logTensorStats("Input tensor", inputFloatArray) // Run model inference - measure time val modelRunStartTime = SystemClock.uptimeMillis() model.run(inputBuffers, outputBuffers) val modelRunTime = SystemClock.uptimeMillis() - modelRunStartTime Log.d(TAG, "Model.run() time: $modelRunTime ms") // Read output data - measure time val bufferReadStartTime = SystemClock.uptimeMillis() val outputFloatArray = outputBuffers[0].readFloat() val outputBuffer = FloatBuffer.wrap(outputFloatArray) val bufferReadTime = SystemClock.uptimeMillis() - bufferReadStartTime Log.d(TAG, "Buffer read time: $bufferReadTime ms") val modelExecTime = SystemClock.uptimeMillis() - modelExecStartTime Log.d(TAG, "Total model execution time: $modelExecTime ms") // Optional tensor inspection logTensorStats("Output tensor", outputFloatArray) // POSTPROCESSING PHASE val postprocessStartTime = SystemClock.uptimeMillis() // Process mask from model output val inferenceData = InferenceData(width = w, height = h, channels = c, buffer = outputBuffer) val mask = processImage(inferenceData) val postprocessTime = SystemClock.uptimeMillis() - postprocessStartTime Log.d(TAG, "Postprocessing time (mask creation): $postprocessTime ms") return Segmentation( listOf(Mask(mask, inferenceData.width, inferenceData.height)), coloredLabels, )
9. Постобработка и отображение наложения
После выполнения инференса мы получаем необработанные выходные данные от модели. Нам необходимо обработать эти данные, чтобы создать визуальную маску сегментации, а затем отобразить её на экране.
Откройте ImageSegmentationHelper.kt
- Реализуйте обработку выходных данных : функция
processImageпреобразует необработанные выходные данные модели в формате с плавающей запятой вByteBuffer, представляющий маску сегментации. Она делает это, находя класс с наибольшей вероятностью для каждого пикселя. ЗаменитеTODOна (~строка 238):val mask = ByteBuffer.allocateDirect(inferenceData.width * inferenceData.height) for (i in 0 until inferenceData.height) { for (j in 0 until inferenceData.width) { val offset = inferenceData.channels * (i * inferenceData.width + j) var maxIndex = 0 var maxValue = inferenceData.buffer.get(offset) for (index in 1 until inferenceData.channels) { if (inferenceData.buffer.get(offset + index) > maxValue) { maxValue = inferenceData.buffer.get(offset + index) maxIndex = index } } mask.put(i * inferenceData.width + j, maxIndex.toByte()) } } return mask
Откройте файл MainViewModel.kt
- Сбор и обработка результатов сегментации : Теперь вернемся к
MainViewModel, чтобы обработать результаты сегментации изImageSegmentationHelper.segmentationUiShareFlowсобираетSegmentationResult, преобразует маску в цветноеBitmapи предоставляет его пользовательскому интерфейсу. ЗаменитеTODOв свойствеsegmentationUiShareFlowна (~строка 63) – не заменяйте уже имеющийся код, просто заполните тело:viewModelScope.launch { imageSegmentationHelper.segmentation .filter { it.segmentation.masks.isNotEmpty() } .map { val segmentation = it.segmentation val mask = segmentation.masks[0] val maskArray = mask.data val width = mask.width val height = mask.height val pixelSize = width * height val pixels = IntArray(pixelSize) val colorLabels = segmentation.coloredLabels.mapIndexed { index, coloredLabel -> ColorLabel(index, coloredLabel.label, coloredLabel.argb) } // Set color for pixels for (i in 0 until pixelSize) { val colorLabel = colorLabels[maskArray[i].toInt()] val color = colorLabel.getColor() pixels[i] = color } // Get image info val overlayInfo = OverlayInfo(pixels = pixels, width = width, height = height) val inferenceTime = it.inferenceTime Pair(overlayInfo, inferenceTime) } .collect { flow.emit(it) } }
Open view/SegmentationOverlay.kt
Последний шаг — правильно сориентировать сегментированное наложение, когда пользователь переключается на фронтальную камеру. Изображение с фронтальной камеры, естественно, зеркально отражено, поэтому нам нужно применить такое же горизонтальное отражение к нашему Bitmap наложения, чтобы обеспечить его правильное выравнивание с предварительным просмотром с камеры.
- Обработка ориентации наложения : Найдите
TODOв файлеSegmentationOverlay.ktи замените его следующим кодом. Этот код проверяет, активна ли фронтальная камера, и если да, то применяет горизонтальное отражение к растровомуBitmapперед его отрисовкой наCanvas. (~строка 42):val orientedBitmap = if (lensFacing == CameraSelector.LENS_FACING_FRONT) { // Create a matrix for horizontal flipping val matrix = Matrix().apply { preScale(-1f, 1f) } Bitmap.createBitmap(image, 0, 0, image.width, image.height, matrix, false).also { image.recycle() } } else { image }
10. Запустите и используйте готовое приложение.
Вы завершили все необходимые изменения в коде. Теперь пора запустить приложение и увидеть результаты своей работы!
- Запуск приложения : Подключите ваше устройство Android и нажмите кнопку «Запустить» на панели инструментов Android Studio.

- Проверьте работу функций : После запуска приложения вы увидите прямую трансляцию с камеры с цветным сегментированным наложением.
- Переключение камер : Нажмите на значок переворота камеры вверху, чтобы переключаться между передней и задней камерами. Обратите внимание, как правильно ориентируется наложенное изображение.
- Смена ускорителя : Нажмите кнопку «CPU» или «GPU» внизу, чтобы переключить аппаратный ускоритель. Обратите внимание на изменение времени вывода, отображаемого в нижней части экрана. GPU должен работать значительно быстрее.
- Используйте изображение из галереи : коснитесь вкладки «Галерея» вверху, чтобы выбрать изображение из фотогалереи вашего устройства. Приложение выполнит сегментацию выбранного статического изображения.

Теперь у вас есть полностью функциональное приложение для сегментации изображений в реальном времени, работающее на платформе LiteRT!
11. Расширенные (необязательно): Использование NPU
В этом репозитории также содержится версия приложения, оптимизированная для нейронных процессоров (NPU). Версия для NPU может обеспечить значительное повышение производительности на устройствах, имеющих совместимый NPU.
Чтобы попробовать версию для NPU, откройте проект kotlin_npu/android в Android Studio. Код очень похож на версию для CPU/GPU и настроен на использование делегата NPU.
Для использования делегатской карты NPU вам необходимо зарегистрироваться в программе раннего доступа .
12. Поздравляем!
Вы успешно разработали Android-приложение, выполняющее сегментацию изображений в реальном времени с использованием LiteRT. Вы научились:
- Интегрируйте среду выполнения LiteRT в приложение для Android.
- Загрузите и запустите модель сегментации изображений TFLite.
- Предварительная обработка входных данных модели.
- Обработайте выходные данные модели для создания маски сегментации.
- Используйте CameraX для работы с камерой в режиме реального времени.
Следующие шаги
- Попробуйте другую модель сегментации изображений.
- Поэкспериментируйте с различными делегатами LiteRT (CPU, GPU, NPU).