Android'de ML Kit ile dijital mürekkebi tanıma

Makine Öğrenimi Kiti'nin dijital mürekkep tanıma özelliğiyle yüzlerce dilde dijital yüzeylerde elle yazılmış metinleri tanıyabilir ve çizimleri sınıflandırabilirsiniz.

Deneyin

Başlamadan önce

  1. Proje düzeyindeki build.gradle dosyanıza, hem buildscript hem de allprojects bölümlerinize Google'ın Maven deposunu eklediğinizden emin olun.
  2. ML Kit Android kitaplıklarının bağımlılıklarını, modülünüzün uygulama düzeyindeki Gradle dosyasına ekleyin. Bu dosya genellikle app/build.gradle olan:
dependencies {
  // ...
  implementation 'com.google.mlkit:digital-ink-recognition:18.1.0'
}

Artık Ink nesnedeki metinleri tanımaya başlamak için hazırsınız.

Ink nesnesi oluşturun

Bir Ink nesnesi oluşturmanın ana yolu, onu dokunmatik ekranda çizmektir. Android'de bu amaçla Kanvas kullanabilirsiniz. Dokunma etkinliği işleyicileriniz, kullanıcının Ink nesnesine çizdiği darbelerdeki noktaları depolamak için aşağıdaki kod snippet'inde gösterilen addNewTouchEvent() yöntemini çağırmalıdır.

Bu genel kalıp, aşağıdaki kod snippet'inde gösterilmektedir. Daha kapsamlı bir örnek için ML Kit hızlı başlangıç örneğine bakın.

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();

DigitalInkRekred'in bir örneğini alma

Tanıma işlemi gerçekleştirmek için Ink örneğini bir DigitalInkRecognizer nesnesine gönderin. Aşağıdaki kod, BCP-47 etiketinden böyle bir tanıyıcının nasıl örnekleneceğini göstermektedir.

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());

Ink nesnesini işleyin

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

Yukarıdaki örnek kod, tanıma modelinin sonraki bölümde açıklandığı gibi zaten indirilmiş olduğunu varsayar.

Model indirmelerini yönetme

Dijital mürekkep tanıma API'si yüzlerce dili desteklese de her dil, herhangi bir tanıma yapılmadan önce bazı verilerin indirilmesini gerektirir. Her dil için yaklaşık 20 MB depolama alanı gerekir. Bu, RemoteModelManager nesnesi tarafından işlenir.

Yeni model indirin

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

Bir modelin önceden indirilip indirilmediğini kontrol etme

Kotlin

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

Java

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

İndirilen bir modeli silme

Bir modelin cihazın depolama alanından kaldırılması yer açar.

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

Metin tanıma doğruluğunu iyileştirmeye yönelik ipuçları

Metin tanımanın doğruluğu diller arasında farklılık gösterebilir. Doğruluk yazı stiline de bağlıdır. Dijital Mürekkep Tanıma, pek çok yazma stilini işleyecek şekilde eğitilmiş olsa da sonuçlar kullanıcıdan kullanıcıya değişiklik gösterebilir.

Metin tanıyıcının doğruluğunu artırmanın bazı yolları aşağıda verilmiştir. Bu tekniklerin emoji, otomatik çizim ve şekillere yönelik çizim sınıflandırıcıları için geçerli olmadığını unutmayın.

Yazma alanı

Birçok uygulamanın, kullanıcı girişi için iyi tanımlanmış bir yazma alanı vardır. Bir simgenin anlamı, kısmen simgelerin bulunduğu yazı alanının boyutuna göre belirlenir. Örneğin, küçük veya büyük harf "o" veya "c" ile virgül ile düz eğik çizgi arasındaki fark.

Tanıyıcıya, yazma alanının genişliğini ve yüksekliğini söylemek doğruluğu artırabilir. Ancak tanıyıcı, yazma alanının yalnızca tek bir metin satırı içerdiğini varsayar. Fiziksel yazma alanı kullanıcının iki veya daha fazla satır yazmasına izin verecek kadar büyükse tek bir metin satırının yüksekliğine dair en iyi tahmininiz olan bir yükseklikle bir ComposeArea'dan geçerek daha iyi sonuçlar alabilirsiniz. Tanıyıcıya ilettiğiniz WriteArea nesnesinin, ekrandaki fiziksel yazma alanına tam olarak karşılık gelmesi gerekmez. WriteArea yüksekliğini bu şekilde değiştirmek bazı dillerde diğerlerine göre daha iyi sonuç verir.

Yazma alanını belirtirken genişliğini ve yüksekliğini fırça koordinatlarıyla aynı birimlerde belirtin. x,y koordinat bağımsız değişkenlerinin birim gereksinimi yoktur. API tüm birimleri normalleştirdiğinden, tek önemli nokta çizgilerin göreli boyutu ve konumudur. Sisteminiz için uygun olan ölçekte koordinatlar verebilirsiniz.

Bağlam öncesi

Bağlam ön bilgisi, Ink içinde tanımaya çalıştığınız darbelerden hemen önce gelen metindir. Tanıyıcıya bağlam öncesi hakkında bilgi vererek yardımcı olabilirsiniz.

Örneğin, el yazısı harfleri "n" ve "u" genellikle birbiriyle karıştırılır. Kullanıcı kısmi olarak "arg" kelimesini girmişse "ument" veya "nment" olarak algılanabilen darbelerle devam edebilir. Bağlam öncesi "bağımsız değişken" kelimesinin belirtilmesi, "bağımsız değişken" kelimesinin "bağımsız değişken" kelimesinden daha olası olduğu için belirsizliği ortadan kaldırır.

Bağlam ön bilgisi, tanıyıcının kelime sonlarını, yani kelimeler arasındaki boşlukları belirlemesine de yardımcı olabilir. Boşluk karakteri yazabilirsiniz ama bir karakter çizemezsiniz. Öyleyse tanıyıcı bir kelimenin bitip bir sonrakinin başladığını nasıl belirleyebilir? Kullanıcı zaten "merhaba" yazdıysa ve "dünya" kelimesiyle devam ediyorsa bağlam öncesi "dünya" dizesini döndürür. Ancak bağlam öncesi "hello" ifadesini belirtirseniz "helloworld", "helloword"den daha anlamlı olduğu için model, başında boşlukla birlikte "world" dizesini döndürür.

Boşluklar dahil olmak üzere en fazla 20 karakterden oluşan mümkün olan en uzun bağlam öncesi dizesini sağlamanız gerekir. Dize daha uzunsa tanıyıcı yalnızca son 20 karakteri kullanır.

Aşağıdaki kod örneğinde, bir yazma alanının nasıl tanımlanacağı ve bağlam öncesi belirtmek için bir RecognitionContext nesnesinin nasıl kullanılacağı gösterilmektedir.

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

Çizgi sıralama

Tanıma doğruluğu kulaçların sırasına göre hassastır. Tanımlayıcılar, darbelerin insanların doğal yazdıkları sırayla gerçekleşmesini bekler. Örneğin, İngilizce için soldan sağa doğru yazılırlar. Bu kalıptan farklı olan herhangi bir durumda (örneğin, son kelimeyle başlayan İngilizce bir cümle yazmak) daha az doğru sonuçlar verir.

Başka bir örnek de Ink kelimesinin ortasındaki bir kelimenin kaldırılıp başka bir kelimeyle değiştirilmesidir. Düzeltme muhtemelen bir cümlenin ortasındadır ancak düzeltme darbeleri çizgi dizisinin sonundadır. Bu durumda, yeni yazılan kelimeyi API'ye ayrı olarak göndermenizi ve kendi mantığınızı kullanarak sonucu önceki tanımalarla birleştirmenizi öneririz.

Belirsiz şekillerle başa çıkma

Tanıyıcıya sağlanan şeklin anlamının belirsiz olduğu durumlar vardır. Örneğin, kenarları çok yuvarlanmış bir dikdörtgen, dikdörtgen veya elips olarak görülebilir.

Net olmayan bu destek kayıtları, mevcut olduğunda tanıma puanları kullanılarak ele alınabilir. Yalnızca şekil sınıflandırıcılar puan verir. Model çok güvenliyse en yüksek sonucun puanı, ikinci en iyiden çok daha iyi olur. Belirsizlik varsa ilk iki sonucun puanları yakın olur. Ayrıca, şekil sınıflandırıcılarının tüm Ink işaretini tek bir şekil olarak yorumladığını unutmayın. Örneğin, Ink öğesinde bir dikdörtgen ve birbirine bitişik bir elips varsa tek bir tanıma adayı iki şekli temsil edemediğinden sonuç olarak tanıyıcı ikisinden birini (ya da tamamen farklı bir şeyi) döndürebilir.