Hallo! Willkommen zurück bei unserer Reihe über CameraX und Jetpack Compose. In den vorherigen Beiträgen haben wir die Grundlagen für die Einrichtung einer Kameravorschau behandelt und die Funktion „Zum Fokussieren tippen“ hinzugefügt.
🧱 Teil 1: Eine einfache Kameravorschau mit dem neuen Artifact „camera-compose“ erstellen. Wir haben die Berechtigungsverwaltung und die grundlegende Integration behandelt.
👆 Teil 2: Das Compose-Gesten-System, Grafiken und Coroutinen verwenden, um eine visuelle Tippen-zum-Fokus-Funktion zu implementieren.
🔦 Teil 3 (dieser Beitrag): Compose-UI-Elemente über der Kameravorschau einblenden, um die Nutzerfreundlichkeit zu verbessern.
📂 Teil 4: Adaptive APIs und das Compose-Animations-Framework verwenden, um auf faltbaren Smartphones reibungslose Animationen in den und aus dem Tischmodus zu ermöglichen.
In diesem Beitrag geht es um etwas visuell Ansprechenderes: die Implementierung eines Spotlight-Effekts über der Kameravorschau, wobei als Grundlage für den Effekt die Gesichtserkennung dienen soll. Warum? Keine Ahnung. Aber es sieht auf jeden Fall cool aus 🙂. Und noch wichtiger: Es zeigt, wie wir ganz einfach Sensorkoordinaten in UI-Koordinaten umwandeln können, um sie in Compose zu verwenden.
Gesichtserkennung aktivieren
Zuerst ändern wir das CameraPreviewViewModel, um die Gesichtserkennung zu aktivieren. Wir verwenden die Camera2Interop API, mit der wir über CameraX mit der zugrunde liegenden Camera2 API interagieren können. So können wir Kamerafunktionen nutzen, die von CameraX nicht direkt bereitgestellt werden. Wir müssen die folgenden Änderungen vornehmen:
- Erstellen Sie einen StateFlow, der die Gesichtsgrenzen als Liste von
Rects enthält. - Legen Sie die Option für die Erfassungsanfrage
STATISTICS_FACE_DETECT_MODEauf „FULL“ fest, um die Gesichtserkennung zu aktivieren. - Legen Sie einen
CaptureCallbackfest, um die Gesichtsinformationen aus dem Erfassungsergebnis abzurufen.
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 { ... }
Nach diesen Änderungen gibt unser Viewmodel eine Liste von Rect-Objekten aus, die die Begrenzungsrahmen der erkannten Gesichter in Sensorkoordinaten darstellen.
Sensorkoordinaten in UI-Koordinaten umwandeln
Die Begrenzungsrahmen der erkannten Gesichter, die wir im letzten Abschnitt gespeichert haben, verwenden Koordinaten im Sensorkoordinatensystem. Um die Begrenzungsrahmen in unserer Benutzeroberfläche zu zeichnen, müssen wir diese Koordinaten so transformieren, dass sie im Compose-Koordinatensystem korrekt sind. Dazu müssen wir Folgendes tun:
- Sensorkoordinaten in Koordinaten des Vorschaupuffers umwandeln
- Koordinaten des Vorschaupuffers in Compose-UI-Koordinaten umwandeln
Diese Transformationen werden mithilfe von Transformationsmatrizen durchgeführt. Jede Transformation hat eine eigene Matrix:
- Unsere
SurfaceRequestenthält eineTransformationInfo-Instanz mit einersensorToBufferTranform-Matrix. - Unser
CameraXViewfinderhat einen zugehörigenCoordinateTransformer. Sie erinnern sich vielleicht, dass wir diesen Transformer bereits im vorherigen Blogpost verwendet haben, um die Koordinaten für „Zum Fokussieren tippen“ zu transformieren.
Wir können eine Hilfsmethode erstellen, die die Transformation für uns durchführt:
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 }
- Wir iterieren durch die Liste der erkannten Gesichter und führen für jedes Gesicht die Transformation aus.
- Die
CoordinateTransformer.transformMatrix, die wir von unseremCameraXViewfindererhalten, transformiert standardmäßig Koordinaten von der Benutzeroberfläche in Pufferkoordinaten. In unserem Fall soll die Matrix umgekehrt funktionieren und Pufferkoordinaten in UI-Koordinaten transformieren. Daher verwenden wir die Methodeinvert(), um die Matrix zu invertieren. - Zuerst transformieren wir das Gesicht mit der
sensorToBufferTransformMatrixvon Sensorkoordinaten in Pufferkoordinaten und dann mit derbufferToUiTransformMatrixvon Pufferkoordinaten in UI-Koordinaten.
Spotlight-Effekt implementieren
Jetzt aktualisieren wir das zusammensetzbare Element CameraPreviewContent, um den Spotlight-Effekt zu zeichnen. Wir verwenden ein Canvas zusammensetzbares Element, um eine Farbverlaufsmaske über die Vorschau zu zeichnen, sodass die erkannten Gesichter sichtbar sind:
@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 ) } } } } }
So funktionierts:
- Wir rufen die Liste der Gesichter aus dem Viewmodel ab.
- Damit nicht jedes Mal der gesamte Bildschirm neu zusammengesetzt wird, wenn sich die Liste der erkannten Gesichter ändert, verwenden wir
derivedStateOf, um zu verfolgen, ob überhaupt Gesichter erkannt werden. Dies kann dann mitAnimatedVisibilityverwendet werden, um das farbige Overlay ein- und auszublenden. - Die
surfaceRequestenthält die Informationen, die wir zum Transformieren von Sensorkoordinaten in Pufferkoordinaten in derSurfaceRequest.TransformationInfobenötigen. Wir verwenden die FunktionproduceState, um einen Listener in der Oberflächenanfrage einzurichten und diesen Listener zu löschen, wenn das zusammensetzbare Element den Kompositionsbaum verlässt. - Wir verwenden ein
Canvas, um ein durchscheinendes rosa Rechteck zu zeichnen, das den gesamten Bildschirm abdeckt. - Wir verschieben das Lesen der Variablen
sensorFaceRects, bis wir uns im ZeichenblockCanvasbefinden. Dann transformieren wir die Koordinaten in UI-Koordinaten. - Wir iterieren über die erkannten Gesichter und zeichnen für jedes Gesicht einen radialen Farbverlauf, der das Innere des Gesichtsrechtecks transparent macht.
- Wir verwenden
BlendMode.DstOut, um sicherzustellen, dass der Farbverlauf aus dem rosa Rechteck ausgeschnitten wird und so der Spotlight-Effekt entsteht.
Hinweis: Wenn Sie die Kamera in DEFAULT_FRONT_CAMERA ändern, wird der Spotlight-Effekt gespiegelt. Dies ist ein bekanntes Problem, das in der Google-Problemverfolgung. dokumentiert ist.
Ergebnis
Mit diesem Code haben wir einen voll funktionsfähigen Spotlight-Effekt, der erkannte Gesichter hervorhebt. Das vollständige Code-Snippet finden Sie hier.
Dieser Effekt ist nur der Anfang. Mit Compose können Sie eine Vielzahl visuell beeindruckender Kamerafunktionen erstellen. Da wir Sensor- und Pufferkoordinaten in Compose-UI-Koordinaten und zurück transformieren können, können wir alle Compose-UI-Funktionen nutzen und nahtlos in das zugrunde liegende Kamerasystem integrieren. Mit Animationen, erweiterten UI-Grafiken, einfacher UI-Statusverwaltung und vollständiger Gestensteuerung sind Ihrer Fantasie keine Grenzen gesetzt.
Im letzten Beitrag der Reihe erfahren Sie, wie Sie adaptive APIs und das Compose-Animations-Framework verwenden können, um nahtlos zwischen verschiedenen Kamera-UIs auf faltbaren Geräten zu wechseln. Bleiben Sie dran!
Für die Code-Snippets in diesem Blog gilt die folgende Lizenz:
// Copyright 2024 Google LLC. SPDX-License-Identifier: Apache-2.0
Vielen Dank an Nick Butcher, Alex Vanyo, Trevor McGuire, Don Turner und Lauren Ward für die Überprüfung und das Feedback. Möglich gemacht durch die harte Arbeit von Yasith Vidanaarachch.
Weiterlesen
-
Anleitungen
In diesem Artikel erfahren Sie, wie Sie die Test-API „waitUntil“ in Compose verwenden, um zu warten, bis bestimmte Bedingungen erfüllt sind.
Jose Alcérreca • Lesezeit: 3 Minuten
-
Anleitungen
Da ein übermäßiger Akkuverbrauch für Android-Nutzer ein wichtiges Thema ist, hat Google erhebliche Schritte unternommen, um Entwicklern dabei zu helfen, energieeffizientere Apps zu entwickeln.
Alice Yuan • Lesezeit: 8 Minuten
-
Anleitungen
Wir möchten Ihnen Beispiele für KI-basierte Funktionen mit On-Device- und Cloud-Modellen geben und Sie dazu anregen, Ihren Nutzern ein ansprechendes Nutzererlebnis zu bieten.
Thomas Ezan, Ivy Knight • Lesezeit: 2 Minuten
Auf dem Laufenden bleiben
Lassen Sie sich Woche für Woche die neuesten Informationen zur Android-Entwicklung zusenden.