Dzięki rozpoznawaniu pisma odręcznego w ML Kit możesz rozpoznawać tekst napisany odrę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.gradle
na poziomie projektu dodaj repozytorium Maven firmy Google do sekcjibuildscript
iallprojects
. - Dodaj zależności dla bibliotek ML Kit na Androida do pliku Gradle na poziomie aplikacji modułu, który zwykle znajduje się w tym miejscu:
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 tworzenia obiektu Ink
jest narysowanie go na ekranie dotykowym. Na urządzeniach z Androidem możesz w tym celu użyć płótna. Obsługa zdarzeń dotykowych powinna wywoływać metodę addNewTouchEvent()
pokazaną w tym fragmencie kodu, aby przechowywać punkty w pociągnięciach, które użytkownik rysuje w obiekcie Ink
.
Ten ogólny wzorzec pokazujemy w tym fragmencie kodu. Bardziej szczegółowy 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 API rozpoznawania pisma odręcznego obsługuje setki języków, ale w przypadku każdego z nich przed rozpoznaniem trzeba pobrać pewne dane. Każdy język wymaga około 20 MB miejsca na dane. Zajmuje się 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 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 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. Funkcja rozpoznawania pisma odręcznego jest trenowana 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 zwiększenie dokładności rozpoznawania tekstu. Pamiętaj, że te techniki nie mają zastosowania do klasyfikatorów rysunków w przypadku emoji, automatycznego rysowania 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ślone 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” oraz między przecinkiem a ukośnikiem.
Podanie rozpoznawaniu szerokości i wysokości obszaru pisania może zwiększyć dokładność. Jednak rozpoznawanie zakłada, że obszar pisania zawiera tylko jeden 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 wartość WritingArea o wysokości, która jest Twoim najlepszym oszacowaniem wysokości pojedynczego wiersza tekstu. Obiekt WritingArea przekazywany do rozpoznawania nie musi dokładnie odpowiadać fizycznemu obszarowi pisania na ekranie. Zmiana wysokości obszaru pisania w ten sposób sprawdza się 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 pociągnięcia. Argumenty współrzędnych x i y nie wymagają 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 jest odpowiednia dla Twojego systemu.
Kontekst przed
Kontekst poprzedzający to tekst, który bezpośrednio poprzedza znaki w Ink
, które próbujesz rozpoznać. Możesz pomóc rozpoznawaniu, podając mu kontekst.
Na przykład litery pisane „n” i „u” są często mylone. Jeśli użytkownik wpisał już część słowa „arg”, może kontynuować wpisywanie znaków, które można rozpoznać jako „ument” lub „nment”. Określenie kontekstu poprzedzającego „arg” rozwiązuje niejednoznaczność, ponieważ słowo „argument” jest bardziej prawdopodobne niż „argnment”.
Kontekst przed słowem może też pomóc rozpoznawaniu w określaniu przerw między słowami, czyli spacji. Możesz wpisać spację, ale nie możesz jej narysować. Jak więc system rozpoznawania może określić, kiedy kończy się jedno słowo, a zaczyna drugie? Jeśli użytkownik napisał już „hello” i kontynuuje pisanie słowa „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ększy sens niż „helloword”.
Podaj jak najdłuższy ciąg znaków przed kontekstem (maksymalnie 20 znaków, w tym spacji). Jeśli ciąg jest dłuższy, rozpoznawanie obejmuje tylko ostatnie 20 znaków.
Poniższy przykładowy kod pokazuje, jak zdefiniować obszar pisania i użyć obiektu RecognitionContext
do określenia kontekstu wstępnego.
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 kreślenia. Rozpoznawanie pisma odręcznego 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ącego się od ostatniego słowa, daje mniej dokładne wyniki.
Inny przykład to usunięcie słowa ze środka Ink
i zastąpienie go innym słowem. Poprawka prawdopodobnie znajduje się w środku zdania, ale pociągnięcia związane z poprawką 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
Czasami znaczenie kształtu przekazanego do rozpoznawania jest niejednoznaczne. Na przykład prostokąt o bardzo zaokrąglonych krawędziach może być postrzegany jako prostokąt lub elipsa.
W takich niejasnych przypadkach można używać wyników rozpoznawania, jeśli są dostępne. Tylko klasyfikatory kształtów podają wyniki. Jeśli model jest bardzo pewny, wynik najlepszego rezultatu będzie znacznie lepszy niż drugiego w kolejności. Jeśli istnieje niepewność, wyniki dla 2 najlepszych rezultatów będą zbliżone. Pamiętaj też, że klasyfikatory kształtów interpretują cały znak Ink
jako jeden kształt. Jeśli na przykład 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ć dwóch kształtów.