Dzięki funkcji rozpoznawania pisma cyfrowego w ML Kit możesz rozpoznawać tekst pisany na cyfrowej powierzchni w setkach języków oraz klasyfikować szkice.
Wypróbuj
- Aby zobaczyć przykład użycia tego interfejsu API, wypróbuj przykładową aplikację.
Zanim zaczniesz
- W pliku
build.gradle
na poziomie projektu dodaj repozytorium Maven firmy Google w sekcjachbuildscript
iallprojects
. - Dodaj zależności do bibliotek ML Kit na Androida do pliku Gradle na poziomie aplikacji modułu. Jest to zwykle:
app/build.gradle
dependencies {
// ...
implementation 'com.google.mlkit:digital-ink-recognition:18.1.0'
}
Możesz już rozpoznawać tekst w obiektach Ink
.
Tworzenie obiektu Ink
Głównym sposobem tworzenia obiektu Ink
jest narysowanie go na ekranie dotykowym. Na urządzeniu z Androidem możesz użyć Canvasa. Obsługa zdarzenia dotyku powinna wywoływać metodę addNewTouchEvent()
, aby przechowywać punkty w ścieżkach rysowanych przez użytkownika w obiekcie Ink
.
Ten ogólny schemat przedstawia poniższy fragment kodu. Bardziej kompletny przykład znajdziesz w pliku przykładowym ML Kit.
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 DigitalInkRecognizer
Aby przeprowadzić rozpoznawanie, wyślij instancję Ink
do obiektu DigitalInkRecognizer
. Poniższy kod pokazuje, jak utworzyć instancję takiego rozpoznawacza 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));
Przykładowy kod powyżej zakłada, że model rozpoznawania został już pobrany zgodnie z opisem w następnej sekcji.
Zarządzanie pobieraniem modeli
Interfejs API rozpoznawania pisma odręcznego obsługuje setki języków, ale przed rozpoczęciem rozpoznawania należy pobrać dane dotyczące każdego z nich. Na każdy język potrzeba około 20 MB miejsca na dane. Zarządza nim obiekt RemoteModelManager
.
Pobieranie nowego modelu
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 powoduje 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 poprawy dokładności rozpoznawania tekstu
Dokładność rozpoznawania tekstu może się różnić w zależności od języka. Dokładność zależy też od stylu pisania. Chociaż rozpoznawanie pisma cyfrowego jest trenowane pod kątem obsługi wielu stylów pisania, wyniki mogą się różnić w zależności od użytkownika.
Oto kilka sposobów na poprawę dokładności rozpoznawania tekstu. Pamiętaj, że te techniki nie dotyczą klasyfikatorów rysunków dla emotikonów, automatycznego rysowania i kształtów.
Obszar pisania
Wiele aplikacji ma dobrze zdefiniowany obszar pisania, w którym użytkownik może wprowadzać tekst. Znaczenie symbolu jest częściowo określane przez jego rozmiar w stosunku do rozmiaru obszaru pisma, który go zawiera. Na przykład różnica między małą i wielką literą „o” lub „c” albo między przecinkiem a ukośnikiem.
Podanie rozpoznawalności szerokości i wysokości obszaru pisania może zwiększyć dokładność. Rozpoznawca zakłada jednak, że obszar pisania zawiera tylko jeden wiersz tekstu. Jeśli fizyczna powierzchnia pisania jest wystarczająco duża, aby umożliwić użytkownikowi zapisanie 2 lub więcej wierszy, możesz uzyskać lepsze wyniki, przekazując WritingArea z wysokość odpowiadającą Twojemu najlepszemu szacowaniu wysokości pojedynczego wiersza tekstu. Obiekt WritingArea przekazywany do rozpoznawacza nie musi dokładnie odpowiadać fizycznej obszarowi pisania na ekranie. Zmiana wysokości WritingArea w ten sposób działa lepiej w niektórych językach niż w innych.
Podczas określania obszaru pisania podaj jego szerokość i wysokość w tych samych jednostkach co współrzędne obrysu. Argumenty x i y nie wymagają podania jednostki miary – interfejs API normalizuje wszystkie jednostki, więc jedyne, co się liczy, to względna wielkość i położenie kresek. Możesz przesłać współrzędne w dowolnej skali, która ma sens w Twoim systemie.
Kontekst wstępny
Wstępny kontekst to tekst bezpośrednio poprzedzający znaki w Ink
, które chcesz rozpoznać. Możesz pomóc rozpoznawacza, podając mu wstępny kontekst.
Na przykład litery „n” i „u” pisane kursywą są często ze sobą mylone. Jeśli użytkownik wpisał już część słowa „arg”, może kontynuować pisanie za pomocą ruchów, które mogą zostać rozpoznane jako „ument” lub „nment”. Podanie wstępnego kontekstu „arg” rozwiązuje niejednoznaczność, ponieważ słowo „argument” jest bardziej prawdopodobne niż „argnment”.
Kontekst sytuacyjny może też pomóc rozpoznawacza w identyfikowaniu podziału wyrazów i spacji między wyrazami. Możesz wpisać spację, ale nie możesz jej narysować. Jak więc rozpoznawacz może określić, kiedy kończy się jedno słowo, a zaczyna następne? Jeśli użytkownik napisał już „hello” (cześć) i kontynuuje pisanie słowa „world” (świat), bez kontekstu rozpoznawanie zwraca ciąg znaków „world”. Jeśli jednak podasz kontekst wstępny „hello”, model zwróci ciąg znaków „world” z przecinem wiodącym, ponieważ „helloworld” ma więcej sensu niż „helloword”.
Podaj jak najdłuższy ciąg znaków przed kontekstem (maksymalnie 20 znaków, w tym spacje). Jeśli ciąg jest dłuższy, rozpoznawacz używa tylko ostatnich 20 znaków.
Przykładowy kod poniżej pokazuje, jak zdefiniować obszar pisania i użyć obiektu RecognitionContext
, aby określić kontekst wstępny.
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ść uderzeń
Dokładność rozpoznawania zależy od kolejności kresek. Rozpoznawacze oczekują, że pociągnięcia będą wykonywane w kolejności, w jakiej ludzie naturalnie piszą, np. w języku angielskim od lewej do prawej. Wszelkie odstępstwa od tego wzoru, np. pisanie zdania w języku angielskim, zaczynając od ostatniego słowa, daje mniej dokładne wyniki.
Innym przykładem jest usunięcie słowa w środku wyrażenia Ink
i zastąpienie go innym słowem. Poprawka znajduje się prawdopodobnie w środku zdania, ale jej linie są na końcu sekwencji linii.
W takim przypadku zalecamy wysłanie do interfejsu API nowo napisanego słowa osobno i zlanie wyniku z poprzednimi rozpoznaniami za pomocą własnej logiki.
Praca z niejednoznacznymi kształtami
Czasami znaczenie kształtu przekazanego do rozpoznawania jest niejednoznaczne. Na przykład prostokąt z bardzo zaokrąglonymi rogami może być postrzegany jako prostokąt lub elipsa.
W takich niejasnych przypadkach można użyć wyników rozpoznawania, jeśli są dostępne. Wyniki dostarczają tylko klasyfikatory kształtu. Jeśli model jest bardzo pewny siebie, wynik pierwszego wyniku będzie znacznie lepszy niż wynik drugiego najlepszego. Jeśli nie ma pewności, wyniki 2 najlepszych wyników będą zbliżone. Pamiętaj też, że klasyfikatory kształtów interpretują cały Ink
jako jeden kształt. Jeśli na przykład Ink
zawiera prostokąt i elipsę obok siebie, rozpoznawacz może zwrócić jeden z nich (lub coś zupełnie innego), ponieważ pojedynczy kandydat do rozpoznania nie może reprezentować 2 kształtów.