Mengenali tinta digital dengan ML Kit di Android

Dengan pengenalan tinta digital ML Kit, Anda dapat mengenali teks yang ditulis tangan di platform digital dalam ratusan bahasa, serta mengklasifikasikan sketsa.

Cobalah

Sebelum memulai

  1. Di file build.gradle level project, pastikan Anda memasukkan repositori Maven Google di bagian buildscript dan allprojects.
  2. Tambahkan dependensi untuk library Android ML Kit ke file Gradle level aplikasi modul Anda, biasanya app/build.gradle:
dependencies {
  // ...
  implementation 'com.google.mlkit:digital-ink-recognition:18.1.0'
}

Sekarang Anda siap untuk mulai mengenali teks dalam objek Ink.

Membuat objek Ink

Cara utama untuk mem-build objek Ink adalah dengan menggambarnya di layar sentuh. Di Android, Anda dapat menggunakan Canvas untuk tujuan ini. Pengendali peristiwa sentuh Anda harus memanggil metode addNewTouchEvent() yang ditampilkan dalam cuplikan kode berikut untuk menyimpan titik-titik dalam goresan yang digambar pengguna ke objek Ink.

Pola umum ini ditunjukkan dalam cuplikan kode berikut. Lihat contoh panduan memulai ML Kit untuk contoh yang lebih lengkap.

Kotlin

var inkBuilder = Ink.builder()
lateinit var strokeBuilder: Ink.Stroke.Builder

// Call this each time there is a new event.
fun addNewTouchEvent(event: MotionEvent) {
  val action = event.actionMasked
  val x = event.x
  val y = event.y
  var t = System.currentTimeMillis()

  // If your setup does not provide timing information, you can omit the
  // third paramater (t) in the calls to Ink.Point.create
  when (action) {
    MotionEvent.ACTION_DOWN -> {
      strokeBuilder = Ink.Stroke.builder()
      strokeBuilder.addPoint(Ink.Point.create(x, y, t))
    }
    MotionEvent.ACTION_MOVE -> strokeBuilder!!.addPoint(Ink.Point.create(x, y, t))
    MotionEvent.ACTION_UP -> {
      strokeBuilder.addPoint(Ink.Point.create(x, y, t))
      inkBuilder.addStroke(strokeBuilder.build())
    }
    else -> {
      // Action not relevant for ink construction
    }
  }
}

...

// This is what to send to the recognizer.
val ink = inkBuilder.build()

Java

Ink.Builder inkBuilder = Ink.builder();
Ink.Stroke.Builder strokeBuilder;

// Call this each time there is a new event.
public void addNewTouchEvent(MotionEvent event) {
  float x = event.getX();
  float y = event.getY();
  long t = System.currentTimeMillis();

  // If your setup does not provide timing information, you can omit the
  // third paramater (t) in the calls to Ink.Point.create
  int action = event.getActionMasked();
  switch (action) {
    case MotionEvent.ACTION_DOWN:
      strokeBuilder = Ink.Stroke.builder();
      strokeBuilder.addPoint(Ink.Point.create(x, y, t));
      break;
    case MotionEvent.ACTION_MOVE:
      strokeBuilder.addPoint(Ink.Point.create(x, y, t));
      break;
    case MotionEvent.ACTION_UP:
      strokeBuilder.addPoint(Ink.Point.create(x, y, t));
      inkBuilder.addStroke(strokeBuilder.build());
      strokeBuilder = null;
      break;
  }
}

...

// This is what to send to the recognizer.
Ink ink = inkBuilder.build();

Mendapatkan instance DigitalInkRecognizer

Untuk menjalankan pengenalan, kirim instance Ink ke objek DigitalInkRecognizer. Kode di bawah menunjukkan cara membuat instance pengenal tersebut dari tag BCP-47.

Kotlin

// Specify the recognition model for a language
var modelIdentifier: DigitalInkRecognitionModelIdentifier
try {
  modelIdentifier = DigitalInkRecognitionModelIdentifier.fromLanguageTag("en-US")
} catch (e: MlKitException) {
  // language tag failed to parse, handle error.
}
if (modelIdentifier == null) {
  // no model was found, handle error.
}
var model: DigitalInkRecognitionModel =
    DigitalInkRecognitionModel.builder(modelIdentifier).build()


// Get a recognizer for the language
var recognizer: DigitalInkRecognizer =
    DigitalInkRecognition.getClient(
        DigitalInkRecognizerOptions.builder(model).build())

Java

// Specify the recognition model for a language
DigitalInkRecognitionModelIdentifier modelIdentifier;
try {
  modelIdentifier =
    DigitalInkRecognitionModelIdentifier.fromLanguageTag("en-US");
} catch (MlKitException e) {
  // language tag failed to parse, handle error.
}
if (modelIdentifier == null) {
  // no model was found, handle error.
}

DigitalInkRecognitionModel model =
    DigitalInkRecognitionModel.builder(modelIdentifier).build();

// Get a recognizer for the language
DigitalInkRecognizer recognizer =
    DigitalInkRecognition.getClient(
        DigitalInkRecognizerOptions.builder(model).build());

Memproses objek Ink

Kotlin

recognizer.recognize(ink)
    .addOnSuccessListener { result: RecognitionResult ->
      // `result` contains the recognizer's answers as a RecognitionResult.
      // Logs the text from the top candidate.
      Log.i(TAG, result.candidates[0].text)
    }
    .addOnFailureListener { e: Exception ->
      Log.e(TAG, "Error during recognition: $e")
    }

Java

recognizer.recognize(ink)
    .addOnSuccessListener(
        // `result` contains the recognizer's answers as a RecognitionResult.
        // Logs the text from the top candidate.
        result -> Log.i(TAG, result.getCandidates().get(0).getText()))
    .addOnFailureListener(
        e -> Log.e(TAG, "Error during recognition: " + e));

Kode contoh di atas mengasumsikan bahwa model pengenalan telah didownload, seperti yang dijelaskan di bagian berikutnya.

Mengelola download model

Meskipun API pengenalan tinta digital mendukung ratusan bahasa, setiap bahasa memerlukan beberapa data untuk didownload sebelum pengenalan apa pun. Diperlukan penyimpanan sekitar 20 MB per bahasa. Hal ini ditangani oleh objek RemoteModelManager.

Download model baru

Kotlin

import com.google.mlkit.common.model.DownloadConditions
import com.google.mlkit.common.model.RemoteModelManager

var model: DigitalInkRecognitionModel =  ...
val remoteModelManager = RemoteModelManager.getInstance()

remoteModelManager.download(model, DownloadConditions.Builder().build())
    .addOnSuccessListener {
      Log.i(TAG, "Model downloaded")
    }
    .addOnFailureListener { e: Exception ->
      Log.e(TAG, "Error while downloading a model: $e")
    }

Java

import com.google.mlkit.common.model.DownloadConditions;
import com.google.mlkit.common.model.RemoteModelManager;

DigitalInkRecognitionModel model = ...;
RemoteModelManager remoteModelManager = RemoteModelManager.getInstance();

remoteModelManager
    .download(model, new DownloadConditions.Builder().build())
    .addOnSuccessListener(aVoid -> Log.i(TAG, "Model downloaded"))
    .addOnFailureListener(
        e -> Log.e(TAG, "Error while downloading a model: " + e));

Memeriksa apakah model sudah didownload

Kotlin

var model: DigitalInkRecognitionModel =  ...
remoteModelManager.isModelDownloaded(model)

Java

DigitalInkRecognitionModel model = ...;
remoteModelManager.isModelDownloaded(model);

Menghapus model yang didownload

Menghapus model dari penyimpanan perangkat akan mengosongkan ruang penyimpanan.

Kotlin

var model: DigitalInkRecognitionModel =  ...
remoteModelManager.deleteDownloadedModel(model)
    .addOnSuccessListener {
      Log.i(TAG, "Model successfully deleted")
    }
    .addOnFailureListener { e: Exception ->
      Log.e(TAG, "Error while deleting a model: $e")
    }

Java

DigitalInkRecognitionModel model = ...;
remoteModelManager.deleteDownloadedModel(model)
                  .addOnSuccessListener(
                      aVoid -> Log.i(TAG, "Model successfully deleted"))
                  .addOnFailureListener(
                      e -> Log.e(TAG, "Error while deleting a model: " + e));

Tips untuk meningkatkan akurasi pengenalan teks

Keakuratan pengenalan teks dapat bervariasi antarbahasa. Akurasi juga tergantung pada gaya penulisan. Meskipun Pengenalan Tinta Digital dilatih untuk menangani berbagai jenis gaya penulisan, hasilnya dapat bervariasi dari satu pengguna ke pengguna lainnya.

Berikut ini beberapa cara untuk meningkatkan akurasi pengenal teks. Perhatikan bahwa teknik ini tidak berlaku bagi pengklasifikasi gambar untuk emoji, gambar otomatis, dan bentuk.

Area penulisan

Banyak aplikasi yang memiliki area penulisan yang didefinisikan dengan baik untuk input pengguna. Arti simbol sebagian ditentukan oleh ukurannya relatif terhadap ukuran area tulisan yang memuatnya. Misalnya, perbedaan antara huruf kecil atau besar "o" atau "c", dan koma versus garis miring ke depan.

Memberi tahu pengenal lebar dan tinggi area tulisan dapat meningkatkan akurasi. Namun, pengenal mengasumsikan bahwa area penulisan hanya berisi satu baris teks. Jika area penulisan fisik cukup besar untuk memungkinkan pengguna menulis dua baris atau lebih, Anda bisa mendapatkan hasil yang lebih baik dengan meneruskan WritingArea dengan tinggi yang merupakan perkiraan terbaik untuk tinggi satu baris teks. Objek WritingArea yang Anda teruskan ke pengenal tidak harus sama persis dengan area penulisan fisik di layar. Mengubah tinggi WritingArea dengan cara ini bekerja lebih baik dalam beberapa bahasa dibandingkan bahasa lainnya.

Saat Anda menentukan area tulisan, tentukan lebar dan tingginya dalam unit yang sama dengan koordinat goresan. Argumen koordinat x,y tidak memiliki persyaratan satuan - API menormalisasi semua unit, sehingga satu-satunya hal yang penting adalah ukuran relatif dan posisi goresan. Anda bebas meneruskan koordinat dalam skala apa pun yang sesuai untuk sistem Anda.

Pra-konteks

Pra-konteks adalah teks yang berada tepat sebelum goresan dalam Ink yang ingin Anda kenali. Anda dapat membantu pengenal dengan memberitahukan pra-konteksnya.

Misalnya, huruf kursif "n" dan "u" sering disalahartikan satu sama lain. Jika pengguna telah memasukkan sebagian kata "arg", mereka dapat melanjutkan dengan goresan yang dapat dikenali sebagai "ument" atau "nment". Menentukan "arg" pra-konteks akan menyelesaikan ambiguitas, karena kata "argument" lebih mungkin daripada "argnment".

Pra-konteks juga dapat membantu pengenal untuk mengidentifikasi jeda kata dan spasi di antara kata. Anda dapat mengetik karakter spasi, tetapi tidak dapat menggambarnya. Jadi, bagaimana cara pengenal dapat menentukan kapan satu kata berakhir dan kata berikutnya dimulai? Jika pengguna telah menulis "halo" dan melanjutkan dengan kata tertulis "world", tanpa konteks pra-konteks, pengenal akan menampilkan string "world". Namun, jika Anda menentukan pra-konteks "hello", model akan menampilkan string "world", dengan spasi di awal, karena "hello world" lebih masuk akal daripada "helloword".

Anda harus menyediakan string pra-konteks terpanjang yang memungkinkan, hingga 20 karakter, termasuk spasi. Jika string lebih panjang, pengenal hanya akan menggunakan 20 karakter terakhir.

Contoh kode di bawah menunjukkan cara menentukan area penulisan dan menggunakan objek RecognitionContext untuk menentukan pra-konteks.

Kotlin

var preContext : String = ...;
var width : Float = ...;
var height : Float = ...;
val recognitionContext : RecognitionContext =
    RecognitionContext.builder()
        .setPreContext(preContext)
        .setWritingArea(WritingArea(width, height))
        .build()

recognizer.recognize(ink, recognitionContext)

Java

String preContext = ...;
float width = ...;
float height = ...;
RecognitionContext recognitionContext =
    RecognitionContext.builder()
                      .setPreContext(preContext)
                      .setWritingArea(new WritingArea(width, height))
                      .build();

recognizer.recognize(ink, recognitionContext);

Pengurutan goresan

Akurasi pengenalan sensitif terhadap urutan goresan. Pengenal mengharapkan goresan terjadi sesuai urutan yang akan ditulis orang secara alami; misalnya kiri-ke-kanan untuk bahasa Inggris. Kasus apa pun yang menyimpang dari pola ini, seperti menulis kalimat bahasa Inggris yang dimulai dengan kata terakhir, akan memberikan hasil yang kurang akurat.

Contoh lainnya adalah saat kata di tengah Ink dihapus dan diganti dengan kata lain. Revisi mungkin berada di tengah kalimat, tetapi goresan untuk revisi berada di akhir urutan goresan. Dalam hal ini, sebaiknya kirim kata yang baru ditulis secara terpisah ke API dan gabungkan hasilnya dengan pengenalan sebelumnya menggunakan logika Anda sendiri.

Menangani bentuk yang ambigu

Ada kasus saat arti bentuk yang diberikan kepada pengenal ambigu. Misalnya, persegi panjang dengan tepi yang sangat membulat dapat dilihat sebagai persegi panjang atau elips.

Kasus yang tidak jelas ini dapat ditangani dengan menggunakan skor pengenalan jika tersedia. Hanya pengklasifikasi bentuk yang memberikan skor. Jika modelnya sangat yakin, skor hasil teratas akan jauh lebih baik daripada skor terbaik kedua. Jika ada ketidakpastian, skor untuk dua hasil teratas akan mendekati. Selain itu, perlu diingat bahwa pengklasifikasi bentuk menafsirkan seluruh Ink sebagai satu bentuk. Misalnya, jika Ink berisi persegi panjang dan elips di samping satu sama lain, pengenal dapat menampilkan salah satunya (atau sesuatu yang sama sekali berbeda) sebagai hasilnya, karena satu kandidat pengenalan tidak dapat merepresentasikan dua bentuk.