Xây dựng ứng dụng Android viết tay về thuật toán phân loại bằng MediaPipe Tasks

1. Giới thiệu

MediaPipe là gì?

MediaPipe Solutions giúp bạn áp dụng các giải pháp học máy (ML) cho các ứng dụng của mình. Thư viện này cung cấp một khung làm việc để định cấu hình quy trình xử lý được tạo sẵn nhằm cung cấp đầu ra tức thì, hấp dẫn và hữu ích cho người dùng. Bạn thậm chí có thể tuỳ chỉnh các giải pháp này bằng MediaPipe Model Maker để cập nhật các mô hình mặc định.

Phân loại hình ảnh là một trong nhiều nhiệm vụ liên quan đến tầm nhìn của công nghệ học máy mà MediaPipe Solutions cung cấp. MediaPipe Tasks có sẵn trên Android, iOS, Python (bao gồm cả Raspberry Pi!) và web.

Trong lớp học lập trình này, bạn sẽ bắt đầu bằng một ứng dụng Android cho phép vẽ các chữ số trên màn hình, sau đó thêm chức năng phân loại các chữ số được vẽ đó thành một giá trị duy nhất từ 0 đến 9.

Kiến thức bạn sẽ học được

  • Cách kết hợp tác vụ phân loại hình ảnh trong ứng dụng Android với MediaPipe Tasks.

Bạn cần có

  • Một phiên bản đã cài đặt của Android Studio (lớp học lập trình này được viết và thử nghiệm bằng Android Studio Giraffe).
  • Một thiết bị Android hoặc trình mô phỏng để chạy ứng dụng.
  • Kiến thức cơ bản về phát triển Android (không phải là "Hello World" ("Xin chào thế giới"), nhưng nó cũng không xa lắm!

2. Thêm MediaPipe Tasks vào ứng dụng Android

Tải ứng dụng Android Starter

Lớp học lập trình này sẽ bắt đầu bằng một mẫu tạo sẵn cho phép bạn vẽ trên màn hình. Bạn có thể tìm thấy ứng dụng khởi động đó trong kho lưu trữ Mẫu MediaPipe chính thức tại đây. Sao chép kho lưu trữ hoặc tải tệp zip xuống bằng cách nhấp vào Mã > Tải tệp ZIP xuống.

Nhập ứng dụng vào Android Studio

  1. Mở Android Studio
  2. Trên màn hình Welcome to Android Studio (Chào mừng bạn đến với Android Studio), hãy chọn Open (Mở) ở góc trên cùng bên phải.

a0b5b070b802e4ea.png

  1. Chuyển đến vị trí bạn đã sao chép hoặc tải kho lưu trữ xuống rồi mở thư mục codelabs/digitclassifier/android/start.
  2. Xác minh rằng mọi thứ đã mở đúng cách bằng cách nhấp vào mũi tên chạy màu xanh lục ( 7e15a9c9e1620fe7.png.) ở trên cùng bên phải trong Android Studio
  3. Bạn sẽ thấy ứng dụng mở ra với màn hình đen mà bạn có thể vẽ cũng như nút Xoá để đặt lại màn hình đó. Mặc dù bạn có thể vẽ trên màn hình đó, nhưng nó không có tác dụng gì khác cả, vì vậy chúng ta sẽ bắt đầu khắc phục vấn đề đó ngay bây giờ.

11a0f6fe021fdc92.jpeg

Mẫu

Khi chạy ứng dụng này lần đầu tiên, bạn có thể nhận thấy một tệp có tên mnist.tflite được tải xuống và lưu trữ trong thư mục Assets của ứng dụng. Để đơn giản, chúng tôi đã sử dụng một mô hình đã biết là MNIST. Mô hình này sẽ phân loại các chữ số và thêm vào ứng dụng thông qua việc sử dụng tập lệnh download_models.gradle trong dự án. Nếu quyết định huấn luyện mô hình tuỳ chỉnh của riêng mình, chẳng hạn như mô hình cho chữ cái viết tay, thì bạn cần xoá tệp download_models.gradle, xoá tham chiếu đến nó trong tệp build.gradle ở cấp ứng dụng và thay đổi tên của mô hình vào lúc khác trong mã (cụ thể là trong tệp DigitClassifierHelper.kt).

Cập nhật build.gradle

Trước khi có thể bắt đầu sử dụng MediaPipe Tasks, bạn cần nhập thư viện.

  1. Mở tệp build.gradle trong mô-đun app của bạn, rồi di chuyển xuống khối phần phụ thuộc.
  2. Bạn sẽ thấy nhận xét ở cuối khối đó có nội dung // BƯỚC 1 Nhập phần phụ thuộc.
  3. Thay thế dòng đó bằng cách triển khai sau
implementation("com.google.mediapipe:tasks-vision:latest.release")
  1. Nhấp vào nút Sync Now (Đồng bộ hoá ngay) xuất hiện trong biểu ngữ ở đầu Android Studio để tải phần phụ thuộc này xuống.

3. Tạo trình trợ giúp về thuật toán phân loại bằng chữ số MediaPipe Tasks

Ở bước tiếp theo, bạn sẽ điền vào một lớp học đảm nhận phần việc khó khăn cho việc phân loại bằng công nghệ học máy. Hãy mở DigitClassifierHelper.kt và bắt đầu nào!

  1. Tìm nhận xét ở đầu lớp có nội dung // BƯỚC 2 Tạo trình nghe
  2. Thay thế dòng đó bằng mã sau. Thao tác này sẽ tạo một trình nghe được dùng để chuyển kết quả từ lớp DigitClassifierHelper trở lại bất cứ nơi nào đang nghe các kết quả đó (trong trường hợp này, đó sẽ là lớp DigitCanvasFragment của bạn, nhưng chúng tôi sẽ sớm xử lý)
// STEP 2 Create listener

interface DigitClassifierListener {
    fun onError(error: String)
    fun onResults(
        results: ImageClassifierResult,
        inferenceTime: Long
    )
}
  1. Bạn cũng cần chấp nhận DigitClassifierListener làm tham số không bắt buộc cho lớp:
class DigitClassifierHelper(
    val context: Context,
    val digitClassifierListener: DigitClassifierListener?
) {
  1. Chuyển đến dòng có nội dung // BƯỚC 3 xác định thuật toán phân loại, hãy thêm dòng sau để tạo phần giữ chỗ cho ImageClassifier sẽ được dùng cho ứng dụng này:

// BƯỚC 3 xác định thuật toán phân loại

private var digitClassifier: ImageClassifier? = null
  1. Thêm hàm sau đây vào nơi bạn thấy nhận xét // BƯỚC 4 thiết lập thuật toán phân loại:
// 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)
    }
}

Có một vài điều đang xảy ra trong phần trên, vì vậy hãy xem xét các phần nhỏ hơn để thực sự hiểu điều gì đang xảy ra.

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

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

Khối này sẽ xác định các tham số mà ImageClassifier sử dụng. Trong đó bao gồm cả mô hình được lưu trữ trong ứng dụng của bạn (mnist.tflite) trong BaseOptions và RunningMode trong ImageClassifierOptions. Trong trường hợp này là IMAGE, còn VIDEO và LIVE_STREAM là các lựa chọn bổ sung có sẵn. Các thông số có sẵn khác là Maxresults (giới hạn mô hình trả về số lượng kết quả tối đa) và "ScoreScore" đặt mức độ tin cậy tối thiểu mà mô hình cần có trong một kết quả trước khi trả về kết quả.

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

Sau khi tạo các tuỳ chọn cấu hình, bạn có thể tạo ImageClassifier mới bằng cách truyền ngữ cảnh và các tuỳ chọn. Nếu xảy ra lỗi trong quá trình khởi chạy đó, lỗi sẽ được trả về thông qua DigitClassifierListener.

  1. Vì chúng ta sẽ khởi chạy ImageClassifier trước khi sử dụng, nên bạn có thể thêm một khối init để gọi lệnh setupDigitClassifier().
init {
    setupDigitClassifier()
}
  1. Cuối cùng, cuộn xuống nhận xét có nội dung // BƯỚC 5 tạo hàm phân loại và thêm mã sau. Hàm này sẽ chấp nhận Bitmap mà trong trường hợp này là chữ số được vẽ, chuyển đổi thành đối tượng MediaPipe Image (MPImage) rồi phân loại hình ảnh đó bằng ImageClassifier cũng như ghi lại khoảng thời gian suy luận trước khi trả về kết quả đó qua 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)
    }
}

Đó là tất cả các bước cho tệp trợ giúp! Trong phần tiếp theo, bạn sẽ điền vào các bước cuối cùng để bắt đầu phân loại các số đã vẽ.

4. Chạy dự đoán bằng tác vụ MediaPipe

Bạn có thể bắt đầu phần này bằng cách mở lớp DigitCanvasFragment trong Android Studio. Đây là nơi tất cả công việc sẽ diễn ra.

  1. Ở cuối tệp này, bạn sẽ thấy một nhận xét có nội dung // BƯỚC 6 Thiết lập trình nghe. Bạn sẽ thêm các hàm onresult() và onError() liên kết với trình nghe tại đây.
// 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())
    }
}

onKết quả() là đặc biệt quan trọng vì nó sẽ hiển thị kết quả nhận được từ ImageClassifier. Vì lệnh gọi lại này được kích hoạt từ một luồng trong nền, nên bạn cũng cần chạy bản cập nhật giao diện người dùng trên luồng giao diện người dùng của Android.

  1. Khi thêm các hàm mới qua một giao diện ở bước trên, bạn cũng cần thêm phần khai báo phương thức triển khai ở đầu lớp.
class DigitCanvasFragment : Fragment(), DigitClassifierHelper.DigitClassifierListener
  1. Ở đầu lớp, bạn sẽ thấy một nhận xét có nội dung // STEP 7a Khởi tạo thuật toán phân loại. Đây là nơi bạn sẽ đặt phần khai báo cho DigitClassifierHelper.
// STEP 7a Initialize classifier.
private lateinit var digitClassifierHelper: DigitClassifierHelper
  1. Di chuyển xuống // STEP 7b Khởi tạo thuật toán phân loại,bạn có thể khởi chạy digitClassifierHelper trong hàm 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. Để biết các bước cuối cùng, hãy tìm nhận xét // BƯỚC 8a*: classify* rồi thêm mã sau để gọi một hàm mới mà bạn sẽ thêm sau giây lát. Khối mã này sẽ kích hoạt quy trình phân loại khi bạn nhấc ngón tay ra khỏi vùng vẽ trong ứng dụng.
// STEP 8a: classify
classifyDrawing()
  1. Cuối cùng, hãy tìm nhận xét // STEP 8b classify để thêm hàm classifyDrawing() mới. Thao tác này sẽ trích xuất bitmap từ canvas, sau đó chuyển bitmap vào DigitClassifierHelper để thực hiện phân loại nhằm nhận kết quả trong hàm giao diện onresult().
// STEP 8b classify
private fun classifyDrawing() {
    val bitmap = fragmentDigitCanvasBinding.digitCanvas.getBitmap()
    digitClassifierHelper.classify(bitmap)
}

5. Triển khai và thử nghiệm ứng dụng

Sau khi làm xong, bạn sẽ có một ứng dụng hoạt động có thể phân loại các chữ số được vẽ trên màn hình! Hãy tiếp tục và triển khai ứng dụng cho Trình mô phỏng Android hoặc thiết bị Android thực để kiểm thử.

  1. Nhấp vào Run (Chạy) (7e15a9c9e1620fe7.png.) trên thanh công cụ Android Studio để chạy ứng dụng.
  2. Vẽ bất kỳ chữ số nào vào bảng vẽ và xem ứng dụng có nhận ra được chữ số đó không. Cả hai đều phải cho thấy chữ số mà mô hình cho rằng đã được vẽ, cũng như khoảng thời gian cần để dự đoán chữ số đó.

7f37187f8f919638.gif

6. Xin chúc mừng!

Các bạn đã làm được! Trong lớp học lập trình này, bạn đã tìm hiểu cách thêm tính năng phân loại hình ảnh vào ứng dụng Android và cụ thể là cách phân loại các chữ số viết tay bằng mô hình MNIST.

Các bước tiếp theo

  • Giờ đây, bạn có thể phân loại các chữ số, bạn có thể huấn luyện mô hình của riêng mình để phân loại các chữ cái được vẽ hoặc phân loại động vật hoặc vô số các mục khác. Bạn có thể tìm thấy tài liệu để huấn luyện mô hình phân loại hình ảnh mới bằng MediaPipe Model Maker trên trang developers.google.com/mediapipe.
  • Tìm hiểu về Tác vụ MediaPipe khác có sẵn dành cho Android, bao gồm cả Phát hiện khuôn mặt, Nhận dạng bằng cử chỉ và Phân loại âm thanh.

Chúng tôi rất mong nhận được tất cả những nội dung thú vị mà bạn làm ra!