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

ML Kit'in dijital mürekkep tanıma özelliği sayesinde, dijital bir yüzeyde el yazısıyla yazılmış yüzlerce dilde metinleri tanıyabilir ve eskizleri sınıflandırabilirsiniz.

Deneyin

Başlamadan önce

  1. Proje düzeyindeki build.gradle dosyanızda, Google'ın Maven deposunu hem buildscript hem de allprojects bölümlerinize 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 olur:
dependencies {
  // ...
  implementation 'com.google.mlkit:digital-ink-recognition:18.1.0'
}

Artık Ink nesnedeki metinleri tanımaya başlamaya hazırsınız.

Ink nesnesi oluşturma

Ink nesnesi oluşturmanın ana yolu, nesneyi dokunmatik ekranda çizmektir. Android'de bu amaçla Tuval kullanabilirsiniz. Dokunma etkinliği işleyicileriniz, kullanıcının Ink nesnesine çizdiği fırçalardaki 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 göz atı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();

DigitalInkRecognizer örneği alma

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

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şle

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, bir sonraki bölümde açıklandığı gibi tanıma modelinin zaten indirildiğini varsayar.

Model indirmelerini yönetme

Dijital mürekkep tanıma API'si yüzlerce dili desteklese de her dil, tanımadan önce bazı verilerin indirilmesini gerektirir. Her dil için yaklaşık 20 MB depolama alanı gerekir. Bu işlem 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 daha önce indirilip indirilmediğini kontrol etme

Kotlin

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

Java

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

İndirilen bir modeli silme

Bir model cihazın depolama alanından kaldırıldığında yer açılır.

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ıma doğruluğu diller arasında değişiklik gösterebilir. Doğruluk, yazma stiline de bağlıdır. Dijital Mürekkep Tanıma, birçok farklı yazma stilini işleyecek şekilde eğitilmiş olsa da sonuçlar kullanıcıdan kullanıcıya farklılık gösterebilir.

Aşağıda metin tanıyıcının doğruluğunu iyileştirmeye yönelik bazı yöntemler verilmiştir. Bu tekniklerin emojiler, otomatik çizimler ve şekillere yönelik çizim sınıflandırıcıları için geçerli olmadığını unutmayın.

Yazma alanı

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

Tanıyıcıya, yazma alanının genişliğini ve yüksekliğini söylemeniz doğruluğu artırabilir. Bununla birlikte, tanıyıcı, yazma alanının yalnızca tek bir metin satırı içerdiğini varsayar. Fiziksel yazı alanı, kullanıcının iki veya daha fazla satır yazmasına izin verecek kadar büyükse, tek bir satırlık metnin yüksekliğine ilişkin en iyi tahmininiz olan bir WriteArea ile bir yazı alanı geçirerek 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. Yazma Alanı 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 çizgi koordinatlarıyla aynı birim cinsinden belirtin. x,y koordinatı bağımsız değişkenlerinin birim gereksinimi yoktur. API tüm birimleri normalleştirdiğinden, önemli olan tek şey çizgilerin göreli boyutu ve konumudur. Sisteminiz için uygun olan ölçeklerde koordinatları aktarabilirsiniz.

Bağlam öncesi

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

Örneğin el yazısı "n" ve "u" harfleri çoğu zaman birbiriyle karıştırılır. Kullanıcı zaten "argüman" kısmi kelimesini girdiyse "ument" veya "nment" olarak tanınabilen çizgilerle devam edebilir. "bağımsız değişken" kelimesinin "bağımsız değişken"den daha olası olması nedeniyle, bağlam öncesi "bağımsız değişkenin" belirtilmesi belirsizliği giderir.

Bağlam ön bağlamı, tanıyıcının kelime boşluklarını, kelimeler arasındaki boşlukları tanımlamasına da yardımcı olabilir. Boşluk karakteri yazabilirsiniz, ancak bir karakter çizemezsiniz. Öyleyse, bir tanıyıcı bir kelimenin bitip bir sonrakinin ne zaman başlayacağını nasıl belirleyebilir? Kullanıcı zaten "merhaba" yazmışsa ve yazılı "dünya" kelimesiyle devam ediyorsa, tanıyıcı ön bağlam bilgisi olmadan "dünya" dizesini döndürür. Bununla birlikte, bağlam öncesi "merhaba" ifadesini belirtirseniz model, "merhaba" dizesini baştaki boşlukla birlikte "dünya" dizesini döndürür, çünkü "merhaba dünya", "merhaba"dan daha anlamlıdır.

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

Aşağıdaki kod örneğinde, yazma alanının nasıl tanımlanacağı ve ön bağlam belirtmek için 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ıralaması

Tanıma doğruluğu, darbelerin sırası konusunda hassastır. Tanıyıcılar, çizgilerin insanların doğal olarak yazdıkları sırayla gerçekleşmesini beklerler; örneğin, İngilizce için soldan sağa. Bu kalıptan farklı olan herhangi bir kullanım (örneğin, son kelimeden başlayarak İngilizce bir cümle yazma) daha az doğru sonuçlar verir.

Başka bir örnek de Ink etiketinin 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üzeltmedeki çizgiler çizgi dizisinin sonundadır. Bu durumda, yeni yazılan kelimeyi API'ye ayrı olarak göndermenizi ve sonucu kendi mantığınızı kullanarak ö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, çok yuvarlatılmış kenarlara sahip bir dikdörtgen, dikdörtgen veya elips olarak görülebilir.

Bu belirsiz durumlar, mümkün olduğunda tanıma puanları kullanılarak ele alınabilir. Yalnızca şekil sınıflandırıcılar puan verir. Model çok güveniyorsa en iyi sonucun puanı, ikinci en iyi sonuca göre çok daha iyi olur. Belirsizlik varsa ilk iki sonucun puanları birbirine yakın olacaktır. Ayrıca, şekil sınıflandırıcıların tüm Ink öğesini tek bir şekil olarak yorumladığını unutmayın. Örneğin, Ink, birbirine yan yana bir dikdörtgen ve elips içeriyorsa, tek bir tanıma adayı iki şekli temsil edemediğinden, tanıyıcı sonuç olarak bunlardan birini (veya tamamen farklı bir şeyi) döndürebilir.