Membangun aplikasi Android pengklasifikasi digit yang ditulis tangan dengan MediaPipe Tasks

1. Pengantar

Apa itu MediaPipe?

MediaPipe Solutions dapat Anda gunakan untuk menerapkan solusi machine learning (ML) ke aplikasi Anda. Solusi ini menyediakan framework untuk mengonfigurasi pipeline pemrosesan prebuilt 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 digit numerik di layar, lalu Anda akan menambahkan fungsi yang mengklasifikasikan digit yang digambar tersebut sebagai nilai tunggal 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 terinstal (codelab ini ditulis dan diuji dengan Android Studio Giraffe).
  • Perangkat Android atau emulator untuk menjalankan aplikasi.
  • Pengetahuan dasar tentang pengembangan Android (ini bukan "Halo Dunia", tetapi tidak terlalu jauh!).

2. Menambahkan MediaPipe Tasks 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 tersebut untuk memulai di repositori 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 codelab/digitclassifier/android/start.
  2. Pastikan bahwa semuanya dibuka dengan benar dengan mengklik panah run berwarna hijau ( 7e15a9c9e1620fe7.pngS) di kanan atas Android Studio
  3. Anda akan melihat aplikasi terbuka dengan layar hitam tempat Anda dapat menggambar, serta tombol Hapus untuk mereset layar tersebut. Meskipun Anda dapat menggambar di layar tersebut, hal ini tidak akan melakukan banyak hal lain, jadi kita akan mulai memperbaikinya sekarang.

11a0f6fe021fdc92.jpeg

Model

Saat pertama kali menjalankan aplikasi, Anda mungkin melihat file bernama mnist.tflite telah didownload dan disimpan di direktori assets pada aplikasi. Demi kemudahan, kami telah mengambil model yang dikenal, MNIST, yang mengklasifikasikan digit, lalu menambahkannya ke aplikasi melalui penggunaan skrip download_models.gradle dalam project. Jika memutuskan untuk melatih model kustom sendiri, seperti model untuk huruf tulisan tangan, Anda harus menghapus file download_models.gradle, menghapus referensi ke model tersebut di file build.gradle tingkat aplikasi, dan ubah nama model nanti dalam kode (khususnya di file DigitClassifierHelper.kt).

Mengupdate build.gradle

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

  1. Buka file build.gradle yang berada di modul app, lalu scroll ke bawah ke blok Dependency.
  2. Anda akan melihat komentar di bagian bawah blok tersebut yang bertuliskan // LANGKAH 1 Dependency Import.
  3. Ganti baris tersebut dengan penerapan 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 Tugas MediaPipe

Untuk langkah selanjutnya, Anda akan mengisi kelas yang akan melakukan tugas sulit untuk klasifikasi machine learning. 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 mana saja yang memproses hasil tersebut (dalam hal ini, class tersebut adalah class DigitCanvasFragment Anda, tetapi kita akan segera melakukannya sampai ke 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. Pada baris yang bertuliskan // LANGKAH 3 Define pengklasifikasi, tambahkan baris berikut guna membuat placeholder untuk ImageClassifier yang akan digunakan untuk aplikasi ini:

// LANGKAH 3 tentukan pengklasifikasi

private var digitClassifier: ImageClassifier? = null
  1. Tambahkan fungsi berikut 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 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) pada 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 diperlukan model untuk memiliki hasil sebelum mengembalikannya.

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 ada yang tidak beres dengan proses inisialisasi tersebut, error akan ditampilkan melalui DigitClassifierListener.

  1. Karena kita ingin melakukan inisialisasi ImageClassifier sebelum digunakan, Anda dapat menambahkan blok init untuk memanggil setupDigitClassifier().
init {
    setupDigitClassifier()
}
  1. Terakhir, scroll ke bawah ke bagian 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 merekam durasi inferensi, 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)
    }
}

Itu saja untuk file {i>help<i}. Di bagian berikutnya, Anda akan mengisi langkah terakhir untuk mulai mengklasifikasikan angka yang digambar.

4. Menjalankan inferensi dengan Tugas MediaPipe

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

  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 perlu 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 Inisialisasi pengklasifikasi. Di sinilah Anda akan menempatkan deklarasi untuk DigitClassifierHelper.
// STEP 7a Initialize classifier.
private lateinit var digitClassifierHelper: DigitClassifierHelper
  1. Bergerak ke bawah ke // STEP 7b 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 // LANGKAH 8a*: klasifikasikan* dan tambahkan kode berikut untuk memanggil fungsi baru yang akan Anda tambahkan beberapa saat lagi. Blok kode ini akan memicu klasifikasi saat Anda mengangkat jari dari area gambar di aplikasi.
// STEP 8a: classify
classifyDrawing()
  1. Terakhir, cari komentar // LANGKAH 8b klasifikasi untuk menambahkan fungsi fungsi pengklasifikasiMenggambar() baru. Tindakan ini akan mengekstrak bitmap dari kanvas, lalu meneruskannya ke DigitClassifierHelper untuk melakukan klasifikasi guna menerima hasilnya 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 itu, Anda seharusnya memiliki aplikasi yang berfungsi dan dapat mengklasifikasikan digit yang digambar di layar! Lanjutkan dan deploy aplikasi ke Android Emulator atau perangkat Android fisik untuk mengujinya.

  1. Klik Run ( 7e15a9c9e1620fe7.pngS) di toolbar Android Studio untuk menjalankan aplikasi.
  2. Gambar digit apa pun ke buku gambar dan lihat apakah aplikasi dapat mengenalinya. Keduanya harus menampilkan digit yang diyakini telah digambar oleh model, serta waktu yang diperlukan untuk memprediksi digit tersebut.

7f37187f8f919638.gif

6. Selamat!

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

Langkah berikutnya

  • Setelah dapat mengklasifikasikan angka, Anda dapat melatih model Anda sendiri untuk mengklasifikasikan huruf yang digambar, mengklasifikasikan hewan, atau item lainnya dalam jumlah 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 lain yang tersedia untuk Android, termasuk Deteksi Wajah Landmark, Pengenalan Gestur, dan Klasifikasi Audio.

Kami menantikan semua hal keren yang Anda buat.