Пользователи ожидают, что сторонние приложения будут снимать фото и видео с тем же высоким качеством, что и встроенная камера устройства, независимо от условий освещения или уровня аппаратного обеспечения. Чтобы обеспечить такое единообразие в расширяющейся экосистеме Android, необходимо учитывать ключевые архитектурные особенности. На этом занятии мы покажем, как создавать надежные профессиональные конвейеры захвата, которые максимально используют возможности устройств и при этом стабильно работают на всех форм-факторах.
Мы рассмотрим последние достижения в CameraX и покажем, как интегрировать передовые возможности, такие как фото с движением и улучшения от ИИ на устройстве, через сервисы Google Play, а также пространственные возможности с помощью Jetpack XR SDK. Мы также рассмотрим навык CameraX, который позволяет выбранному вами агенту кодирования автоматически реализовывать концепции, описанные в этом докладе, и многое другое.
CameraXViewfinder адаптирует предварительный просмотр камеры для разных форм-факторов, например складных устройств. В CameraX 1.7 добавлено управление жестами и поддержка подсветки экрана.ProjectedDeviceContext позволяет CameraX делать снимки напрямую с очков.Расшифровка
Здравствуйте!
Добро пожаловать на наше занятие.
Меня зовут Аман, а это Скотт.
Сегодня мы рады рассказать о том, как вы можете улучшить работу камеры на разных устройствах с помощью CameraX и искусственного интеллекта на устройстве.
Давайте начнем.
Подумайте о том, как вы используете свои телефоны.
Мы часто используем камеру телефона, чтобы запечатлеть моменты и поделиться ими с близкими.
Камера стала центральным элементом смартфона.
На самом деле, хорошая камера часто является ключевым фактором при покупке смартфонов.
Сегодня мы рассмотрим две основные темы, связанные с созданием премиум-функций камеры.
Сначала мы рассмотрим некоторые функции CameraX, а затем узнаем о некоторых методах машинного обучения на устройстве.
Начнем с CameraX.
Jetpack CameraX обеспечивает работу камеры на Android.
Помимо CameraX, есть ещё Camera1 и Camera2, которые входят в состав фреймворка платформы Android.
Какую библиотеку выбрать?
Camera1 – это исходный API камеры на Android.
Сейчас он устарел, поэтому в новых приложениях следует использовать Camera2 или CameraX.
Camera2 – это преемник Camera1.
Поэтому, если вам требуется низкоуровневое управление камерой для сложных сценариев использования, Camera2 – хороший вариант.
Однако в этом случае вам придется самостоятельно настраивать конфигурации для разных устройств.
И наконец, Jetpack CameraX основана на Camera2.
Он предоставляет согласованный API и автоматически обрабатывает конфигурации для конкретных устройств.
Рекомендуется для большинства разработчиков.
Мы хотим сделать очень важное заявление.
Мы планируем отключить Camera1. Это начнется с принудительного применения правил Google Play в следующем году в Android 18.
Если вы все ещё используете устаревший API Camera1, перейдите на CameraX или Camera2.
Почему стоит использовать CameraX?
Во-первых, он поддерживает Android 7 и более поздние версии, что делает его совместимым почти с 99 % активных устройств Android.
Во-вторых, он прост в использовании и предоставляет простые API для распространенных сценариев использования, таких как предварительный просмотр, захват и запись с меньшим количеством кода.
В-третьих, он обеспечивает единообразие на разных устройствах, уменьшая количество проблем, связанных с конкретными устройствами, таких как растяжение предварительного просмотра, что довольно часто встречается на складных устройствах и планшетах.
И наконец, он упрощает реализацию расширенных возможностей OEM, таких как ночной и портретный режимы.
А теперь я передаю слово Скотту, который расскажет о некоторых функциях CameraX.
Передаю слово Скотту.
Спасибо, Аман.
Здравствуйте!
Меня зовут Скотт.
Сегодня мы расскажем о нескольких функциях CameraX, которые помогут вам создать приложение для камеры профессионального уровня.
Первая функция – адаптивный видоискатель.
Он позволяет легко просматривать, как будет выглядеть интерфейс на устройствах разных типов и с разными размерами экрана.
Сейчас у пользователей есть множество устройств, включая складные телефоны, ноутбуки и XR-устройства.
Поэтому сейчас как никогда важно адаптировать камеру к новым форм-факторам.
А предпросмотр камеры – это вообще отдельная история, особенно на складных устройствах с широким экраном.
На таких устройствах в разложенном виде внутренняя камера расположена горизонтально, а задняя – вертикально.
Если предположить, что все датчики камеры установлены вертикально, то это приведет к пространственному несоответствию и вызовет искажение или боковое смещение.
CameraXViewfinder, он разработан, чтобы полностью справиться со сложностью за вас, обеспечивая бесперебойную работу предварительного просмотра камеры во всех форм-факторах.
Давайте посмотрим, как использовать composable-функцию CameraXViewfinder.
var surfaceRequest by remember {
mutableStateOf<SurfaceRequest?>(null)
}
LaunchedEffect(...) {
preview.setSurfaceProvider { surfaceRequest = it }
cameraProvider.bindToLifecycle(..., preview)
}
surfaceRequest?.let { request ->
CameraXViewfinder(
surfaceRequest = request,
contentScale = ContentScale.Fit
)
}Сначала вы объявляете переменную контента surfaceRequest, которая будет содержать запрос к Surface.
Извините.
Во-вторых, в LaunchedEffect вы настраиваете поставщика Surface, чтобы он получал объект запроса Surface.
Затем вы привязываетесь к жизненному циклу и запускаете камеру.
И наконец, когда состояние обновляется с помощью действительного запроса поверхности и запускает рекомпозицию, вы устанавливаете запрос непосредственно в CameraXViewfinder.
Здесь вы можете выбрать, как будет показываться contentScale: вписываться в экран или обрезаться.
Вот и все.
В CameraX 1.7 мы добавили несколько новых функций в CameraXViewfinder.
в том числе новые жесты, например нажатие для фокусировки или сведение пальцев для масштабирования.
Также поддерживается вспышка экрана, которая позволяет использовать экран для осветления селфи.
Внедрить эту функцию очень просто.
CameraXViewfinder(
surfaceRequest = viewModel.surfaceRequest,
contentScale = ContentScale.Fit,
isTapToFocusEnabled = true,
isPinchToZoomEnabled = true,
onScreenFlashReady =
{
imageCapture.screenFlash = it
imageCapture.flashMode = FLASH_MODE_SCREEN
}
)Вам нужно лишь включить фокусировку по нажатию и масштабирование сведением пальцев.
Для подсветки экрана вы получаете экземпляр Screen Flash из обратного вызова onScreenFlashReady, а затем передаете его в ImageCapture и устанавливаете режим вспышки FLASH_MODE_SCREEN.
нужно будет
Мы увидели, как CameraX позволяет просматривать изображения с камеры, которые без проблем работают на всех форм-факторах.
Теперь давайте сделаем ещё один шаг и попробуем сделать снимок на камеру в Android XR.
Существует три категории XR-устройств. Первая – XR-шлемы.
Это полностью закрытые гарнитуры с эффектом погружения, которые используют камеру для съемки физического мира и отображения его внутри гарнитуры.
Второй – проводные XR-очки.
Сегодня мы анонсировали XREAL Aura в этой категории.
Как вы видите на презентации, это прозрачные очки, подключенные к вычислительному блоку, которые позволяют видеть виртуальный контент, наложенный на реальный мир.
И третий – аудио- и видеоочки.
Это легкие очки.
Они могут сочетать аудио, камеру и дисплей для голосового управления и удобного использования.
Первые две категории, XR-шлемы и проводные XR-очки, также поддерживают Camera2 и CameraX, как и обычные телефоны.
Поэтому сегодня мы сосредоточимся только на аудио и видеоочках.
На этих очках приложения запускаются на телефоне Android, а не на очках, но здесь есть доступ как к камере телефона, так и к камере очков.
Главный вопрос заключается в том, как узнать, к какой камере вы обращаетесь?
Все просто.
При стандартном контексте CameraX получает доступ к камере телефона.
А с помощью ProjectedDeviceContext CameraX получает доступ к камере очков.
Поэтому разные контексты будут приводить к разным целевым камерам.
Помните, что разрешения для камеры и очков предоставляются отдельно.
Разрешения для телефона и очков запрашиваются отдельно.
Другой вариант – использовать Projected Activity. Это действие также выполняется на основном устройстве Android, но интерфейс проецируется на очки.
И здесь правило меняется.
Если вы используете стандартный режим Context, вы получите доступ к камере очков.
Если вы хотите получить доступ к камере телефона, используйте HostContext.
Давайте посмотрим на код и узнаем, как сделать снимок с помощью камеры очков.
// In a coroutine
val imageCapture = ImageCapture.Builder().build()
ProjectedContext.isProjectedDeviceConnected(activity, coroutineContext)
.collectLatest { isConnected ->
if (isConnected) {
val projectedContext = ProjectedContext.createProjectedDeviceContext(activity)
val cameraConfig = CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig())
.setRepeatingStreamForced(false)
.build()
val cameraProvider = LifecycleCameraProvider.createInstance(projectedContext, cameraConfig)
cameraProvider.bindToLifecycle(activity, DEFAULT_BACK_CAMERA, imageCapture)
}
}
// Shutter button clicked
imageCapture.takePicture(...)Сначала вы вызываете isProjectedDeviceConnected, чтобы проверить, подключены ли очки.
Затем вы звоните в createProjectedDeviceContext, чтобы получить projectedContext.
Во-вторых, здесь вы используете LifecycleCameraProvider для инициализации CameraX вместо ProcessCameraProvider.
ProcessCameraProvider – это синглтон, контекст которого нельзя изменить после инициализации.
Таким образом, использование LifecycleCameraProvider позволяет вам менять контекст в любое время.
Затем отключите дополнительный повторяющийся поток в CameraX.
Это необходимо, поскольку современные очки поддерживают только один поток.
Сейчас API является внутренним, но скоро мы сделаем его общедоступным.
нужно будет
Дальше все как в стандартной библиотеке CameraX: вы просто привязываете ImageCapture и можете снимать через очки.
Окей.
Далее мы рассмотрим, как CameraX помогает найти комбинацию функций, которые работают вместе и позволяют максимально эффективно использовать устройство.
Современные приложения для камеры отличаются мощными функциями, и пользователи ожидают, что камера будет поддерживать такие функции, как HDR, 60 кадров в секунду и стабилизация предварительного просмотра, и все это одновременно.
Но мы знаем, что разработчикам не всегда просто объединить эти функции.
Если функции поддерживаются по отдельности, это не гарантирует, что они будут работать вместе.
Из-за этой неопределенности разработчики вынуждены действовать консервативно и ограничивать возможности пользователей на мощных устройствах.
Чтобы решить эту проблему, мы представили Feature Group API, который работает на основе фреймворка Camera2 для запросов комбинаций функций.
Использовать его можно двумя способами. Первый – с помощью requiredFeatureGroup проверить и включить комбинацию функций, если она поддерживается.
Во-вторых, вы можете указать приоритет функций с помощью preferredFeatureGroup, и CameraX выберет для вас наилучшую поддерживаемую комбинацию.
Вот пример.
Справа, слева, извините, ничего не включено, просто обычный телефон, обычная запись.
Слева включены HDR, 60 кадров/с и стабилизация предпросмотра.
И вы можете видеть, что качество значительно улучшилось: видео стало ярче, плавнее и без дрожания.
Давайте посмотрим на код.
cameraProvider.bindToLifecycle(
lifecycleOwner,
cameraSelector,
SessionConfig(
useCases = listOf(preview, videoCapture),
// HDR_HLG10 > FPS_60 > Preview Stabilization.
preferredFeatureGroup =
listOf(HDR_HLG10, FPS_60, PREVIEW_STABILIZATION),
)
)В этом примере мы используем preferredFeatureGroup, чтобы задать список приоритетных функций: HDR, 60 кадров/с и стабилизация предварительного просмотра.
CameraX проверит комбинацию и выберет лучшие варианты, поддерживаемые устройством.
В CameraX 1.6 и более поздних версиях мы добавили запись в формате 4K и стабилизацию видео прямо в Feature Group API.
// CameraX 1.6+ supports:
// 4K, video stabilization & Effect
val sessionConfig = SessionConfig(
useCases = listOf(preview, videoCapture),
requiredFeatureGroup =
setOf(UHD_RECORDING, VIDEO_STABILIZATION),
effects = listOf(overlayEffect)
)Сочетание 4K и стабилизации видео поддерживается многими устройствами премиум-класса.
Теперь с помощью Feature Group API вы можете включить их вместе.
Кроме того, теперь вы можете применять эффект в реальном времени с помощью Feature Group API, что раньше было невозможно.
Теперь давайте поговорим о том, как получить высококачественное изображение в CameraX.
CameraX теперь поддерживает съемку в формате DNG RAW.
Вы получаете доступ к необработанному и несжатому изображению в формате RAW прямо с датчика камеры для профессионального редактирования.
А теперь давайте поговорим об улучшенном расширении "Ночной режим".
Это расширение позволяет делать яркие и четкие фотографии при слабом освещении без использования вспышки.
Мы также добавили новый API индикатора ночного режима.
Он будет отслеживать, когда в текущей среде темно и подходит ли она для ночного режима, и вы сможете использовать его для показа индикатора ночного режима.
Кроме того, теперь Ultra HDR поддерживается в расширениях CameraX.
Это обеспечивает более широкий динамический диапазон и яркие цвета в ночном режиме. Ночной режим с Ultra HDR сейчас поддерживается на многих современных устройствах премиум-класса.
Здесь вы можете увидеть разницу между обычной съемкой, ночной съемкой и ночной съемкой с Ultra HDR.
Давайте посмотрим, как это сделать.
val cameraInfo = cameraProvider.getCameraInfo(cameraSelector,
ExtensionSessionConfig(ExtensionMode.NIGHT, extensionsManager))
val imageCapabilities = ImageCapture.getImageCaptureCapabilities(cameraInfo)
val imageCapture = ImageCapture.Builder().apply {
if (imageCapabilities.supportedOutputFormats
.contains(OUTPUT_FORMAT_JPEG_ULTRA_HDR)) {
setOutputFormat(OUTPUT_FORMAT_JPEG_ULTRA_HDR)
}
}.build()
val extensionSessionConfig = ExtensionSessionConfig(
ExtensionMode.NIGHT, extensionsManager, preview, imageCapture
)
cameraProvider.bindToLifecycle(..., extensionSessionConfig)Сначала создайте ExtensionSessionConfig для ночного режима и используйте его, чтобы получить экземпляр CameraInfo.
Затем вы используете CameraInfo, чтобы получить возможность фотосъемки, а затем запрашиваете, поддерживается ли Ultra HDR.
Затем выберите формат вывода Ultra HDR.
Наконец, вы снова создаете ExtensionSessionConfig с вариантами использования и привязываете его к жизненному циклу.
Теперь вы можете делать фотографии в ночном режиме в формате Ultra HDR.
Мы также добавили в CameraX поддержку фото с движением.
Фото с движением позволяют запечатлеть не только изображение, но и звук и движение, чтобы сохранить момент.
Мы стандартизируем этот формат для разных телефонов и устройств Android, чтобы упростить поддержку фото с движением.
Фото с движением появятся в CameraX в первом квартале 2027 года.
Когда она станет доступна, просто включите режим "Фото с движением" в приложении "ImageCapture.Builder" и начните снимать.
val imageCapture = ImageCapture.Builder()
.setMotionPhotoMode(
ImageCapture.MOTION_PHOTO_MODE_ON,
customConfig
)
.build()
cameraProvider.bindToLifecycle(.. imageCapture)
imageCapture.takePicture(...)Теперь давайте посмотрим, как CameraX позволяет записывать видео с высокой скоростью.
Четкая запись быстро движущихся объектов – важная функция современных приложений для камеры.
Это особенно полезно для динамичных сцен, например спортивных соревнований.
Это достигается за счет высокой скорости съемки – 120 или 240 кадров в секунду.
Есть два способа.
Сначала закодируйте видео с той же частотой кадров, а затем создайте видео с высокой частотой кадров.
Другой вариант – закодировать видео с частотой 30 кадров/с, чтобы создать готовое к воспроизведению замедленное видео.
Например, одна секунда видео, записанного с частотой 120 кадров в секунду, будет воспроизводиться четыре секунды с частотой 30 кадров в секунду.
Таким образом, этот отрезок времени создает эффект замедленной съемки.
Чтобы реализовать высокоскоростную запись, вы создаете HighSpeedVideoSessionConfig с помощью videoCapture.
val recorder = Recorder.Builder().build()
val videoCapture = VideoCapture.withOutput(recorder)
val frameRateRange = cameraInfo.getSupportedFrameRateRanges(
HighSpeedVideoSessionConfig(videoCapture, preview)).first()
val sessionConfig = HighSpeedVideoSessionConfig(
videoCapture,
preview,
frameRateRange = frameRateRange,
isSlowMotionEnabled = true
)
cameraProvider.bindToLifecycle(
lifecycleOwner, cameraSelector, sessionConfig)
// Start recording slow motion videos.
val recording = recorder.prepareRecording(context, outputOption)
.start(executor, {})Затем вы устанавливаете для isSlowMotionEnabled значение true, чтобы закодировать его со скоростью 30 кадров в секунду для создания замедленного видео, или устанавливаете для него значение false, чтобы создать видео с высокой частотой кадров.
Когда замедленная съемка включена, Google Фото автоматически распознает ее и добавляет значок замедленной съемки на значок видео.
Давайте посмотрим, как это работает в Google Фото.
Замедленная съемка делает видео с котом более драматичным.
Моя кошка не любит, когда я ее трогаю.
Извините.
Наконец, давайте посмотрим на улучшения в Concurrent Camera API.
Concurrent API позволяет приложению одновременно транслировать видео с фронтальной и основной камер.
В этом году мы улучшили Concurrent Camera API.
Вы можете динамически обновлять композицию камеры после привязки к жизненному циклу и открытия камеры.
Это включает изменение положения, размера и порядка рендеринга.
Вы также можете добавить стили, например скругленные углы, толщину и цвет границы.
Вот как это сделать: используйте CompositionSettings, чтобы настроить стиль, например скругление углов, толщину границы и цвет, и setZOrder, чтобы настроить порядок отрисовки.
val concurrentCamera =
cameraProvider.bindToLifecycle(list(config1, config2))
// CameraX 1.7 features: rounded corner, border width & color
val pipSettings = CompositionSettings.Builder()
.setOffset(0.2f, 0.2f)
.setScale(0.35f, 0.35f)
.setRoundedCornerRatio(0.15f)
.setBorderWidthRatio(0.02f)
.setBorderColor(Color.WHITE)
.setZOrder(1)
.build()
// CameraX 1.7 features: dynamical composition update
concurrentCamera.setCompositionSettings(
listOf(mainSettings, pipSettings))Затем вы просто вызываете setCompositionSettings на активной параллельной камере, чтобы динамически обновлять композицию.
нужно будет
Теперь вы можете динамически обновлять композицию.
Окей.
Теперь мы рассмотрели все функции.
Важное примечание: мы настоятельно рекомендуем перейти на CameraX 1.6 и более поздние версии.
Это поможет избежать сбоев на устройствах, поддерживающих новый формат Eclipsa Video, и обеспечит работу расширения CameraX после обновления Pixel.
На этом первое занятие завершено.
Мы рассмотрели функции CameraX, которые помогают создавать профессиональные приложения для камеры. Теперь я передаю слово Аману, который расскажет об ИИ на устройстве.
Спасибо, Скотт, за то, что рассказал нам о функциях CameraX.
Теперь давайте посмотрим, как можно использовать машинное обучение на устройстве для создания интеллектуальных камер.
Машинное обучение на устройстве позволяет использовать камеру в двух основных областях.
Первый – предварительная съемка.
Здесь вы можете анализировать предварительный просмотр с камеры в реальном времени, например для обнаружения объектов, или улучшать сам предварительный просмотр.
Второй этап – постобработка.
После того как вы сделали снимок, вы можете улучшить его качество или, например, сгенерировать описание.
Теперь давайте рассмотрим некоторые решения машинного обучения на устройстве, которые мы предлагаем для ваших приложений камеры.
Первая библиотека машинного обучения на устройстве, о которой мы поговорим, – это ML Kit.
ML Kit был создан в первую очередь для решения распространенных проблем в области зрения и естественного языка, таких как распознавание текста, распознавание лиц и языковой перевод.
В прошлом году мы представили ML Kit.
Вот несколько примеров API ML Kit Vision, например обнаружение сетки лица, сегментация объектов и обнаружение объектов в реальном времени.
Да.
В прошлом году мы представили GenAI API.
Эти API используют возможности Gemini Nano, чтобы обеспечивать такие функции, как краткий пересказ текста, описание изображений и многое другое.
Ниже приведен пример описания изображения с помощью Prompt API.
Мы передаем модели битовую карту изображения с текстовым запросом, и в результате получаем описание: "Маленький зеленый робот Android, похожий на кактус, сидит на черной поверхности".
И все это происходит на устройстве.
Звучит довольно круто, правда?
Prompt API используется в Adobe Photoshop, чтобы сделать приложение удобнее для пользователей.
При редактировании объектов пользователям часто приходится управлять несколькими слоями в приложении Photoshop.
С помощью Prompt API приложение теперь может автоматически переименовывать слои.
Prompt API также используется для создания подписей и хештегов к изображениям, что очень удобно для публикации в социальных сетях.
Давайте посмотрим на код для описания изображения с помощью Prompt API.
// 1. Initialize the on-device GenerativeModel client
val generativeModel = Generation.getClient()
// 2. Check whether Gemini Nano is available on the device
val status = generativeModel.checkStatus()
when (status) {
FeatureStatus.UNAVAILABLE -> {}
FeatureStatus.DOWNLOADABLE -> {}
FeatureStatus.DOWNLOADING -> {}
FeatureStatus.AVAILABLE -> {}
}Сначала вам понадобится объект клиента generativeModel. Затем проверьте, доступна ли модель Gemini Nano на вашем устройстве или можно ли ее скачать.
Затем сгенерируйте запрос на контент, объединив изображение и текстовый запрос, и, наконец, передайте этот запрос в вызов generateContent, чтобы запустить инференс на Gemini Nano и получить описание изображения.
// 3. Build a multimodal request with the image and text prompt
val request = generateContentRequest(
ImagePart(bitmap),
TextPart("Describe this image in detail.")
) {}
// 4. Run inference on-device and get the description
val response = generativeModel.generateContent(request)
val description = response.candidates.first().textДа, следующая библиотека машинного обучения на устройстве – MediaPipe.
MediaPipe предлагает широкий спектр решений машинного обучения для ваших приложений.
Вы можете быстро перейти на сайт MediaPipe, чтобы попробовать разные примеры, такие как распознавание лиц, обнаружение объектов, сегментация изображений и многое другое.
Все эти варианты использования очень похожи на ML Kit, так какую же библиотеку вам следует использовать?
Хотя MediaPipe и ML Kit решают схожие проблемы, есть несколько ключевых различий.
Первый – поддержка платформ.
MediaPipe работает на Android, iOS, Web и Python, тогда как ML Kit ориентирован только на Android и iOS.
Кроме того, MediaPipe позволяет настраивать модели, а ML Kit предоставляет готовые API.
И наконец, MediaPipe – это решение с открытым исходным кодом, а ML Kit – SDK от Google.
Вот пример распознавания жестов с помощью MediaPipe Vision API.
Этот API распознает жесты рук в реальном времени по изображению с камеры.
Например, вы можете начать или остановить запись видео, просто показав жест рукой на расстоянии от телефона.
Давайте посмотрим на код.
Сначала нужно создать объект GestureRecognizerOptions, указав путь к модели, режим прямой трансляции и прослушиватель результатов.
// 1. Build GestureRecognizerOptions
val options = GestureRecognizerOptions.builder().setBaseOptions(
BaseOptions.builder().setModelAssetPath(MP_RECOGNIZER_TASK).build())
.setRunningMode(RunningMode.LIVE_STREAM)
.setResultListener { result, image -> /* Handle detected gesture */ }
.build()
// 2. Create the GestureRecognizer
val gestureRecognizer = GestureRecognizer.createFromOptions(context, options)
// 3. Convert CameraX ImageProxy to MediaPipe MPImage
val bitmap = rotateBitmap(imageProxy.toBitmap(),
imageProxy.imageInfo.rotationDegrees)
val mpImage = BitmapImageBuilder(bitmap).build()
// 4. Run gesture recognition on the image
gestureRecognizer.recognizeAsync(mpImage, frameTime)Затем создайте объект распознавателя жестов из параметров.
Затем получите ImageProxy CameraX и преобразуйте его в битовую карту, а затем используйте битовую карту для создания объекта MediaPipe mpImage.
Наконец, вам нужно передать объект MediaPipe mpImage в вызов recognizeAsync, чтобы распознавать жесты в реальном времени.
Далее у нас есть функция улучшения изображений и видео на устройстве, доступная через API сервисов Google Play.
Эта модель обеспечивает такие возможности, как тональная компрессия, устранение размытия и суперразрешение на большинстве устройств премиум-класса.
Вот пример изображения, тона которого были сопоставлены, а разрешение увеличено с помощью этой модели.
На улучшенном изображении цвета неба и моря более насыщенные, а резкость выше.
Давайте посмотрим на код для улучшения изображений.
// Step 1: Verify device support & install module
val enhancementClient = Enhancement.getClient(context)
suspend fun prepare(): Boolean {
if (!enhancementClient.isDeviceSupported().await()) return false
if (!enhancementClient.isModuleInstalled().await()) {
check(enhancementClient.installModule().await()) {
"Enhancement module install failed"
}
}
return true
}Сначала нужно проверить, поддерживает ли устройство эту функцию, и при необходимости установить модуль улучшения.
Затем создайте объект EnhancementOptions, указав битовую карту и улучшения, которые вы хотите применить.
// Step 2: Select Enhancement options & create session
val options = EnhancementOptions(
width = bitmap.width,
height = bitmap.height,
enhancementMode = EnhancementMode.BITMAP,
isTonemappingEnabled = true,
isDeblurAndDenoisePhotoEnabled = true,
isDeblurAndDenoiseVideoEnabled = false,
isUpscalePhotoEnabled = false,
isUpscaleVideoEnabled = false
)
val enhancementSession = enhancementClient
.createSessionAsync(options, enhancementExecutor)Затем создайте сеанс клиента с помощью API сервисов Google Play, определите обратный вызов для обработки полученного растрового изображения и вызовите функцию обработки с растровым изображением и параметрами для улучшения изображения.
// Step 3: Process Enhancement
val enhancedBitmap = suspendCancellableCoroutine { continuation ->
val enhancementCallback = object : EnhancementCallback {
override fun onBitmapProcessed(result: Bitmap) {
if (continuation.isActive) continuation.resume(result)
}
override fun onError(statusCode: Int) {
if (continuation.isActive) {
continuation.resumeWithException(
Exception("Bitmap processing failed: $statusCode")
)
}
}
override fun onSurfaceProcessed(timestamp: Long) {}
}
enhancementSession.process(bitmap, options, enhancementCallback)
}Недавно мы интегрировали Video Enhancement API в Jetpack Media3.
Чтобы узнать больше об API для улучшения изображений и видео, посмотрите наше выступление.
Последняя функция машинного обучения на устройстве, о которой мы расскажем, – это усиление при слабом освещении.
Съемка при слабом освещении – распространенная проблема для пользователей смартфонов.
Представьте, что пользователь делает снимок или участвует в видеовстрече в плохо освещенном помещении.
Функция "Улучшение при слабом освещении" решает эту проблему, повышая яркость изображения в реальном времени.
Есть два варианта усиления при слабом освещении: аппаратное и программное.
Версия аппаратного обеспечения использует аппаратное обеспечение OEM, чтобы улучшать кадры с плохим освещением без машинного обучения.
В то время как версия ПО использует модель ML на устройстве для улучшения кадров.
Давайте посмотрим, как интегрировать CameraX с программным усилением при слабом освещении, которое также называется Google Low Light Boost.
Для работы функции Google Low Light Boost требуются две основные поверхности: входная поверхность для необработанных кадров камеры и выходная целевая поверхность для улучшенных выходных кадров.
Чтобы интегрировать его с CameraX, просто оберните конвейер Google Low Light Boost в CameraEffect.
CameraX автоматически управляет жизненным циклом и маршрутизацией Surface.
Наконец, в четвертом квартале 2026 года мы запустим эффект усиления при слабом освещении в CameraX.
// Integrates SW Low Light Boost
val lowLightBoostEffect =
LowLightBoostEffect(
CameraEffect.PREVIEW or
CameraEffect.VIDEO_CAPTURE or
CameraEffect.IMAGE_CAPTURE
)
val sessionConfig = SessionConfig(
useCases = listOf(preview, imageCapture),
effects = lowLightBoostEffect
)
cameraProvider.bindToLifecycle(.. sessionConfig)Это позволит вам создать объект Low Light Boost и добавить его в конфигурацию сеанса, а затем привязать конфигурацию сеанса к жизненному циклу камеры. На поддерживаемых устройствах все будет работать автоматически.
На этом мы завершаем оба раздела.
Давайте кратко повторим, о чем мы сегодня говорили.
Сегодня мы поговорили о том, как улучшить работу камеры на разных устройствах с помощью CameraX и машинного обучения на устройстве.
Во-первых, с помощью CameraXViewfinder вы можете включить адаптацию предварительного просмотра камеры к различным форм-факторам.
Мы также узнали, что CameraX позволяет вести съемку на устройствах XR.
Затем Feature Group API поможет вам найти оптимальное сочетание аппаратных функций.
Мы также изучили обновления функции захвата изображений и узнали о высокоскоростной видеозаписи для замедленных видео.
Наконец, мы узнали, как динамически улучшать предварительный просмотр параллельных камер.
Затем мы рассмотрели некоторые решения для машинного обучения на устройстве, такие как ML Kit и MediaPipe, для создания интеллектуальных визуальных возможностей.
Наконец, мы увидели, как работает улучшение изображений и видео на устройстве, а также усиление при слабом освещении, которое позволяет сделать изображение в окне предварительного просмотра камеры ярче в реальном времени.
Как упоминалось ранее, мы планируем отключить Camera1, поэтому настоятельно рекомендуем перейти на CameraX.
Чтобы помочь вам с переходом, мы недавно запустили навык CameraX.
Также у нас есть руководство по переносу и репозиторий с примерами использования камеры.
По этой ссылке вы найдете дополнительные ресурсы о функциях, которые мы сегодня рассмотрели.
Здесь вы найдете ссылки на документацию, блог и ресурсы по переносу.
Я сделаю паузу.
Окей.
Спасибо всем.
На этом наш семинар подходит к концу.
var surfaceRequest by remember {
mutableStateOf<SurfaceRequest?>(null)
}
LaunchedEffect(...) {
preview.setSurfaceProvider { surfaceRequest = it }
cameraProvider.bindToLifecycle(..., preview)
}
surfaceRequest?.let { request ->
CameraXViewfinder(
surfaceRequest = request,
contentScale = ContentScale.Fit
)
}CameraXViewfinder(
surfaceRequest = viewModel.surfaceRequest,
contentScale = ContentScale.Fit,
isTapToFocusEnabled = true,
isPinchToZoomEnabled = true,
onScreenFlashReady =
{
imageCapture.screenFlash = it
imageCapture.flashMode = FLASH_MODE_SCREEN
}
)// In a coroutine
val imageCapture = ImageCapture.Builder().build()
ProjectedContext.isProjectedDeviceConnected(activity, coroutineContext)
.collectLatest { isConnected ->
if (isConnected) {
val projectedContext = ProjectedContext.createProjectedDeviceContext(activity)
val cameraConfig = CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig())
.setRepeatingStreamForced(false)
.build()
val cameraProvider = LifecycleCameraProvider.createInstance(projectedContext, cameraConfig)
cameraProvider.bindToLifecycle(activity, DEFAULT_BACK_CAMERA, imageCapture)
}
}
// Shutter button clicked
imageCapture.takePicture(...)cameraProvider.bindToLifecycle(
lifecycleOwner,
cameraSelector,
SessionConfig(
useCases = listOf(preview, videoCapture),
// HDR_HLG10 > FPS_60 > Preview Stabilization.
preferredFeatureGroup =
listOf(HDR_HLG10, FPS_60, PREVIEW_STABILIZATION),
)
)// CameraX 1.6+ supports:
// 4K, video stabilization & Effect
val sessionConfig = SessionConfig(
useCases = listOf(preview, videoCapture),
requiredFeatureGroup =
setOf(UHD_RECORDING, VIDEO_STABILIZATION),
effects = listOf(overlayEffect)
)val cameraInfo = cameraProvider.getCameraInfo(cameraSelector,
ExtensionSessionConfig(ExtensionMode.NIGHT, extensionsManager))
val imageCapabilities = ImageCapture.getImageCaptureCapabilities(cameraInfo)
val imageCapture = ImageCapture.Builder().apply {
if (imageCapabilities.supportedOutputFormats
.contains(OUTPUT_FORMAT_JPEG_ULTRA_HDR)) {
setOutputFormat(OUTPUT_FORMAT_JPEG_ULTRA_HDR)
}
}.build()
val extensionSessionConfig = ExtensionSessionConfig(
ExtensionMode.NIGHT, extensionsManager, preview, imageCapture
)
cameraProvider.bindToLifecycle(..., extensionSessionConfig)val imageCapture = ImageCapture.Builder()
.setMotionPhotoMode(
ImageCapture.MOTION_PHOTO_MODE_ON,
customConfig
)
.build()
cameraProvider.bindToLifecycle(.. imageCapture)
imageCapture.takePicture(...)val recorder = Recorder.Builder().build()
val videoCapture = VideoCapture.withOutput(recorder)
val frameRateRange = cameraInfo.getSupportedFrameRateRanges(
HighSpeedVideoSessionConfig(videoCapture, preview)).first()
val sessionConfig = HighSpeedVideoSessionConfig(
videoCapture,
preview,
frameRateRange = frameRateRange,
isSlowMotionEnabled = true
)
cameraProvider.bindToLifecycle(
lifecycleOwner, cameraSelector, sessionConfig)
// Start recording slow motion videos.
val recording = recorder.prepareRecording(context, outputOption)
.start(executor, {})val concurrentCamera =
cameraProvider.bindToLifecycle(list(config1, config2))
// CameraX 1.7 features: rounded corner, border width & color
val pipSettings = CompositionSettings.Builder()
.setOffset(0.2f, 0.2f)
.setScale(0.35f, 0.35f)
.setRoundedCornerRatio(0.15f)
.setBorderWidthRatio(0.02f)
.setBorderColor(Color.WHITE)
.setZOrder(1)
.build()
// CameraX 1.7 features: dynamical composition update
concurrentCamera.setCompositionSettings(
listOf(mainSettings, pipSettings))// 1. Initialize the on-device GenerativeModel client
val generativeModel = Generation.getClient()
// 2. Check whether Gemini Nano is available on the device
val status = generativeModel.checkStatus()
when (status) {
FeatureStatus.UNAVAILABLE -> {}
FeatureStatus.DOWNLOADABLE -> {}
FeatureStatus.DOWNLOADING -> {}
FeatureStatus.AVAILABLE -> {}
}// 3. Build a multimodal request with the image and text prompt
val request = generateContentRequest(
ImagePart(bitmap),
TextPart("Describe this image in detail.")
) {}
// 4. Run inference on-device and get the description
val response = generativeModel.generateContent(request)
val description = response.candidates.first().text// 1. Build GestureRecognizerOptions
val options = GestureRecognizerOptions.builder().setBaseOptions(
BaseOptions.builder().setModelAssetPath(MP_RECOGNIZER_TASK).build())
.setRunningMode(RunningMode.LIVE_STREAM)
.setResultListener { result, image -> /* Handle detected gesture */ }
.build()
// 2. Create the GestureRecognizer
val gestureRecognizer = GestureRecognizer.createFromOptions(context, options)
// 3. Convert CameraX ImageProxy to MediaPipe MPImage
val bitmap = rotateBitmap(imageProxy.toBitmap(),
imageProxy.imageInfo.rotationDegrees)
val mpImage = BitmapImageBuilder(bitmap).build()
// 4. Run gesture recognition on the image
gestureRecognizer.recognizeAsync(mpImage, frameTime)// Step 1: Verify device support & install module
val enhancementClient = Enhancement.getClient(context)
suspend fun prepare(): Boolean {
if (!enhancementClient.isDeviceSupported().await()) return false
if (!enhancementClient.isModuleInstalled().await()) {
check(enhancementClient.installModule().await()) {
"Enhancement module install failed"
}
}
return true
}// Step 2: Select Enhancement options & create session
val options = EnhancementOptions(
width = bitmap.width,
height = bitmap.height,
enhancementMode = EnhancementMode.BITMAP,
isTonemappingEnabled = true,
isDeblurAndDenoisePhotoEnabled = true,
isDeblurAndDenoiseVideoEnabled = false,
isUpscalePhotoEnabled = false,
isUpscaleVideoEnabled = false
)
val enhancementSession = enhancementClient
.createSessionAsync(options, enhancementExecutor)// Step 3: Process Enhancement
val enhancedBitmap = suspendCancellableCoroutine { continuation ->
val enhancementCallback = object : EnhancementCallback {
override fun onBitmapProcessed(result: Bitmap) {
if (continuation.isActive) continuation.resume(result)
}
override fun onError(statusCode: Int) {
if (continuation.isActive) {
continuation.resumeWithException(
Exception("Bitmap processing failed: $statusCode")
)
}
}
override fun onSurfaceProcessed(timestamp: Long) {}
}
enhancementSession.process(bitmap, options, enhancementCallback)
}// Integrates SW Low Light Boost
val lowLightBoostEffect =
LowLightBoostEffect(
CameraEffect.PREVIEW or
CameraEffect.VIDEO_CAPTURE or
CameraEffect.IMAGE_CAPTURE
)
val sessionConfig = SessionConfig(
useCases = listOf(preview, imageCapture),
effects = lowLightBoostEffect
)
cameraProvider.bindToLifecycle(.. sessionConfig)