用户希望第三方应用能够像设备的原生相机一样,无论在何种光照条件下或硬件层级如何,都能拍摄出高保真度的照片和视频。在不断扩展的 Android 生态系统中实现这种一致性,需要考虑关键的架构因素。本讲座将演示如何构建强大的专业级拍摄流水线,以充分发挥设备功能,同时在所有设备规格上保持可靠性。
我们将探索 CameraX 的最新进展,向您展示如何通过 Google Play 服务集成动态照片和设备端 AI 美化功能等前沿功能,以及如何使用 Jetpack XR SDK 打造空间体验。我们还将介绍 CameraX 技能,该技能可让您选择的编码智能体自动实现本演讲中介绍的概念,以及更多其他功能!
CameraXViewfinder 可在可折叠设备等不同设备规格上调整相机预览。在 CameraX 1.7 中,它添加了手势控制和屏幕闪烁支持。ProjectedDeviceContext 可让 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。
就是这么简单
我们已经了解了 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,现在就可以通过眼镜进行拍摄了。
好的。
接下来,我们来看看 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。
我们还提供了迁移指南和相机示例代码库,为您提供指导。
您可以访问此链接,获取有关我们今天介绍的功能的更多资源。
其中包含文档、博客和迁移资源的链接,一应俱全。
我先暂停一下。
好的。
谢谢大家。
我们的讲座到此结束。
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)