Os usuários esperam que os apps de terceiros capturem fotos e vídeos com a mesma alta fidelidade da câmera nativa do dispositivo, independente das condições de iluminação ou do nível de hardware. Oferecer essa consistência no ecossistema Android em expansão envolve considerações importantes sobre a arquitetura. Esta sessão demonstra como criar pipelines de captura robustos e profissionais que maximizam os recursos do dispositivo e permanecem confiáveis em todos os formatos.
Vamos explorar os avanços mais recentes da CameraX, mostrando como integrar recursos de ponta, como fotos com movimento e ajustes com IA no dispositivo, usando o Google Play Services, além de experiências espaciais com o SDK do Jetpack para XR. Também vamos analisar nossa habilidade do CameraX, que permite que o agente de programação de sua escolha implemente automaticamente os conceitos desta palestra e muito mais.
CameraXViewfinder adapta as prévias da câmera em diferentes formatos, como dispositivos dobráveis. Na versão 1.7, ela adiciona controles por gestos e suporte ao flash da tela.ProjectedDeviceContext permite que o CameraX capture imagens diretamente dos óculos.Transcrição
Olá!
A sessão já vai começar!
Meu nome é Aman, e este é o Scott.
Hoje, vamos falar sobre como você pode melhorar as experiências de câmera em vários dispositivos com o CameraX e a inteligência no dispositivo.
Vamos começar!
Pense em como você usa seus smartphones.
Muitas vezes, usamos a câmera do celular para registrar momentos e compartilhar com as pessoas que amamos.
A câmera se tornou central na experiência com o smartphone.
Na verdade, uma boa câmera costuma ser um fator decisivo na hora de comprar um smartphone.
Hoje, vamos abordar dois temas amplos para criar experiências de câmera premium.
Primeiro, vamos conhecer alguns recursos do CameraX e, em seguida, algumas técnicas de machine learning no dispositivo.
Vamos começar com a CameraX.
O Jetpack CameraX é responsável pela experiência da câmera no Android.
Além do CameraX, também temos o Camera1 e o Camera2, que fazem parte do framework da plataforma Android.
Então, qual biblioteca você deve usar?
A Camera1 foi a API de câmera original do Android.
Ela foi descontinuada, então os novos apps precisam usar a Camera2 ou a CameraX.
A Camera2 sucedeu a Camera1.
Portanto, se você precisa de controle de câmera de baixo nível para casos de uso complexos, a Camera2 é uma boa opção.
No entanto, é necessário lidar com as configurações específicas do dispositivo por conta própria.
Por fim, o Jetpack CameraX é baseado na Camera2.
Ela fornece uma API consistente e processa automaticamente as configurações específicas do dispositivo.
É a opção recomendada para a maioria dos desenvolvedores.
Temos um aviso muito importante.
Planejamos desativar a Camera1, e isso vai começar com as restrições da política do Google Play no próximo ano, no Android 18.
Se você ainda usa a API Camera1 legada, migre para o CameraX ou o Camera2.
Então, por que usar a CameraX?
Primeiro, ele é compatível com o Android 7 e versões mais recentes, o que o torna compatível com quase 99% dos dispositivos Android ativos.
Em segundo lugar, ele é fácil de usar e oferece APIs simples para casos de uso comuns, como visualização, captura e gravação com menos código.
Em terceiro lugar, ela oferece uma experiência consistente em todos os dispositivos, reduzindo problemas específicos, como o alongamento da visualização, que é bastante comum em dobráveis e tablets.
Por fim, ele simplifica a implementação de recursos avançados de OEM, como o modo noturno e o modo retrato.
Agora, gostaria de convidar o Scott para falar sobre alguns dos recursos do CameraX.
Agora é com você, Scott.
Obrigado, Aman.
Olá!
Meu nome é Scott.
Hoje, vamos falar sobre vários recursos da CameraX para que você possa criar uma experiência de câmera de nível profissional.
O primeiro é o Visor Adaptável.
Ele foi projetado para eliminar completamente as dificuldades de lidar com a visualização em diferentes formatos e tamanhos de tela.
Os usuários agora têm uma variedade de dispositivos, incluindo dobráveis, smartphones, laptops e dispositivos XR.
Por isso, é mais importante do que nunca criar uma experiência de câmera adaptada aos novos formatos.
E a visualização da câmera é notoriamente difícil de acertar, especialmente em dispositivos dobráveis widescreen.
Nesses dispositivos, quando abertos, a câmera interna é montada horizontalmente, enquanto a traseira é montada verticalmente.
Portanto, se você presumir que todos os sensores de detecção da câmera são montados verticalmente, isso vai levar a uma incompatibilidade espacial e causar distorção ou inclinação.
E o CameraXViewfinder foi projetado para lidar completamente com a complexidade para você, garantindo que a visualização da câmera funcione perfeitamente em todos os formatos.
Vamos ver como usar o elemento combinável 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
)
}Primeiro, declare a variável de conteúdo surfaceRequest para armazenar a solicitação de superfície.
Entendi.
Em segundo lugar, em LaunchedEffect, você define o provedor de superfície para capturar um objeto de solicitação de superfície.
Em seguida, vincule ao ciclo de vida e inicie a câmera.
Por fim, sempre que o estado for atualizado com uma solicitação de superfície válida e acionar a recomposição, defina a solicitação diretamente para o CameraXViewfinder.
E aqui, você pode definir o contentScale para ajustar ou cortar, dependendo da sua preferência.
Pronto, agora está tudo certo.
Na CameraX 1.7, adicionamos vários recursos novos ao CameraXViewfinder.
Isso inclui novos gestos, como tocar para focar ou fazer gesto de pinça para dar zoom.
Ele também é compatível com o flash da tela, que permite usar uma tela para iluminar sua selfie.
É fácil implementar esse recurso.
CameraXViewfinder(
surfaceRequest = viewModel.surfaceRequest,
contentScale = ContentScale.Fit,
isTapToFocusEnabled = true,
isPinchToZoomEnabled = true,
onScreenFlashReady =
{
imageCapture.screenFlash = it
imageCapture.flashMode = FLASH_MODE_SCREEN
}
)Basta ativar o toque para focar e o gesto de pinça para dar zoom.
Para o flash da tela, você recebe a instância do flash da tela do callback onScreenFlashReady, transmite para o ImageCapture e define um modo de flash como FLASH_MODE_SCREEN.
Pronto.
Vimos como o CameraX permite uma prévia da câmera que funciona perfeitamente em todos os formatos.
Agora vamos dar um passo adiante com uma captura de câmera no Android XR.
Existem três categorias de dispositivos XR: primeiro, os headsets XR.
São headsets totalmente fechados e imersivos que usam uma câmera para capturar o mundo físico e exibir dentro do headset.
E, em segundo lugar, os óculos XR com fio.
Anunciamos o XREAL Aura na palestra de hoje nessa categoria.
Como você pode ver nas palestras, esses são os óculos transparentes conectados ao disco de computação que permitem visualizar o conteúdo virtual sobreposto ao mundo real.
E o terceiro, óculos com áudio e tela virtual.
Estes são os óculos leves.
Eles podem combinar áudio, câmera e tela para experiências viva-voz e de fácil visualização.
As duas primeiras categorias, headset XR e óculos XR com fio, também são compatíveis com Camera2 e CameraX, assim como um smartphone comum.
Por isso, hoje vamos focar apenas nos óculos com áudio e com tela virtual.
Nesses óculos, os apps são executados no smartphone Android host, em vez de nos óculos, mas aqui há acesso à câmera do smartphone e à câmera dos óculos.
A questão principal é: como saber com qual câmera você está falando?
É simples.
Com o Context padrão, o CameraX acessa a câmera do smartphone.
E com o ProjectedDeviceContext, o CameraX acessa a câmera dos óculos.
Portanto, contextos diferentes levam a câmeras de destino diferentes.
E lembre-se de que as permissões da câmera e dos óculos são separadas.
As permissões do smartphone e dos óculos são separadas, então você precisa solicitar a permissão dos óculos separadamente.
A outra opção é usar a atividade projetada, que também é executada nos dispositivos Android host, mas a interface é projetada nos óculos.
E aqui a regra muda.
Se você estiver usando o Contexto padrão, vai acessar a câmera dos óculos.
Se você quiser acessar a câmera do smartphone, use o HostContext.
Vamos analisar o código e ver como capturar usando a câmera dos óculos.
// 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(...)Primeiro, invoque isProjectedDeviceConnected para conferir se os óculos estão conectados ou não.
Em seguida, chame createProjectedDeviceContext para receber um projectedContext.
Em segundo lugar, aqui você usa o LifecycleCameraProvider para inicializar o CameraX, em vez do ProcessCameraProvider.
O ProcessCameraProvider é um singleton, então não é possível mudar o contexto depois da inicialização.
Portanto, o uso do LifecycleCameraProvider permite que você mude o contexto quando quiser.
Em seguida, desative o fluxo de repetição adicional no CameraX.
Isso é necessário porque os óculos atuais só aceitam um único stream.
No momento, a API é interna, mas será disponibilizada ao público em breve.
Pronto.
A partir daqui, é o CameraX padrão. Basta vincular o ImageCapture e você já estará capturando pelos óculos.
Ok.
Em seguida, vamos ver como o CameraX ajuda você a encontrar a combinação de recursos que funcionam juntos e aproveitar ao máximo o dispositivo.
Os apps de câmera modernos são definidos por recursos avançados, e os usuários esperam que a câmera ofereça suporte a recursos como HDR, 60 FPS e estabilização de visualização, tudo ao mesmo tempo.
Mas, como desenvolvedores, sabemos que combinar esses recursos nem sempre é simples.
Verificar se cada recurso é compatível não garante que eles vão funcionar juntos.
Essa incerteza força os desenvolvedores a serem conservadores e limita o que os usuários podem experimentar em dispositivos compatíveis.
Para resolver isso, introduzimos a API Feature Group, que usa o framework de consulta de combinação de recursos da Camera2.
Há duas maneiras de usar: primeiro, use requiredFeatureGroup para verificar e ativar a combinação de recursos quando ela for compatível.
Em segundo lugar, você pode fornecer sua prioridade de recurso por preferredFeatureGroup, e a CameraX vai escolher a melhor combinação compatível para você.
E aqui está o exemplo.
À direita, à esquerda, desculpe, sem nada ativado, apenas um smartphone comum, gravação comum.
À esquerda, ativamos o HDR, 60 QPS e a Estabilização de Prévia ao mesmo tempo.
E você pode ver que a qualidade melhorou significativamente com o vídeo mais brilhante, suave e sem trepidação.
Vamos analisar o código.
cameraProvider.bindToLifecycle(
lifecycleOwner,
cameraSelector,
SessionConfig(
useCases = listOf(preview, videoCapture),
// HDR_HLG10 > FPS_60 > Preview Stabilization.
preferredFeatureGroup =
listOf(HDR_HLG10, FPS_60, PREVIEW_STABILIZATION),
)
)Aqui, usamos o preferredFeatureGroup para fornecer nossa lista de prioridades de recursos, que é HDR, 60 QPS e, em seguida, estabilização de prévia.
O CameraX vai verificar a combinação e selecionar as melhores opções compatíveis com o dispositivo.
Com o CameraX 1.6, adicionamos a gravação em 4K e a estabilização de vídeo diretamente à API Groups.
// CameraX 1.6+ supports:
// 4K, video stabilization & Effect
val sessionConfig = SessionConfig(
useCases = listOf(preview, videoCapture),
requiredFeatureGroup =
setOf(UHD_RECORDING, VIDEO_STABILIZATION),
effects = listOf(overlayEffect)
)A combinação de 4K e estabilização de vídeo é compatível com muitos dispositivos premium.
Agora, com a API Feature Group, é possível ativar os dois juntos com confiança.
Além disso, agora você tem flexibilidade para aplicar o efeito em tempo real com a API Feature Group, o que não era possível antes.
Agora vamos falar sobre como fazer capturas de imagens de alta qualidade na CameraX.
A CameraX agora oferece suporte à captura de RAW DNG.
Ele dá acesso à imagem RAW não processada e não compactada diretamente do sensor da câmera para edição profissional.
Em seguida, melhoramos nossa extensão do Modo Noturno.
Essa extensão permite capturar fotos claras e nítidas em condições de pouca luz sem usar o flash.
Além disso, lançamos uma nova API de indicador de modo noturno.
Ele vai monitorar quando o ambiente atual estiver escuro e for adequado para o Modo Noturno, e você poderá usá-lo para mostrar o indicador do Modo Noturno.
Além disso, o Ultra HDR agora é compatível com as extensões da CameraX.
Isso traz um intervalo dinâmico mais amplo e cores vibrantes para o Modo Noturno, que, com o Ultra HDR, é compatível com muitos dispositivos premium recentes.
Aqui você pode ver a diferença entre os modos normal, noturno e Modo Noturno com Ultra HDR.
Vamos ver como implementar isso.
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)Primeiro, crie um ExtensionSessionConfig para o Modo noturno e use-o para acessar a instância da CameraInfo.
Em seguida, use o CameraInfo para acessar o recurso de captura de imagens e consulte se o Ultra HDR é compatível.
Em seguida, defina o formato de saída como Ultra HDR.
Por fim, crie o ExtensionSessionConfig novamente com os casos de uso e vincule ao ciclo de vida.
Agora você já pode usar o Modo Noturno com fotos em Ultra HDR.
Também estamos trazendo o suporte para fotos com movimento para o CameraX.
As fotos com movimento capturam o som e o movimento ao redor da foto, preservando o momento, não apenas uma imagem estática.
Padronizamos isso em diferentes smartphones e dispositivos Android, facilitando o suporte às fotos com movimento.
As fotos com movimento vão chegar à CameraX no primeiro trimestre de 2027.
Quando estiver disponível, basta definir o modo de fotos com movimento em ImageCapture.Builder para começar a capturar as fotos com movimento.
val imageCapture = ImageCapture.Builder()
.setMotionPhotoMode(
ImageCapture.MOTION_PHOTO_MODE_ON,
customConfig
)
.build()
cameraProvider.bindToLifecycle(.. imageCapture)
imageCapture.takePicture(...)Em seguida, vamos analisar como a CameraX permite a gravação de vídeos em alta velocidade.
Registrar ações rápidas com nitidez é um recurso essencial dos apps de câmera modernos.
É especialmente útil para cenários de ritmo acelerado, como esportes.
Isso é feito com uma captura de alta velocidade a 120 ou 240 qps.
Há duas maneiras de usá-lo.
Primeiro, codifique com o mesmo frame rate e, em seguida, crie um vídeo com frame rate alto.
A outra opção é codificar a 30 qps para criar um vídeo em câmera lenta pronto para reprodução.
Por exemplo, um segundo gravado a 120 QPS leva quatro segundos para ser reproduzido a 30 QPS.
Assim, esse período cria um efeito de câmera lenta.
Para implementar a gravação em alta velocidade, crie um HighSpeedVideoSessionConfig com seu 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, {})Em seguida, defina isSlowMotionEnabled como verdadeiro para codificar a 30 qps e criar um vídeo em câmera lenta ou defina como falso para criar um vídeo com frame rate de alta velocidade.
Quando a câmera lenta está ativada, o Google Fotos identifica isso automaticamente e adiciona um selo de câmera lenta à miniatura.
Vamos ver como isso funciona no Google Fotos.
Aqui, a câmera lenta dá um efeito dramático ao gato.
Meu gato nunca deixa eu tocar nele.
Sinto muito.
Por fim, vamos conferir algumas melhorias na nossa API de câmera simultânea.
A API simultânea permite que o app faça streaming das câmeras frontal e traseira ao mesmo tempo.
Este ano, fizemos algumas melhorias na API Concurrent Camera.
É possível atualizar dinamicamente a composição da câmera depois de vincular ao ciclo de vida e abrir uma câmera.
Isso inclui mudar a posição, o tamanho e a ordem de renderização.
Também é possível adicionar estilos, como cantos arredondados, largura da borda e cor da borda.
Para fazer isso, basta usar o CompositionSettings para configurar a estilização, como arredondamento de cantos, largura da borda e cor, e usar um setZOrder para ajustar a ordem de renderização.
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))Em seguida, basta invocar o setCompositionSettings na câmera simultânea ativa para atualizar a composição de forma dinâmica.
Pronto.
Agora você pode atualizar a composição dinamicamente.
Ok.
Agora que já analisamos todos os recursos,
Temos um aviso importante: recomendamos que você faça upgrade para o CameraX 1.6 e versões mais recentes.
Isso pode evitar falhas em dispositivos futuros que oferecem suporte ao novo formato, o Eclipsa Video, e também garantir que a extensão do CameraX continue funcionando na atualização futura do Pixel.
Com isso, encerramos nossa primeira sessão.
Abordamos os recursos da CameraX que ajudam a criar uma experiência de câmera de nível profissional. Agora, gostaria de convidar Aman para falar sobre nossa próxima sessão, que é sobre IA no dispositivo.
Obrigado, Scott, por nos explicar os recursos do CameraX.
Agora, vamos ver como você pode usar o machine learning no dispositivo para criar experiências de câmera inteligentes.
O machine learning no dispositivo permite uma ampla variedade de casos de uso de câmera em duas áreas principais.
A primeira é a pré-captura.
Aqui, é possível analisar a visualização da câmera em tempo real para fazer coisas como detecção de objetos ou melhorar a própria visualização.
A segunda é a pós-captura.
Depois de capturar a imagem, você pode melhorar a qualidade dela ou gerar uma descrição, por exemplo.
Agora, vamos falar sobre algumas das soluções de aprendizado de máquina no dispositivo que temos para seus apps de câmera.
A primeira biblioteca de ML no dispositivo que vamos abordar é o Kit de ML.
O Kit de ML foi criado principalmente para resolver problemas comuns nos domínios de visão e linguagem natural, como reconhecimento de texto, detecção facial e tradução de idiomas.
No ano passado, apresentamos o Kit de ML.
Confira alguns exemplos de APIs Vision do Kit de ML, como detecção de malha facial, segmentação de objetos e detecção de objetos em tempo real.
Sim.
No ano passado, lançamos as APIs de IA generativa.
Essas APIs usam o poder do Gemini Nano para oferecer recursos como resumo de texto, descrição de imagens e muito mais.
Confira um exemplo de descrição de imagem usando a API Prompt.
Passamos o bitmap da imagem com o comando de texto para o modelo, e a descrição do resultado é um pequeno robô Android verde com um design semelhante a um cacto em uma superfície preta.
E tudo isso acontece no dispositivo.
Não é incrível?
O Adobe Photoshop usa a API Prompt para melhorar a experiência do usuário no app.
Ao editar recursos, os usuários geralmente precisam gerenciar várias camadas no app Photoshop.
Usando a API Prompt, agora o app pode renomear automaticamente as camadas desorganizadas.
O app também usa a API Prompt para gerar legendas e hashtags para imagens, o que é muito útil para compartilhar nas redes sociais.
Vamos analisar o código para descrição de imagens usando a 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 -> {}
}Primeiro, você precisa de um objeto de cliente generativeModel. Depois, verifique se o Gemini Nano está disponível ou pode ser baixado no seu dispositivo.
Em seguida, gere uma solicitação de conteúdo combinando a imagem e o comando de texto e, por fim, forneça essa solicitação à chamada generateContent para executar a inferência no Gemini Nano e receber a descrição da imagem.
// 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().textSim, a próxima biblioteca de ML no dispositivo é o MediaPipe.
O MediaPipe oferece uma ampla variedade de soluções de machine learning para seus aplicativos.
Acesse o site do MediaPipe para testar diferentes exemplos, como detecção facial, detecção de objetos, segmentação de imagens e muito mais.
Todos esses casos de uso são muito semelhantes ao Kit de ML. Então, qual biblioteca você deve usar?
Embora o MediaPipe e o Kit de ML resolvam problemas semelhantes, há algumas diferenças importantes.
Primeiro, o suporte à plataforma.
O MediaPipe funciona no Android, iOS, na Web e no Python, enquanto o Kit de ML se concentra apenas no Android e no iOS.
Além disso, o MediaPipe oferece personalização de modelos, enquanto o Kit de ML fornece APIs prontas para uso.
Por fim, enquanto o MediaPipe é de código aberto, o Kit de ML é um SDK do Google.
Veja um exemplo de reconhecimento de gestos usando as APIs MediaPipe Vision.
Essa API detecta os gestos com as mãos em tempo real usando a entrada de câmera.
Isso pode ser usado, por exemplo, para iniciar ou interromper a gravação da câmera usando gestos com a mão simples enquanto você está longe do smartphone.
Vamos analisar o código.
Primeiro, você precisa criar o objeto GestureRecognizerOptions, fornecendo o caminho do modelo, o modo de transmissão ao vivo e um listener de resultados.
// 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)Em seguida, crie um objeto de reconhecimento de gestos com base nas opções.
Em seguida, pegue o ImageProxy do CameraX, converta-o em um bitmap e use o bitmap para gerar um objeto mpImage do MediaPipe.
Por fim, forneça o objeto mpImage do MediaPipe à chamada recognizeAsync para detectar gestos em tempo real.
Em seguida, temos o recurso de melhoria de imagens e vídeos no dispositivo, disponível pela API do Google Play Services.
Esse modelo oferece recursos como mapeamento de tons, remoção de desfoque e super resolução na maioria dos dispositivos premium.
Veja um exemplo de uma imagem com mapeamento de tons e upscaling usando esse modelo.
É possível ver claramente as cores mais ricas no céu e no mar e a nitidez aprimorada na imagem melhorada.
Vamos analisar o código para melhoria de imagem.
// 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
}Primeiro, é preciso consultar se o dispositivo é compatível ou não e, se necessário, instalar o módulo de melhoria no dispositivo.
Em seguida, crie o objeto EnhancementOptions especificando o bitmap e as melhorias que você quer usar.
// 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)Em seguida, crie uma sessão de cliente com a API Google Play Services, defina um callback para processar o bitmap resultante e, por fim, chame a função de processamento com o bitmap e as opções para melhorar a imagem.
// 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)
}Recentemente, integramos as APIs de melhoria de vídeo ao Jetpack Media3.
Confira nossa palestra sobre mídia para saber mais sobre as APIs Image and Video Enhancement.
O último recurso de ML no dispositivo que vamos abordar é o Modo de Pouca Luz.
Capturar imagens com pouca luz é um problema comum para usuários de smartphones.
Pense em usuários que capturam imagens ou fazem videochamadas em ambientes com pouca iluminação.
O recurso de aumento do modo pouca luz resolve esse problema, clareando a visualização da câmera em tempo real.
Há duas variantes da Otimização em Pouca Luz: hardware e software.
A versão do hardware usa o hardware do OEM para ajustar os frames com pouca iluminação sem usar aprendizado de máquina.
Já a versão do software usa um modelo de ML no dispositivo para melhorar os frames.
Vamos dar uma olhada rápida em como integrar a CameraX com o Software Low Light Boost, também conhecido como Google Low Light Boost.
O Google Low Light Boost requer duas superfícies principais: primeiro, você precisa de uma superfície de entrada para os frames brutos da câmera e uma superfície de destino de saída para os frames de saída aprimorados.
Para integrar com o CameraX, basta envolver o pipeline do Google Low Light Boost em um CameraEffect.
O CameraX processa automaticamente o ciclo de vida e o roteamento da superfície para você.
Por fim, vamos lançar o efeito de otimização em pouca luz no CameraX no quarto trimestre de 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)Com isso, você pode criar um objeto de otimização em pouca luz e adicioná-lo à configuração da sessão. Depois, vincule a configuração da sessão ao ciclo de vida da câmera, e ela vai funcionar nos dispositivos compatíveis.
Isso nos leva ao fim das duas seções.
Vamos resumir rapidamente o que abordamos hoje.
Hoje, falamos sobre como melhorar as experiências de câmera em vários dispositivos com o CameraX e o machine learning no dispositivo.
Primeiro, com o CameraXViewfinder, é possível ativar as prévias da câmera para se adaptarem a diferentes formatos.
Em seguida, vimos que o CameraX também permite capturar em dispositivos XR.
Em seguida, a API Groups ajuda a encontrar a combinação ideal de recursos de hardware.
Também exploramos as atualizações da Captura de Imagens e aprendemos sobre a gravação de vídeos em alta velocidade para vídeos em câmera lenta.
Por fim, aprendemos a melhorar a prévia da câmera simultânea de forma dinâmica.
Depois, analisamos algumas das soluções de machine learning no dispositivo, como o Kit de ML e o MediaPipe, para criar experiências de visão inteligente.
Por fim, vimos a melhoria de imagem e vídeo no dispositivo, além do aumento do modo pouca luz para clarear a prévia da câmera em tempo real.
Como mencionado anteriormente, planejamos desativar a Camera1. Por isso, recomendamos que você migre para a CameraX.
Para facilitar essa migração, lançamos recentemente a habilidade do CameraX.
Também temos um guia de migração e o repositório de exemplos de câmera para orientar você.
Acesse este link para conferir mais recursos sobre as funcionalidades que abordamos hoje.
Ele inclui links para a documentação, o blog e os recursos de migração, tudo em um só lugar.
Vou fazer uma pausa rápida.
Ok.
Agradeço a todos.
Isso encerra nossa conversa.
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)