应用基础知识

借助 CameraX 和设备端智能技术,提升跨设备的相机体验

扬声器

关于

演讲简介

用户希望第三方应用能够像设备的原生相机一样,无论在何种光照条件下或硬件层级如何,都能拍摄出高保真度的照片和视频。在不断扩展的 Android 生态系统中实现这种一致性,需要考虑关键的架构因素。本讲座将演示如何构建强大的专业级拍摄流水线,以充分发挥设备功能,同时在所有设备规格上保持可靠性。

我们将探索 CameraX 的最新进展,向您展示如何通过 Google Play 服务集成动态照片和设备端 AI 美化功能等前沿功能,以及如何使用 Jetpack XR SDK 打造空间体验。我们还将介绍 CameraX 技能,该技能可让您选择的编码智能体自动实现本演讲中介绍的概念,以及更多其他功能!

摘要

  • 由于 Camera1 已被弃用,因此 Jetpack CameraX 是 Android 应用的推荐相机库。明年,Play 政策违规处置措施将开始在 Android 18 中停用 Camera1。
  • CameraXViewfinder 可在可折叠设备等不同设备规格上调整相机预览。在 CameraX 1.7 中,它添加了手势控制和屏幕闪烁支持。
  • 音频和显示眼镜在主机手机上运行应用,同时公开两个设备上的摄像头。传递 ProjectedDeviceContext 可让 CameraX 直接从眼镜中拍摄图片。
  • Feature Group API 可帮助应用选择受支持的相机功能组合。从 CameraX 1.6 开始,它支持将 4K 录制与视频防抖功能结合使用。
  • 设备端机器学习技术通过机器学习套件和 MediaPipe 分析相机预览和拍摄的媒体内容。开发者可以运行从实时手势检测到生成式图片说明等各种任务。

资源

Gemini Notebook

主题

  • camerax
  • android-camera
  • on-device-ml
  • gemini-nano
  • mediapipe
  • ml-kit
  • android-xr

转写文稿

简介和 CameraX 概览

  • 大家好

  • 欢迎参加我们的讲座。

  • 我是 Aman,还有 Scott 和我一起。

  • 今天,我们非常高兴能与大家探讨如何借助 CameraX 和设备端智能技术,在各种设备上提升相机体验。

  • 下面,我们正式进入主题。

  • 想想您是如何使用手机的。

  • 我们经常使用手机摄像头捕捉精彩瞬间,并与亲朋好友分享。

  • 相机已成为智能手机体验的核心。

  • 事实上,在购买智能手机时,相机功能往往是关键考量因素。

  • 今天,我们将围绕两大主题,探讨如何打造卓越的相机体验。

  • 首先,我们将探索一些 CameraX 功能,然后了解一些设备端机器学习技术。

  • 我们先从 CameraX 开始。

  • Jetpack CameraX 为 Android 上的相机体验提供支持。

  • 除了 CameraX 之外,我们还有 Camera1 和 Camera2,它们是 Android 平台框架的一部分。

  • 那么,您应该使用哪个库?

  • Camera1 是 Android 上的原始相机 API。

  • 该 API 现已弃用,因此新应用应使用 Camera2 或 CameraX。

  • Camera2 继承了 Camera1。

  • 因此,如果您需要低级别的相机控件来支持复杂用例,那么 Camera2 是一个不错的选择。

  • 不过,您需要自行处理设备专用配置。

  • 最后,Jetpack CameraX 基于 Camera2 构建。

  • 它提供一致的 API,并自动处理设备专用配置。

  • 对于大多数开发者,这是推荐的选择。

  • 我们有一项非常重要的提醒。

  • 我们计划停用 Camera1,这将从明年 Android 18 中的 Play 政策违规处置开始。

  • 如果您仍在使用旧版 Camera1 API,则需要迁移到 CameraX 或 Camera2。

  • 那么,为什么要使用 CameraX 呢?

  • 首先,它支持 Android 7 及更高版本,因此几乎与 99% 的活跃 Android 设备兼容。

  • 其次,它易于使用,并为预览、拍摄和录制等常见用例提供了简单的 API,只需编写更少的代码。

  • 第三,它可减少设备特有的问题(例如预览拉伸,这在可折叠设备和平板电脑上很常见),从而在各种设备上提供一致的体验。

  • 最后,它简化了夜间模式和人像模式等高级 OEM 功能的实现。

  • 现在,我想邀请 Scott 来介绍一些 CameraX 功能。

  • Scott,交给你了。

  • 谢谢,Aman。

  • 大家好

  • 我叫 Scott。

  • 今天,我们将介绍 CameraX 的多项功能,帮助您打造专业级的相机体验。

自适应取景器功能

  • 首先是自适应取景器。

  • 它旨在彻底解决跨不同设备规格和屏幕尺寸处理预览的难题。

  • 用户现在拥有各种设备,包括折叠屏手机、手机、笔记本电脑和 XR 设备。

  • 因此,打造能够适配各种新设备规格的相机体验变得空前重要。

  • 众所周知,相机预览很难做到完美,尤其是在宽屏可折叠设备上。

  • 在这些设备上,展开时,其前置摄像头水平安装,而后置摄像头垂直安装。

  • 因此,如果您假设所有摄像头传感器都是垂直安装的,就会导致空间不匹配,从而导致失真或侧向。

  • CameraXViewfinder 旨在为您完全处理复杂性,从而确保您的相机预览在所有设备规格上无缝运行。

  • 我们来看看如何使用 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 请求对象。

  • 然后,绑定到生命周期,再启动相机。

  • 最后,每当状态更新并包含有效的 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
            }
    )
  • 只需将“点按对焦”和“双指张合缩放”设置为 true 即可。

  • 对于屏幕闪烁,您需要从 onScreenFlashReady 回调中获取屏幕闪烁实例,然后将其传递给 ImageCapture 并将闪光灯模式设置为 FLASH_MODE_SCREEN。

  • 就是这么简单

使用 Android XR 拍摄

  • 我们已经了解了 CameraX 如何实现可在所有设备规格上无缝运行的相机预览。

  • 现在,我们更进一步,在 Android XR 上使用相机拍摄。

  • XR 设备分为三类:第一类是 XR 头戴设备。

  • 这些是全封闭式沉浸式头戴设备,使用摄像头捕捉物理世界,并在头戴设备内部显示。

  • 第二种是有线 XR 眼镜。

  • 今天,我们在主旨演讲中宣布了 XREAL Aura 这一类别。

  • 正如您在主旨演讲中所见,这些透视眼镜连接到计算盒,让您可以看到叠加在现实世界之上的虚拟内容。

  • 第三种是音频眼镜和显示眼镜。

  • 这些是轻量级眼镜。

  • 它们可以结合音频、摄像头和显示屏,提供免触摸和一目了然的体验。

  • 前两类设备(XR 头戴设备和有线 XR 眼镜)也支持 Camera2 和 CameraX,就像普通手机一样。

  • 因此,今天我们将重点介绍音频眼镜和显示眼镜。

  • 在这款眼镜上,应用在 Android 手机(即主机 Android 手机)上运行,而不是在眼镜上运行,但可以同时访问手机摄像头和眼镜摄像头。

  • 关键问题是,您如何知道自己正在与哪个摄像头对话?

  • 所以很简单。

  • 使用标准 Context 时,CameraX 会访问手机摄像头。

  • 借助 ProjectedDeviceContext,CameraX 可以访问眼镜摄像头。

  • 因此,不同的 Context 会导致不同的目标摄像头。

  • 请注意,摄像头和眼镜权限是分开的。

  • 手机和眼镜权限是分开的,因此您需要单独请求眼镜权限。

  • 另一种选择是使用投影 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(而非 ProcessCameraProvider)来初始化 CameraX。

  • ProcessCameraProvider 是一个单例,初始化后无法更改 Context。

  • 因此,使用 LifecycleCameraProvider 可让您随时更改 Context。

  • 接下来,您需要在 CameraX 中停用额外的重复流。

  • 这是因为目前的眼镜仅支持单流。

  • 该 API 目前为内部 API,但我们很快就会将其公开。

  • 就是这么简单

  • 从这里开始就是标准的 CameraX,您只需绑定 ImageCapture,现在就可以通过眼镜进行拍摄了。

拍摄功能和 API

  • 好的。

  • 接下来,我们来看看 CameraX 如何帮助您找到可协同工作的功能组合,并充分利用设备。

  • 现代相机应用具有强大的功能,用户希望相机能够同时支持 HDR、60 FPS 和预览防抖等功能。

  • 但作为开发者,我们知道将这些功能组合在一起并不总是那么简单。

  • 检查各个支持功能是否受支持并不能保证它们可以协同工作。

  • 这种不确定性迫使开发者采取保守策略,限制了用户在性能强大的设备上所能获得的体验。

  • 为了解决这个问题,我们推出了 Feature Group API,该 API 由 Camera2 功能组合查询框架提供支持。

  • 该功能有两种使用方式:第一种是使用 requiredFeatureGroup 检查并启用功能组合(如果支持)。

  • 其次,您可以通过 preferredFeatureGroup 提供功能优先级,然后 CameraX 会为您选择最受支持的组合。

  • 下面是一个示例。

  • 左侧是普通手机,没有启用任何功能,只是普通录音。

  • 在左侧,我们同时启用了 HDR、60 FPS 和预览防抖。

  • 您可以看到,视频质量显著提升,画面更明亮、更流畅、更稳定。

  • 我们来看一下代码。

    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 FPS,然后是预览防抖。

  • 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。

  • 它会监控当前环境是否光线昏暗,是否适合使用夜间模式,您可以使用它来显示夜间模式指示器。

  • CameraX 扩展程序现在支持 Ultra HDR。

  • 这为夜间模式带来了更宽的动态范围和鲜艳的色彩,目前许多近期推出的高端设备都支持采用 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 年第 1 季度推出动态照片功能。

  • 该功能推出后,只需在 ImageCapture.Builder 中设置动态照片模式,即可开始拍摄动态照片。

    val imageCapture = ImageCapture.Builder()
            .setMotionPhotoMode(
                 ImageCapture.MOTION_PHOTO_MODE_ON,
                 customConfig
             )
            .build()
    
    cameraProvider.bindToLifecycle(.. imageCapture)
    imageCapture.takePicture(...)
  • 接下来,我们来了解一下 CameraX 如何实现高速视频录制。

  • 清晰地记录快速移动的动作是现代相机应用的一项基本功能。

  • 这在体育赛事等快节奏场景中尤为实用。

  • 这是通过以 120 或 240 fps 的速度进行高速拍摄来实现的。

  • 您可以通过两种方式使用它。

  • 首先,以相同的帧速率进行编码,然后制作真正的高帧率视频。

  • 另一种方法是以 30 fps 的帧率对其进行编码,以创建可直接播放的慢动作视频。

  • 例如,以 120 fps 录制的一秒视频,在以 30 fps 播放时需要四秒。

  • 因此,这段时间会产生慢动作效果。

  • 如需实现高速录制,您需要使用 videoCapture 创建 HighSpeedVideoSessionConfig。

    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 fps 的帧率进行编码,从而创建慢动作视频;或者将其设置为 false,以创建高帧率视频。

  • 启用慢动作后,Google 相册会自动识别,并在缩略图上添加慢动作标记。

  • 我们来看看它在 Google 相册中的运作方式。

  • 在这里,慢动作给猫咪带来了戏剧性的效果。

  • 我的猫从来不让我碰它。

  • 所以,抱歉。

  • 最后,我们来看看并发 API(即 Concurrent Camera API)的一些改进。

  • 并发 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 的功能,这些功能可帮助您打造专业级的相机体验。现在,我想邀请 Aman 来介绍下一个关于设备端 AI 的主题。

设备端机器学习

  • 感谢 Scott 为我们讲解 CameraX 的功能。

  • 现在,我们来看看如何利用设备端机器学习技术打造智能相机体验。

  • 设备端机器学习可在两大领域实现广泛的相机应用场景。

  • 首先是预拍摄。

  • 在这里,您可以实时分析相机预览,以执行对象检测等操作,也可以增强预览本身。

  • 第二阶段是拍摄后。

  • 拍摄图片后,您可以提升其画质,或执行生成图片说明等操作。

  • 现在,我们来深入了解一些适用于相机应用的设备端机器学习解决方案。

  • 我们要讨论的第一个设备端机器学习库是机器学习套件。

  • 机器学习套件主要用于解决视觉和自然语言领域的常见问题,例如文本识别、人脸检测和语言翻译。

  • 去年,我们推出了 ML Kit。

  • 以下是机器学习套件 Vision API 的一些示例,例如人脸网格检测、主体分割和实时对象检测。

  • 是。

  • 去年,我们推出了生成式 AI API。

  • 这些 API 利用 Gemini Nano 的强大功能,可实现文本总结、图片描述等功能。

  • 以下是使用 Prompt API 生成图片描述的示例。

  • 我们将图片位图和文本提示词传递给模型,结果描述为:一个绿色的小 Android 机器人,采用仙人掌般的设计,坐在一块黑色表面上。

  • 所有这些操作都在设备上完成。

  • 很酷吧?

  • Adobe Photoshop 一直在使用 Prompt API 来提升其应用内的用户体验。

  • 在编辑素材资源时,用户通常需要在 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 网站,尝试各种示例,例如人脸检测、对象检测、图像分割等。

  • 所有这些应用场景听起来都与机器学习套件非常相似,那么您应该使用哪个库呢?

  • 虽然 MediaPipe 和机器学习套件解决的问题类似,但两者之间存在一些关键差异。

  • 首先是平台支持。

  • MediaPipe 适用于 Android、iOS、Web 和 Python,而机器学习套件仅专注于 Android 和 iOS。

  • 其次,MediaPipe 支持模型自定义,而 ML Kit 提供现成可用的 API。

  • 最后,MediaPipe 是开源的,而机器学习套件是 Google SDK。

  • 以下是使用 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)
  • 然后,根据这些选项创建一个手势识别器对象。

  • 接下来,获取 CameraX ImageProxy 并将其转换为位图,然后使用该位图生成 MediaPipe mpImage 对象。

  • 最后,您需要向 recognizeAsync 调用提供 MediaPipe mpImage 对象,以实时检测手势。

图片和视频增强

  • 接下来,我们推出了通过 Google Play 服务 API 提供的设备端图片和视频增强功能。

  • 此模型可在大多数高端设备上提供色调映射、去模糊和智能超清等功能。

  • 以下是使用此模型进行色调映射和放大后的图片示例。

  • 您可以清楚地看到,美化后的图片中天空和大海的色彩更加丰富,清晰度也更高。

  • 我们来看看图片自动美化的代码。

    // 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)
  • 接下来,使用 Google Play 服务 API 创建客户端会话,然后定义一个回调来处理生成的位图,最后使用位图和选项调用处理函数来美化图片。

    // 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)
    }
  • 我们最近将视频增强 API 集成到了 Jetpack Media3 中。

  • 您可以观看我们的媒体讲座,详细了解图片和视频增强 API。

  • 我们要介绍的最后一个设备端机器学习功能是弱光增强。

  • 在弱光环境下拍摄是智能手机用户经常面临的一个问题。

  • 想象一下,用户在昏暗的环境中拍摄照片或进行视频通话。

  • 弱光增强功能可实时提亮相机预览画面,从而解决此问题。

  • 弱光增强功能有两种变体:硬件和软件。

  • 硬件版本使用 OEM 硬件来增强光线昏暗的帧,而无需使用机器学习。

  • 相比之下,软件版本则使用设备端机器学习模型来增强画面。

  • 我们来快速了解一下如何将 CameraX 与软件弱光增强功能(也称为 Google 弱光增强功能)集成。

  • Google 弱光增强功能需要两个关键 Surface:首先,您需要一个用于原始相机帧的输入 Surface,以及一个用于增强后输出帧的输出目标 Surface。

  • 如需与 CameraX 集成,只需将 Google 弱光增强流水线封装在 CameraEffect 中即可。

  • CameraX 会自动为您无缝处理 Surface 生命周期和路由。

  • 最后,我们将在 2026 年第 4 季度在 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)
  • 这样,您就可以创建弱光增强对象,并将其添加到会话配置中,然后将会话配置绑定到相机生命周期,这样它就可以在受支持的设备上运行。

摘要和迁移

  • 至此,两个部分的内容都已介绍完毕。

  • 我们来快速总结一下今天介绍的所有内容。

  • 今天,我们探讨了如何借助 CameraX 和设备端机器学习技术,在各种设备上提升相机体验。

  • 首先,借助 CameraXViewfinder,您可以让相机预览画面适应不同的设备规格。

  • 接下来,我们了解到 CameraX 还支持在 XR 设备上进行拍摄。

  • 然后,Feature Group API 可帮助您找到最佳硬件功能组合。

  • 我们还探索了“图片拍摄”功能的升级,并了解了用于慢动作视频的高速视频拍摄功能。

  • 最后,我们学习了如何动态增强并发摄像头预览。

  • 接下来,我们介绍了一些设备端机器学习解决方案,例如机器学习套件和 MediaPipe,它们可用于打造智能视觉体验。

  • 最后,我们还看到了设备端图片和视频增强功能,以及可实时提亮相机预览画面的弱光增强功能。

  • 如前所述,我们计划停用 Camera1,因此强烈建议您迁移到 CameraX。

  • 为了支持此迁移,我们最近推出了 CameraX skill。

  • 我们还提供了迁移指南和相机示例代码库,为您提供指导。

  • 您可以访问此链接,获取有关我们今天介绍的功能的更多资源。

  • 其中包含文档、博客和迁移资源的链接,一应俱全。

  • 我先暂停一下。

  • 好的。

  • 谢谢大家。

  • 我们的讲座到此结束。

代码

代码段

“自适应取景器功能”中 04:18 处的 Kotlin 代码段
var surfaceRequest by remember {
    mutableStateOf<SurfaceRequest?>(null)
}

LaunchedEffect(...) {
    preview.setSurfaceProvider { surfaceRequest = it }
    cameraProvider.bindToLifecycle(..., preview)
}

surfaceRequest?.let { request ->
    CameraXViewfinder(
        surfaceRequest = request,
        contentScale = ContentScale.Fit
    )
}
“自适应取景器功能”中 05:14 处的 Kotlin 代码段
CameraXViewfinder(
    surfaceRequest = viewModel.surfaceRequest,
    contentScale = ContentScale.Fit,
    isTapToFocusEnabled = true,
    isPinchToZoomEnabled = true,
    onScreenFlashReady =
        {
           imageCapture.screenFlash = it
           imageCapture.flashMode = FLASH_MODE_SCREEN
        }
)
“使用 Android XR 拍摄照片和视频”中 07:30 处的 Kotlin 代码段
// 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(...)
“捕获功能和 API”中 09:42 处的 Kotlin 代码段
cameraProvider.bindToLifecycle(
    lifecycleOwner,
    cameraSelector,
    SessionConfig(
        useCases = listOf(preview, videoCapture),
        // HDR_HLG10 > FPS_60 > Preview Stabilization.
        preferredFeatureGroup =
           listOf(HDR_HLG10, FPS_60, PREVIEW_STABILIZATION),
    )
)
“拍摄功能和 API”中 09:58 处的 Kotlin 代码段
// CameraX 1.6+ supports:
// 4K, video stabilization & Effect
val sessionConfig = SessionConfig(
   useCases = listOf(preview, videoCapture),
   requiredFeatureGroup =
     setOf(UHD_RECORDING, VIDEO_STABILIZATION),
   effects = listOf(overlayEffect)
)
“拍摄功能和 API”视频中 11:20 处的 Kotlin 代码段
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)
“Capture features and APIs”中 12:11 处的 Kotlin 代码段
val imageCapture = ImageCapture.Builder()
        .setMotionPhotoMode(
             ImageCapture.MOTION_PHOTO_MODE_ON,
             customConfig
         )
        .build()

cameraProvider.bindToLifecycle(.. imageCapture)
imageCapture.takePicture(...)
“拍摄功能和 API”中 13:06 处的 Kotlin 代码段
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, {})
“拍摄功能和 API”中 14:18 处的 Kotlin 代码段
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))
“设备端机器学习”中 17:51 处的 Kotlin 代码段
// 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 -> {}
}
“设备端机器学习”中 18:06 处的 Kotlin 代码段
// 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
“设备端机器学习”中 19:38 处的 Kotlin 代码段
// 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)
“图片和视频增强”中 20:44 处的 Kotlin 代码段
// 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
}
“图片和视频增强”中 20:57 处的 Kotlin 代码段
// 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)
“图片和视频增强”中 21:05 处的 Kotlin 代码段
// 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)
}
“图片和视频增强”中 22:51 处的 Kotlin 代码段
// 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)