Android पर LiteRT की मदद से, रीयल-टाइम में इमेज सेगमेंटेशन की सुविधा को बेहतर बनाना

1. शुरू करने से पहले

कोड टाइप करने से, आपको कोड याद रखने में मदद मिलती है. साथ ही, इससे आपको कॉन्टेंट को बेहतर तरीके से समझने में भी मदद मिलती है. कॉपी-पेस्ट करने से समय बच सकता है. हालांकि, इस तरीके का इस्तेमाल करने से, लंबे समय में कोडिंग की बेहतर क्षमताएं हासिल की जा सकती हैं और काम को ज़्यादा असरदार तरीके से किया जा सकता है.

इस कोडलैब में, आपको एक ऐसा Android ऐप्लिकेशन बनाने का तरीका बताया जाएगा जो Google के TensorFlow Lite के नए रनटाइम, LiteRT का इस्तेमाल करके, लाइव कैमरा फ़ीड पर रीयल-टाइम में इमेज सेगमेंटेशन करता है. आपको एक स्टार्टर Android ऐप्लिकेशन लेना होगा. इसके बाद, उसमें इमेज सेगमेंटेशन की सुविधाएँ जोड़नी होंगी. हम प्रीप्रोसेसिंग, अनुमान, और पोस्टप्रोसेसिंग के चरणों के बारे में भी जानेंगे. आपको:

  • ऐसा Android ऐप्लिकेशन बनाओ जो रीयल-टाइम में इमेज को सेगमेंट करता हो.
  • पहले से ट्रेन किए गए LiteRT इमेज सेगमेंटेशन मॉडल को इंटिग्रेट करें.
  • मॉडल के लिए, इनपुट इमेज को पहले से प्रोसेस करें.
  • सीपीयू और जीपीयू ऐक्सेलरेटर के लिए, LiteRT रनटाइम का इस्तेमाल करें.
  • सेगमेंटेशन मास्क दिखाने के लिए, मॉडल के आउटपुट को प्रोसेस करने का तरीका जानें.
  • सामने वाले कैमरे के लिए, फ़ोकस को अडजस्ट करने का तरीका जानें.

आखिर में, आपको नीचे दी गई इमेज जैसा कुछ दिखेगा:

ऐप्लिकेशन पूरा हो गया

ज़रूरी शर्तें

इस कोडलैब को अनुभवी मोबाइल डेवलपर के लिए डिज़ाइन किया गया है. इसमें मशीन लर्निंग के बारे में जानकारी दी गई है. आपको इनके बारे में जानकारी होनी चाहिए:

  • Kotlin और Android Studio का इस्तेमाल करके Android डेवलपमेंट
  • इमेज प्रोसेसिंग के बुनियादी कॉन्सेप्ट

आपको क्या सीखने को मिलेगा

  • Android ऐप्लिकेशन में LiteRT रनटाइम को इंटिग्रेट करने और इस्तेमाल करने का तरीका.
  • पहले से ट्रेन किए गए LiteRT मॉडल का इस्तेमाल करके, इमेज सेगमेंटेशन कैसे किया जाता है.
  • मॉडल के लिए, इनपुट इमेज को प्रीप्रोसेस करने का तरीका.
  • मॉडल के लिए अनुमान लगाने की प्रोसेस कैसे चलाएं.
  • नतीजों को विज़ुअलाइज़ करने के लिए, सेगमेंटेशन मॉडल के आउटपुट को प्रोसेस करने का तरीका.
  • रीयल-टाइम में कैमरा फ़ीड प्रोसेस करने के लिए, CameraX का इस्तेमाल कैसे करें.

आपको किन चीज़ों की ज़रूरत होगी

  • Android Studio का नया वर्शन (v2025.1.1 पर टेस्ट किया गया).
  • फ़िज़िकल Android डिवाइस. इसे Galaxy और Pixel डिवाइसों पर सबसे अच्छी तरह से टेस्ट किया गया है.
  • GitHub से लिया गया सैंपल कोड.
  • Kotlin में Android डेवलपमेंट की बुनियादी जानकारी.

2. इमेज सेगमेंटेशन

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

उदाहरण के लिए, सिर्फ़ यह जानने के बजाय कि बॉक्स में कोई ‘व्यक्ति' है, यह पता लगाया जा सकता है कि उस व्यक्ति के पिक्सल कौनसे हैं. इस ट्यूटोरियल में, पहले से ट्रेन किए गए मशीन लर्निंग मॉडल का इस्तेमाल करके, Android डिवाइस पर रीयल-टाइम में इमेज सेगमेंटेशन करने का तरीका बताया गया है.

सेगमेंटेशन का उदाहरण

LiteRT: डिवाइस पर मौजूद एमएल की सुविधा को बेहतर बनाना

मोबाइल डिवाइसों पर रीयल-टाइम में हाई-फ़िडेलिटी सेगमेंटेशन की सुविधा देने वाली मुख्य टेक्नोलॉजी LiteRT है. LiteRT, TensorFlow Lite के लिए Google का अगली पीढ़ी का हाई-परफ़ॉर्मेंस वाला रनटाइम है. इसे इस तरह से डिज़ाइन किया गया है कि यह हार्डवेयर से सबसे अच्छी परफ़ॉर्मेंस दे सके.

यह सुविधा, जीपीयू (ग्राफ़िक्स प्रोसेसिंग यूनिट) और एनपीयू (न्यूरल प्रोसेसिंग यूनिट) जैसे हार्डवेयर ऐक्सलरेटर का बेहतर तरीके से इस्तेमाल करके ऐसा करती है. सेगमेंटेशन मॉडल के ज़्यादा कंप्यूटेशनल वर्कलोड को अलग-अलग कामों के लिए इस्तेमाल की जाने वाली मशीन सीपीयू से इन खास प्रोसेसर पर ट्रांसफ़र करके, LiteRT अनुमान लगाने में लगने वाले समय को काफ़ी कम कर देता है. इसकी मदद से, लाइव कैमरा फ़ीड पर जटिल मॉडल को आसानी से चलाया जा सकता है. साथ ही, मशीन लर्निंग की मदद से सीधे तौर पर आपके फ़ोन पर किए जा सकने वाले कामों को बढ़ाया जा सकता है. इस लेवल की परफ़ॉर्मेंस के बिना, रीयल-टाइम सेगमेंटेशन बहुत धीमा होगा और इससे उपयोगकर्ता को अच्छा अनुभव नहीं मिलेगा.

3. सेट अप करें

रिपॉज़िटरी का क्लोन बनाना

सबसे पहले, LiteRT के लिए डेटा स्टोर करने की जगह को क्लोन करें:

git clone https://github.com/google-ai-edge/litert-samples.git

litert-samples/compiled_model_api/image_segmentation में वे सभी संसाधन मौजूद हैं जिनकी आपको ज़रूरत होगी. इस कोडलैब के लिए, आपको सिर्फ़ kotlin_cpu_gpu/android_starter प्रोजेक्ट की ज़रूरत होगी. अगर आपको कोई समस्या आ रही है, तो पूरा हो चुका प्रोजेक्ट देखें: kotlin_cpu_gpu/android

फ़ाइल पाथ के बारे में जानकारी

इस ट्यूटोरियल में, Linux/macOS फ़ॉर्मैट में फ़ाइल पाथ दिए गए हैं. अगर Windows का इस्तेमाल किया जा रहा है, तो आपको पाथ में इसके हिसाब से बदलाव करना होगा.

Android Studio के प्रोजेक्ट व्यू और स्टैंडर्ड फ़ाइल सिस्टम व्यू के बीच के अंतर को समझना भी ज़रूरी है. Android Studio प्रोजेक्ट व्यू, आपके प्रोजेक्ट की फ़ाइलों को व्यवस्थित तरीके से दिखाता है. इन्हें Android डेवलपमेंट के लिए व्यवस्थित किया जाता है. इस ट्यूटोरियल में दिए गए फ़ाइल पाथ, फ़ाइल सिस्टम के पाथ के बारे में बताते हैं. ये Android Studio के प्रोजेक्ट व्यू में मौजूद पाथ के बारे में नहीं बताते.

स्टार्टर ऐप्लिकेशन इंपोर्ट करना

आइए, सबसे पहले स्टार्टर ऐप्लिकेशन को Android Studio में इंपोर्ट करें.

  1. Android Studio खोलें और Open को चुनें.

Android Studio Open

  1. kotlin_cpu_gpu/android_starter डायरेक्ट्री पर जाएं और उसे खोलें.

Android Starter

यह पक्का करने के लिए कि आपके ऐप्लिकेशन के लिए सभी डिपेंडेंसी उपलब्ध हैं, इंपोर्ट करने की प्रोसेस पूरी होने के बाद आपको अपने प्रोजेक्ट को Gradle फ़ाइलों के साथ सिंक करना चाहिए.

  1. Android Studio के टूलबार में जाकर, Sync Project with Gradle Files को चुनें.

मेन्यू सिंक करने की सुविधा

  1. कृपया इस चरण को न छोड़ें. अगर यह काम नहीं करता है, तो ट्यूटोरियल के बाकी हिस्से का कोई मतलब नहीं रहेगा.

स्टार्टर ऐप्लिकेशन चलाना

प्रोजेक्ट को Android Studio में इंपोर्ट करने के बाद, अब ऐप्लिकेशन को पहली बार चलाया जा सकता है.

अपने Android डिवाइस को यूएसबी केबल से कंप्यूटर से कनेक्ट करें. इसके बाद, Android Studio टूलबार में मौजूद चलाएं पर क्लिक करें.

'चलाएं' बटन

आपके डिवाइस पर ऐप्लिकेशन लॉन्च होना चाहिए. आपको कैमरे का लाइव फ़ीड दिखेगा, लेकिन अभी सेगमेंटेशन नहीं होगा. इस ट्यूटोरियल में, फ़ाइल में किए गए सभी बदलाव litert-samples/compiled_model_api/image_segmentation/kotlin_cpu_gpu/android_starter/app/src/main/java/com/google/ai/edge/examples/image_segmentation डायरेक्ट्री में सेव होंगे. अब आपको पता चल गया होगा कि Android Studio, इस डायरेक्ट्री को फिर से क्यों व्यवस्थित करता है 😃.

प्रोजेक्ट डायरेक्ट्री

आपको ImageSegmentationHelper.kt, MainViewModel.kt, और view/SegmentationOverlay.kt फ़ाइलों में भी TODO टिप्पणियां दिखेंगी. यहां दिए गए तरीके में, आपको इन TODO को भरकर इमेज सेगमेंटेशन की सुविधा लागू करनी होगी.

4. स्टार्टर ऐप्लिकेशन के बारे में जानकारी

स्टार्टर ऐप्लिकेशन में पहले से ही बुनियादी यूज़र इंटरफ़ेस (यूआई) और कैमरे को मैनेज करने का लॉजिक मौजूद होता है. यहां मुख्य फ़ाइलों के बारे में खास जानकारी दी गई है:

  • app/src/main/java/com/google/ai/edge/examples/image_segmentation/MainActivity.kt: यह ऐप्लिकेशन का मुख्य एंट्री पॉइंट है. यह Jetpack Compose का इस्तेमाल करके यूज़र इंटरफ़ेस सेट अप करता है और कैमरे की अनुमतियों को मैनेज करता है.
  • app/src/main/java/com/google/ai/edge/examples/image_segmentation/MainViewModel.kt: यह ViewModel, यूज़र इंटरफ़ेस (यूआई) की स्थिति को मैनेज करता है. साथ ही, इमेज सेगमेंटेशन की प्रोसेस को व्यवस्थित करता है.
  • app/src/main/java/com/google/ai/edge/examples/image_segmentation/ImageSegmentationHelper.kt: यहां हम इमेज सेगमेंटेशन के लिए मुख्य लॉजिक जोड़ेंगे. यह मॉडल को लोड करने, कैमरे के फ़्रेम को प्रोसेस करने, और अनुमान लगाने का काम करेगा.
  • app/src/main/java/com/google/ai/edge/examples/image_segmentation/view/CameraScreen.kt: यह कंपोज़ेबल फ़ंक्शन, कैमरे की झलक और सेगमेंटेशन ओवरले दिखाता है.
  • app/download_model.gradle: यह स्क्रिप्ट selfie_multiclass.tflite को डाउनलोड करती है. यह पहले से ट्रेन किया गया TensorFlow Lite इमेज सेगमेंटेशन मॉडल है, जिसका हम इस्तेमाल करेंगे.

5. LiteRT के बारे में जानकारी और डिपेंडेंसी जोड़ना

अब स्टार्टर ऐप्लिकेशन में इमेज सेगमेंटेशन की सुविधा जोड़ते हैं.

1. LiteRT डिपेंडेंसी जोड़ना

सबसे पहले, आपको अपने प्रोजेक्ट में LiteRT लाइब्रेरी जोड़नी होगी. यह Google के ऑप्टिमाइज़ किए गए रनटाइम के साथ, उपयोगकर्ता के डिवाइस पर मशीन लर्निंग की सुविधा चालू करने का पहला और ज़रूरी चरण है.

app/build.gradle.kts फ़ाइल खोलें और dependencies ब्लॉक में यह लाइन जोड़ें:

// LiteRT for on-device ML
implementation(libs.litert)

डिपेंडेंसी जोड़ने के बाद, अपने प्रोजेक्ट को Gradle फ़ाइलों के साथ सिंक करें. इसके लिए, Android Studio के सबसे ऊपर दाएं कोने में मौजूद, अभी सिंक करें बटन पर क्लिक करें.

अभी सिंक करें

2. Key LiteRT API के बारे में जानकारी

ImageSegmentationHelper.kt खोलें

लागू करने का कोड लिखने से पहले, LiteRT API के उन मुख्य कॉम्पोनेंट को समझना ज़रूरी है जिनका इस्तेमाल किया जाएगा. पक्का करें कि आपने com.google.ai.edge.litert पैकेज से इंपोर्ट किया हो. साथ ही, ImageSegmentationHelper.kt के सबसे ऊपर ये इंपोर्ट जोड़ें:

import com.google.ai.edge.litert.Accelerator
import com.google.ai.edge.litert.CompiledModel
  • CompiledModel: यह आपके TFLite मॉडल के साथ इंटरैक्ट करने के लिए सेंट्रल क्लास है. यह एक ऐसा मॉडल होता है जिसे पहले से कंपाइल किया गया होता है. साथ ही, इसे किसी खास हार्डवेयर ऐक्सेलरेटर (जैसे कि सीपीयू या जीपीयू) के लिए ऑप्टिमाइज़ किया गया होता है. प्री-कंपाइलेशन, LiteRT की एक मुख्य सुविधा है. इससे अनुमान लगाने की प्रोसेस तेज़ और ज़्यादा असरदार हो जाती है.
  • CompiledModel.Options: इस बिल्डर क्लास का इस्तेमाल, CompiledModel को कॉन्फ़िगर करने के लिए किया जाता है. सबसे अहम सेटिंग यह तय करना है कि आपको अपने मॉडल को चलाने के लिए, किस हार्डवेयर ऐक्सलरेटर का इस्तेमाल करना है.
  • Accelerator: इस enum की मदद से, अनुमान लगाने के लिए हार्डवेयर चुना जा सकता है. स्टार्टर प्रोजेक्ट को इन विकल्पों को मैनेज करने के लिए पहले से ही कॉन्फ़िगर किया गया है:
    • Accelerator.CPU: डिवाइस के सीपीयू पर मॉडल चलाने के लिए. यह सबसे ज़्यादा डिवाइसों के साथ काम करने वाला विकल्प है.
    • Accelerator.GPU: डिवाइस के जीपीयू पर मॉडल चलाने के लिए. इमेज पर आधारित मॉडल के लिए, यह सीपीयू की तुलना में ज़्यादा तेज़ होता है.
  • इनपुट और आउटपुट बफ़र (TensorBuffer): LiteRT, मॉडल के इनपुट और आउटपुट के लिए TensorBuffer का इस्तेमाल करता है. इससे आपको मेमोरी पर बेहतर कंट्रोल मिलता है. साथ ही, डेटा की गैर-ज़रूरी कॉपी नहीं बनती हैं. आपको ये बफ़र, model.createInputBuffers() और model.createOutputBuffers() का इस्तेमाल करके सीधे अपने CompiledModel इंस्टेंस से मिलेंगे. इसके बाद, इनमें अपना इनपुट डेटा लिखें और इनसे नतीजे पढ़ें.
  • model.run(): यह वह फ़ंक्शन है जो अनुमान को लागू करता है. इसमें इनपुट और आउटपुट बफ़र पास किए जाते हैं. इसके बाद, LiteRT चुने गए हार्डवेयर ऐक्सेलरेटर पर मॉडल चलाने का मुश्किल काम करता है.

6. ImageSegmentationHelper को लागू करने की शुरुआती प्रोसेस पूरी करना

अब कुछ कोड लिखने का समय आ गया है. आपको ImageSegmentationHelper.kt को लागू करने की शुरुआती प्रोसेस पूरी करनी होगी. इसमें LiteRT मॉडल को रखने के लिए, Segmenter प्राइवेट क्लास सेट अप करना और इसे सही तरीके से रिलीज़ करने के लिए, cleanup() फ़ंक्शन लागू करना शामिल है.

  1. Segmenter क्लास और cleanup() फ़ंक्शन को पूरा करें: ImageSegmentationHelper.kt फ़ाइल में, आपको Segmenter नाम की प्राइवेट क्लास और cleanup() नाम के फ़ंक्शन का स्ट्रक्चर दिखेगा. सबसे पहले, मॉडल को होल्ड करने के लिए कंस्ट्रक्टर को तय करके, इनपुट/आउटपुट बफ़र के लिए प्रॉपर्टी बनाकर, और मॉडल को रिलीज़ करने के लिए close() तरीके को जोड़कर, Segmenter क्लास को पूरा करें. इसके बाद, इस नए close() तरीके को कॉल करने के लिए, cleanup() फ़ंक्शन लागू करें.मौजूदा Segmenter क्लास और cleanup() फ़ंक्शन को इससे बदलें: (~लाइन 83)
    private class Segmenter(
        // Add this argument
        private val model: CompiledModel,
        private val coloredLabels: List<ColoredLabel>,
    ) {
        // Add these private vals
        private val inputBuffers = model.createInputBuffers()
        private val outputBuffers = model.createOutputBuffers()
    
        fun cleanup() {
          // cleanup buffers
          inputBuffers.forEach { it.close() }
          outputBuffers.forEach { it.close() }
          // cleanup model
          model.close()
        }
    }
    
  2. toAccelerator तरीके को तय करें: यह तरीका, एक्सेलरेटर मेन्यू से तय किए गए एक्सेलरेटर एनम को इंपोर्ट किए गए LiteRT मॉड्यूल (~लाइन 225) के लिए खास तौर पर बनाए गए एक्सेलरेटर एनम से मैप करता है:
    fun toAccelerator(acceleratorEnum: AcceleratorEnum): Accelerator {
      return when (acceleratorEnum) {
        AcceleratorEnum.CPU -> Accelerator.CPU
        AcceleratorEnum.GPU -> Accelerator.GPU
      }
    }
    
  3. CompiledModel को शुरू करें: अब initSegmenter फ़ंक्शन ढूंढें. यहां CompiledModel इंस्टेंस बनाया जाएगा. साथ ही, इसका इस्तेमाल अब तय की गई Segmenter क्लास को इंस्टैंशिएट करने के लिए किया जाएगा. यह कोड, मॉडल को तय किए गए ऐक्सेलरेटर (सीपीयू या जीपीयू) के साथ सेट अप करता है और उसे अनुमान लगाने के लिए तैयार करता है. initSegmenter में मौजूद TODO को इस कोड से बदलें (Cmd/Ctrl+f ‘initSegmenter` या ~लाइन 62):
    cleanup()
    try {
      withContext(singleThreadDispatcher) {
        val model =
          CompiledModel.create(
            context.assets,
            "selfie_multiclass.tflite",
            CompiledModel.Options(toAccelerator(acceleratorEnum)),
            null,
          )
        segmenter = Segmenter(model, coloredLabels)
        Log.d(TAG, "Created an image segmenter")
      }
    } catch (e: Exception) {
      Log.i(TAG, "Create LiteRT from selfie_multiclass is failed: ${e.message}")
      _error.emit(e)
    }
    

7. सेगमेंटेशन और प्रीप्रोसेसिंग शुरू करना

अब हमारे पास मॉडल है. इसलिए, हमें सेगमेंटेशन की प्रोसेस शुरू करनी होगी और मॉडल के लिए इनपुट डेटा तैयार करना होगा.

ट्रिगर सेगमेंटेशन

सेगमेंटेशन की प्रोसेस MainViewModel.kt में शुरू होती है. इसे कैमरे से फ़्रेम मिलते हैं.

MainViewModel.kt खोलें

  1. कैमरा फ़्रेम से सेगमेंटेशन ट्रिगर करना: MainViewModel में मौजूद segment फ़ंक्शन, सेगमेंटेशन टास्क के लिए एंट्री पॉइंट होते हैं. जब भी कैमरे से कोई नई इमेज उपलब्ध होती है या गैलरी से कोई इमेज चुनी जाती है, तब इन्हें कॉल किया जाता है. इसके बाद, ये फ़ंक्शन हमारे ImageSegmentationHelper में segment तरीके को कॉल करते हैं. दोनों segment फ़ंक्शन में मौजूद TODO को इससे बदलें (लाइन ~107):
    // For ImageProxy (from CameraX)
    fun segment(imageProxy: ImageProxy) {
        segmentJob =
            viewModelScope.launch {
                imageSegmentationHelper.segment(imageProxy.toBitmap(), imageProxy.imageInfo.rotationDegrees)
                imageProxy.close()
            }
    }
    
    // For Bitmaps (from gallery)
    fun segment(bitmap: Bitmap, rotationDegrees: Int) {
        segmentJob =
            viewModelScope.launch {
                val argbBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true)
                imageSegmentationHelper.segment(argbBitmap, rotationDegrees)
            }
    }
    

इमेज को पहले से प्रोसेस करना

अब इमेज को पहले से प्रोसेस करने के लिए, ImageSegmentationHelper.kt पर वापस जाएं.

ImageSegmentationHelper.kt खोलें

  1. Public segment फ़ंक्शन लागू करें: यह फ़ंक्शन, रैपर के तौर पर काम करता है. यह Segmenter क्लास में मौजूद private segment फ़ंक्शन को कॉल करता है. TODO को (~लाइन 95) से बदलें:
    try {
      withContext(singleThreadDispatcher) {
        segmenter?.segment(bitmap, rotationDegrees)?.let { if (isActive) _segmentation.emit(it) }
      }
    } catch (e: Exception) {
      Log.i(TAG, "Image segment error occurred: ${e.message}")
      _error.emit(e)
    }
    
  2. प्री-प्रोसेसिंग लागू करना: Segmenter क्लास के अंदर मौजूद प्राइवेट segment फ़ंक्शन में, हम इनपुट इमेज में ज़रूरी बदलाव करेंगे, ताकि उसे मॉडल के लिए तैयार किया जा सके. इसमें इमेज को बड़ा या छोटा करना, घुमाना, और सामान्य करना शामिल है. इसके बाद, यह फ़ंक्शन इनफ़रेंस करने के लिए, दूसरे प्राइवेट segment फ़ंक्शन को कॉल करेगा. segment(bitmap: Bitmap, ...) फ़ंक्शन में मौजूद TODO को इससे बदलें (~लाइन 121):
    val totalStartTime = SystemClock.uptimeMillis()
    val rotation = -rotationDegrees / 90
    val (h, w) = Pair(256, 256)
    
    // Preprocessing
    val preprocessStartTime = SystemClock.uptimeMillis()
    var image = bitmap.scale(w, h, true)
    image = rot90Clockwise(image, rotation)
    val inputFloatArray = normalize(image, 127.5f, 127.5f)
    Log.d(TAG, "Preprocessing time: ${SystemClock.uptimeMillis() - preprocessStartTime} ms")
    
    // Inference
    val inferenceStartTime = SystemClock.uptimeMillis()
    val segmentResult = segment(inputFloatArray)
    Log.d(TAG, "Inference time: ${SystemClock.uptimeMillis() - inferenceStartTime} ms")
    
    Log.d(TAG, "Total segmentation time: ${SystemClock.uptimeMillis() - totalStartTime} ms")
    return SegmentationResult(segmentResult, SystemClock.uptimeMillis() - inferenceStartTime)
    

8. LiteRT की मदद से प्राइमरी इन्फ़रेंस करना

इनपुट डेटा को पहले से प्रोसेस करने के बाद, अब हम LiteRT का इस्तेमाल करके मुख्य अनुमान लगा सकते हैं.

ImageSegmentationHelper.kt खोलें

  1. मॉडल को लागू करना: प्राइवेट segment(inputFloatArray: FloatArray) फ़ंक्शन में, हम सीधे तौर पर LiteRT run() तरीके से इंटरैक्ट करते हैं. हम पहले से प्रोसेस किए गए डेटा को इनपुट बफ़र में लिखते हैं, मॉडल को चलाते हैं, और आउटपुट बफ़र से नतीजे पढ़ते हैं. इस फ़ंक्शन में मौजूद TODO को (~लाइन 188) से बदलें:
    val (h, w, c) = Triple(256, 256, 6)
    
    // MODEL EXECUTION PHASE
    val modelExecStartTime = SystemClock.uptimeMillis()
    
    // Write input data - measure time
    val bufferWriteStartTime = SystemClock.uptimeMillis()
    inputBuffers[0].writeFloat(inputFloatArray)
    val bufferWriteTime = SystemClock.uptimeMillis() - bufferWriteStartTime
    Log.d(TAG, "Buffer write time: $bufferWriteTime ms")
    
    // Optional tensor inspection
    logTensorStats("Input tensor", inputFloatArray)
    
    // Run model inference - measure time
    val modelRunStartTime = SystemClock.uptimeMillis()
    model.run(inputBuffers, outputBuffers)
    val modelRunTime = SystemClock.uptimeMillis() - modelRunStartTime
    Log.d(TAG, "Model.run() time: $modelRunTime ms")
    
    // Read output data - measure time
    val bufferReadStartTime = SystemClock.uptimeMillis()
    val outputFloatArray = outputBuffers[0].readFloat()
    val outputBuffer = FloatBuffer.wrap(outputFloatArray)
    val bufferReadTime = SystemClock.uptimeMillis() - bufferReadStartTime
    Log.d(TAG, "Buffer read time: $bufferReadTime ms")
    
    val modelExecTime = SystemClock.uptimeMillis() - modelExecStartTime
    Log.d(TAG, "Total model execution time: $modelExecTime ms")
    
    // Optional tensor inspection
    logTensorStats("Output tensor", outputFloatArray)
    
    // POSTPROCESSING PHASE
    val postprocessStartTime = SystemClock.uptimeMillis()
    
    // Process mask from model output
    val inferenceData = InferenceData(width = w, height = h, channels = c, buffer = outputBuffer)
    val mask = processImage(inferenceData)
    
    val postprocessTime = SystemClock.uptimeMillis() - postprocessStartTime
    Log.d(TAG, "Postprocessing time (mask creation): $postprocessTime ms")
    
    return Segmentation(
      listOf(Mask(mask, inferenceData.width, inferenceData.height)),
      coloredLabels,
    )
    

9. प्रोसेस होने के बाद ओवरले दिखाना

अनुमान लगाने की प्रोसेस पूरी होने के बाद, हमें मॉडल से रॉ आउटपुट मिलता है. हमें इस आउटपुट को प्रोसेस करके, विज़ुअल सेगमेंटेशन मास्क बनाना होगा. इसके बाद, इसे स्क्रीन पर दिखाना होगा.

ImageSegmentationHelper.kt खोलें

  1. आउटपुट प्रोसेसिंग लागू करना: processImage फ़ंक्शन, मॉडल से मिले रॉ फ़्लोटिंग-पॉइंट आउटपुट को ByteBuffer में बदलता है. यह , सेगमेंटेशन मास्क को दिखाता है. यह हर पिक्सल के लिए, सबसे ज़्यादा संभावना वाली क्लास का पता लगाकर ऐसा करता है. इसके TODO को इससे बदलें (~line 238):
    val mask = ByteBuffer.allocateDirect(inferenceData.width * inferenceData.height)
    for (i in 0 until inferenceData.height) {
        for (j in 0 until inferenceData.width) {
            val offset = inferenceData.channels * (i * inferenceData.width + j)
    
            var maxIndex = 0
            var maxValue = inferenceData.buffer.get(offset)
    
            for (index in 1 until inferenceData.channels) {
                if (inferenceData.buffer.get(offset + index) > maxValue) {
                    maxValue = inferenceData.buffer.get(offset + index)
                    maxIndex = index
                }
            }
            mask.put(i * inferenceData.width + j, maxIndex.toByte())
        }
    }
    return mask
    

MainViewModel.kt खोलें

  1. सेगमेंटेशन के नतीजों को इकट्ठा और प्रोसेस करना: अब हम ImageSegmentationHelper से मिले सेगमेंटेशन के नतीजों को प्रोसेस करने के लिए, MainViewModel पर वापस जाते हैं. segmentationUiShareFlow, SegmentationResult को इकट्ठा करता है. इसके बाद, मास्क को रंगीन Bitmap में बदलता है और उसे यूज़र इंटरफ़ेस (यूआई) को उपलब्ध कराता है. segmentationUiShareFlow प्रॉपर्टी में मौजूद TODO को (~लाइन 63) से बदलें. पहले से मौजूद कोड को न बदलें, सिर्फ़ मुख्य हिस्सा भरें:
    viewModelScope.launch {
      imageSegmentationHelper.segmentation
        .filter { it.segmentation.masks.isNotEmpty() }
        .map {
          val segmentation = it.segmentation
          val mask = segmentation.masks[0]
          val maskArray = mask.data
          val width = mask.width
          val height = mask.height
          val pixelSize = width * height
          val pixels = IntArray(pixelSize)
    
          val colorLabels =
            segmentation.coloredLabels.mapIndexed { index, coloredLabel ->
              ColorLabel(index, coloredLabel.label, coloredLabel.argb)
            }
          // Set color for pixels
          for (i in 0 until pixelSize) {
            val colorLabel = colorLabels[maskArray[i].toInt()]
            val color = colorLabel.getColor()
            pixels[i] = color
          }
          // Get image info
          val overlayInfo = OverlayInfo(pixels = pixels, width = width, height = height)
    
          val inferenceTime = it.inferenceTime
          Pair(overlayInfo, inferenceTime)
        }
        .collect { flow.emit(it) }
    }
    

view/SegmentationOverlay.kt खोलें

आखिरी चरण में, उपयोगकर्ता के सामने वाले कैमरे पर फ़्लिप करने पर, सेगमेंटेशन ओवरले को सही तरीके से ओरिएंट करना होता है. फ़्रंट कैमरे के लिए, कैमरा फ़ीड को डिफ़ॉल्ट रूप से फ़्लिप किया जाता है. इसलिए, हमें अपने ओवरले Bitmap पर भी हॉरिज़ॉन्टल फ़्लिपिंग लागू करनी होगी, ताकि यह कैमरे की झलक के साथ सही तरीके से अलाइन हो सके.

  1. ओवरले ओरिएंटेशन मैनेज करना: SegmentationOverlay.kt फ़ाइल में TODO ढूंढें और इसे इस कोड से बदलें. यह कोड यह पता लगाता है कि सामने की ओर मौजूद कैमरा चालू है या नहीं. अगर कैमरा चालू है, तो यह कोड, ओवरले Bitmap को Canvas पर ड्रॉ करने से पहले, उसे हॉरिज़ॉन्टल फ़्लिप करता है. (~लाइन 42):
    val orientedBitmap =
      if (lensFacing == CameraSelector.LENS_FACING_FRONT) {
        // Create a matrix for horizontal flipping
        val matrix = Matrix().apply { preScale(-1f, 1f) }
        Bitmap.createBitmap(image, 0, 0, image.width, image.height, matrix, false).also {
          image.recycle()
        }
      } else {
        image
      }
    

10. फ़ाइनल ऐप्लिकेशन को चलाना और उसका इस्तेमाल करना

अब आपने कोड में सभी ज़रूरी बदलाव कर लिए हैं. अब ऐप्लिकेशन को चलाने और अपने काम को ऐक्शन में देखने का समय है!

  1. ऐप्लिकेशन चलाना: अपने Android डिवाइस को कनेक्ट करें. इसके बाद, Android Studio टूलबार में मौजूद चलाएं पर क्लिक करें.

'चलाएं' बटन

  1. सुविधाओं को आज़माएं: ऐप्लिकेशन लॉन्च होने के बाद, आपको लाइव कैमरा फ़ीड दिखेगा. इसमें रंगीन सेगमेंटेशन ओवरले होगा.
    • कैमरे स्विच करें: फ़्रंट और बैक कैमरे के बीच स्विच करने के लिए, सबसे ऊपर मौजूद कैमरा फ़्लिप करने वाले आइकॉन पर टैप करें. ध्यान दें कि ओवरले सही तरीके से कैसे दिखता है.
    • ऐक्सेलरेटर बदलें: हार्डवेयर ऐक्सेलरेटर बदलने के लिए, सबसे नीचे मौजूद "सीपीयू" या "जीपीयू" बटन पर टैप करें. स्क्रीन पर सबसे नीचे दिख रहे अनुमान लगाने में लगने वाले समय में हुए बदलाव को देखें. जीपीयू की परफ़ॉर्मेंस काफ़ी बेहतर होनी चाहिए.
    • गैलरी में मौजूद इमेज का इस्तेमाल करना: अपने डिवाइस की फ़ोटो गैलरी से कोई इमेज चुनने के लिए, सबसे ऊपर मौजूद "गैलरी" टैब पर टैप करें. ऐप्लिकेशन, चुनी गई स्टैटिक इमेज पर सेगमेंटेशन करेगा.

अन्य यूज़र इंटरफ़ेस (यूआई)

अब आपके पास LiteRT की मदद से बनाया गया, पूरी तरह से काम करने वाला इमेज सेगमेंटेशन ऐप्लिकेशन है!

11. ऐडवांस (ज़रूरी नहीं): एनपीयू का इस्तेमाल करना

इस रिपॉज़िटरी में, ऐप्लिकेशन का ऐसा वर्शन भी शामिल है जिसे न्यूरल प्रोसेसिंग यूनिट (एनपीयू) के लिए ऑप्टिमाइज़ किया गया है. एनपीयू वर्शन, उन डिवाइसों पर परफ़ॉर्मेंस को बेहतर बना सकता है जिनमें एनपीयू की सुविधा काम करती है.

एनपीयू वर्शन को आज़माने के लिए, Android Studio में kotlin_npu/android प्रोजेक्ट खोलें. यह कोड, सीपीयू/जीपीयू वर्शन से काफ़ी मिलता-जुलता है. इसे NPU डेलिगेट का इस्तेमाल करने के लिए कॉन्फ़िगर किया गया है.

एनपीयू डेलिगेट को लागू करने का उदाहरण देखने के लिए, हमारा सैंपल ऐप्लिकेशन देखें.

12. बधाई हो!

आपने LiteRT का इस्तेमाल करके, रीयल-टाइम में इमेज सेगमेंटेशन करने वाला Android ऐप्लिकेशन बना लिया है. आपने इन कामों को करने का तरीका सीखा:

  • Android ऐप्लिकेशन में LiteRT रनटाइम को इंटिग्रेट करें.
  • TFLite इमेज सेगमेंटेशन मॉडल को लोड और रन करें.
  • मॉडल के इनपुट को पहले से प्रोसेस करना.
  • सेगमेंटेशन मास्क बनाने के लिए, मॉडल के आउटपुट को प्रोसेस करें.
  • रीयल-टाइम कैमरा ऐप्लिकेशन के लिए, CameraX का इस्तेमाल करें.

अगले चरण

  • इमेज सेगमेंटेशन के लिए कोई दूसरा मॉडल आज़माएं.
  • अलग-अलग LiteRT डेलिगेट (सीपीयू, जीपीयू, एनपीयू) के साथ एक्सपेरिमेंट करें.

ज़्यादा जानें