Rozpoznawanie tuszów cyfrowych za pomocą ML Kit na Androidzie

Funkcja cyfrowego rozpoznawania atramentu w ML Kit pozwala rozpoznawać tekst odręczny cyfrowe w setkach języków i klasyfikować szkice.

Wypróbuj

Zanim zaczniesz

  1. W pliku build.gradle na poziomie projektu dodaj repozytorium Google Maven w sekcjach buildscript i allprojects.
  2. Dodaj zależności bibliotek ML Kit na Androida do pliku Gradle na poziomie aplikacji modułu, którym jest zwykle app/build.gradle:
dependencies {
  // ...
  implementation 'com.google.mlkit:digital-ink-recognition:18.1.0'
}

Teraz możesz zacząć rozpoznawać tekst w obiektach Ink.

Tworzenie obiektu Ink

Głównym sposobem tworzenia obiektu Ink jest rysowanie go na ekranie dotykowym. Wł. Androida, możesz użyć Canvas dla w tym celu. Twoje moduły obsługi zdarzeń dotknięcia powinien wywołać addNewTouchEvent() następujący fragment kodu, aby zapisać punkty w pociągnięciach i rysuje obiekt Ink.

Ten ogólny wzorzec przedstawiono w poniższym fragmencie kodu. Zobacz Przykład krótkiego wprowadzenia do ML Kit , aby uzyskać pełniejszy przykład.

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

Pobieranie instancji DigitalInkAdaptiver

Aby przeprowadzić rozpoznawanie, wyślij instancję Ink do DigitalInkRecognizer obiekt. Poniższy kod pokazuje, jak utworzyć taką instancję modułu rozpoznawania z tagu 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());

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

W przykładowym kodzie powyżej założono, że model rozpoznawania został już pobrane, jak opisano w następnej sekcji.

Zarządzanie pobieraniem modeli

Chociaż interfejs API rozpoznawania atramentu cyfrowego obsługuje setki języków, każdy z nich wymaga pobrania pewnych danych przed rozpoznaniem. W pobliżu Wymagane jest 20 MB miejsca na dane dla każdego języka. Zajmuje się to RemoteModelManager obiekt.

Pobierz nowy model

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

Sprawdzanie, czy model został już pobrany

Kotlin

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

Java

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

Usuwanie pobranego modelu

Usunięcie modelu z pamięci urządzenia spowoduje zwolnienie miejsca.

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

Wskazówki dotyczące zwiększania dokładności rozpoznawania tekstu

Dokładność rozpoznawania tekstu może być różna w zależności od języka. Dokładność zależy też na styl pisania. Podczas gdy cyfrowe rozpoznawanie atramentu jest trenowane tak, by radzić sobie z wieloma stylami pisania, wyniki mogą być różne w zależności od użytkownika.

Oto kilka sposobów na zwiększenie dokładności rozpoznawania tekstu. Pamiętaj, że te techniki nie są stosowane do klasyfikatorów rysunków dla emotikonów, automatycznego rysowania i kształtów.

Miejsce do pisania

Wiele aplikacji ma dobrze zdefiniowany obszar do pisania przez użytkownika. Znaczenie symbolu to zależy częściowo od rozmiaru obszaru pisania, w którym się znajduje. na przykład różnicę między małą lub wielką literą „o”; lub „c” i przecinek zamiast a. ukośnik prawy.

Określanie szerokości i wysokości obszaru pisania może zwiększyć dokładność rozpoznawania. Pamiętaj jednak: moduł rozpoznawania zakłada, że obszar do pisania zawiera tylko jeden wiersz tekstu. Jeśli obszar pisania jest wystarczająco duży, aby użytkownik mógł napisać dwa lub więcej wierszy, przez podanie obszaru do pisania o wysokości, która jest najdokładniejszym oszacowaniem wysokości jednego wiersza. Obiekt WriteArea przekazywany do modułu rozpoznawania nie musi odpowiadać z użyciem fizycznego obszaru do pisania na ekranie. Zmienianie wysokości obszaru do pisania w ten sposób w niektórych językach sprawdza się lepiej niż w innych.

Określasz obszar pisania, podając jego szerokość i wysokość w tych samych jednostkach co kreska. . Argumenty współrzędnych x,y nie mają wymagań dotyczących jednostek – interfejs API normalizuje wszystkie jednostek, więc zwracają uwagę tylko na ich względny rozmiar i pozycję pociągnięć. Masz prawo i w dowolnej skali.

Przed kontekstem

Wstępny kontekst to tekst, który bezpośrednio poprzedza kreski w Ink, które nie są w stanie rozpoznać. Możesz pomóc modułowi rozpoznawania, podając mu informacje o wstępnym kontekście.

na przykład pisane kursywą litery „n” i „u” są często mylone. Jeśli użytkownik ma już wpisało się częściowe słowo „arg”, mogą kontynuować ciągi znaków, które będą rozpoznawane jako „ument” czy „nment”. Określanie elementu „arg” powiązanego ze wstępnie kontekstem rozwiązuje niejednoznaczność, „argument” jest bardziej prawdopodobne niż „argnment”.

Kontekst wstępny może też pomóc systemowi rozpoznawania w wykrywaniu podziałów słów, czyli spacji między słowami. Dostępne opcje wpisz spację, ale nie możesz narysować znaku, więc jak moduł rozpoznawania może określić, kiedy kończy się jeden wyraz? i zaczyna następna? Jeśli użytkownik napisał już „Cześć” i kontynuuje od słowa pisanego „world” (bez wstępnego kontekstu) moduł rozpoznawania zwraca ciąg „world” (świat). Jeśli jednak określisz parametr „hello”, model zwróci ciąg znaków „ na świecie”, z wiodącym spacją, ponieważ „witaj” świecie”. ma sens niż „helloword”.

Musisz podać najdłuższy możliwy ciąg znaków poprzedzający kontekst, do 20 znaków, w tym spacje. Jeśli ciąg jest dłuższy, moduł rozpoznawania używa tylko ostatnich 20 znaków.

Poniższy przykładowy kod pokazuje, jak zdefiniować obszar do pisania i używać RecognitionContext obiekt do określenia wstępnego kontekstu.

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

Kolejność ruchów

Dokładność rozpoznawania zależy od kolejności ruchów. Moduły rozpoznawania oczekują, że udar mózgu będzie występują w takiej kolejności, w jakiej ludzie naturalnie napisali; np. od lewej do prawej w przypadku języka angielskiego. Dowolne wychodząc z tego wzorca, np. wpisując angielskie zdanie zaczynające się od ostatniego słowa, daje mniej dokładne wyniki.

Innym przykładem jest usunięcie słowa w środku ciągu Ink i zastąpienie go lub inne słowo. Rewizja znajduje się prawdopodobnie w środku zdania, ale znajdziesz w niej kreski znajdują się na końcu sekwencji pociągnięć. W takim przypadku zalecamy wysłanie nowo utworzonego słowa oddzielnie do interfejsu API i scalanie z wcześniejszymi rozpoznawaniami przy użyciu własnej logiki.

Radzenie sobie z niejednoznacznymi kształtami

W niektórych przypadkach znaczenie kształtu przekazanego modułowi rozpoznawania jest niejednoznaczne. Dla: na przykład prostokąt z mocno zaokrąglonymi krawędziami może być prostokątny albo elipsa.

W takich niejasnych przypadkach można użyć wyników rozpoznawania, jeśli są dostępne. Tylko wyniki są podawane przez klasyfikatory kształtów. Jeśli model jest bardzo pewny, wynik najlepszego to dużo lepsze niż drugi najlepszy. Jeśli brak pewności co do dokładności, wyniki dwóch pierwszych pozycji będą być blisko. Pamiętaj też, że klasyfikatory kształtów interpretują cały element Ink jako jednego kształtu. Jeśli na przykład Ink zawiera prostokąt i elipsę obok każdego z nich moduł rozpoznawania może zwrócić jeden lub drugi element (albo coś zupełnie innego) jako ponieważ jeden kandydat do rozpoznawania nie może reprezentować dwóch kształtów.