با MediaPipe Tasks یک برنامه Android طبقه‌بندی‌کننده رقمی دست‌نویس بسازید

1. مقدمه

MediaPipe چیست؟

MediaPipe Solutions به شما امکان می دهد راه حل های یادگیری ماشینی (ML) را در برنامه های خود اعمال کنید. این یک چارچوب برای پیکربندی خطوط لوله پردازش از پیش ساخته شده است که خروجی فوری، جذاب و مفید را به کاربران ارائه می دهد. حتی می توانید این راه حل ها را با MediaPipe Model Maker سفارشی کنید تا مدل های پیش فرض را به روز کنید.

طبقه بندی تصویر یکی از چندین کار بینایی ML است که MediaPipe Solutions ارائه می کند. MediaPipe Tasks برای اندروید، iOS، پایتون (از جمله Raspberry Pi!) و وب در دسترس است.

در این Codelab، شما با یک برنامه اندروید شروع می‌کنید که به شما امکان می‌دهد ارقام عددی را روی صفحه بکشید، سپس عملکردی را اضافه خواهید کرد که آن ارقام ترسیم شده را به عنوان یک مقدار از 0 تا 9 طبقه‌بندی می‌کند.

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

  • نحوه گنجاندن یک کار طبقه بندی تصویر در یک برنامه اندروید با MediaPipe Tasks .

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

  • نسخه نصب شده اندروید استودیو (این کد لبه با Android Studio Giraffe نوشته و تست شده است).
  • یک دستگاه اندروید یا شبیه ساز برای اجرای برنامه.
  • دانش اولیه توسعه اندروید (این «Hello World» نیست، اما خیلی هم دور نیست!).

2. MediaPipe Tasks را به برنامه اندروید اضافه کنید

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

این کد لبه با یک نمونه از پیش ساخته شده شروع می شود که به شما امکان می دهد روی صفحه بکشید. می توانید آن برنامه شروع را در مخزن رسمی MediaPipe Samples در اینجا بیابید. با کلیک روی Code > Download ZIP، مخزن را کلون کنید یا فایل فشرده را دانلود کنید.

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

  1. اندروید استودیو را باز کنید.
  2. از صفحه خوش آمدید به Android Studio ، در گوشه بالا سمت راست گزینه Open را انتخاب کنید.

a0b5b070b802e4ea.png

  1. به جایی که مخزن را شبیه‌سازی یا دانلود کرده‌اید بروید و پوشه codelabs/digitclassifier/android/start را باز کنید.
  2. بررسی کنید که همه چیز به درستی باز شده است با کلیک بر روی فلش اجرای سبز ( 7e15a9c9e1620fe7.png ) در سمت راست بالای Android Studio
  3. باید ببینید که برنامه با صفحه سیاهی باز می شود که می توانید روی آن بکشید و همچنین دکمه Clear برای بازنشانی آن صفحه نمایش داده می شود. در حالی که می‌توانید روی آن صفحه نقاشی بکشید، کار دیگری انجام نمی‌دهد، بنابراین ما اکنون شروع به رفع آن می‌کنیم.

11a0f6fe021fdc92.jpeg

مدل

هنگامی که برای اولین بار برنامه را اجرا می کنید، ممکن است متوجه شوید که فایلی به نام mnist.tflite دانلود و در فهرست دارایی های برنامه شما ذخیره می شود. برای سادگی، ما قبلا یک مدل شناخته شده، MNIST، که ارقام را طبقه بندی می کند، گرفته ایم و با استفاده از اسکریپت download_models.gradle در پروژه، آن را به برنامه اضافه کرده ایم. اگر تصمیم دارید مدل سفارشی خود را آموزش دهید، مانند مدلی برای حروف دست نویس، باید فایل download_models.gradle را حذف کنید، مرجع آن را در فایل build.gradle سطح برنامه خود حذف کنید و نام مدل را بعداً تغییر دهید. کد (به طور خاص در فایل DigitClassifierHelper.kt ).

build.gradle را به روز کنید

قبل از اینکه بتوانید از MediaPipe Tasks استفاده کنید، باید کتابخانه را وارد کنید.

  1. فایل build.gradle واقع در ماژول برنامه خود را باز کنید، سپس به سمت پایین به بلوک وابستگی ها بروید.
  2. شما باید یک نظر در پایین آن بلوک ببینید که می گوید // STEP 1 Dependency Import .
  3. آن خط را با پیاده سازی زیر جایگزین کنید
implementation("com.google.mediapipe:tasks-vision:latest.release")
  1. برای دانلود این وابستگی روی دکمه Sync Now که در بنر بالای Android Studio ظاهر می شود کلیک کنید.

3. یک راهنمای طبقه‌بندی‌کننده رقم MediaPipe Tasks ایجاد کنید

برای مرحله بعدی، کلاسی را پر خواهید کرد که کارهای سنگین را برای طبقه بندی یادگیری ماشین شما انجام می دهد. DigitClassifierHelper.kt را باز کنید و بیایید شروع کنیم!

  1. نظری را در بالای کلاس پیدا کنید که می گوید // STEP 2 ایجاد شنونده
  2. آن خط را با کد زیر جایگزین کنید. این یک شنونده ایجاد می کند که برای ارسال نتایج از کلاس DigitClassifierHelper به هر جایی که برای آن نتایج گوش می دهد استفاده می شود (در این مورد کلاس DigitCanvasFragment شما خواهد بود، اما به زودی به آنجا خواهیم رسید)
// STEP 2 Create listener

interface DigitClassifierListener {
    fun onError(error: String)
    fun onResults(
        results: ImageClassifierResult,
        inferenceTime: Long
    )
}
  1. همچنین باید یک DigitClassifierListener را به عنوان یک پارامتر اختیاری برای کلاس بپذیرید:
class DigitClassifierHelper(
    val context: Context,
    val digitClassifierListener: DigitClassifierListener?
) {
  1. با رفتن به خطی که می گوید // STEP 3 define classifier ، خط زیر را اضافه کنید تا یک مکان نگهدار برای ImageClassifier که برای این برنامه استفاده می شود ایجاد کنید:

// مرحله 3 طبقه بندی کننده را تعریف کنید

private var digitClassifier: ImageClassifier? = null
  1. تابع زیر را در جایی که نظر را می بینید اضافه کنید // STEP 4 setup classifier :
// STEP 4 set up classifier
private fun setupDigitClassifier() {

    val baseOptionsBuilder = BaseOptions.builder()
        .setModelAssetPath("mnist.tflite")

    // Describe additional options
    val optionsBuilder = ImageClassifierOptions.builder()
        .setRunningMode(RunningMode.IMAGE)
        .setBaseOptions(baseOptionsBuilder.build())

    try {
        digitClassifier =
            ImageClassifier.createFromOptions(
                context,
                optionsBuilder.build()
            )
    } catch (e: IllegalStateException) {
        digitClassifierListener?.onError(
            "Image classifier failed to initialize. See error logs for " +
                    "details"
        )
        Log.e(TAG, "MediaPipe failed to load model with error: " + e.message)
    }
}

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

val baseOptionsBuilder = BaseOptions.builder()
    .setModelAssetPath("mnist.tflite")

// Describe additional options
val optionsBuilder = ImageClassifierOptions.builder()
    .setRunningMode(RunningMode.IMAGE)
    .setBaseOptions(baseOptionsBuilder.build())

این بلوک پارامترهای استفاده شده توسط ImageClassifier را تعریف می کند. این شامل مدل ذخیره شده در برنامه شما ( mnist.tflite ) در زیر BaseOptions و RunningMode در ImageClassifierOptions است که در این مورد IMAGE است، اما VIDEO و LIVE_STREAM گزینه های موجود دیگری هستند. سایر پارامترهای موجود MaxResults هستند که مدل را به حداکثر تعداد نتایج برمی‌گرداند و ScoreThreshold که حداقل اطمینانی را که مدل باید قبل از برگرداندن یک نتیجه داشته باشد را تعیین می‌کند.

try {
    digitClassifier =
        ImageClassifier.createFromOptions(
            context,
            optionsBuilder.build()
        )
} catch (e: IllegalStateException) {
    digitClassifierListener?.onError(
        "Image classifier failed to initialize. See error logs for " +
                "details"
    )
    Log.e(TAG, "MediaPipe failed to load model with error: " + e.message)
}

پس از ایجاد گزینه های پیکربندی خود، می توانید ImageClassifier جدید خود را با عبور در یک زمینه و گزینه ها ایجاد کنید. اگر مشکلی در فرآیند اولیه سازی رخ دهد، یک خطا از طریق DigitClassifierListener شما برگردانده می شود.

  1. از آنجایی که می خواهیم ImageClassifier را قبل از استفاده مقداردهی اولیه کنیم، می توانید یک بلوک init را برای فراخوانی setupDigitClassifier() اضافه کنید.
init {
    setupDigitClassifier()
}
  1. در نهایت، به سمت کامنتی بروید که می گوید // STEP 5 ایجاد تابع طبقه بندی کنید و کد زیر را اضافه کنید. این تابع یک Bitmap را می‌پذیرد که در این مورد رقم ترسیم شده است، آن را به یک شی MediaPipe Image (MPImage) تبدیل می‌کند و سپس آن تصویر را با استفاده از ImageClassifier طبقه‌بندی می‌کند، و همچنین مدت زمان استنتاج را قبل از برگرداندن آن نتایج ثبت می‌کند. DigitClassifierListener.
// STEP 5 create classify function
fun classify(image: Bitmap) {
    if (digitClassifier == null) {
        setupDigitClassifier()
    }

    // Convert the input Bitmap object to an MPImage object to run inference.
    // Rotating shouldn't be necessary because the text is being extracted from
    // a view that should always be correctly positioned.
    val mpImage = BitmapImageBuilder(image).build()

    // Inference time is the difference between the system time at the start and finish of the
    // process
    val startTime = SystemClock.uptimeMillis()

    // Run image classification using MediaPipe Image Classifier API
    digitClassifier?.classify(mpImage)?.also { classificationResults ->
        val inferenceTimeMs = SystemClock.uptimeMillis() - startTime
        digitClassifierListener?.onResults(classificationResults, inferenceTimeMs)
    }
}

و این برای فایل کمکی است! در قسمت بعدی مراحل نهایی را برای شروع طبقه بندی اعداد ترسیم شده خود پر می کنید.

4. استنتاج را با MediaPipe Tasks اجرا کنید

می توانید این بخش را با باز کردن کلاس DigitCanvasFragment در اندروید استودیو شروع کنید، جایی که همه کارها در آنجا انجام می شود.

  1. در انتهای این فایل باید نظری را ببینید که می گوید // STEP 6 Set up listener . شما توابع onResults() و onError() مرتبط با listener را در اینجا اضافه خواهید کرد.
// STEP 6 Set up listener
override fun onError(error: String) {
    activity?.runOnUiThread {
        Toast.makeText(requireActivity(), error, Toast.LENGTH_SHORT).show()
        fragmentDigitCanvasBinding.tvResults.text = ""
    }
}

override fun onResults(
    results: ImageClassifierResult,
    inferenceTime: Long
) {
    activity?.runOnUiThread {
        fragmentDigitCanvasBinding.tvResults.text = results
            .classificationResult()
            .classifications().get(0)
            .categories().get(0)
            .categoryName()

        fragmentDigitCanvasBinding.tvInferenceTime.text = requireActivity()
            .getString(R.string.inference_time, inferenceTime.toString())
    }
}

onResults () بسیار مهم است زیرا نتایج دریافت شده از ImageClassifier را نمایش می دهد. از آنجایی که این تماس از یک رشته پس‌زمینه شروع می‌شود، شما همچنین باید به‌روزرسانی‌های رابط کاربری خود را روی رشته رابط کاربری اندروید اجرا کنید.

  1. همانطور که در مرحله بالا در حال اضافه کردن توابع جدید از یک رابط هستید، همچنین باید اعلان پیاده سازی را در بالای کلاس اضافه کنید.
class DigitCanvasFragment : Fragment(), DigitClassifierHelper.DigitClassifierListener
  1. به سمت بالای کلاس باید نظری را ببینید که می گوید // STEP 7a Initialize classifier . اینجا جایی است که اعلان DigitClassifierHelper را قرار می دهید.
// STEP 7a Initialize classifier.
private lateinit var digitClassifierHelper: DigitClassifierHelper
  1. با حرکت به پایین به // STEP 7b Initialize classifier، می توانید digitClassifierHelper را در تابع onViewCreated() مقداردهی اولیه کنید.
// STEP 7b Initialize classifier
// Initialize the digit classifier helper, which does all of the
// ML work. This uses the default values for the classifier.
digitClassifierHelper = DigitClassifierHelper(
    context = requireContext(), digitClassifierListener = this
)
  1. برای آخرین مراحل، نظر // STEP 8a *: classify* را پیدا کنید و کد زیر را برای فراخوانی یک تابع جدید که در یک لحظه اضافه می کنید اضافه کنید. وقتی انگشت خود را از قسمت طراحی در برنامه بردارید، این بلوک کد طبقه بندی را آغاز می کند.
// STEP 8a: classify
classifyDrawing()
  1. در نهایت، به دنبال نظر // STEP 8b classify برای اضافه کردن تابع classifyDrawing() جدید بگردید. این یک بیت مپ را از بوم استخراج می کند، سپس آن را به DigitClassifierHelper ارسال می کند تا طبقه بندی را انجام دهد تا نتایج را در تابع رابط onResults() دریافت کند.
// STEP 8b classify
private fun classifyDrawing() {
    val bitmap = fragmentDigitCanvasBinding.digitCanvas.getBitmap()
    digitClassifierHelper.classify(bitmap)
}

5. برنامه را مستقر و آزمایش کنید

پس از همه اینها، شما باید یک برنامه کاربردی داشته باشید که بتواند ارقام ترسیم شده را روی صفحه طبقه بندی کند! ادامه دهید و برنامه را در یک شبیه ساز اندروید یا یک دستگاه فیزیکی اندروید برای آزمایش آن مستقر کنید.

  1. روی Run کلیک کنید ( 7e15a9c9e1620fe7.png ) در نوار ابزار اندروید استودیو برای اجرای برنامه.
  2. هر رقمی را روی صفحه طراحی بکشید و ببینید آیا برنامه می تواند آن را تشخیص دهد یا خیر. هم باید رقمی را که مدل معتقد است ترسیم شده است و هم اینکه چقدر طول کشیده تا آن رقم را پیش بینی کند، نمایش دهد.

7f37187f8f919638.gif

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

تو انجامش دادی! در این کد لبه شما یاد گرفته اید که چگونه طبقه بندی تصاویر را به یک برنامه اندروید اضافه کنید، و به طور خاص چگونه ارقام ترسیم شده با دست را با استفاده از مدل MNIST طبقه بندی کنید.

مراحل بعدی

  • اکنون که می توانید ارقام را طبقه بندی کنید، ممکن است بخواهید مدل خود را برای طبقه بندی حروف ترسیم شده یا طبقه بندی حیوانات یا تعداد بی پایانی از موارد دیگر آموزش دهید. می‌توانید مستندات مربوط به آموزش مدل طبقه‌بندی تصویر جدید را با MediaPipe Model Maker در صفحه developers.google.com/mediapipe بیابید.
  • درباره سایر وظایف MediaPipe که برای Android در دسترس هستند، از جمله تشخیص چهره نقطه عطف، تشخیص حرکت، و طبقه بندی صدا اطلاعات کسب کنید.

ما مشتاقانه منتظر همه چیزهای جالب شما هستیم!