사용자는 조명 조건이나 하드웨어 등급과 관계없이 서드 파티 앱이 기기의 기본 카메라와 동일한 고화질로 사진과 동영상을 촬영할 것으로 기대합니다. 확장되는 Android 생태계 전반에서 이러한 일관성을 제공하려면 주요 아키텍처 고려사항이 필요합니다. 이 세션에서는 모든 폼 팩터에서 안정성을 유지하면서 기기 기능을 극대화하는 강력하고 전문적인 캡처 파이프라인을 구성하는 방법을 보여줍니다.
CameraX의 최신 개선사항을 살펴보고, Jetpack XR SDK를 사용한 공간 경험과 함께 Google Play 서비스를 통해 모션 사진 및 온디바이스 AI 보정과 같은 최첨단 기능을 통합하는 방법을 보여드립니다. 또한 원하는 코딩 에이전트가 이 강연의 개념을 자동으로 구현할 수 있도록 하는 CameraX 기술 등도 살펴봅니다.
CameraXViewfinder는 폴더블과 같은 다양한 폼 팩터에 맞게 카메라 미리보기를 조정합니다. CameraX 1.7에서는 동작 컨트롤과 화면 플래시 지원이 추가되었습니다.ProjectedDeviceContext를 전달하면 CameraX가 글라스에서 직접 이미지를 캡처할 수 있습니다.스크립트
안녕하세요.
이 세션에 오신 것을 환영합니다.
저는 아만이고, 스콧도 함께 있습니다.
오늘은 CameraX와 기기 내 인텔리전스를 사용하여 여러 기기에서 카메라 환경을 개선하는 방법에 대해 이야기해 보겠습니다.
그럼 시작해 보겠습니다.
휴대전화를 어떻게 사용하는지 생각해 보세요.
우리는 종종 휴대전화 카메라를 사용하여 순간을 포착하고 사랑하는 사람들과 공유합니다.
카메라는 스마트폰 경험의 핵심이 되었습니다.
실제로 좋은 카메라는 스마트폰을 구매할 때 중요한 요소가 되는 경우가 많습니다.
오늘은 프리미엄 카메라 환경을 구축하기 위한 두 가지 광범위한 주제를 다룹니다.
먼저 몇 가지 CameraX 기능을 살펴본 다음 기기 내 머신러닝 기법에 관해 알아봅니다.
CameraX부터 시작하겠습니다.
Jetpack CameraX는 Android의 카메라 환경을 지원합니다.
CameraX 외에도 Android 플랫폼 프레임워크의 일부인 Camera1과 Camera2가 있습니다.
그렇다면 어떤 라이브러리를 사용해야 할까요?
Camera1은 Android의 원래 카메라 API였습니다.
현재는 지원 중단되었으므로 새 앱은 Camera2 또는 CameraX를 사용해야 합니다.
Camera2는 Camera1의 후속 버전입니다.
따라서 복잡한 사용 사례를 위해 하위 수준의 카메라 제어가 필요한 경우 Camera2를 사용하는 것이 좋습니다.
하지만 기기별 구성을 직접 처리해야 합니다.
마지막으로 Jetpack CameraX는 Camera2를 기반으로 빌드됩니다.
일관된 API를 제공하고 기기별 구성을 자동으로 처리합니다.
대부분의 개발자에게 권장되는 옵션입니다.
매우 중요한 공지사항이 있습니다.
Google은 Camera1을 사용 중지할 계획이며, 이는 내년 Android 18의 Play 정책 시행부터 시작됩니다.
기존 Camera1 API를 계속 사용하고 있다면 CameraX 또는 Camera2로 이전해야 합니다.
그렇다면 CameraX를 사용해야 하는 이유는 무엇일까요?
첫째, Android 7 이상을 지원하므로 활성 Android 기기의 거의 99% 와 호환됩니다.
둘째, 사용하기 쉽고 미리보기, 캡처, 녹화와 같은 일반적인 사용 사례를 위한 간단한 API를 제공하므로 코드가 적게 필요합니다.
셋째, 폴더블 및 태블릿에서 흔히 발생하는 미리보기 늘이기와 같은 기기별 문제를 줄여 기기 전반에서 일관된 환경을 제공합니다.
마지막으로 야간 모드 및 인물 사진과 같은 고급 OEM 기능의 구현을 간소화합니다.
이제 스콧을 모시고 CameraX 기능에 관해 이야기해 보겠습니다.
스콧, 부탁할게요.
아만, 감사합니다.
안녕하세요.
제 이름은 스콧입니다.
오늘은 전문가 수준의 카메라 환경을 구축할 수 있는 여러 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 콘텐츠 변수를 선언합니다.
죄송합니다.
두 번째로, LaunchedEffect에서 노출 영역 요청 객체를 포착하도록 노출 영역 제공자를 설정합니다.
그런 다음 수명 주기에 결합하고 카메라를 시작합니다.
마지막으로, 유효한 노출 영역 요청으로 상태가 업데이트되고 재구성이 트리거될 때마다 요청을 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가 글라스 카메라에 액세스합니다.
따라서 컨텍스트가 다르면 타겟 카메라가 달라집니다.
카메라와 안경 권한은 별개입니다.
휴대전화와 글라스 권한은 별개이므로 글라스 권한을 별도로 요청해야 합니다.
다른 옵션은 호스트 Android 기기에서도 실행되는 Projected Activity를 사용하는 것이지만 UI는 안경에 투사됩니다.
여기서 규칙이 바뀝니다.
표준 컨텍스트를 사용하는 경우 안경 카메라에 액세스하게 됩니다.
휴대전화 카메라에 액세스하려면 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를 가져옵니다.
두 번째로, 여기서는 ProcessCameraProvider 대신 LifecycleCameraProvider를 사용하여 CameraX를 초기 설정합니다.
ProcessCameraProvider는 초기화 후에 컨텍스트를 변경할 수 없는 싱글톤입니다.
따라서 LifecycleCameraProvider를 사용하면 언제든지 컨텍스트를 변경할 수 있습니다.
그런 다음 CameraX에서 추가 반복 스트림을 사용 중지해야 합니다.
현재 안경은 단일 스트림만 지원하므로 이 작업이 필요합니다.
API는 현재 내부용이지만 곧 공개할 예정입니다.
이제 모든 작업이 완료되었습니다.
여기서부터는 표준 CameraX입니다. ImageCapture를 바인딩하기만 하면 이제 글라스를 통해 캡처할 수 있습니다.
좋습니다.
다음으로 CameraX를 사용하여 함께 작동하는 기능 조합을 찾고 기기를 최대한 활용하는 방법을 알아보겠습니다.
최신 카메라 앱은 강력한 기능으로 정의되며 사용자는 카메라가 HDR, 60FPS, 미리보기 손떨림 보정과 같은 기능을 동시에 지원할 것으로 기대합니다.
하지만 개발자로서 이러한 기능을 결합하는 것이 항상 간단하지는 않다는 것을 알고 있습니다.
개별 지원 기능 지원을 확인한다고 해서 함께 작동한다는 보장은 없습니다.
이러한 불확실성으로 인해 개발자는 보수적으로 접근할 수밖에 없으며, 이는 사용자가 고성능 기기에서 경험할 수 있는 것을 제한합니다.
이 문제를 해결하기 위해 Camera2 기능 조합 쿼리 프레임워크를 기반으로 하는 Feature Group API를 도입했습니다.
이를 사용하는 방법에는 두 가지가 있습니다. 첫째, requiredFeatureGroup를 사용하여 기능 조합이 지원되는 경우 이를 확인하고 사용 설정합니다.
두 번째로, preferredFeatureGroup를 통해 기능 우선순위를 제공하면 CameraX가 가장 적합한 지원 조합을 선택합니다.
예를 들어 보겠습니다.
오른쪽, 아니 왼쪽에는 아무것도 사용 설정하지 않은 일반 휴대전화로 일반 녹음을 진행하고 있습니다.
왼쪽에서는 HDR, 60FPS, 미리보기 흔들림 보정을 모두 사용 설정합니다.
더 밝고 부드러우며 흔들림 없는 동영상으로 화질이 크게 개선된 것을 확인할 수 있습니다.
코드를 살펴보겠습니다.
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, 60FPS, 미리보기 안정화)을 제공합니다.
그러면 CameraX가 조합을 확인하고 기기에서 지원하는 최적의 조합을 선택합니다.
CameraX 1.6 이상에서는 Feature Group API에 4K 녹화 및 동영상 흔들림 보정 기능이 추가됩니다.
// 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 확장 프로그램에서 울트라 HDR이 지원됩니다.
이를 통해 야간 모드에 더 넓은 다이내믹 레인지와 생생한 색상을 제공하며, 울트라 HDR을 사용하는 야간 모드는 현재 많은 최신 프리미엄 기기에서 지원됩니다.
여기에서 일반 모드, 야간 모드, 울트라 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를 사용하여 이미지 캡처 기능을 가져오고 울트라 HDR이 지원되는지 쿼리합니다.
그런 다음 출력 형식을 울트라 HDR로 설정합니다.
마지막으로 사용 사례를 사용하여 ExtensionSessionConfig를 다시 만들고 수명 주기에 바인딩합니다.
이제 울트라 HDR 사진으로 야간 모드를 사용하여 촬영할 준비가 되었습니다.
또한 CameraX에 모션 사진 지원을 추가합니다.
모션 사진은 사진 주변의 소리와 움직임을 포착하여 정지 이미지가 아닌 순간을 보존합니다.
Google은 다양한 휴대전화, Android 기기에서 이를 표준화하고 모션 사진을 더 쉽게 지원할 수 있도록 합니다.
모션 사진은 2027년 1분기에 CameraX에 도입될 예정입니다.
사용 가능해지면 ImageCapture.Builder에서 모션 사진 모드를 설정하기만 하면 모션 사진을 촬영할 수 있습니다.
val imageCapture = ImageCapture.Builder()
.setMotionPhotoMode(
ImageCapture.MOTION_PHOTO_MODE_ON,
customConfig
)
.build()
cameraProvider.bindToLifecycle(.. imageCapture)
imageCapture.takePicture(...)다음으로 CameraX에서 고속 동영상 녹화를 사용 설정하는 방법을 살펴보겠습니다.
빠르게 움직이는 동작을 선명하게 녹화하는 것은 최신 카메라 앱의 필수 기능입니다.
스포츠와 같이 빠르게 진행되는 시나리오에 특히 유용합니다.
이는 120fps 또는 240fps의 고속 캡처를 통해 달성됩니다.
사용 방법은 두 가지입니다.
먼저 동일한 프레임 속도로 인코딩한 다음 실제 고프레임 속도 동영상을 만듭니다.
다른 옵션은 30fps로 인코딩하여 바로 재생할 수 있는 슬로우 모션 동영상을 만드는 것입니다.
예를 들어 120fps로 녹화된 1초는 30fps로 재생하는 데 4초가 걸립니다.
따라서 이 시간 연장은 슬로 모션 효과를 만듭니다.
고속 녹화를 구현하려면 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로 설정하여 30fps로 인코딩하여 슬로 모션 동영상을 만들거나 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 동영상을 지원하는 향후 기기에서 비정상 종료를 방지하고 향후 Pixel 업데이트에서도 CameraX 확장 프로그램이 계속 작동하도록 할 수 있습니다.
이것으로 첫 번째 세션을 마무리하겠습니다.
전문가 수준의 카메라 환경을 빌드하는 데 도움이 되는 CameraX 기능을 다뤘습니다. 이제 아만을 초대하여 기기 내 AI에 관한 다음 세션을 진행하겠습니다.
스콧, CameraX 기능을 설명해 주셔서 감사합니다.
이제 온디바이스 머신러닝을 활용하여 지능형 카메라 환경을 구축하는 방법을 살펴보겠습니다.
온디바이스 머신러닝은 두 가지 주요 영역에서 다양한 카메라 사용 사례를 지원합니다.
첫 번째는 사전 캡처입니다.
여기에서 카메라 미리보기를 실시간으로 분석하여 객체 감지와 같은 작업을 수행하거나 미리보기 자체를 개선할 수 있습니다.
두 번째는 캡처 후입니다.
이미지를 캡처한 후에는 이미지 품질을 보정하거나 이미지에 대한 설명을 생성하는 등의 작업을 할 수 있습니다.
이제 카메라 앱을 위한 기기 내 머신러닝 솔루션을 살펴보겠습니다.
첫 번째로 살펴볼 온디바이스 ML 라이브러리는 ML Kit입니다.
ML Kit는 주로 텍스트 인식, 얼굴 인식, 언어 번역과 같은 비전 및 자연어 도메인의 일반적인 문제를 해결하기 위해 빌드되었습니다.
작년에 ML Kit를 도입했습니다.
다음은 ML Kit Vision API의 몇 가지 예입니다(예: 얼굴 메시 인식, 피사체 분할, 실시간 객체 감지).
예.
작년에 Google은 GenAI 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예, 다음으로 살펴볼 온디바이스 ML 라이브러리는 MediaPipe입니다.
MediaPipe는 애플리케이션을 위한 다양한 머신러닝 솔루션을 제공합니다.
MediaPipe 웹사이트를 방문하면 얼굴 인식, 객체 감지, 이미지 분할 등 다양한 샘플을 빠르게 사용해 볼 수 있습니다.
이러한 사용 사례는 모두 ML Kit와 매우 유사한데, 어떤 라이브러리를 사용해야 할까요?
MediaPipe와 ML Kit는 유사한 문제를 해결하지만 몇 가지 주요 차이점이 있습니다.
첫 번째는 플랫폼 지원입니다.
MediaPipe는 Android, iOS, 웹, Python에서 작동하는 반면 ML Kit는 Android와 iOS에만 중점을 둡니다.
다음으로 MediaPipe는 모델 맞춤설정을 제공하는 반면 ML Kit는 바로 사용 가능한 API를 제공합니다.
마지막으로 MediaPipe는 오픈소스이지만 ML Kit는 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 객체를 생성합니다.
마지막으로 MediaPipe mpImage 객체를 recognizeAsync 호출에 제공하여 실시간으로 동작을 감지해야 합니다.
다음으로, 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)
}최근 Jetpack Media3에 동영상 자동 개선 기능 API를 통합했습니다.
미디어 강연을 통해 Image and Video Enhancement API에 관해 자세히 알아보세요.
마지막으로 살펴볼 온디바이스 ML 기능은 저조도 부스트입니다.
저조도 환경에서 촬영하는 것은 스마트폰 사용자에게 일반적인 문제입니다.
사용자가 어두운 환경에서 이미지를 촬영하거나 영상 통화를 하는 경우를 생각해 보세요.
어두운 조명 부스트는 카메라 미리보기를 실시간으로 밝게 하여 이 문제를 해결합니다.
라이트 부스트에는 하드웨어와 소프트웨어의 두 가지 변형이 있습니다.
하드웨어 버전은 OEM 하드웨어를 사용하여 머신러닝을 사용하지 않고 어두운 프레임을 개선합니다.
반면 소프트웨어 버전은 온디바이스 ML 모델을 사용하여 프레임을 개선합니다.
CameraX를 Google 어두운 조명 부스트라고도 하는 소프트웨어 어두운 조명 부스트와 통합하는 방법을 간단히 살펴보겠습니다.
Google Low Light Boost에는 두 가지 주요 표시 경로가 필요합니다. 먼저 원시 카메라 프레임의 입력 표시 경로와 보정된 출력 프레임의 출력 타겟 표시 경로가 필요합니다.
CameraX와 통합하려면 Google 어두운 조명 부스트 파이프라인을 CameraEffect 내에 래핑하기만 하면 됩니다.
CameraX는 노출 영역 수명 주기와 라우팅을 자동으로 원활하게 처리합니다.
마지막으로, 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를 사용하면 하드웨어 기능의 최적 조합을 찾을 수 있습니다.
또한 이미지 캡처 업그레이드를 살펴보고 슬로 모션 동영상을 위한 고속 동영상 녹화에 대해 알아봤습니다.
마지막으로 동시 카메라 미리보기를 동적으로 개선하는 방법을 알아봤습니다.
다음으로 지능형 비전 환경을 빌드하기 위한 ML Kit 및 MediaPipe와 같은 온디바이스 머신러닝 솔루션을 살펴봤습니다.
마지막으로, 카메라 미리보기를 실시간으로 밝게 해주는 어두운 조명 부스트와 함께 기기 내 이미지 및 동영상 자동 개선 기능을 살펴보았습니다.
앞서 언급했듯이 Camera1을 사용 중지할 예정이므로 CameraX로 이전하는 것이 좋습니다.
이러한 이전을 지원하기 위해 최근 CameraX 기술을 출시했습니다.
또한 이전 가이드와 카메라 샘플 저장소도 참고할 수 있습니다.
이 링크를 방문하면 오늘 다룬 기능에 관한 추가 리소스를 확인할 수 있습니다.
문서, 블로그, 마이그레이션 리소스 링크가 모두 한곳에 포함되어 있습니다.
잠시 멈추겠습니다.
좋습니다.
감사합니다.
이것으로 발표를 마치겠습니다.
var surfaceRequest by remember {
mutableStateOf<SurfaceRequest?>(null)
}
LaunchedEffect(...) {
preview.setSurfaceProvider { surfaceRequest = it }
cameraProvider.bindToLifecycle(..., preview)
}
surfaceRequest?.let { request ->
CameraXViewfinder(
surfaceRequest = request,
contentScale = ContentScale.Fit
)
}CameraXViewfinder(
surfaceRequest = viewModel.surfaceRequest,
contentScale = ContentScale.Fit,
isTapToFocusEnabled = true,
isPinchToZoomEnabled = true,
onScreenFlashReady =
{
imageCapture.screenFlash = it
imageCapture.flashMode = FLASH_MODE_SCREEN
}
)// In a coroutine
val imageCapture = ImageCapture.Builder().build()
ProjectedContext.isProjectedDeviceConnected(activity, coroutineContext)
.collectLatest { isConnected ->
if (isConnected) {
val projectedContext = ProjectedContext.createProjectedDeviceContext(activity)
val cameraConfig = CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig())
.setRepeatingStreamForced(false)
.build()
val cameraProvider = LifecycleCameraProvider.createInstance(projectedContext, cameraConfig)
cameraProvider.bindToLifecycle(activity, DEFAULT_BACK_CAMERA, imageCapture)
}
}
// Shutter button clicked
imageCapture.takePicture(...)cameraProvider.bindToLifecycle(
lifecycleOwner,
cameraSelector,
SessionConfig(
useCases = listOf(preview, videoCapture),
// HDR_HLG10 > FPS_60 > Preview Stabilization.
preferredFeatureGroup =
listOf(HDR_HLG10, FPS_60, PREVIEW_STABILIZATION),
)
)// CameraX 1.6+ supports:
// 4K, video stabilization & Effect
val sessionConfig = SessionConfig(
useCases = listOf(preview, videoCapture),
requiredFeatureGroup =
setOf(UHD_RECORDING, VIDEO_STABILIZATION),
effects = listOf(overlayEffect)
)val cameraInfo = cameraProvider.getCameraInfo(cameraSelector,
ExtensionSessionConfig(ExtensionMode.NIGHT, extensionsManager))
val imageCapabilities = ImageCapture.getImageCaptureCapabilities(cameraInfo)
val imageCapture = ImageCapture.Builder().apply {
if (imageCapabilities.supportedOutputFormats
.contains(OUTPUT_FORMAT_JPEG_ULTRA_HDR)) {
setOutputFormat(OUTPUT_FORMAT_JPEG_ULTRA_HDR)
}
}.build()
val extensionSessionConfig = ExtensionSessionConfig(
ExtensionMode.NIGHT, extensionsManager, preview, imageCapture
)
cameraProvider.bindToLifecycle(..., extensionSessionConfig)val imageCapture = ImageCapture.Builder()
.setMotionPhotoMode(
ImageCapture.MOTION_PHOTO_MODE_ON,
customConfig
)
.build()
cameraProvider.bindToLifecycle(.. imageCapture)
imageCapture.takePicture(...)val recorder = Recorder.Builder().build()
val videoCapture = VideoCapture.withOutput(recorder)
val frameRateRange = cameraInfo.getSupportedFrameRateRanges(
HighSpeedVideoSessionConfig(videoCapture, preview)).first()
val sessionConfig = HighSpeedVideoSessionConfig(
videoCapture,
preview,
frameRateRange = frameRateRange,
isSlowMotionEnabled = true
)
cameraProvider.bindToLifecycle(
lifecycleOwner, cameraSelector, sessionConfig)
// Start recording slow motion videos.
val recording = recorder.prepareRecording(context, outputOption)
.start(executor, {})val concurrentCamera =
cameraProvider.bindToLifecycle(list(config1, config2))
// CameraX 1.7 features: rounded corner, border width & color
val pipSettings = CompositionSettings.Builder()
.setOffset(0.2f, 0.2f)
.setScale(0.35f, 0.35f)
.setRoundedCornerRatio(0.15f)
.setBorderWidthRatio(0.02f)
.setBorderColor(Color.WHITE)
.setZOrder(1)
.build()
// CameraX 1.7 features: dynamical composition update
concurrentCamera.setCompositionSettings(
listOf(mainSettings, pipSettings))// 1. Initialize the on-device GenerativeModel client
val generativeModel = Generation.getClient()
// 2. Check whether Gemini Nano is available on the device
val status = generativeModel.checkStatus()
when (status) {
FeatureStatus.UNAVAILABLE -> {}
FeatureStatus.DOWNLOADABLE -> {}
FeatureStatus.DOWNLOADING -> {}
FeatureStatus.AVAILABLE -> {}
}// 3. Build a multimodal request with the image and text prompt
val request = generateContentRequest(
ImagePart(bitmap),
TextPart("Describe this image in detail.")
) {}
// 4. Run inference on-device and get the description
val response = generativeModel.generateContent(request)
val description = response.candidates.first().text// 1. Build GestureRecognizerOptions
val options = GestureRecognizerOptions.builder().setBaseOptions(
BaseOptions.builder().setModelAssetPath(MP_RECOGNIZER_TASK).build())
.setRunningMode(RunningMode.LIVE_STREAM)
.setResultListener { result, image -> /* Handle detected gesture */ }
.build()
// 2. Create the GestureRecognizer
val gestureRecognizer = GestureRecognizer.createFromOptions(context, options)
// 3. Convert CameraX ImageProxy to MediaPipe MPImage
val bitmap = rotateBitmap(imageProxy.toBitmap(),
imageProxy.imageInfo.rotationDegrees)
val mpImage = BitmapImageBuilder(bitmap).build()
// 4. Run gesture recognition on the image
gestureRecognizer.recognizeAsync(mpImage, frameTime)// Step 1: Verify device support & install module
val enhancementClient = Enhancement.getClient(context)
suspend fun prepare(): Boolean {
if (!enhancementClient.isDeviceSupported().await()) return false
if (!enhancementClient.isModuleInstalled().await()) {
check(enhancementClient.installModule().await()) {
"Enhancement module install failed"
}
}
return true
}// Step 2: Select Enhancement options & create session
val options = EnhancementOptions(
width = bitmap.width,
height = bitmap.height,
enhancementMode = EnhancementMode.BITMAP,
isTonemappingEnabled = true,
isDeblurAndDenoisePhotoEnabled = true,
isDeblurAndDenoiseVideoEnabled = false,
isUpscalePhotoEnabled = false,
isUpscaleVideoEnabled = false
)
val enhancementSession = enhancementClient
.createSessionAsync(options, enhancementExecutor)// Step 3: Process Enhancement
val enhancedBitmap = suspendCancellableCoroutine { continuation ->
val enhancementCallback = object : EnhancementCallback {
override fun onBitmapProcessed(result: Bitmap) {
if (continuation.isActive) continuation.resume(result)
}
override fun onError(statusCode: Int) {
if (continuation.isActive) {
continuation.resumeWithException(
Exception("Bitmap processing failed: $statusCode")
)
}
}
override fun onSurfaceProcessed(timestamp: Long) {}
}
enhancementSession.process(bitmap, options, enhancementCallback)
}// Integrates SW Low Light Boost
val lowLightBoostEffect =
LowLightBoostEffect(
CameraEffect.PREVIEW or
CameraEffect.VIDEO_CAPTURE or
CameraEffect.IMAGE_CAPTURE
)
val sessionConfig = SessionConfig(
useCases = listOf(preview, imageCapture),
effects = lowLightBoostEffect
)
cameraProvider.bindToLifecycle(.. sessionConfig)