تقسیم‌بندی سریع تصویر در اندروید با LiteRT

۱. قبل از شروع

تایپ کردن کد راهی عالی برای تقویت حافظه و تعمیق درک شما از مطالب است. در حالی که کپی-پیست کردن می‌تواند در زمان صرفه‌جویی کند، سرمایه‌گذاری روی این تمرین می‌تواند در درازمدت منجر به کارایی بیشتر و مهارت‌های کدنویسی قوی‌تر شود.

در این آزمایشگاه کد، شما یاد خواهید گرفت که چگونه یک برنامه اندروید بسازید که با استفاده از ران‌تایم جدید گوگل برای TensorFlow Lite، LiteRT، قطعه‌بندی تصویر را به صورت بلادرنگ روی یک دوربین زنده انجام دهد. شما یک برنامه اندروید مبتدی را انتخاب کرده و قابلیت‌های قطعه‌بندی تصویر را به آن اضافه خواهید کرد. همچنین مراحل پیش‌پردازش، استنتاج و پس‌پردازش را نیز بررسی خواهیم کرد. شما:

  • یک برنامه اندروید بسازید که تصاویر را به صورت بلادرنگ (real-time) قطعه‌بندی کند.
  • یک مدل قطعه‌بندی تصویر LiteRT از پیش آموزش‌دیده را ادغام کنید.
  • تصویر ورودی برای مدل را پیش‌پردازش کنید.
  • از LiteRT runtime برای شتاب‌دهی CPU و GPU استفاده کنید.
  • نحوه پردازش خروجی مدل برای نمایش ماسک تقسیم‌بندی را درک کنید.
  • نحوه تنظیم دوربین جلو را یاد بگیرید.

در نهایت، چیزی شبیه به تصویر زیر ایجاد خواهید کرد:

برنامه تکمیل شده

پیش‌نیازها

این آزمایشگاه کد برای توسعه‌دهندگان باتجربه موبایل که می‌خواهند در زمینه یادگیری ماشین تجربه کسب کنند، طراحی شده است. شما باید با موارد زیر آشنا باشید:

  • توسعه اندروید با استفاده از کاتلین و اندروید استودیو
  • مفاهیم اولیه پردازش تصویر

آنچه یاد خواهید گرفت

  • نحوه ادغام و استفاده از LiteRT runtime در یک برنامه اندروید.
  • نحوه انجام قطعه‌بندی تصویر با استفاده از یک مدل LiteRT از پیش آموزش‌دیده.
  • نحوه پیش‌پردازش تصویر ورودی برای مدل.
  • نحوه اجرای استنتاج برای مدل.
  • نحوه پردازش خروجی یک مدل قطعه‌بندی برای تجسم نتایج.
  • نحوه استفاده از CameraX برای پردازش فید دوربین در لحظه.

آنچه نیاز دارید

  • نسخه جدیدی از اندروید استودیو (آزمایش شده روی نسخه ۲۰۲۵.۱.۱).
  • یک دستگاه اندروید فیزیکی. بهتر است روی دستگاه‌های گلکسی و پیکسل آزمایش شود.
  • کد نمونه (از گیت‌هاب).
  • آشنایی اولیه با توسعه اندروید با زبان کاتلین

۲. قطعه‌بندی تصویر

تقسیم‌بندی تصویر یک وظیفه بینایی کامپیوتر است که شامل تقسیم یک تصویر به چندین بخش یا ناحیه می‌شود. برخلاف تشخیص شیء که یک کادر مرزی در اطراف یک شیء رسم می‌کند، تقسیم‌بندی تصویر یک کلاس یا برچسب خاص را به هر پیکسل در تصویر اختصاص می‌دهد. این امر درک بسیار دقیق‌تر و جزئی‌تری از محتوای تصویر را فراهم می‌کند و به شما امکان می‌دهد شکل و مرز دقیق هر شیء را بدانید.

برای مثال، به جای اینکه فقط بدانید یک «شخص» در یک جعبه قرار دارد، می‌توانید دقیقاً بدانید کدام پیکسل‌ها متعلق به آن شخص هستند. این آموزش نحوه انجام قطعه‌بندی تصویر در زمان واقعی را در یک دستگاه اندروید با استفاده از یک مدل یادگیری ماشین از پیش آموزش دیده نشان می‌دهد.

مثال تقسیم‌بندی

LiteRT: پیشروی در یادگیری ماشین روی دستگاه

یک فناوری کلیدی که امکان تقسیم‌بندی با دقت بالا و بلادرنگ را در دستگاه‌های تلفن همراه فراهم می‌کند، LiteRT است. LiteRT به عنوان نسل بعدی و با عملکرد بالای زمان اجرای گوگل برای TensorFlow Lite، به گونه‌ای مهندسی شده است که بهترین عملکرد مطلق را از سخت‌افزار اصلی دریافت کند.

این امر از طریق استفاده هوشمندانه و بهینه از شتاب‌دهنده‌های سخت‌افزاری مانند GPU (واحد پردازش گرافیکی) و NPU (واحد پردازش عصبی) محقق می‌شود. LiteRT با انتقال حجم کار محاسباتی شدید مدل قطعه‌بندی از CPU عمومی به این پردازنده‌های تخصصی، زمان استنتاج را به طرز چشمگیری کاهش می‌دهد. این شتاب همان چیزی است که اجرای روان مدل‌های پیچیده را بر روی یک تصویر زنده دوربین امکان‌پذیر می‌کند و مرز آنچه را که می‌توانیم با یادگیری ماشینی مستقیماً بر روی تلفن خود به دست آوریم، گسترش می‌دهد. بدون این سطح از عملکرد، قطعه‌بندی در زمان واقعی برای یک تجربه کاربری خوب بسیار کند و ناهموار خواهد بود.

۳. آماده شوید

مخزن را کلون کنید

ابتدا، مخزن LiteRT را کلون کنید:

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

litert-samples/v2/image_segmentation دایرکتوری است که تمام منابع مورد نیاز شما را در خود جای داده است. برای این آزمایشگاه کد، فقط به پروژه kotlin_cpu_gpu/android_starter نیاز خواهید داشت. در صورت بروز مشکل، می‌توانید پروژه نهایی را بررسی کنید: kotlin_cpu_gpu/android

نکته‌ای در مورد مسیرهای فایل

این آموزش مسیر فایل‌ها را در قالب لینوکس/مک مشخص می‌کند. اگر از ویندوز استفاده می‌کنید، باید مسیرها را متناسب با آن تنظیم کنید.

همچنین توجه به تمایز بین نمای پروژه اندروید استودیو و نمای استاندارد سیستم فایل مهم است. نمای پروژه اندروید استودیو نمایشی ساختاریافته از فایل‌های پروژه شماست که برای توسعه اندروید سازماندهی شده است. مسیرهای فایل در این آموزش به مسیرهای سیستم فایل اشاره دارند، نه مسیرهای موجود در نمای پروژه اندروید استودیو.

برنامه شروع کننده را وارد کنید

بیایید با وارد کردن برنامه اولیه به اندروید استودیو شروع کنیم.

  1. اندروید استودیو را باز کنید و گزینه Open را انتخاب کنید.

اندروید استودیو باز است

  1. به پوشه‌ی kotlin_cpu_gpu/android_starter بروید و آن را باز کنید.

شروع کننده اندروید

برای اطمینان از اینکه همه وابستگی‌ها برای برنامه شما در دسترس هستند، باید پس از اتمام فرآیند وارد کردن، پروژه خود را با فایل‌های gradle همگام‌سازی کنید.

  1. از نوار ابزار اندروید استودیو، گزینه‌ی «همگام‌سازی پروژه با فایل‌های گرادل» (Sync Project with Gradle Files) را انتخاب کنید.

همگام‌سازی منو

  1. لطفاً این مرحله را نادیده نگیرید - اگر این مرحله کار نکند، بقیه آموزش بی‌معنی خواهد بود.

برنامه شروع کننده را اجرا کنید

حالا که پروژه را به اندروید استودیو وارد کرده‌اید، آماده‌اید تا برای اولین بار برنامه را اجرا کنید.

دستگاه اندروید خود را از طریق USB به رایانه متصل کنید و در نوار ابزار اندروید استودیو روی Run کلیک کنید.

دکمه اجرا

برنامه باید روی دستگاه شما اجرا شود. شما یک فید دوربین زنده را خواهید دید، اما هنوز هیچ تقسیم‌بندی‌ای انجام نخواهد شد. تمام ویرایش‌های فایلی که در این آموزش انجام خواهید داد، در دایرکتوری litert-samples/v2/image_segmentation/kotlin_cpu_gpu/android_starter/app/src/main/java/com/google/ai/edge/examples/image_segmentation قرار خواهند گرفت (حالا می‌دانید که چرا اندروید استودیو این را بازسازی می‌کند 😃).

مدیر پروژه

همچنین می‌توانید کامنت‌های TODO را در فایل‌های ImageSegmentationHelper.kt ، MainViewModel.kt و view/SegmentationOverlay.kt مشاهده کنید. در مراحل بعدی، با پر کردن این TODO ها، قابلیت تقسیم‌بندی تصویر را پیاده‌سازی خواهید کرد.

۴. اپلیکیشن اولیه را درک کنید

برنامه‌ی آغازین از قبل یک رابط کاربری اولیه و منطق کار با دوربین دارد. در اینجا مروری سریع بر فایل‌های کلیدی داریم:

  • 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 : این تابع Composable پیش‌نمایش دوربین و پوشش تقسیم‌بندی را نمایش می‌دهد.
  • app/download_model.gradle : این اسکریپت selfie_multiclass.tflite را دانلود می‌کند. این مدل قطعه‌بندی تصویر TensorFlow Lite از پیش آموزش‌دیده است که ما از آن استفاده خواهیم کرد.

۵. درک LiteRT و افزودن وابستگی‌ها

حالا، بیایید قابلیت تقسیم‌بندی تصویر را به برنامه‌ی اولیه اضافه کنیم.

۱. وابستگی LiteRT را اضافه کنید

ابتدا باید کتابخانه LiteRT را به پروژه خود اضافه کنید. این اولین قدم حیاتی برای فعال کردن یادگیری ماشینی روی دستگاه با زمان اجرای بهینه شده گوگل است.

فایل app/build.gradle.kts را باز کنید و خط زیر را به بلوک dependencies اضافه کنید:

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

پس از افزودن وابستگی، با کلیک روی دکمه Sync Now که در گوشه بالا سمت راست اندروید استودیو ظاهر می‌شود، پروژه خود را با فایل‌های Gradle همگام‌سازی کنید.

اکنون همگام‌سازی کنید

۲. درک API های کلیدی LiteRT

ImageSegmentationHelper.kt را باز کنید

قبل از نوشتن کد پیاده‌سازی، درک اجزای اصلی LiteRT API که استفاده خواهید کرد، مهم است. مطمئن شوید که از بسته com.google.ai.edge.litert وارد می‌کنید، importهای زیر را به بالای ImageSegmentationHelper.kt اضافه کنید:

import com.google.ai.edge.litert.Accelerator
import com.google.ai.edge.litert.CompiledModel
  • CompiledModel : این کلاس مرکزی برای تعامل با مدل TFLite شماست. این کلاس، مدلی را نشان می‌دهد که از قبل کامپایل شده و برای یک شتاب‌دهنده سخت‌افزاری خاص (مانند CPU یا GPU) بهینه شده است. این پیش‌کامپایل، یکی از ویژگی‌های کلیدی LiteRT است که منجر به استنتاج سریع‌تر و کارآمدتر می‌شود.
  • CompiledModel.Options : شما از این کلاس سازنده برای پیکربندی CompiledModel استفاده می‌کنید. مهمترین تنظیم، مشخص کردن شتاب‌دهنده سخت‌افزاری است که می‌خواهید برای اجرای مدل خود استفاده کنید.
  • Accelerator : این enum به شما امکان می‌دهد سخت‌افزار مورد نیاز برای استنتاج را انتخاب کنید. پروژه اولیه از قبل برای مدیریت این گزینه‌ها پیکربندی شده است:
    • Accelerator.CPU : برای اجرای مدل روی CPU دستگاه. این گزینه سازگارترین گزینه برای همه است.
    • Accelerator.GPU : برای اجرای مدل روی GPU دستگاه. این اغلب برای مدل‌های مبتنی بر تصویر به طور قابل توجهی سریع‌تر از CPU است.
  • بافرهای ورودی و خروجی ( TensorBuffer ) : LiteRT از TensorBuffer برای ورودی‌ها و خروجی‌های مدل استفاده می‌کند. این به شما کنترل دقیقی بر حافظه می‌دهد و از کپی‌های غیرضروری داده‌ها جلوگیری می‌کند. شما این بافرها را مستقیماً از نمونه CompiledModel خود با استفاده از model.createInputBuffers() و model.createOutputBuffers() دریافت خواهید کرد و سپس داده‌های ورودی خود را در آنها می‌نویسید و نتایج را از آنها می‌خوانید.
  • model.run() : این تابعی است که استنتاج را اجرا می‌کند. شما بافرهای ورودی و خروجی را به آن ارسال می‌کنید و LiteRT وظیفه پیچیده اجرای مدل روی شتاب‌دهنده سخت‌افزاری انتخاب شده را بر عهده می‌گیرد.

۶. پیاده‌سازی اولیه‌ی ImageSegmentationHelper را تکمیل کنید

حالا وقت نوشتن کد است. شما پیاده‌سازی اولیه‌ی ImageSegmentationHelper.kt را تکمیل خواهید کرد. این شامل راه‌اندازی کلاس خصوصی Segmenter برای نگهداری مدل LiteRT و پیاده‌سازی تابع cleanup() برای آزادسازی صحیح آن است.

  1. تکمیل کلاس Segmenter و تابع cleanup() : در فایل ImageSegmentationHelper.kt ، اسکلتی برای یک کلاس خصوصی به نام Segmenter و تابعی به نام cleanup() خواهید یافت. ابتدا، کلاس Segmenter را با تعریف سازنده آن برای نگهداری مدل، ایجاد ویژگی‌هایی برای بافرهای ورودی/خروجی و افزودن یک متد close() برای آزادسازی مدل، تکمیل کنید. سپس، تابع cleanup() را برای فراخوانی این متد close() جدید پیاده‌سازی کنید. کلاس 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های شتاب‌دهنده تعریف‌شده از منوی شتاب‌دهنده را به enumهای شتاب‌دهنده مختص ماژول‌های LiteRT واردشده نگاشت می‌کند (~خط ۲۲۵):
    fun toAccelerator(acceleratorEnum: AcceleratorEnum): Accelerator {
      return when (acceleratorEnum) {
        AcceleratorEnum.CPU -> Accelerator.CPU
        AcceleratorEnum.GPU -> Accelerator.GPU
      }
    }
    
  3. مقداردهی اولیه CompiledModel : اکنون تابع initSegmenter را پیدا کنید. در اینجا نمونه CompiledModel را ایجاد کرده و از آن برای نمونه‌سازی کلاس Segmenter که اکنون تعریف کرده‌اید استفاده خواهید کرد. این کد مدل را با شتاب‌دهنده مشخص‌شده (CPU یا GPU) تنظیم کرده و آن را برای استنتاج آماده می‌کند. TODO در initSegmenter با پیاده‌سازی زیر جایگزین کنید (Cmd/Ctrl+f 'initSegmenter` یا ~line 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)
    }
    

۷. شروع قطعه‌بندی و پیش‌پردازش

حالا که یک مدل داریم، باید فرآیند قطعه‌بندی را آغاز کنیم و داده‌های ورودی را برای مدل آماده کنیم.

تقسیم‌بندی تریگر

فرآیند قطعه‌بندی در MainViewModel.kt آغاز می‌شود که فریم‌ها را از دوربین دریافت می‌کند.

MainViewModel.kt را باز کنید .

  1. فعال‌سازی قطعه‌بندی از فریم‌های دوربین : توابع segment در MainViewModel نقطه ورود برای وظیفه قطعه‌بندی ما هستند. این توابع هر زمان که تصویر جدیدی از دوربین در دسترس باشد یا از گالری انتخاب شود، فراخوانی می‌شوند. سپس این توابع، متد segment را در ImageSegmentationHelper ما فراخوانی می‌کنند. TODO های هر دو تابع segment را با موارد زیر جایگزین کنید (خط ~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. پیاده‌سازی تابع segment عمومی : این تابع به عنوان یک پوشش عمل می‌کند که تابع segment خصوصی را درون کلاس Segmenter فراخوانی می‌کند. TODO با (~خط ۹۵) جایگزین کنید:
    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. پیاده‌سازی پیش‌پردازش : تابع private segment درون کلاس Segmenter جایی است که ما تبدیل‌های لازم را روی تصویر ورودی انجام می‌دهیم تا آن را برای مدل آماده کنیم. این شامل مقیاس‌بندی، چرخش و نرمال‌سازی تصویر می‌شود. سپس این تابع یک تابع private segment دیگر را برای انجام استنتاج فراخوانی می‌کند. TODO را در segment(bitmap: Bitmap, ...) با (~خط 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)
    

۸. استنتاج اولیه با LiteRT

با پیش‌پردازش داده‌های ورودی، اکنون می‌توانیم استنتاج اصلی را با استفاده از LiteRT اجرا کنیم.

ImageSegmentationHelper.kt را باز کنید

  1. پیاده‌سازی اجرای مدل : تابع private 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,
    )
    

۹. پس‌پردازش و نمایش لایه رویی

پس از اجرای استنتاج، یک خروجی خام از مدل دریافت می‌کنیم. باید این خروجی را پردازش کنیم تا یک ماسک تقسیم‌بندی بصری ایجاد کنیم و سپس آن را روی صفحه نمایش دهیم.

ImageSegmentationHelper.kt را باز کنید

  1. پیاده‌سازی پردازش خروجی : تابع processImage خروجی خام ممیز شناور از مدل را به یک ByteBuffer تبدیل می‌کند که نشان‌دهنده ماسک تقسیم‌بندی است. این کار را با یافتن کلاسی با بالاترین احتمال برای هر پیکسل انجام می‌دهد. TODO آن را با (~خط ۲۳۸) جایگزین کنید:
    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. جمع‌آوری و پردازش نتایج تقسیم‌بندی : اکنون به MainViewModel برمی‌گردیم تا نتایج تقسیم‌بندی را از ImageSegmentationHelper پردازش کنیم. segmentationUiShareFlow SegmentationResult جمع‌آوری می‌کند، ماسک را به یک Bitmap رنگارنگ تبدیل می‌کند و آن را در اختیار رابط کاربری قرار می‌دهد. TODO در ویژگی segmentationUiShareFlow با (~خط ۶۳) جایگزین کنید - کد موجود در آنجا را جایگزین نکنید، فقط بدنه را پر کنید:
    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. مدیریت جهت قرارگیری لایه : TODO را در فایل SegmentationOverlay.kt پیدا کنید و آن را با کد زیر جایگزین کنید. این کد بررسی می‌کند که آیا دوربین جلو فعال است یا خیر و در این صورت، قبل از ترسیم لایه Bitmap روی Canvas ، یک چرخش افقی به آن اعمال می‌کند. (~خط ۴۲):
    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
      }
    

۱۰. اجرا و استفاده از برنامه نهایی

اکنون تمام تغییرات لازم در کد را انجام داده‌اید. وقت آن است که برنامه را اجرا کنید و نتیجه کار خود را در عمل ببینید!

  1. اجرای برنامه : دستگاه اندروید خود را متصل کنید و در نوار ابزار اندروید استودیو روی Run کلیک کنید.

دکمه اجرا

  1. ویژگی‌ها را آزمایش کنید : به محض اجرای برنامه، باید تصویر زنده دوربین را با یک پوشش رنگی از تقسیم‌بندی‌ها مشاهده کنید.
    • تغییر دوربین : برای تغییر بین دوربین جلو و عقب، روی آیکون چرخش دوربین در بالا ضربه بزنید. توجه کنید که چگونه لایه رویی به درستی جهت گیری می‌کند.
    • تغییر شتاب‌دهنده : برای تغییر شتاب‌دهنده سخت‌افزاری، روی دکمه «CPU» یا «GPU» در پایین صفحه ضربه بزنید. تغییر زمان استنتاج نمایش داده شده در پایین صفحه را مشاهده کنید. پردازنده گرافیکی (GPU) باید به طور قابل توجهی سریع‌تر باشد.
    • استفاده از تصویر گالری : برای انتخاب تصویر از گالری عکس دستگاه خود، روی زبانه «گالری» در بالا ضربه بزنید. برنامه، تقسیم‌بندی را روی تصویر ثابت انتخاب شده اجرا خواهد کرد.

رابط کاربری دیگر

اکنون شما یک برنامه‌ی قطعه‌بندی تصویر کاملاً کاربردی و بلادرنگ دارید که توسط LiteRT پشتیبانی می‌شود!

۱۱. پیشرفته (اختیاری): استفاده از NPU

این مخزن همچنین شامل نسخه‌ای از برنامه است که برای واحدهای پردازش عصبی (NPU) بهینه شده است. نسخه NPU می‌تواند افزایش عملکرد قابل توجهی را در دستگاه‌هایی که دارای NPU سازگار هستند، ارائه دهد.

برای امتحان کردن نسخه NPU، پروژه kotlin_npu/android را در اندروید استودیو باز کنید. کد بسیار شبیه به نسخه CPU/GPU است و برای استفاده از نماینده NPU پیکربندی شده است.

برای استفاده از نماینده NPU، باید در برنامه دسترسی زودهنگام ثبت نام کنید.

۱۲. تبریک می‌گویم!

شما با موفقیت یک برنامه اندروید ساختید که با استفاده از LiteRT، قطعه‌بندی تصویر را به صورت بلادرنگ انجام می‌دهد. شما یاد گرفته‌اید که چگونه:

  • ادغام زمان اجرای LiteRT در یک برنامه اندروید.
  • یک مدل تقسیم‌بندی تصویر TFLite را بارگذاری و اجرا کنید.
  • ورودی مدل را پیش‌پردازش کنید.
  • خروجی مدل را برای ایجاد یک ماسک تقسیم‌بندی پردازش کنید.
  • برای یک برنامه دوربین در لحظه از CameraX استفاده کنید.

مراحل بعدی

  • یک مدل تقسیم‌بندی تصویر متفاوت را امتحان کنید.
  • با نمایندگان مختلف LiteRT (CPU، GPU، NPU) آزمایش کنید.

اطلاعات بیشتر