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

۱. مقدمه

مدیاپایپ چیست؟

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

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

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

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

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

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

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

۲. وظایف MediaPipe را به برنامه اندروید اضافه کنید

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

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

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

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

a0b5b070b802e4ea.png

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

11a0f6fe021fdc92.jpeg

مدل

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

به‌روزرسانی build.gradle

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

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

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

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

  1. کامنت بالای کلاس که می‌گوید // مرحله ۲ ایجاد شنونده را پیدا کنید
  2. آن خط را با کد زیر جایگزین کنید. این یک شنونده (listener) ایجاد می‌کند که برای ارسال نتایج از کلاس 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 بروید، خط زیر را اضافه کنید تا یک placeholder برای ImageClassifier که برای این برنامه استفاده خواهد شد، ایجاد شود:

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

private var digitClassifier: ImageClassifier? = null
  1. تابع زیر را در جایی که کامنت // STEP 4 set up 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 جدید خود را با ارسال یک context و گزینه‌ها ایجاد کنید. اگر در فرآیند مقداردهی اولیه مشکلی پیش بیاید، از طریق DigitClassifierListener شما خطایی برگردانده می‌شود.

  1. از آنجایی که می‌خواهیم ImageClassifier را قبل از استفاده، مقداردهی اولیه کنیم، می‌توانید یک بلوک init برای فراخوانی setupDigitClassifier() اضافه کنید.
init {
    setupDigitClassifier()
}
  1. در نهایت، به پایین اسکرول کنید تا به کامنتی برسید که می‌گوید // STEP 5 create classification function و کد زیر را اضافه کنید. این تابع یک Bitmap را می‌پذیرد که در این مورد رقم رسم شده است، آن را به یک شیء تصویر MediaPipe (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)
    }
}

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

۴. اجرای استنتاج با وظایف MediaPipe

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

  1. در پایین‌ترین بخش این فایل، باید کامنتی با این مضمون ببینید : // مرحله ۶: راه‌اندازی شنونده . در اینجا توابع onResults() و onError() مرتبط با شنونده را اضافه خواهید کرد.
// 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. با رفتن به // مرحله 7b، مقداردهی اولیه طبقه‌بندی‌کننده، می‌توانید 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 *: classification* را پیدا کنید و کد زیر را برای فراخوانی تابع جدیدی که در ادامه اضافه خواهید کرد، اضافه کنید. این بلوک کد، وقتی انگشت خود را از روی ناحیه ترسیم در برنامه برمی‌دارید، طبقه‌بندی را فعال می‌کند.
// STEP 8a: classify
classifyDrawing()
  1. در نهایت، به دنبال کامنت // STEP 8b class بگردید تا تابع جدید ()classedDrawing را اضافه کنید. این تابع یک بیت‌مپ از بوم استخراج می‌کند، سپس آن را به DigitClassifierHelper منتقل می‌کند تا طبقه‌بندی را انجام دهد و نتایج را در تابع رابط ()onResults دریافت کند.
// STEP 8b classify
private fun classifyDrawing() {
    val bitmap = fragmentDigitCanvasBinding.digitCanvas.getBitmap()
    digitClassifierHelper.classify(bitmap)
}

۵. اپلیکیشن را مستقر و آزمایش کنید

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

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

7f37187f8f919638.gif

۶. تبریک می‌گویم!

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

مراحل بعدی

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

منتظر همه کارهای قشنگی که می‌سازیم هستیم!