तरीका बताने वाले लेख

CameraX और Jetpack Compose की मदद से स्पॉटलाइट इफ़ेक्ट बनाना

पढ़ने में 8 मिनट लगेंगे
योलांडा फेरहूफ़ की प्रोफ़ाइल देखें
Jolanda Verhoef डेवलपर रिलेशंस इंजीनियर

नमस्ते! CameraX और Jetpack Compose के बारे में बताने वाली हमारी सीरीज़ में आपका फिर से स्वागत है. पिछली पोस्ट में, हमने कैमरा प्रीव्यू सेट अप करने की बुनियादी बातें बताई थीं. साथ ही, टैप-टू-फ़ोकस की सुविधा जोड़ी थी.

🧱 पहला हिस्सा: नए camera-compose आर्टफ़ैक्ट का इस्तेमाल करके, बुनियादी कैमरा प्रीव्यू बनाना. हमने अनुमति मैनेज करने और बुनियादी इंटिग्रेशन के बारे में बताया था.

👆 दूसरा हिस्सा: Compose के जेस्चर सिस्टम, ग्राफ़िक्स, और कोरोटीन का इस्तेमाल करके, विज़ुअल टैप-टू-फ़ोकस लागू करना.

🔦 तीसरा हिस्सा (यह पोस्ट): Compose के यूज़र इंटरफ़ेस (यूआई) एलिमेंट को अपने कैमरा प्रीव्यू पर ओवरले करने का तरीका जानना, ताकि उपयोगकर्ता को बेहतर अनुभव मिल सके.

📂 चौथा हिस्सा: फ़ोल्ड किए जा सकने वाले फ़ोन पर, टेबलटॉप मोड में आसानी से ऐनिमेट करने के लिए, अडैप्टिव एपीआई और Compose के ऐनिमेशन फ़्रेमवर्क का इस्तेमाल करना.

इस पोस्ट में, हम कुछ ज़्यादा विज़ुअली आकर्षक चीज़ के बारे में जानेंगे. जैसे, अपने कैमरा प्रीव्यू पर स्पॉटलाइट इफ़ेक्ट लागू करना. इसके लिए, हम चेहरे की पहचान को इफ़ेक्ट के आधार के तौर पर इस्तेमाल करेंगे. आप पूछ सकते हैं कि ऐसा क्यों? मुझे पक्के तौर पर नहीं पता. हालांकि, यह देखने में काफ़ी अच्छा लगता है 🙂. इससे भी अहम बात यह है कि इससे यह पता चलता है कि हम सेंसर के कोऑर्डिनेट को यूज़र इंटरफ़ेस (यूआई) के कोऑर्डिनेट में आसानी से कैसे बदल सकते हैं. इससे हमें Compose में उनका इस्तेमाल करने में मदद मिलती है!

face-detection.gif

चेहरे की पहचान की सुविधा चालू करना

सबसे पहले, CameraPreviewViewModel में बदलाव करके, चेहरे की पहचान की सुविधा चालू करते हैं. हम Camera2Interop एपीआई का इस्तेमाल करेंगे. इससे हमें 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 ऑब्जेक्ट की सूची दिखाता है. यह सूची, सेंसर के कोऑर्डिनेट में पहचाने गए चेहरों के बाउंडिंग बॉक्स को दिखाती है.

सेंसर के कोऑर्डिनेट को यूज़र इंटरफ़ेस (यूआई) के कोऑर्डिनेट में बदलना

पिछले सेक्शन में, पहचाने गए चेहरों के बाउंडिंग बॉक्स में सेंसर कोऑर्डिनेट सिस्टम के कोऑर्डिनेट का इस्तेमाल किया गया है. अपने यूज़र इंटरफ़ेस (यूआई) में बाउंडिंग बॉक्स बनाने के लिए, हमें इन कोऑर्डिनेट को बदलना होगा, ताकि वे Compose कोऑर्डिनेट सिस्टम में सही हों. हमें यह करना होगा:

  • सेंसर के कोऑर्डिनेट को प्रीव्यू बफ़र के कोऑर्डिनेट में बदलना
  • प्रीव्यू बफ़र के कोऑर्डिनेट को Compose के यूज़र इंटरफ़ेस (यूआई) के कोऑर्डिनेट में बदलना

इन बदलावों को ट्रांसफ़ॉर्मेशन मैट्रिक्स का इस्तेमाल करके किया जाता है. हर बदलाव के लिए, अलग मैट्रिक्स होता है:

  • हमारे 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
}
  • हम पहचाने गए चेहरों की सूची पर इटरेट करते हैं. साथ ही, हर चेहरे के लिए बदलाव करते हैं.
  • CoordinateTransformer.transformMatrix, जो हमें अपने CameraXViewfinder से मिलता है, डिफ़ॉल्ट रूप से यूज़र इंटरफ़ेस (यूआई) के कोऑर्डिनेट को बफ़र के कोऑर्डिनेट में बदलता है. हमारे मामले में, हम चाहते हैं कि मैट्रिक्स दूसरी तरह से काम करे. यानी, बफ़र के कोऑर्डिनेट को यूज़र इंटरफ़ेस (यूआई) के कोऑर्डिनेट में बदले. इसलिए, हम मैट्रिक्स को उलटने के लिए, invert() तरीके का इस्तेमाल करते हैं.
  • हम सबसे पहले, sensorToBufferTransformMatrix का इस्तेमाल करके, चेहरे को सेंसर के कोऑर्डिनेट से बफ़र के कोऑर्डिनेट में बदलते हैं. इसके बाद, bufferToUiTransformMatrix का इस्तेमाल करके, उन बफ़र के कोऑर्डिनेट को यूज़र इंटरफ़ेस (यूआई) के कोऑर्डिनेट में बदलते हैं.

स्पॉटलाइट इफ़ेक्ट लागू करना

अब, स्पॉटलाइट इफ़ेक्ट बनाने के लिए, 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 ड्रॉ ब्लॉक के अंदर न हों. इसके बाद, हम कोऑर्डिनेट को यूज़र इंटरफ़ेस (यूआई) के कोऑर्डिनेट में बदलते हैं.
  • हम पहचाने गए चेहरों पर इटरेट करते हैं. साथ ही, हर चेहरे के लिए, रेडियल ग्रेडिएंट बनाते हैं. इससे चेहरे के आयत का अंदरूनी हिस्सा पारदर्शी हो जाएगा.
  • हम BlendMode.DstOut का इस्तेमाल करके यह पक्का करते हैं कि हम गुलाबी आयत से ग्रेडिएंट को काट रहे हैं. इससे स्पॉटलाइट इफ़ेक्ट बनता है.

ध्यान दें: जब आप कैमरे को DEFAULT_FRONT_CAMERA पर बदलते हैं, तो आपको दिखेगा कि स्पॉटलाइट मिरर की तरह दिख रहा है! यह एक जानी-मानी समस्या है, जिसे Google Issue Tracker. में ट्रैक किया जाता है.

नतीजा

इस कोड की मदद से, हमारे पास पूरी तरह से काम करने वाला स्पॉटलाइट इफ़ेक्ट है. इससे पहचाने गए चेहरों को हाइलाइट किया जाता है. कोड का पूरा स्निपेट यहां देखा जा सकता है.

यह इफ़ेक्ट सिर्फ़ शुरुआत है. Compose की मदद से, कैमरे के कई शानदार अनुभव बनाए जा सकते हैं. सेंसर और बफ़र के कोऑर्डिनेट को Compose के यूज़र इंटरफ़ेस (यूआई) के कोऑर्डिनेट में और वापस बदलने की सुविधा का मतलब है कि हम Compose के यूज़र इंटरफ़ेस (यूआई) की सभी सुविधाओं का इस्तेमाल कर सकते हैं. साथ ही, उन्हें कैमरे के बुनियादी सिस्टम के साथ आसानी से इंटिग्रेट कर सकते हैं. ऐनिमेशन, यूज़र इंटरफ़ेस (यूआई) के बेहतर ग्राफ़िक्स, यूज़र इंटरफ़ेस (यूआई) के आसान स्टेट मैनेजमेंट, और जेस्चर पर पूरा कंट्रोल होने की वजह से, आपकी कल्पना की कोई सीमा नहीं है!

सीरीज़ की आखिरी पोस्ट में, हम फ़ोल्ड किए जा सकने वाले डिवाइसों पर, कैमरे के अलग-अलग यूज़र इंटरफ़ेस (यूआई) के बीच आसानी से ट्रांज़िशन करने के लिए, अडैप्टिव एपीआई और Compose के ऐनिमेशन फ़्रेमवर्क का इस्तेमाल करने का तरीका जानेंगे. हमारे साथ बने रहें!


इस ब्लॉग में दिए गए कोड स्निपेट के लिए, यह लाइसेंस लागू होता है:

// Copyright 2024 Google LLC. SPDX-License-Identifier: Apache-2.0

समीक्षा करने और सुझाव देने के लिए, Nick Butcher, Alex Vanyo, Trevor McGuire, Don Turner, और Lauren Ward का बहुत-बहुत धन्यवाद. यह Yasith Vidanaarachch की कड़ी मेहनत की वजह से मुमकिन हो पाया है.

 

इसे लिखा है:
पढ़ना जारी रखें