Dzięki rozpoznawaniu pisma odręcznego w ML Kit możesz rozpoznawać tekst napisany ręcznie na powierzchni cyfrowej w setkach języków, a także klasyfikować szkice.
Wypróbuj
- Wypróbuj przykładową aplikację, aby zobaczyć przykład użycia tego interfejsu API.
Zanim zaczniesz
- W pliku
build.gradlena poziomie projektu sprawdź, czy w sekcjachbuildscriptiallprojectsznajduje się repozytorium Google Maven. - Dodaj zależności bibliotek ML Kit na Androida do pliku Gradle na poziomie modułu, który zwykle znajduje się w
app/build.gradle:
dependencies {
// ...
implementation 'com.google.mlkit:digital-ink-recognition:19.0.0'
}
Możesz już zacząć rozpoznawać tekst w obiektach Ink.
Tworzenie obiektu Ink
Głównym sposobem tworzenia obiektu Ink jest rysowanie go na ekranie dotykowym. W
Androidzie możesz użyć do
tego
Canvasa. Obsługa zdarzeń dotknięcia powinna wywoływać metodę addNewTouchEvent() pokazaną w tym fragmencie kodu, aby przechowywać punkty w pociągnięciach rysowanych przez użytkownika w obiekcie Ink.
Ten ogólny wzorzec jest pokazany w tym fragmencie kodu. Bardziej kompletny przykład znajdziesz w krótkim wprowadzeniu do 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
rozpoznawania na podstawie 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, jak opisano w następnej sekcji.
Zarządzanie pobieraniem modeli
Interfejs Digital Ink Recognition API obsługuje setki języków, ale przed rozpoznaniem każdego z nich trzeba pobrać odpowiednie dane. Na każdy język potrzeba około 20 MB miejsca na dane. Zarządza tym 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 zwalnia miejsce.
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. Rozpoznawanie pisma odręcznego jest trenowane pod kątem obsługi wielu stylów pisania, ale 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 mają zastosowania do klasyfikatorów rysunków emoji, autodraw i kształtów.
Obszar pisania
Wiele aplikacji ma dobrze zdefiniowany obszar pisania, w którym użytkownik może wprowadzać dane. Znaczenie symbolu jest częściowo określane przez jego rozmiar w stosunku do rozmiaru obszaru pisania, w którym się znajduje. Na przykład różnica między małą i wielką literą „o” lub „c” a przecinkiem i ukośnikiem.
Podanie rozpoznawania szerokości i wysokości obszaru pisania może poprawić dokładność. Rozpoznawanie zakłada jednak, że obszar pisania zawiera tylko 1 wiersz tekstu. Jeśli fizyczny obszar pisania jest wystarczająco duży, aby użytkownik mógł napisać 2 lub więcej wierszy, możesz uzyskać lepsze wyniki, przekazując WritingArea o wysokości, która jest najlepszym oszacowaniem wysokości 1 wiersza tekstu. Obiekt WritingArea przekazywany do rozpoznawania nie musi dokładnie odpowiadać fizycznemu obszarowi pisania na ekranie. Zmiana wysokości WritingArea w ten sposób działa lepiej w niektórych językach niż w innych.
Gdy określasz obszar pisania, podaj jego szerokość i wysokość w tych samych jednostkach co współrzędne pociągnięcia. Argumenty współrzędnych x i y nie mają wymagań dotyczących jednostek – interfejs API normalizuje wszystkie jednostki, więc liczy się tylko względny rozmiar i położenie pociągnięć. Możesz przekazywać współrzędne w dowolnej skali, która ma sens w Twoim systemie.
Kontekst wstępny
Kontekst wstępny to tekst, który bezpośrednio poprzedza pociągnięcia w obiekcie Ink, który próbujesz rozpoznać. Możesz pomóc rozpoznawaniu, informując go o kontekście wstępnym.
Na przykład litery „n” i „u” pisane kursywą są często mylone. Jeśli użytkownik wpisał już częściowe słowo „arg”, może kontynuować pociągnięcia, które można rozpoznać jako „ument” lub „nment”. Określenie kontekstu wstępnego „arg” rozwiązuje niejednoznaczność, ponieważ słowo „argument” jest bardziej prawdopodobne niż „argnment”.
Kontekst wstępny może też pomóc rozpoznawaniu w identyfikowaniu podziałów wyrazów, czyli spacji między wyrazami. Możesz wpisać spację, ale nie możesz jej narysować. Jak więc rozpoznawanie może określić, kiedy kończy się jeden wyraz, a zaczyna następny? Jeśli użytkownik napisał już „hello” i kontynuuje słowem „world”, bez kontekstu wstępnego rozpoznawanie zwróci ciąg znaków „world”. Jeśli jednak określisz kontekst wstępny „hello”, model zwróci ciąg znaków „ world” ze spacją na początku, ponieważ „hello world” ma więcej sensu niż „helloword”.
Powinieneś podać jak najdłuższy ciąg znaków kontekstu wstępnego, maksymalnie 20 znaków, w tym spacje. Jeśli ciąg znaków jest dłuższy, rozpoznawanie używa tylko ostatnich 20 znaków.
Poniższy przykładowy kod 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ść pociągnięć
Dokładność rozpoznawania zależy od kolejności pociągnięć. Rozpoznawanie oczekuje, że pociągnięcia będą wykonywane w kolejności, w jakiej ludzie naturalnie piszą, np. od lewej do prawej w przypadku języka angielskiego. Każdy przypadek, który odbiega od tego wzorca, np. napisanie 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 obiektu Ink i zastąpienie go innym słowem. Poprawka znajduje się prawdopodobnie w środku zdania, ale pociągnięcia dotyczące poprawki są na końcu sekwencji pociągnięć.
W takim przypadku zalecamy wysłanie nowo napisanego słowa osobno do interfejsu API i połączenie wyniku z poprzednimi rozpoznaniami za pomocą własnej logiki.
Radzenie sobie z niejednoznacznymi kształtami
W niektórych przypadkach znaczenie kształtu przekazanego do rozpoznawania jest niejednoznaczne. Na przykład prostokąt o bardzo zaokrąglonych krawędziach można uznać za prostokąt lub elipsę.
W takich niejasnych przypadkach można używać wyników rozpoznawania, gdy są dostępne. Wyniki są dostępne tylko w przypadku klasyfikatorów kształtów. Jeśli model jest bardzo pewny, wynik najlepszy będzie znacznie lepszy niż drugi najlepszy. Jeśli występuje niepewność, wyniki 2 najlepszych wyników będą zbliżone. Pamiętaj też, że klasyfikatory kształtów interpretują cały obiekt Ink jako pojedynczy kształt. Jeśli na przykład obiekt Ink zawiera prostokąt i elipsę obok siebie, rozpoznawanie może zwrócić jeden z nich (lub coś zupełnie innego), ponieważ pojedynczy kandydat rozpoznawania nie może reprezentować 2 kształtów.