Dzięki rozpoznawaniu cyfrowego atramentu w ML Kit możesz rozpoznawać tekst odręczny na powierzchni cyfrowej w setkach języków, a także klasyfikować szkice.
Wypróbuj
- Wypróbuj przykładową aplikację, aby zobaczyć, jak można używać tego interfejsu API.
Zanim zaczniesz
- W pliku
build.gradlena poziomie projektu dodaj repozytorium Google Maven w sekcjachbuildscriptiallprojects. - Dodaj zależności dla bibliotek ML Kit na Androida do pliku Gradle na poziomie aplikacji modułu, który zwykle ma nazwę
app/build.gradle:
dependencies {
// ...
implementation 'com.google.mlkit:digital-ink-recognition:19.0.0'
}
Możesz teraz rozpocząć rozpoznawanie tekstu w obiektach Ink.
Tworzenie obiektu Ink
Głównym sposobem na zbudowanie obiektu Ink jest narysowanie go na ekranie dotykowym. Na Androidzie możesz w tym celu użyć Canvas. Moduły obsługi zdarzeń dotknięcia powinny wywoływać metodę addNewTouchEvent() pokazaną w tym fragmencie kodu, aby zapisać punkty w pociągnięciach, które użytkownik rysuje w obiekcie Ink.
Ten ogólny wzorzec jest przedstawiony w poniższym fragmencie kodu. Bardziej szczegółowy przykład znajdziesz w przykładowym 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 API rozpoznawania cyfrowego atramentu obsługuje setki języków, ale każdy język wymaga pobrania pewnych danych przed rozpoczęciem rozpoznawania. Każdy język wymaga około 20 MB miejsca na dane. Odpowiada za to 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. Chociaż funkcja rozpoznawania pisma cyfrowego jest przeszkolona w zakresie obsługi wielu różnych stylów pisania, wyniki mogą się różnić 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 dotyczą klasyfikatorów rysunków w przypadku 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 pisania, który go zawiera. Na przykład różnicę między małą i wielką literą „o” lub „c” albo między przecinkiem a ukośnikiem.
Podanie rozpoznającemu szerokości i wysokości obszaru pisania może poprawić dokładność. Jednak rozpoznawanie zakłada, że obszar pisania zawiera tylko 1 wiersz tekstu. Jeśli fizyczny obszar pisania jest wystarczająco duży, aby użytkownik mógł napisać co najmniej 2 wiersze, lepsze wyniki możesz uzyskać, przekazując obiekt WritingArea o wysokości, która jest Twoim najlepszym oszacowaniem wysokości pojedynczego wiersza tekstu. Obiekt WritingArea przekazywany do modułu rozpoznawania nie musi dokładnie odpowiadać fizycznemu obszarowi pisania na ekranie. Zmiana wysokości obszaru pisania w ten sposób działa lepiej w niektórych językach.
Podczas określania obszaru pisania podaj jego szerokość i wysokość w tych samych jednostkach co współrzędne pociągnięcia. Argumenty współrzędnych x,y nie wymagają jednostek – interfejs API normalizuje wszystkie jednostki, więc liczy się tylko względny rozmiar i pozycja pociągnięć. Możesz przekazywać współrzędne w dowolnej skali, która jest odpowiednia dla Twojego systemu.
Kontekst poprzedzający
Kontekst wstępny to tekst, który bezpośrednio poprzedza pociągnięcia w obszarze Ink, który próbujesz rozpoznać. Możesz pomóc rozpoznawaniu, podając kontekst wstępny.
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 przerw między słowami, czyli spacji. Możesz wpisać spację, ale nie możesz jej narysować, więc jak rozpoznawanie pisma odręcznego może określić, gdzie kończy się jedno słowo, a zaczyna drugie? Jeśli użytkownik napisał już „hello” i kontynuuje, pisząc słowo „world”, bez kontekstu wstępnego moduł rozpoznawania zwraca ciąg „world”. Jeśli jednak określisz kontekst wstępny „hello”, model zwróci ciąg „ world” ze spacją na początku, ponieważ „hello world” ma więcej sensu niż „helloword”.
Należy podać jak najdłuższy ciąg znaków przed kontekstem, 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 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ą występować w kolejności, w jakiej ludzie naturalnie piszą, na przykład od lewej do prawej w przypadku języka angielskiego. Każdy przypadek, który odbiega od tego wzorca, np. napisanie angielskiego zdania zaczynającego się od ostatniego słowa, daje mniej dokładne wyniki.
Innym przykładem jest usunięcie słowa w środku Ink i zastąpienie go innym słowem. Korekta prawdopodobnie znajduje się w środku zdania, ale pociągnięcia związane z korektą znajdują się na końcu sekwencji pociągnięć.
W takim przypadku zalecamy wysłanie nowo napisanego słowa osobno do interfejsu API i scalenie wyniku z wcześniejszymi rozpoznaniami przy użyciu własnej logiki.
Radzenie sobie z niejednoznacznymi kształtami
W niektórych przypadkach znaczenie kształtu podanego w rozpoznawaniu jest niejednoznaczne. Na przykład prostokąt z bardzo zaokrąglonymi krawędziami może być postrzegany jako prostokąt lub elipsa.
Takie niejednoznaczne przypadki można rozwiązać, korzystając z wyników rozpoznawania, jeśli są dostępne. Tylko klasyfikatory kształtów podają wyniki. Jeśli model jest bardzo pewny, wynik pierwszego wyniku będzie znacznie lepszy niż drugiego. Jeśli występuje niepewność, wyniki dla 2 najlepszych wyników będą zbliżone. Pamiętaj też, że klasyfikatory kształtów interpretują cały element Ink jako pojedynczy kształt. Jeśli na przykład Ink zawiera prostokąt i elipsę obok siebie, rozpoznawanie może zwrócić jeden z tych kształtów (lub coś zupełnie innego), ponieważ pojedynczy kandydat do rozpoznania nie może reprezentować 2 kształtów.