你好啊!歡迎繼續收看 CameraX 和 Jetpack Compose 系列影片。在先前的文章中,我們介紹了設定相機預覽的基礎知識,並新增了輕觸對焦功能。
🧱 第 1 部分:使用新的 camera-compose 構件,建構基本攝影機預覽畫面。我們已介紹權限處理和基本整合。
👆 第 2 部分:使用 Compose 手勢系統、圖像和協同程式,實作輕觸對焦的視覺效果。
🔦 第 3 部分 (本文):瞭解如何在相機預覽畫面中疊加 Compose UI 元素,提供更豐富的使用者體驗。
📂 第 4 部分:使用自適應 API 和 Compose 動畫架構,在摺疊式手機上順暢地切換桌面模式。
在這篇文章中,我們將深入探討更具視覺吸引力的內容,也就是在攝影機預覽畫面中實作聚光燈效果,並以臉部偵測做為效果的基礎。你可能會問為什麼?不確定。但看起來很酷 🙂。更重要的是,這項功能示範了如何輕鬆將感應器座標轉換為 UI 座標,以便在 Compose 中使用!
啟用臉部偵測功能
首先,請修改 CameraPreviewViewModel,啟用臉部偵測功能。我們將使用 Camera2Interop API,透過 CameraX 與基礎 Camera2 API 互動。這讓我們有機會使用 CameraX 未直接公開的相機功能。我們需要進行下列變更:
- 建立 StateFlow,其中包含臉部界線,做為
Rect清單。 - 將
STATISTICS_FACE_DETECT_MODE擷取要求選項設為 FULL,啟用臉部偵測功能。 - 設定
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繪製覆蓋整個畫面的半透明粉紅色矩形。 - 我們會延後讀取
sensorFaceRects變數,直到進入Canvas繪製區塊為止。接著,我們會將座標轉換為 UI 座標。 - 我們會疊代偵測到的臉部,並為每個臉部繪製放射狀漸層,讓臉部矩形內部呈現透明。
- 我們使用
BlendMode.DstOut,確保從粉紅色矩形剪下漸層,建立聚光燈效果。
注意:將攝影機變更為 DEFAULT_FRONT_CAMERA 時,你會發現聚光燈畫面會鏡像翻轉!這是已知問題,已在 Google Issue Tracker中追蹤。
結果
有了這段程式碼,我們就能完全正常運作聚光燈效果,醒目顯示偵測到的臉部。如要查看完整程式碼片段,請按這裡。
這只是開始,您可以使用 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 分鐘
-
操作說明
我們希望透過這些範例,讓您瞭解如何運用裝置端和雲端模型打造 AI 輔助功能,為使用者創造愉悅的體驗。
Thomas Ezan, Ivy Knight • 閱讀時間:2 分鐘
隨時掌握最新消息
每週透過電子郵件接收最新的 Android 開發洞察資訊。