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ış metinleri yüzlerce dilde tanıyabilir ve çizimleri 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 (genellikle app/build.gradle) ekleyin:
dependencies {
  // ...
  implementation 'com.google.mlkit:digital-ink-recognition:19.0.0'
}

Artık Ink nesnelerindeki metinleri tanımaya hazırsınız.

Ink nesnesi oluşturma

Ink nesnesi oluşturmanın ana yolu, nesneyi dokunmatik ekranda çizmektir. Android'de bu amaçla Canvas kullanabilirsiniz. Dokunma etkinliği işleyicileriniz, kullanıcının Ink nesnesine çizdiği vuruşlardaki 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 Kiti 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();

DigitalInkRecognizer örneği alma

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

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

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 bir sonraki bölümde açıklandığı gibi zaten indirilmiş olduğunu varsayar.

Model indirme işlemlerini yönetme

Dijital mürekkep tanıma API'si yüzlerce dili desteklese de her dil için tanıma işleminden önce bazı verilerin indirilmesi gerekir. Dil başına yaklaşık 20 MB depolama alanı gerekir. Bu işlem, RemoteModelManager nesnesi tarafından gerçekleştirilir.

Yeni bir model indirme

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 indirilip indirilmediğini kontrol etme

Kotlin

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

Java

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

İndirilen bir modeli silme

Bir modeli cihazın depolama alanından kaldırdığınızda 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ştirmeyle ilgili ipuçları

Metin tanıma özelliğinin doğruluğu farklı dillerde değişiklik gösterebilir. Doğruluk, yazma stiline de bağlıdır. Dijital Mürekkep Tanıma, birçok yazı stilini işleyecek şekilde eğitilmiş olsa da sonuçlar kullanıcıdan kullanıcıya değişebilir.

Metin tanıma aracının doğruluğunu artırmanın bazı yolları aşağıda verilmiştir. Bu tekniklerin emojiler, otomatik çizim ve şekiller için çizim sınıflandırıcılarında geçerli olmadığını unutmayın.

Yazma alanı

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

Tanıyıcıya yazma alanının genişliğini ve yüksekliğini bildirmek doğruluğu artırabilir. Ancak tanıyıcı, yazma alanında yalnızca tek bir satır metin olduğunu varsayar. Fiziksel yazma alanı, kullanıcının iki veya daha fazla satır yazmasına izin verecek kadar büyükse WritingArea'yı tek bir metin satırının yüksekliğine dair en iyi tahmininiz olan bir yükseklikle ileterek daha iyi sonuçlar elde edebilirsiniz. Tanıyıcıya ilettiğiniz WritingArea nesnesinin, ekrandaki fiziksel yazma alanıyla tam olarak eşleşmesi gerekmez. WritingArea 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 vuruş koordinatlarıyla aynı birimlerde belirtin. X,Y koordinat bağımsız değişkenleri için birim şartı yoktur. API tüm birimleri normalleştirdiğinden yalnızca vuruşların göreceli boyutu ve konumu önemlidir. Sisteminiz için anlamlı olan herhangi bir ölçekte koordinatları iletebilirsiniz.

Ön bağlam

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

Örneğin, el yazısındaki "n" ve "u" harfleri genellikle birbiriyle karıştırılır. Kullanıcı "arg" kelime parçasını zaten girdiyse "ument" veya "nment" olarak tanınabilecek vuruşlarla devam edebilir. "arg" ön bağlamını belirtmek, "argüman" kelimesinin "argümen" kelimesinden daha olası olması nedeniyle belirsizliği giderir.

Ön bağlam, tanıma aracının kelime sonlarını (kelimeler arasındaki boşlukları) tanımlamasına da yardımcı olabilir. Boşluk karakterini yazabilirsiniz ancak çizemezsiniz. Bu durumda, tanıyıcı bir kelimenin nerede bitip diğerinin nerede başladığını nasıl belirleyebilir? Kullanıcı "merhaba" yazdıysa ve "dünya" kelimesiyle devam ederse tanıyıcı, ön bağlam olmadan "dünya" dizesini döndürür. Ancak ön bağlam olarak "merhaba" derseniz model, "merhaba dünya" ifadesi "merhabadünya" ifadesinden daha anlamlı olduğu için başında boşluk olan " dünya" dizesini döndürür.

Boşluklar dahil olmak üzere en fazla 20 karakterden oluşan, mümkün olan en uzun ön bağlam 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 ö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);

Vuruş sırası

Tanıma doğruluğu, vuruşların sırasına duyarlıdır. Tanıyıcılar, vuruşların kullanıcıların doğal olarak yazacağı sırada (ör. İngilizce için soldan sağa) gerçekleşmesini bekler. İngilizce bir cümleyi son kelimesinden başlayarak yazmak gibi bu kalıbın dışına çıkan durumlarda daha az doğru sonuçlar elde edilir.

Başka bir örnek de Ink 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 için yapılan vuruşlar, vuruş sırasının 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 yuvarlak kenarlı bir dikdörtgen, dikdörtgen veya elips olarak görülebilir.

Bu belirsiz durumlar, mevcut olduğunda tanıma puanları kullanılarak ele alınabilir. Yalnızca şekil sınıflandırıcılar puan sağlar. Modelin güven düzeyi çok yüksekse en iyi sonucun puanı, ikinci en iyi sonucun puanından çok daha yüksek olur. Belirsizlik varsa en iyi iki sonucun puanları birbirine yakın olur. 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 yan yana bir dikdörtgen ve bir elips içeriyorsa tanıyıcı, sonuç olarak bunlardan birini (veya tamamen farklı bir şeyi) döndürebilir. Çünkü tek bir tanıma adayı iki şekli temsil edemez.