안녕하세요. CameraX 및 Jetpack Compose에 관한 시리즈에 다시 오신 것을 환영합니다. 이전 게시물에서는 카메라 미리보기 설정의 기본사항을 다루고 탭하여 초점 맞추기 기능을 추가했습니다.
🧱 1부: 새로운 camera-compose 아티팩트를 사용하여 기본 카메라 미리보기 빌드 권한 처리 및 기본 통합을 다루었습니다.
👆 2부: Compose 제스처 시스템, 그래픽, 코루틴을 사용하여 시각적 탭하여 초점 맞추기 구현
🔦 3부 (이 게시물): 더 풍부한 사용자 환경을 위해 카메라 미리보기 위에 Compose UI 요소를 오버레이하는 방법 살펴보기
📂 4부: 적응형 API 및 Compose 애니메이션 프레임워크를 사용하여 폴더블 휴대전화에서 탁자 모드로 원활하게 애니메이션 처리
이 게시물에서는 시각적으로 더 흥미로운 내용을 다루겠습니다. 얼굴 인식을 효과의 기반으로 사용하여 카메라 미리보기 위에 스포트라이트 효과를 구현합니다. 왜냐고요? 잘 모르겠습니다. 하지만 멋있어 보입니다 🙂. 그리고 더 중요한 것은 센서 좌표를 UI 좌표로 쉽게 변환하여 Compose에서 사용할 수 있는 방법을 보여줍니다.
얼굴 인식 사용 설정
먼저 얼굴 인식을 사용 설정하도록 CameraPreviewViewModel을 수정해 보겠습니다. CameraX에서 기본 Camera2 API와 상호작용할 수 있는 Camera2Interop API를 사용합니다. 이렇게 하면 CameraX에서 직접 노출되지 않는 카메라 기능을 사용할 수 있습니다. 다음과 같이 변경해야 합니다.
- 얼굴 경계를
Rect목록으로 포함하는 StateFlow를 만듭니다. - 얼굴 인식을 사용 설정하는 FULL로
STATISTICS_FACE_DETECT_MODE캡처 요청 옵션을 설정합니다. CaptureCallback을 설정하여 캡처 결과에서 얼굴 정보를 가져옵니다.
class CameraPreviewViewModel : ViewModel() { ... private val _sensorFaceRects = MutableStateFlow(listOf<Rect>()) val sensorFaceRects: StateFlow<List<Rect>> = _sensorFaceRects.asStateFlow() private val cameraPreviewUseCase = Preview.Builder() .apply { Camera2Interop.Extender(this) .setCaptureRequestOption( CaptureRequest.STATISTICS_FACE_DETECT_MODE, CaptureRequest.STATISTICS_FACE_DETECT_MODE_FULL ) .setSessionCaptureCallback(object : CameraCaptureSession.CaptureCallback() { override fun onCaptureCompleted( session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult ) { super.onCaptureCompleted(session, request, result) result.get(CaptureResult.STATISTICS_FACES) ?.map { face -> face.bounds.toComposeRect() } ?.toList() ?.let { faces -> _sensorFaceRects.update { faces } } } }) } .build().apply { ... }
이러한 변경사항이 적용되면 뷰 모델은 이제 센서 좌표에서 감지된 얼굴의 경계 상자를 나타내는 Rect 객체 목록을 내보냅니다.
센서 좌표를 UI 좌표로 변환
이전 섹션에 저장한 감지된 얼굴의 경계 상자는 센서 좌표계 의 좌표를 사용합니다. UI에서 경계 상자를 그리려면 Compose 좌표계에서 올바르도록 이러한 좌표를 변환해야 합니다. 다음과 같이 해야 합니다.
- 센서 좌표 를 미리보기 버퍼 좌표 로 변환합니다.
- 미리보기 버퍼 좌표 를 Compose UI 좌표 로 변환합니다.
이러한 변환은 변환 행렬을 사용하여 실행됩니다. 각 변환에는 자체 행렬이 있습니다.
SurfaceRequest는TransformationInfo행렬을 포함하는sensorToBufferTranform인스턴스를 보유합니다.CameraXViewfinder에는 연결된CoordinateTransformer가 있습니다. 이전 블로그 게시물에서 이 변환기를 사용하여 탭하여 초점 맞추기 좌표를 변환한 것을 기억하실 것입니다.
변환을 실행할 수 있는 도우미 메서드를 만들 수 있습니다.
private fun List<Rect>.transformToUiCoords( transformationInfo: SurfaceRequest.TransformationInfo?, uiToBufferCoordinateTransformer: MutableCoordinateTransformer ): List<Rect> = this.map { sensorRect -> val bufferToUiTransformMatrix = Matrix().apply { setFrom(uiToBufferCoordinateTransformer.transformMatrix) invert() } val sensorToBufferTransformMatrix = Matrix().apply { transformationInfo?.let { setFrom(it.sensorToBufferTransform) } } val bufferRect = sensorToBufferTransformMatrix.map(sensorRect) val uiRect = bufferToUiTransformMatrix.map(bufferRect) uiRect }
- 감지된 얼굴 목록을 반복하고 각 얼굴에 변환을 실행합니다.
CameraXViewfinder에서 가져온CoordinateTransformer.transformMatrix는 기본적으로 UI에서 버퍼 좌표로 좌표를 변환합니다. 여기서는 행렬이 버퍼 좌표를 UI 좌표로 변환하는 방식으로 작동하기를 원합니다. 따라서invert()메서드를 사용하여 행렬을 반전합니다.- 먼저
sensorToBufferTransformMatrix를 사용하여 센서 좌표에서 버퍼 좌표로 얼굴을 변환한 다음bufferToUiTransformMatrix를 사용하여 이러한 버퍼 좌표를 UI 좌표로 변환합니다.
스포트라이트 효과 구현
이제 CameraPreviewContent 컴포저블을 업데이트하여 스포트라이트 효과를 그려 보겠습니다. Canvas 컴포저블을 사용하여 미리보기 위에 그라데이션 마스크를 그려 감지된 얼굴을 표시합니다.
@Composable fun CameraPreviewContent( viewModel: CameraPreviewViewModel, modifier: Modifier = Modifier, lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current ) { val surfaceRequest by viewModel.surfaceRequest.collectAsStateWithLifecycle() val sensorFaceRects by viewModel.sensorFaceRects.collectAsStateWithLifecycle() val transformationInfo by produceState<SurfaceRequest.TransformationInfo?>(null, surfaceRequest) { try { surfaceRequest?.setTransformationInfoListener(Runnable::run) { transformationInfo -> value = transformationInfo } awaitCancellation() } finally { surfaceRequest?.clearTransformationInfoListener() } } val shouldSpotlightFaces by remember { derivedStateOf { sensorFaceRects.isNotEmpty() && transformationInfo != null} } val spotlightColor = Color(0xDDE60991) .. surfaceRequest?.let { request -> val coordinateTransformer = remember { MutableCoordinateTransformer() } CameraXViewfinder( surfaceRequest = request, coordinateTransformer = coordinateTransformer, modifier = .. ) AnimatedVisibility(shouldSpotlightFaces, enter = fadeIn(), exit = fadeOut()) { Canvas(Modifier.fillMaxSize()) { val uiFaceRects = sensorFaceRects.transformToUiCoords( transformationInfo = transformationInfo, uiToBufferCoordinateTransformer = coordinateTransformer ) // Fill the whole space with the color drawRect(spotlightColor) // Then extract each face and make it transparent uiFaceRects.forEach { faceRect -> drawRect( Brush.radialGradient( 0.4f to Color.Black, 1f to Color.Transparent, center = faceRect.center, radius = faceRect.minDimension * 2f, ), blendMode = BlendMode.DstOut ) } } } } }
사용 방법은 다음과 같습니다.
- 뷰 모델에서 얼굴 목록을 수집합니다.
- 감지된 얼굴 목록이 변경될 때마다 전체 화면을 재구성하지 않도록
derivedStateOf를 사용하여 얼굴이 감지되었는지 추적합니다. 그런 다음AnimatedVisibility와 함께 사용하여 색상 오버레이를 애니메이션 처리할 수 있습니다. surfaceRequest에는SurfaceRequest.TransformationInfo에서 센서 좌표를 버퍼 좌표로 변환하는 데 필요한 정보가 포함되어 있습니다.produceState함수를 사용하여 표면 요청에 리스너를 설정하고 컴포저블이 컴포지션 트리를 벗어날 때 이 리스너를 삭제합니다.Canvas를 사용하여 전체 화면을 덮는 반투명한 분홍색 직사각형을 그립니다.Canvas그리기 블록 내부에 있을 때까지sensorFaceRects변수의 읽기를 연기합니다. 그런 다음 좌표를 UI 좌표로 변환합니다.- 감지된 얼굴을 반복하고 각 얼굴에 얼굴 직사각형 내부를 투명하게 만드는 방사형 그라데이션을 그립니다.
BlendMode.DstOut을 사용하여 분홍색 직사각형에서 그라데이션을 잘라내어 스포트라이트 효과를 만듭니다.
참고: 카메라를 DEFAULT_FRONT_CAMERA로 변경하면 스포트라이트가 미러링되는 것을 확인할 수 있습니다. _이는_ Google 문제 추적기에서 추적되는 알려진 문제입니다.
결과
이 코드를 사용하면 감지된 얼굴을 강조 표시하는 완전히 작동하는 스포트라이트 효과가 있습니다. 전체 코드 스니펫은 여기에서 확인할 수 있습니다.
이 효과는 시작에 불과합니다. Compose의 기능을 사용하면 시각적으로 멋진 카메라 환경을 무수히 만들 수 있습니다. 센서 및 버퍼 좌표를 Compose UI 좌표로 변환하고 다시 변환할 수 있다는 것은 모든 Compose UI 기능을 활용하고 기본 카메라 시스템과 원활하게 통합할 수 있음을 의미합니다. 애니메이션, 고급 UI 그래픽, 간단한 UI 상태 관리, 전체 제스처 제어를 사용하면 상상력이 한계입니다.
이 시리즈의 마지막 게시물에서는 적응형 API와 Compose 애니메이션 프레임워크를 사용하여 폴더블 기기에서 다양한 카메라 UI 간에 원활하게 전환하는 방법을 살펴보겠습니다. 계속해서 많은 관심 부탁드립니다.
이 블로그의 코드 스니펫에는 다음 라이선스가 있습니다.
// Copyright 2024 Google LLC. SPDX-License-Identifier: Apache-2.0
검토하고 의견을 제공해 주신 Nick Butcher, Alex Vanyo, Trevor McGuire, Don Turner, Lauren Ward께 감사드립니다. Yasith Vidanaarachch의 노력 덕분에 가능했습니다.
계속 읽기
-
방법
이 도움말에서는 Compose에서 waitUntil 테스트 API를 사용하여 특정 조건이 충족될 때까지 기다리는 방법을 알아봅니다.
Jose Alcérreca • 3분 길이
-
방법
Google은 과도한 배터리 소모가 Android 사용자의 가장 큰 관심사라는 점을 인식하고 개발자가 더 전력 효율적인 앱을 빌드할 수 있도록 지원하기 위해 상당한 조치를 취해 왔습니다.
Alice Yuan • 8분 길이
-
방법
Google은 기기 내 모델과 클라우드 모델을 모두 사용하여 AI 지원 기능의 예를 제공하고 사용자를 위한 멋진 환경을 만들도록 영감을 주고자 했습니다.
Thomas Ezan, Ivy Knight • 2분 길이
소식 받아 보기
Android 개발 관련 최신 정보를 이메일로 받아 보세요. 매주