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

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

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

इस कोडलैब में, आपको एक Android ऐप्लिकेशन बनाने का तरीका बताया जाएगा. यह ऐप्लिकेशन, लाइव कैमरा फ़ीड पर रीयल-टाइम इमेज सेगमेंटेशन करता है. इसके लिए, 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: Pushing the Edge of On-Device ML

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

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

3. सेट अप करें

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

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

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

LiteRT/litert/samples/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/litert/samples/image_segmentation/kotlin_cpu_gpu/android_starter/app/src/main/java/com/google/aiedge/examples/image_segmentation डायरेक्ट्री में सेव होंगे. अब आपको पता चल गया होगा कि Android Studio, इस डायरेक्ट्री को फिर से क्यों व्यवस्थित करता है 😃.

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

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

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

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

  • app/src/main/java/com/google/aiedge/examples/image_segmentation/MainActivity.kt: यह ऐप्लिकेशन का मुख्य एंट्री पॉइंट है. यह Jetpack Compose का इस्तेमाल करके यूज़र इंटरफ़ेस (यूआई) सेट अप करता है और कैमरे की अनुमतियों को मैनेज करता है.
  • app/src/main/java/com/google/aiedge/examples/image_segmentation/MainViewModel.kt: यह ViewModel, यूज़र इंटरफ़ेस (यूआई) की स्थिति को मैनेज करता है. साथ ही, इमेज सेगमेंटेशन की प्रोसेस को व्यवस्थित करता है.
  • app/src/main/java/com/google/aiedge/examples/image_segmentation/ImageSegmentationHelper.kt: यहां हम इमेज सेगमेंटेशन के लिए मुख्य लॉजिक जोड़ेंगे. यह मॉडल को लोड करने, कैमरा फ़्रेम को प्रोसेस करने, और अनुमान लगाने का काम करेगा.
  • app/src/main/java/com/google/aiedge/examples/image_segmentation/view/CameraScreen.kt: यह कंपोज़ेबल फ़ंक्शन, कैमरे की झलक और सेगमेंटेशन ओवरले दिखाता है.
  • app/src/main/assets/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() नाम का एक फ़ंक्शन मिलेगा. सबसे पहले, Segmenter क्लास को पूरा करें. इसके लिए, मॉडल को होल्ड करने के लिए कंस्ट्रक्टर को तय करें, इनपुट/आउटपुट बफ़र के लिए प्रॉपर्टी बनाएं, और मॉडल को रिलीज़ करने के लिए close() तरीका जोड़ें. इसके बाद, इस नए 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 तरीके को तय करें: यह तरीका, ऐक्सेलरेटर मेन्यू से तय किए गए ऐक्सेलरेटर enum को इंपोर्ट किए गए LiteRT मॉड्यूल (~लाइन 225) के लिए खास तौर पर बनाए गए ऐक्सेलरेटर enum से मैप करता है:
    fun toAccelerator(acceleratorEnum: AcceleratorEnum): Accelerator {
      return when (acceleratorEnum) {
        AcceleratorEnum.CPU -> Accelerator.CPU
        AcceleratorEnum.GPU -> Accelerator.GPU
      }
    }
    
  3. CompiledModel को शुरू करें: अब initSegmenter फ़ंक्शन ढूंढें. यहां आपको CompiledModel इंस्टेंस बनाना होगा. साथ ही, इसका इस्तेमाल करके, अब तय की गई Segmenter क्लास को इंस्टैंशिएट करना होगा. यह कोड, मॉडल को तय किए गए ऐक्सेलरेटर (सीपीयू या जीपीयू) के साथ सेट अप करता है और इसे अनुमान लगाने के लिए तैयार करता है. TODO में मौजूद TODO को इस कोड से बदलें (Cmd/Ctrl+f ‘initSegmenter` या ~लाइन 62):initSegmenter
    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 को (~line 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 को इससे बदलें (~line 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 में बदलता है. यह ByteBuffer, सेगमेंटेशन मास्क को दिखाता है. यह हर पिक्सल के लिए, सबसे ज़्यादा संभावना वाली क्लास का पता लगाकर ऐसा करता है. इसके TODO को (~लाइन 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 टूलबार में मौजूद चलाएं पर क्लिक करें.

&#39;चलाएं&#39; बटन

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

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

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

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

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

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

एनपीयू डेलिगेट का इस्तेमाल करने के लिए, आपको अर्ली ऐक्सेस प्रोग्राम में रजिस्टर करना होगा.

12. बधाई हो!

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

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

अगले चरण

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

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