Membangun aplikasi Android pengklasifikasi digit yang ditulis tangan dengan MediaPipe Tasks

1. Pengantar

Apa yang dimaksud dengan MediaPipe?

MediaPipe Solutions dapat Anda gunakan untuk menerapkan solusi machine learning (ML) ke aplikasi Anda. Solusi ini menyediakan framework untuk mengonfigurasi pipeline pemrosesan bawaan yang memberikan output segera, menarik, dan bermanfaat bagi pengguna. Anda bahkan dapat menyesuaikan solusi ini dengan MediaPipe Model Maker untuk mengupdate model default.

Klasifikasi gambar adalah salah satu dari beberapa tugas ML vision yang ditawarkan MediaPipe Solutions. MediaPipe Tasks tersedia untuk Android, iOS, Python (termasuk Raspberry Pi!), dan web.

Dalam Codelab ini, Anda akan memulai dengan aplikasi Android yang memungkinkan Anda menggambar angka numerik di layar, lalu Anda akan menambahkan fungsi yang mengklasifikasikan angka yang digambar tersebut sebagai satu nilai dari 0 hingga 9.

Yang akan Anda pelajari

  • Cara menyertakan tugas klasifikasi gambar di aplikasi Android dengan MediaPipe Tasks.

Yang Anda butuhkan

  • Versi Android Studio yang diinstal (codelab ini ditulis dan diuji dengan Android Studio Giraffe).
  • Perangkat Android atau emulator untuk menjalankan aplikasi.
  • Pengetahuan dasar tentang pengembangan Android (ini bukan "Hello World", tetapi tidak jauh berbeda).

2. Menambahkan Tugas MediaPipe ke aplikasi Android

Mendownload aplikasi awal Android

Codelab ini akan dimulai dengan contoh siap pakai yang memungkinkan Anda menggambar di layar. Anda dapat menemukan aplikasi awal tersebut di repo Contoh MediaPipe resmi di sini. Clone repo atau download file zip dengan mengklik Code > Download ZIP.

Mengimpor aplikasi ke Android Studio

  1. Buka Android Studio.
  2. Dari layar Welcome to Android Studio, pilih Open di pojok kanan atas.

a0b5b070b802e4ea.png

  1. Buka tempat Anda meng-clone atau mendownload repositori, lalu buka direktori codelabs/digitclassifier/android/start.
  2. Pastikan semuanya terbuka dengan benar dengan mengklik panah run hijau ( 7e15a9c9e1620fe7.png) di kanan atas Android Studio
  3. Anda akan melihat aplikasi terbuka dengan layar hitam yang dapat Anda gambar, serta tombol Hapus untuk mereset layar tersebut. Meskipun Anda dapat menggambar di layar tersebut, layar tersebut tidak melakukan banyak hal lainnya, jadi kita akan mulai memperbaikinya sekarang.

11a0f6fe021fdc92.jpeg

Model

Saat pertama kali menjalankan aplikasi, Anda mungkin melihat bahwa file bernama mnist.tflite didownload dan disimpan di direktori aset aplikasi Anda. Untuk memudahkan, kita telah mengambil model yang diketahui, MNIST, yang mengklasifikasikan angka, dan menambahkannya ke aplikasi melalui penggunaan skrip download_models.gradle dalam project. Jika Anda memutuskan untuk melatih model kustom Anda sendiri, seperti model untuk huruf tulisan tangan, Anda akan menghapus file download_models.gradle, menghapus referensi ke file tersebut di file build.gradle level aplikasi, dan mengubah nama model nanti dalam kode (khususnya dalam file DigitClassifierHelper.kt).

Mengupdate build.gradle

Sebelum dapat mulai menggunakan MediaPipe Tasks, Anda harus mengimpor library.

  1. Buka file build.gradle yang terletak di modul aplikasi, lalu scroll ke bawah ke blok dependencies.
  2. Anda akan melihat komentar di bagian bawah blok tersebut yang bertuliskan // LANGKAH 1 Impor Dependensi.
  3. Ganti baris tersebut dengan implementasi berikut
implementation("com.google.mediapipe:tasks-vision:latest.release")
  1. Klik tombol Sync Now yang muncul di banner di bagian atas Android Studio untuk mendownload dependensi ini.

3. Membuat helper pengklasifikasi digit MediaPipe Tasks

Untuk langkah berikutnya, Anda akan mengisi class yang akan melakukan pekerjaan berat untuk klasifikasi machine learning Anda. Buka DigitClassifierHelper.kt dan mari kita mulai.

  1. Temukan komentar di bagian atas class yang bertuliskan // LANGKAH 2 Buat pemroses
  2. Ganti baris tersebut dengan kode berikut. Tindakan ini akan membuat pemroses yang akan digunakan untuk meneruskan hasil dari class DigitClassifierHelper kembali ke tempat yang memproses hasil tersebut (dalam hal ini, class DigitCanvasFragment Anda, tetapi kita akan segera sampai di sana)
// STEP 2 Create listener

interface DigitClassifierListener {
    fun onError(error: String)
    fun onResults(
        results: ImageClassifierResult,
        inferenceTime: Long
    )
}
  1. Anda juga harus menerima DigitClassifierListener sebagai parameter opsional untuk class:
class DigitClassifierHelper(
    val context: Context,
    val digitClassifierListener: DigitClassifierListener?
) {
  1. Turun ke baris yang bertuliskan // LANGKAH 3 tentukan pengklasifikasi, tambahkan baris berikut untuk membuat placeholder ImageClassifier yang akan digunakan untuk aplikasi ini:

// LANGKAH 3 menentukan pengklasifikasi

private var digitClassifier: ImageClassifier? = null
  1. Tambahkan fungsi berikut di tempat Anda melihat komentar // LANGKAH 4 siapkan pengklasifikasi:
// 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)
    }
}

Ada beberapa hal yang terjadi di bagian di atas, jadi mari kita lihat bagian yang lebih kecil untuk benar-benar memahami apa yang terjadi.

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

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

Blok ini akan menentukan parameter yang digunakan oleh ImageClassifier. Ini mencakup model yang disimpan dalam aplikasi Anda (mnist.tflite) di bagian BaseOptions dan RunningMode di bagian ImageClassifierOptions, yang dalam hal ini adalah IMAGE, tetapi VIDEO dan LIVE_STREAM adalah opsi tambahan yang tersedia. Parameter lain yang tersedia adalah MaxResults, yang membatasi model untuk menampilkan jumlah hasil maksimum, dan ScoreThreshold, yang menetapkan keyakinan minimum yang harus dimiliki model dalam hasil sebelum menampilkannya.

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)
}

Setelah membuat opsi konfigurasi, Anda dapat membuat ImageClassifier baru dengan meneruskan konteks dan opsi. Jika terjadi masalah pada proses inisialisasi tersebut, error akan ditampilkan melalui DigitClassifierListener Anda.

  1. Karena kita ingin menginisialisasi ImageClassifier sebelum digunakan, Anda dapat menambahkan blok init untuk memanggil setupDigitClassifier().
init {
    setupDigitClassifier()
}
  1. Terakhir, scroll ke bawah ke komentar yang bertuliskan // LANGKAH 5 buat fungsi klasifikasi dan tambahkan kode berikut. Fungsi ini akan menerima Bitmap, yang dalam hal ini adalah digit yang digambar, mengonversinya menjadi objek Gambar MediaPipe (MPImage), lalu mengklasifikasikan gambar tersebut menggunakan ImageClassifier, serta mencatat berapa lama inferensi berlangsung, sebelum menampilkan hasil tersebut melalui 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)
    }
}

Selesai sudah untuk file helper. Di bagian berikutnya, Anda akan mengisi langkah-langkah terakhir untuk mulai mengklasifikasikan angka yang ditarik.

4. Menjalankan inferensi dengan MediaPipe Tasks

Anda dapat memulai bagian ini dengan membuka class DigitCanvasFragment di Android Studio, tempat semua pekerjaan akan dilakukan.

  1. Di bagian paling bawah file ini, Anda akan melihat komentar yang bertuliskan // LANGKAH 6 Siapkan pemroses. Anda akan menambahkan fungsi onResults() dan onError() yang terkait dengan pemroses di sini.
// 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() sangat penting karena akan menampilkan hasil yang diterima dari ImageClassifier. Karena callback ini dipicu dari thread latar belakang, Anda juga harus menjalankan update UI di UI thread Android.

  1. Saat menambahkan fungsi baru dari antarmuka pada langkah di atas, Anda juga harus menambahkan deklarasi implementasi di bagian atas class.
class DigitCanvasFragment : Fragment(), DigitClassifierHelper.DigitClassifierListener
  1. Di bagian atas class, Anda akan melihat komentar yang bertuliskan // STEP 7a Initialize classifier. Di sinilah Anda akan menempatkan deklarasi untuk DigitClassifierHelper.
// STEP 7a Initialize classifier.
private lateinit var digitClassifierHelper: DigitClassifierHelper
  1. Beralih ke // LANGKAH 7b Melakukan inisialisasi pengklasifikasi, Anda dapat melakukan inisialisasi digitClassifierHelper dalam fungsi 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. Untuk langkah terakhir, temukan komentar // STEP 8a*: classify* dan tambahkan kode berikut untuk memanggil fungsi baru yang akan Anda tambahkan sebentar lagi. Blok kode ini akan memicu klasifikasi saat Anda mengangkat jari dari area gambar di aplikasi.
// STEP 8a: classify
classifyDrawing()
  1. Terakhir, cari komentar // STEP 8b classify untuk menambahkan fungsi classifyDrawing() baru. Tindakan ini akan mengekstrak bitmap dari kanvas, lalu meneruskannya ke DigitClassifierHelper untuk melakukan klasifikasi guna menerima hasil dalam fungsi antarmuka onResults().
// STEP 8b classify
private fun classifyDrawing() {
    val bitmap = fragmentDigitCanvasBinding.digitCanvas.getBitmap()
    digitClassifierHelper.classify(bitmap)
}

5. Men-deploy dan menguji aplikasi

Setelah semua itu, Anda akan memiliki aplikasi yang berfungsi dan dapat mengklasifikasikan angka yang digambar di layar. Deploy aplikasi ke Android Emulator atau perangkat Android fisik untuk mengujinya.

  1. Klik Run ( 7e15a9c9e1620fe7.png) di toolbar Android Studio untuk menjalankan aplikasi.
  2. Gambar angka apa pun ke pad gambar dan lihat apakah aplikasi dapat mengenalinya. Model ini akan menampilkan angka yang diyakini model telah digambar, serta waktu yang diperlukan untuk memprediksi angka tersebut.

7f37187f8f919638.gif

6. Selamat!

Anda berhasil! Dalam codelab ini, Anda telah mempelajari cara menambahkan klasifikasi gambar ke aplikasi Android, dan khususnya cara mengklasifikasikan angka yang digambar tangan menggunakan model MNIST.

Langkah berikutnya

  • Setelah dapat mengklasifikasikan angka, Anda mungkin ingin melatih model Anda sendiri untuk mengklasifikasikan huruf yang digambar, atau mengklasifikasikan hewan, atau item lainnya yang tak terbatas. Anda dapat menemukan dokumentasi untuk melatih model klasifikasi gambar baru dengan MediaPipe Model Maker di halaman developers.google.com/mediapipe.
  • Pelajari Tugas MediaPipe lainnya yang tersedia untuk Android, termasuk Deteksi Landmark Wajah, Pengenalan Gestur, dan Klasifikasi Audio.

Kami menantikan semua hal keren yang Anda buat.