Les utilisateurs s'attendent à ce que les applications tierces capturent des photos et des vidéos avec la même fidélité que l'appareil photo natif de leur appareil, quelles que soient les conditions d'éclairage ou le niveau matériel. Pour assurer cette cohérence dans l'écosystème Android en pleine expansion, il est essentiel de prendre en compte certains aspects architecturaux. Cette session explique comment créer des pipelines de capture robustes et professionnels qui maximisent les capacités des appareils tout en restant fiables sur tous les facteurs de forme.
Nous allons explorer les dernières avancées de CameraX et vous montrer comment intégrer des fonctionnalités de pointe telles que les photos animées et les améliorations par IA sur l'appareil via les services Google Play, ainsi que des expériences spatiales à l'aide du SDK Jetpack XR. Nous examinerons également notre compétence CameraX, qui permet à l'agent de codage de votre choix d'implémenter automatiquement les concepts de cette présentation, et plus encore.
CameraXViewfinder adapte les aperçus de l'appareil photo à différents facteurs de forme, tels que les appareils pliables. Dans CameraX 1.7, il ajoute des commandes gestuelles et la prise en charge du clignotement de l'écran.ProjectedDeviceContext permet à CameraX de capturer des images directement à partir des lunettes.Transcription
Bonjour !
Bienvenue à notre session.
Je m'appelle Aman, et je suis accompagné de Scott.
Aujourd'hui, nous sommes ravis de vous expliquer comment améliorer l'expérience de l'appareil photo sur tous les appareils grâce à CameraX et à l'intelligence sur l'appareil.
Commençons.
Réfléchissez à la façon dont vous utilisez vos téléphones.
Nous utilisons souvent l'appareil photo de notre téléphone pour capturer des moments et les partager avec nos proches.
L'appareil photo est devenu un élément central de l'expérience sur smartphone.
En fait, un bon appareil photo est souvent un facteur clé lors de l'achat d'un smartphone.
Aujourd'hui, nous allons aborder deux grands thèmes pour créer des expériences d'appareil photo haut de gamme.
Nous allons d'abord explorer certaines fonctionnalités de CameraX, puis nous découvrirons des techniques de machine learning sur l'appareil.
Commençons par CameraX.
Jetpack CameraX gère l'expérience de l'appareil photo sur Android.
En plus de CameraX, nous disposons également de Camera1 et Camera2, qui font partie du framework de la plate-forme Android.
Alors, quelle bibliothèque devez-vous utiliser ?
Camera1 était l'API d'appareil photo d'origine sur Android.
Elle est désormais obsolète. Les nouvelles applications doivent donc utiliser Camera2 ou CameraX.
Camera2 a succédé à Camera1.
Si vous avez besoin d'un contrôle d'appareil photo de faible niveau pour des cas d'utilisation complexes, Camera2 est l'option idéale.
Toutefois, vous devez gérer vous-même les configurations spécifiques à l'appareil.
Enfin, Jetpack CameraX s'appuie sur Camera2.
Elle fournit une API cohérente et gère automatiquement les configurations spécifiques à l'appareil.
C'est le choix recommandé pour la plupart des développeurs.
Nous avons une remarque très importante à faire.
Nous prévoyons de désactiver Camera1, et cela commencera par l'application des règles Play l'année prochaine dans Android 18.
Si vous utilisez toujours l'ancienne API Camera1, vous devrez migrer vers CameraX ou Camera2.
Alors, pourquoi utiliser CameraX ?
Tout d'abord, elle est compatible avec Android 7 et versions ultérieures, ce qui la rend compatible avec près de 99 % des appareils Android actifs.
Deuxièmement, elle est facile à utiliser et fournit des API simples pour les cas d'utilisation courants tels que l'aperçu, la capture et l'enregistrement, avec moins de code.
Troisièmement, elle offre une expérience cohérente sur tous les appareils en réduisant les problèmes spécifiques à certains appareils, comme l'étirement de l'aperçu, qui est assez courant sur les pliables et les tablettes.
Enfin, elle simplifie l'implémentation de fonctionnalités OEM avancées telles que le mode nuit et le mode portrait.
Je vais maintenant laisser la parole à Scott, qui va vous présenter certaines fonctionnalités de CameraX.
À toi, Scott.
Merci, Aman.
Bonjour !
Je m'appelle Scott.
Aujourd'hui, nous allons aborder plusieurs fonctionnalités de CameraX pour vous permettre de créer une expérience d'appareil photo de qualité professionnelle.
Commençons par le viseur adaptatif.
Il est conçu pour simplifier l'aperçu sur différents facteurs de forme et tailles d'écran.
Les utilisateurs disposent désormais d'une grande variété d'appareils, y compris des pliables, des téléphones, des ordinateurs portables et des appareils XR.
Il est donc plus important que jamais de créer une expérience d'appareil photo adaptée aux nouveaux facteurs de forme.
Et l'aperçu de l'appareil photo est notoirement difficile à obtenir, en particulier sur les appareils pliables à grand écran.
Sur ces appareils, lorsqu'ils sont dépliés, la caméra intérieure est montée horizontalement, tandis que la caméra arrière est montée verticalement.
Si vous supposez que tous les capteurs de caméra sont montés verticalement, vous risquez de créer une incohérence spatiale et de provoquer une distorsion ou une inclinaison.
Et CameraXViewfinder est conçu pour gérer entièrement la complexité à votre place, afin que l'aperçu de votre caméra fonctionne de manière fluide sur tous les facteurs de forme.
Voyons maintenant comment utiliser le 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
)
}Tout d'abord, vous déclarez la variable de contenu surfaceRequest pour stocker la requête de surface.
Désolé.
Ensuite, dans LaunchedEffect, vous définissez le fournisseur de surface pour qu'il capture un objet de requête de surface.
Ensuite, vous l'associez au cycle de vie, puis vous démarrez l'appareil photo.
Enfin, chaque fois que l'état est mis à jour avec une requête de surface valide et déclenche une recomposition, vous définissez la requête directement sur CameraXViewfinder.
Ici, vous pouvez définir l'option contentScale sur "Ajuster" ou "Recadrer", selon vos préférences.
Et voilà, vous êtes prêt.
Dans CameraX 1.7, nous avons ajouté plusieurs nouvelles fonctionnalités à CameraXViewfinder.
Cela inclut de nouveaux gestes, comme appuyer pour faire la mise au point ou pincer pour zoomer.
Il est également compatible avec le flash de l'écran, qui vous permet d'utiliser l'écran pour éclairer votre selfie.
L'implémentation de cette fonctionnalité est simple.
CameraXViewfinder(
surfaceRequest = viewModel.surfaceRequest,
contentScale = ContentScale.Fit,
isTapToFocusEnabled = true,
isPinchToZoomEnabled = true,
onScreenFlashReady =
{
imageCapture.screenFlash = it
imageCapture.flashMode = FLASH_MODE_SCREEN
}
)Il vous suffit d'activer les options "Appuyer pour faire la mise au point" et "Pincer pour zoomer".
Pour le flash de l'écran, vous obtenez l'instance ScreenFlash à partir du rappel onScreenFlashReady, puis vous la transmettez à ImageCapture et définissez un mode flash sur FLASH_MODE_SCREEN.
C'est aussi simple que ça.
Nous avons vu comment CameraX permet d'afficher un aperçu de l'appareil photo qui fonctionne de manière fluide sur tous les facteurs de forme.
Voyons maintenant comment capturer une image avec l'appareil photo sur Android XR.
Il existe trois catégories d'appareils XR. La première est celle des casques XR.
Il s'agit de casques entièrement fermés et immersifs qui utilisent une caméra pour capturer le monde physique et l'afficher à l'intérieur du casque.
et les lunettes XR filaires.
Nous avons annoncé le XREAL Aura dans la keynote de cette catégorie aujourd'hui.
Comme vous pouvez le voir dans les keynotes, il s'agit de lunettes transparentes connectées au boîtier de calcul qui vous permettent de voir le contenu virtuel superposé au monde réel.
Et enfin, les lunettes audio et les lunettes avec affichage.
Ce sont des lunettes légères.
Ils peuvent combiner l'audio, la caméra et l'écran pour des expériences mains libres et faciles à consulter.
Les deux premières catégories, à savoir les casques XR et les lunettes XR filaires, sont également compatibles avec Camera2 et CameraX, tout comme un téléphone standard.
Aujourd'hui, nous allons nous concentrer sur les lunettes audio et les lunettes avec affichage.
Sur ces lunettes, les applications s'exécutent sur le téléphone Android hôte, et non sur les lunettes, mais elles ont accès à la fois à l'appareil photo du téléphone et à celui des lunettes.
La question clé est la suivante : comment savoir à quelle caméra vous vous adressez ?
C'est donc simple.
Avec un contexte standard, CameraX accède à l'appareil photo du téléphone.
Et avec ProjectedDeviceContext, CameraX accède à la caméra des lunettes.
Ainsi, un contexte différent entraînera une caméra cible différente.
N'oubliez pas que les autorisations pour l'appareil photo et les lunettes sont distinctes.
Les autorisations pour le téléphone et les lunettes sont distinctes. Vous devez donc demander l'autorisation pour les lunettes séparément.
Vous pouvez également utiliser l'activité projetée, qui s'exécute également sur les appareils Android hôtes, mais votre UI est projetée sur les lunettes.
Ici, la règle s'inverse.
Si vous utilisez le contexte standard, vous accéderez à la caméra des lunettes.
Si vous souhaitez accéder à l'appareil photo du téléphone, vous devez utiliser HostContext.
Voyons maintenant comment capturer des images à l'aide de la caméra des lunettes.
// 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(...)Tout d'abord, vous appelez isProjectedDeviceConnected pour vérifier si les lunettes sont connectées ou non.
Ensuite, vous appelez createProjectedDeviceContext pour obtenir un projectedContext.
Deuxièmement, vous utilisez ici LifecycleCameraProvider pour initialiser CameraX, au lieu de ProcessCameraProvider.
ProcessCameraProvider est donc un singleton dont vous ne pouvez pas modifier le contexte après l'initialisation.
L'utilisation de LifecycleCameraProvider vous permet donc de modifier le contexte à tout moment.
Ensuite, vous devez désactiver le flux répétitif supplémentaire dans CameraX.
Cela est nécessaire, car les lunettes actuelles ne sont compatibles qu'avec un seul flux.
L'API est actuellement interne, mais nous la rendrons bientôt publique.
C'est aussi simple que ça.
À partir de là, il s'agit de CameraX standard. Il vous suffit de lier ImageCapture pour capturer des images via les lunettes.
OK.
Voyons maintenant comment CameraX vous aide à trouver la combinaison de fonctionnalités qui fonctionnent ensemble et à tirer le meilleur parti de l'appareil.
Les applications d'appareil photo modernes se caractérisent par des fonctionnalités puissantes, et les utilisateurs s'attendent à ce que l'appareil photo prenne en charge des fonctionnalités telles que le HDR, le 60 FPS et la stabilisation de l'aperçu, toutes en même temps.
Mais en tant que développeur, nous savons que combiner ces fonctionnalités n'est pas toujours simple.
Vérifier la compatibilité de chaque fonctionnalité ne garantit pas qu'elles fonctionneront ensemble.
Cette incertitude a forcé les développeurs à être prudents et à limiter l'expérience utilisateur sur les appareils compatibles.
Pour résoudre ce problème, nous avons introduit l'API Feature Group, qui est optimisée par le framework de requête de combinaison de fonctionnalités Camera2.
Il existe deux façons de l'utiliser : d'abord, utilisez requiredFeatureGroup pour vérifier et activer la combinaison de fonctionnalités lorsqu'elle est compatible.
Ensuite, vous pouvez indiquer la priorité de vos fonctionnalités par preferredFeatureGroup, puis CameraX choisit la meilleure combinaison compatible pour vous.
Voici un exemple.
À droite, à gauche, désolé, sans rien activer, juste un téléphone standard, un enregistrement standard.
À gauche, nous activons le HDR, le 60 FPS et la stabilisation de l'aperçu en même temps.
Comme vous pouvez le voir, la qualité est nettement améliorée, avec une vidéo plus lumineuse, plus fluide et sans tremblements.
Examinons maintenant le code.
cameraProvider.bindToLifecycle(
lifecycleOwner,
cameraSelector,
SessionConfig(
useCases = listOf(preview, videoCapture),
// HDR_HLG10 > FPS_60 > Preview Stabilization.
preferredFeatureGroup =
listOf(HDR_HLG10, FPS_60, PREVIEW_STABILIZATION),
)
)Ici, nous utilisons preferredFeatureGroup pour fournir notre liste de priorités de fonctionnalités, à savoir le HDR, puis 60 FPS, puis la stabilisation de l'aperçu.
CameraX vérifie la combinaison et sélectionne les meilleures options compatibles avec l'appareil.
Avec CameraX 1.6 et les versions ultérieures, nous ajoutons l'enregistrement 4K et la stabilisation vidéo directement dans l'API Feature Group.
// CameraX 1.6+ supports:
// 4K, video stabilization & Effect
val sessionConfig = SessionConfig(
useCases = listOf(preview, videoCapture),
requiredFeatureGroup =
setOf(UHD_RECORDING, VIDEO_STABILIZATION),
effects = listOf(overlayEffect)
)De nombreux appareils haut de gamme permettent de combiner la 4K et la stabilisation vidéo.
Désormais, avec l'API Feature Group, vous pouvez les activer ensemble en toute confiance.
De plus, vous pouvez désormais appliquer l'effet en temps réel avec l'API Feature Group, ce qui n'était pas possible auparavant.
Voyons maintenant comment obtenir une capture d'image de haute qualité dans CameraX.
CameraX est désormais compatible avec la capture RAW DNG.
Il vous donne accès à l'image RAW non traitée et non compressée directement depuis le capteur de l'appareil photo pour une retouche professionnelle.
Nous avons également amélioré notre extension Mode nuit.
Cette extension permet de prendre des photos lumineuses et nettes par faible luminosité sans utiliser de flash.
Nous avons également lancé une nouvelle API d'indicateur de mode nuit.
Il surveille l'environnement actuel pour détecter s'il est sombre et s'il est adapté au mode Nuit. Vous pouvez l'utiliser pour afficher l'indicateur du mode Nuit.
De plus, Ultra HDR est désormais compatible avec les extensions CameraX.
Cela permet d'obtenir une plage dynamique plus large et des couleurs éclatantes en mode Nuit. Le mode Nuit avec Ultra HDR est actuellement compatible avec de nombreux appareils haut de gamme récents.
Vous pouvez voir ici la différence entre les modes Normal, Nuit et Mode nuit avec Ultra HDR.
Voyons comment implémenter cela.
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)Commencez par créer un ExtensionSessionConfig pour le mode nuit, puis utilisez-le pour obtenir l'instance CameraInfo.
Ensuite, vous utilisez CameraInfo pour obtenir la fonctionnalité de capture d'image, puis vous vérifiez si l'Ultra HDR est pris en charge.
Ensuite, vous définissez le format de sortie sur Ultra HDR.
Enfin, vous recréez le ExtensionSessionConfig avec les cas d'utilisation et vous le liez au cycle de vie.
Vous êtes maintenant prêt à prendre des photos en mode Nuit avec l'Ultra HDR.
Nous avons également ajouté la compatibilité avec les photos animées à CameraX.
Les photos animées capturent le son et le mouvement autour de la photo, préservant ainsi le moment, et pas seulement une image fixe.
Nous standardisons ce format sur différents téléphones et appareils Android, et facilitons la prise en charge des photos animées.
Les photos animées seront disponibles dans CameraX au premier trimestre 2027.
Une fois qu'elle est disponible, il vous suffit de définir le mode Photos animées dans ImageCapture.Builder pour commencer à prendre des photos animées.
val imageCapture = ImageCapture.Builder()
.setMotionPhotoMode(
ImageCapture.MOTION_PHOTO_MODE_ON,
customConfig
)
.build()
cameraProvider.bindToLifecycle(.. imageCapture)
imageCapture.takePicture(...)Voyons maintenant comment CameraX permet d'enregistrer des vidéos à grande vitesse.
Les applications d'appareil photo modernes doivent impérativement pouvoir enregistrer des actions rapides avec clarté.
Il est particulièrement utile pour les scènes d'action, comme les événements sportifs.
Pour cela, l'appareil capture des images à une vitesse élevée de 120 ou 240 FPS.
Il existe deux façons de l'utiliser.
Tout d'abord, vous allez encoder à la même fréquence d'images, puis vous allez créer une vidéo à fréquence d'images élevée.
L'autre option consiste à l'encoder à 30 fps pour créer une vidéo au ralenti prête à être lue.
Par exemple, une seconde enregistrée à 120 FPS prend quatre secondes à lire à 30 FPS.
Cette extension de la durée crée donc un effet de ralenti.
Pour implémenter l'enregistrement à grande vitesse, vous créez un HighSpeedVideoSessionConfig avec votre 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, {})Ensuite, vous définissez isSlowMotionEnabled sur "true" pour l'encoder à 30 fps afin de créer une vidéo au ralenti, ou sur "false" pour créer une vidéo à fréquence d'images élevée.
Lorsque le ralenti est activé, Google Photos l'identifie automatiquement et ajoute un badge de ralenti à la miniature.
Voyons comment cela fonctionne dans Google Photos.
Ici, le ralenti donne un effet dramatique à ce chat.
Mon chat ne veut jamais que je le touche.
Désolé.
Enfin, voyons quelques améliorations apportées à notre API de caméra simultanée, l'API Concurrent Camera.
L'API simultanée permet à votre application de diffuser simultanément des flux provenant des caméras avant et arrière.
Cette année, nous avons apporté quelques améliorations à l'API Concurrent Camera.
Vous pouvez mettre à jour dynamiquement la composition de l'appareil photo après l'avoir lié au cycle de vie et l'avoir ouvert.
Cela inclut la modification de la position, de la taille et de l'ordre d'affichage.
Vous pouvez également ajouter des styles tels qu'un angle arrondi, une largeur de bordure et une couleur de bordure.
Pour ce faire, il vous suffit d'utiliser CompositionSettings pour configurer le style, comme l'arrondi des angles, la largeur et la couleur de la bordure, et d'utiliser setZOrder pour ajuster l'ordre d'affichage.
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))Ensuite, il vous suffit d'appeler setCompositionSettings sur la caméra simultanée active pour mettre à jour la composition de manière dynamique.
C'est aussi simple que ça.
Vous pouvez maintenant mettre à jour la composition de manière dynamique.
OK.
Nous avons maintenant passé en revue toutes les fonctionnalités.
Nous avons une remarque importante à faire ici. Nous vous recommandons vivement de passer à CameraX 1.6 et versions ultérieures.
Cela peut éviter les plantages sur les futurs appareils compatibles avec le nouveau format, Eclipsa Video, et garantir que votre extension CameraX continuera de fonctionner lors de la prochaine mise à jour des fonctionnalités Pixel.
C'est la fin de notre première session.
Nous avons abordé les fonctionnalités de CameraX qui vous aident à créer une expérience d'appareil photo de qualité professionnelle. Je vais maintenant laisser la parole à Aman pour notre prochaine session sur l'IA sur l'appareil.
Merci, Scott, de nous avoir présenté les fonctionnalités de CameraX.
Voyons maintenant comment vous pouvez exploiter le machine learning sur l'appareil pour créer des expériences d'appareil photo intelligentes.
Le machine learning sur l'appareil permet de nombreux cas d'utilisation de l'appareil photo dans deux domaines principaux.
La première est la précapture.
Ici, vous pouvez analyser l'aperçu de votre caméra en temps réel pour effectuer des opérations telles que la détection d'objets, ou vous pouvez améliorer l'aperçu lui-même.
La deuxième est la post-capture.
Une fois l'image capturée, vous pouvez améliorer sa qualité ou générer une description, par exemple.
Examinons maintenant quelques-unes des solutions de machine learning sur l'appareil que nous proposons pour vos applications d'appareil photo.
La première bibliothèque de ML sur l'appareil dont nous allons parler est ML Kit.
ML Kit a été conçu principalement pour résoudre des problèmes courants dans les domaines de la vision et du langage naturel, tels que la reconnaissance de texte, la détection de visages et la traduction linguistique.
L'année dernière, nous avons lancé ML Kit.
Voici quelques exemples d'API ML Kit Vision, comme la détection de maillage de visage, la segmentation de sujet et la détection d'objets en temps réel.
Oui.
L'année dernière, nous avons lancé les API GenAI.
Ces API exploitent la puissance de Gemini Nano pour permettre des fonctionnalités telles que la synthèse de texte, la description d'images et bien plus encore.
Voici un exemple de description d'image à l'aide de l'API Prompt.
Nous transmettons le bitmap de l'image avec le prompt textuel au modèle, et la description du résultat indique qu'un petit robot Android vert ressemblant à un cactus est assis sur une surface noire.
Et tout cela se passe sur l'appareil.
Impressionnant non ?
Adobe Photoshop utilise l'API Prompt pour améliorer l'expérience utilisateur dans son application.
Lorsqu'ils modifient des assets, les utilisateurs doivent souvent gérer plusieurs calques dans l'application Photoshop.
Grâce à l'API Prompt, l'application peut désormais renommer automatiquement les calques non organisés.
L'application utilise également l'API Prompt pour générer des légendes et des hashtags pour les images, ce qui est très utile pour le partage sur les réseaux sociaux.
Examinons le code permettant de décrire une image à l'aide de l'API Prompt.
// 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 -> {}
}Vous devez d'abord disposer d'un objet client generativeModel, puis vérifier si Gemini Nano est disponible ou téléchargeable sur votre appareil.
Ensuite, générez une requête de contenu en combinant votre image et le prompt textuel, puis transmettez cette requête à l'appel generateContent pour exécuter l'inférence sur Gemini Nano et obtenir la description de votre image.
// 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().textOui, la prochaine bibliothèque de ML sur l'appareil est MediaPipe.
MediaPipe propose un large éventail de solutions de machine learning pour vos applications.
Vous pouvez rapidement consulter le site Web MediaPipe pour essayer différents exemples, tels que la détection de visages, la détection d'objets, la segmentation d'images et bien plus encore.
Tous ces cas d'utilisation ressemblent beaucoup à ML Kit. Alors, quelle bibliothèque devez-vous utiliser ?
Bien que MediaPipe et ML Kit résolvent des problèmes similaires, il existe quelques différences clés.
Le premier est la compatibilité avec les plates-formes.
MediaPipe fonctionne sur Android, iOS, le Web et Python, tandis que ML Kit ne se concentre que sur Android et iOS.
Ensuite, MediaPipe permet de personnaliser les modèles, tandis que ML Kit fournit des API prêtes à l'emploi.
Enfin, alors que MediaPipe est Open Source, ML Kit est un SDK Google.
Voici un exemple de reconnaissance des gestes à l'aide des API MediaPipe Vision.
Cette API détecte les gestes de la main en temps réel à partir de la saisie par photo.
Cela peut être utilisé, par exemple, pour démarrer ou arrêter l'enregistrement de la caméra à l'aide de simples gestes de la main, tout en se tenant à distance du téléphone.
Examinons le code.
Vous devez d'abord créer l'objet GestureRecognizerOptions en fournissant le chemin d'accès au modèle, le mode de diffusion en direct et un écouteur de résultats.
// 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)Créez ensuite un objet de reconnaissance de gestes à partir des options.
Ensuite, récupérez l'ImageProxy CameraX et convertissez-le en bitmap, puis utilisez le bitmap pour générer un objet mpImage MediaPipe.
Enfin, vous devez fournir l'objet MediaPipe mpImage à l'appel recognizeAsync pour détecter les gestes en temps réel.
Ensuite, nous avons l'amélioration des images et des vidéos sur l'appareil, disponible via l'API des services Google Play.
Ce modèle offre des fonctionnalités telles que le mappage de tons, le défloutage et la super-résolution sur la plupart des appareils haut de gamme.
Voici un exemple d'image dont les tons ont été mappés et qui a été mise à l'échelle à l'aide de ce modèle.
Vous pouvez clairement voir que les couleurs du ciel et de la mer sont plus riches, et que la netteté est améliorée dans l'image retouchée.
Examinons le code de l'amélioration des images.
// 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
}Vous devez d'abord vérifier si l'appareil sous-jacent est compatible ou non et, si nécessaire, installer le module d'amélioration sur votre appareil.
Ensuite, créez l'objet EnhancementOptions en spécifiant le bitmap et les améliorations que vous souhaitez appliquer.
// 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)Ensuite, créez une session client avec l'API des services Google Play, puis définissez un rappel pour gérer le bitmap obtenu. Enfin, appelez la fonction de traitement avec le bitmap et les options pour améliorer votre image.
// 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)
}Nous avons récemment intégré les API d'amélioration vidéo à Jetpack Media3.
Vous pouvez regarder notre présentation sur les contenus multimédias pour en savoir plus sur les API Image Enhancement et Video Enhancement.
La dernière fonctionnalité de ML sur l'appareil que nous allons aborder est l'amplification de luminosité.
La capture en basse lumière est un problème courant pour les utilisateurs de smartphones.
Imaginez des utilisateurs prenant des photos ou passant des appels vidéo dans des environnements faiblement éclairés.
L'amplification de la luminosité résout ce problème en éclaircissant l'aperçu de votre appareil photo en temps réel.
Il existe deux variantes de l'Amplificateur de faible luminosité : matérielle et logicielle.
La version matérielle utilise le matériel OEM pour améliorer les images faiblement éclairées sans utiliser le machine learning.
En revanche, la version logicielle utilise un modèle de ML sur l'appareil pour améliorer les images.
Voyons rapidement comment intégrer CameraX à l'amplification logicielle de la luminosité, également appelée amplification de la luminosité Google.
L'amplification de la luminosité de Google nécessite deux surfaces clés : d'abord, une surface d'entrée pour les images brutes de l'appareil photo, puis une surface de sortie cible pour les images de sortie améliorées.
Pour l'intégrer à CameraX, il vous suffit d'encapsuler le pipeline d'amplification de la luminosité de Google dans un CameraEffect.
CameraX gère automatiquement le cycle de vie et le routage de la surface de manière transparente.
Enfin, nous lancerons l'effet d'amplification de luminosité dans CameraX au quatrième trimestre 2026.
// 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)Vous pourrez ainsi créer un objet Amplification de la luminosité et l'ajouter à la configuration de votre session, puis lier la configuration de la session au cycle de vie de votre caméra. Cela fonctionnera sur les appareils compatibles.
Nous voici arrivés au terme de ces deux sections.
Récapitulons rapidement ce que nous avons vu aujourd'hui.
Aujourd'hui, nous avons vu comment améliorer l'expérience de l'appareil photo sur différents appareils grâce à CameraX et au machine learning sur l'appareil.
Tout d'abord, avec CameraXViewfinder, vous pouvez activer l'adaptation des aperçus de votre appareil photo à différents facteurs de forme.
Nous avons ensuite vu que CameraX vous permettait également de capturer des images sur des appareils XR.
Ensuite, l'API Feature Group vous aide à trouver la combinaison optimale de fonctionnalités matérielles.
Nous avons également exploré les améliorations apportées à la capture d'image et découvert l'enregistrement vidéo à grande vitesse pour les vidéos au ralenti.
Enfin, nous avons vu comment améliorer l'aperçu de la caméra simultanée de manière dynamique.
Ensuite, nous avons examiné certaines solutions de machine learning sur l'appareil, telles que ML Kit et MediaPipe, pour créer des expériences de vision intelligentes.
Enfin, nous avons vu l'amélioration des images et des vidéos sur l'appareil, ainsi que l'amplification en faible luminosité pour éclaircir l'aperçu de votre caméra en temps réel.
Comme indiqué précédemment, nous prévoyons de désactiver Camera1. Nous vous encourageons donc vivement à migrer vers CameraX.
Pour faciliter cette migration, nous avons récemment lancé la compétence CameraX.
Nous proposons également un guide de migration et un dépôt d'exemples d'appareil photo pour vous guider.
Vous pouvez cliquer sur ce lien pour accéder à d'autres ressources sur les fonctionnalités que nous avons abordées aujourd'hui.
Il inclut des liens vers la documentation, le blog et les ressources de migration, le tout au même endroit.
Je vais faire une pause.
OK.
Merci à tous.
Nous voici arrivés au terme de notre présentation.
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)