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

1. قبل از شروع

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

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

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

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

برنامه تمام شده

پیش نیازها

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

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

چیزی که یاد خواهید گرفت

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

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

  • نسخه اخیر اندروید استودیو (تست شده در نسخه 2025.1.1).
  • یک دستگاه اندروید فیزیکی بهترین تست روی دستگاه های Galaxy و Pixel است.
  • کد نمونه (از GitHub).
  • دانش اولیه توسعه اندروید در Kotlin.

2. تقسیم بندی تصویر

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

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

نمونه تقسیم بندی

LiteRT: فشار دادن لبه ML روی دستگاه

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

این امر از طریق استفاده هوشمندانه و بهینه از شتاب دهنده های سخت افزاری مانند GPU (واحد پردازش گرافیکی) و NPU (واحد پردازش عصبی) به دست می آید. با بارگذاری حجم کار محاسباتی شدید مدل تقسیم‌بندی از CPU همه منظوره به این پردازنده‌های تخصصی، 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 مشخص می کند. اگر در ویندوز هستید، باید مسیرها را بر اساس آن تنظیم کنید.

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

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

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

  1. Android Studio را باز کرده و Open را انتخاب کنید.

Android Studio باز است

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

Android Starter

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

  1. Sync Project with Gradle Files را از نوار ابزار Android Studio انتخاب کنید.

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

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

برنامه استارتر را اجرا کنید

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

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

دکمه اجرا

برنامه باید روی دستگاه شما راه اندازی شود. شما یک فید دوربین زنده خواهید دید، اما هنوز هیچ بخش‌بندی انجام نمی‌شود. تمام ویرایش‌های فایلی که در این آموزش انجام می‌دهید تحت LiteRT/litert/samples/image_segmentation/kotlin_cpu_gpu/android_starter/app/src/main/java/com/google/aiedge/examples/image_segmentation دایرکتوری خواهند بود.

مدیر پروژه

همچنین نظرات TODO را در فایل های ImageSegmentationHelper.kt ، MainViewModel.kt و view/SegmentationOverlay.kt خواهید دید. در مراحل زیر، با پر کردن این 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 : این تابع Composable پیش‌نمایش دوربین و پوشش تقسیم‌بندی را نمایش می‌دهد.
  • app/src/main/assets/selfie_multiclass.tflite : این مدل تقسیم‌بندی تصویر TensorFlow Lite از پیش آموزش‌دیده است که ما از آن استفاده خواهیم کرد.

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

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

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

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

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

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

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

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

2. API های Key LiteRT را درک کنید

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 شما است. این مدلی را نشان می دهد که برای یک شتاب دهنده سخت افزاری خاص (مانند CPU یا GPU) از قبل کامپایل و بهینه شده است. این پیش کامپایل یکی از ویژگی های کلیدی LiteRT است که منجر به استنتاج سریعتر و کارآمدتر می شود.
  • CompiledModel.Options : شما از این کلاس سازنده برای پیکربندی CompiledModel استفاده می کنید. مهم ترین تنظیم، تعیین شتاب دهنده سخت افزاری است که می خواهید برای اجرای مدل خود استفاده کنید.
  • Accelerator : این فهرست به شما امکان می دهد سخت افزار را برای استنتاج انتخاب کنید. پروژه شروع از قبل برای مدیریت این گزینه ها پیکربندی شده است:
    • Accelerator.CPU : برای اجرای مدل بر روی CPU دستگاه. این سازگارترین گزینه جهانی است.
    • Accelerator.GPU : برای اجرای مدل بر روی GPU دستگاه. این اغلب به طور قابل توجهی سریعتر از CPU برای مدل های مبتنی بر تصویر است.
  • بافرهای ورودی و خروجی ( TensorBuffer ) : LiteRT از TensorBuffer برای ورودی ها و خروجی های مدل استفاده می کند. این به شما کنترل دقیقی بر حافظه می دهد و از کپی های غیر ضروری داده ها جلوگیری می کند. شما این بافرها را مستقیماً از نمونه CompiledModel خود با استفاده از model.createInputBuffers() و model.createOutputBuffers() دریافت می کنید و سپس داده های ورودی خود را روی آنها می نویسید و نتایج را از روی آنها می خوانید.
  • model.run() : این تابعی است که استنتاج را اجرا می کند. شما بافرهای ورودی و خروجی را به آن منتقل می کنید و LiteRT وظیفه پیچیده اجرای مدل را بر روی شتاب دهنده سخت افزاری انتخاب شده انجام می دهد.

6. اجرای Initial 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. روش to Accelerator را تعریف کنید : این روش فهرست‌های شتاب‌دهنده تعریف‌شده را از منوی شتاب‌دهنده به فهرست‌های شتاب‌دهنده مخصوص ماژول‌های LiteRT وارداتی نگاشت می‌کند (~خط 225):
    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)
    }
    

7. بخش بندی و پیش پردازش را شروع کنید

اکنون که یک مدل داریم، باید فرآیند تقسیم بندی را راه اندازی کنیم و داده های ورودی را برای مدل آماده کنیم.

تقسیم بندی ماشه

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

MainViewModel.kt باز کنید

  1. Trigger Segmentation from Camera Frames : توابع 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 با (~خط 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. پیاده سازی پیش پردازش : تابع segment خصوصی در کلاس Segmenter جایی است که ما تغییرات لازم را روی تصویر ورودی انجام می دهیم تا آن را برای مدل آماده کنیم. این شامل مقیاس بندی، چرخش و عادی سازی تصویر است. سپس این تابع تابع 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)
    

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

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

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

  1. اجرای مدل پیاده سازی : تابع segment(inputFloatArray: FloatArray) جایی است که ما مستقیماً با متد run() LiteRT تعامل داریم. داده های از پیش پردازش شده خود را در بافر ورودی می نویسیم، مدل را اجرا می کنیم و نتایج را از بافر خروجی می خوانیم. 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 آن را با (~خط 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. جمع آوری و پردازش نتایج تقسیم بندی : اکنون به MainViewModel برمی گردیم تا نتایج تقسیم بندی را از ImageSegmentationHelper پردازش کنیم. segmentationUiShareFlow SegmentationResult جمع آوری می کند، ماسک را به یک Bitmap رنگارنگ تبدیل می کند و آن را در اختیار UI قرار می دهد. TODO در ویژگی segmentationUiShareFlow با (~خط 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. Handle Overlay Orientation : TODO در فایل SegmentationOverlay.kt پیدا کرده و کد زیر را جایگزین آن کنید. این کد بررسی می‌کند که آیا دوربین جلو فعال است یا نه، قبل از اینکه روی Canvas کشیده شود، یک چرخش افقی روی Bitmap اعمال می‌کند. (~خط 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 خود را وصل کنید و روی Run در نوار ابزار Android Studio کلیک کنید.

دکمه اجرا

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

UI دیگر

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

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

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

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

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

12. تبریک می گویم!

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

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

مراحل بعدی

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

بیشتر بدانید