בעזרת זיהוי דיו דיגיטלי של למידת מכונה, תוכלו לזהות טקסט בכתב יד על משטח דיגיטלי במאות שפות, וגם לסווג רישומים.
רוצה לנסות?
- כדאי לשחק עם האפליקציה לדוגמה כדי לראות שימוש לדוגמה ב-API הזה.
לפני שמתחילים
- בקובץ
build.gradle
ברמת הפרויקט, חשוב לכלול את מאגר ה-Maven של Google בקטעbuildscript
וגם בקטעallprojects
. - מוסיפים את יחסי התלות של ספריות ה-Android של ה-ML Kit לקובץ ה-Gradle ברמת האפליקציה, שהוא בדרך כלל
app/build.gradle
:
dependencies {
// ...
implementation 'com.google.mlkit:digital-ink-recognition:18.1.0'
}
עכשיו אפשר להתחיל לזהות טקסט באובייקטים של Ink
.
יצירת אובייקט מסוג Ink
הדרך העיקרית לבנות אובייקט Ink
היא לשרטט אותו במסך מגע. ב-Android אפשר להשתמש בCanvas למטרה זו. ה-handlers של אירועי מגע אמורים להפעיל את השיטה addNewTouchEvent()
כדי להציג את קטע הקוד הבא, כדי לאחסן את הנקודות בקווים שהמשתמש מושך באובייקט Ink
.
דפוס כללי זה מוצג בקטע הקוד הבא. דוגמה מלאה יותר זמינה בדוגמה למתחילים ב-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();
קבלת מופע של DigitalInkRecognizer
כדי לזהות אותם, שולחים את המכונה Ink
לאובייקט DigitalInkRecognizer
. הקוד הבא מראה איך ליצור זיהוי כזה באמצעות תג 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());
עיבוד אובייקט 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));
הקוד לדוגמה שלמעלה מניח שמודל הזיהוי כבר הורד, כפי שמתואר בקטע הבא.
ניהול הורדות של מודלים
בעוד שה-API לזיהוי דיו דיגיטלי תומך במאות שפות, צריך להוריד נתונים מסוימים לפני כל זיהוי. נפח האחסון הנדרש הוא 20MB לכל שפה. האובייקט הזה RemoteModelManager
מטופל.
הורדת מודל חדש
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));
בדיקה אם מודל מסוים כבר הורד
Kotlin
var model: DigitalInkRecognitionModel = ... remoteModelManager.isModelDownloaded(model)
Java
DigitalInkRecognitionModel model = ...; remoteModelManager.isModelDownloaded(model);
מחיקת מודל שהורדתם
הסרת מודל מהאחסון של המכשיר מפנה מקום.
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));
טיפים לשיפור הדיוק של זיהוי הטקסט
הדיוק של זיהוי טקסט עשוי להשתנות בשפות שונות. מידת הדיוק תלויה גם בסגנון הכתיבה. בעוד שהזיהוי של דיו דיגיטלי מאומן לטיפול בסוגים רבים של סגנונות כתיבה, התוצאות יכולות להשתנות ממשתמש למשתמש.
לפניכם כמה דרכים לשיפור הדיוק של מזהה טקסט. שימו לב: הטכניקות האלה לא רלוונטיות למסווגי שרטוטים עבור אמוג'ים, שרטוטים אוטומטיים וצורות.
תחום כתיבה
באפליקציות רבות יש אזור כתיבה מוגדר היטב לקלט של משתמשים. המשמעות של סמל נקבעת באופן חלקי בהתאם לגודלו של אזור הכתיבה שמכיל אותו. לדוגמה, ההבדל בין אותיות רישיות וקטנות "o" או "c", לבין פסיק לעומת קו נטוי קדימה.
אם אומרים למזהה את הרוחב והגובה של אזור הכתיבה, אפשר לשפר את הדיוק. עם זאת, המזהה מניח שאזור הכתיבה מכיל רק שורת טקסט אחת. אם תחום הכתיבה הפיזית גדול מספיק כדי לאפשר למשתמש לכתוב שתי שורות או יותר, ייתכן שתשיגו תוצאות טובות יותר אם תכתבו בתוך Writeing את הגובה שהוא האומדן הטוב ביותר של גובה שורת טקסט יחידה. האובייקט של WriteArea שאתם מעבירים למזהה לא חייב להיות זהה לחלוטין לאזור הכתיבה הפיזי שמופיע על המסך. שינוי של גובה הכתיבה כך פועל טוב יותר בשפות מסוימות.
כשאתם מציינים את אזור הכתיבה, אתם יכולים לציין את הרוחב והגובה של אותן יחידות בקואורדינטות של החתירה. לארגומנטים x,y קואורדינטות אין דרישה עבור יחידה כלשהי - ממשק ה-API מנרמל את כל היחידות, כך שהדבר החשוב ביותר הוא הגודל היחסי והמיקום של הקווים. אתם יכולים לבחון את הנתונים של קואורדינטות בכל היקף שמתאים למערכת שלכם.
הקשר מראש
הקשר מקדים הוא הטקסט שמופיע מיד לפני התנועות ב-Ink
שרוצים לזהות. תוכלו לעזור ליוצר לספר על כך באמצעות הקשר מראש.
לדוגמה, האותיות 's' ו-'u' המשמשות לעיתים קרובות כ-Google טועות זו לזו. אם המשתמש כבר הזין את המילה החלקית "arg", הוא עשוי להמשיך עם קווי התנועה שניתן לזהות אותם כ-"ment" או כ-"ment". כשאתם מציינים את ההקשר מראש "arg", המשמעות היא דו-משמעית, מכיוון שהמילה "ארגומנט" סבירה יותר מאשר "ארגומנט".
גם הקשר מראש יכול לעזור למזהה לזהות מעברי מילים, המרווחים בין המילים. אתם יכולים להקליד תו של רווח, אבל אי אפשר לצייר תו, אז איך מזהה יכול לזהות מתי מילה אחת מסתיימת והמילה הבאה מתחילה? אם המשתמש כבר כתב "hello" וממשיך עם המילה הכתובה "world", ללא הקשר מראש, המזהה יחזיר את המחרוזת "world". עם זאת, אם מציינים את ההקשר מראש "hello", המודל יחזיר את המחרוזת "world", עם רווח מוביל, מפני ש-"hello world" הגיוני יותר מ-"helloword".
עליכם לספק את המחרוזת הארוכה ביותר של הקשר מראש, באורך של עד 20 תווים, כולל רווחים. אם המחרוזת ארוכה יותר, המזהה ישתמש רק ב-20 התווים האחרונים.
בדוגמת הקוד שבהמשך מוסבר איך להגדיר אזור כתיבה ולהשתמש באובייקט RecognitionContext
כדי לציין הקשר מראש.
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);
סידור קווים
מידת הדיוק של זיהוי היא רגישה לסדר החתירות. המזהים מצפים שחיקות יופיעו בסדר שבו אנשים כותבים באופן טבעי. לדוגמה, משמאל לימין. בכל מקרה שיוצא מהדפוס הזה, כמו כתיבת משפט באנגלית שמתחיל במילה האחרונה, מוצגות תוצאות מדויקות פחות.
דוגמה נוספת: כשמסירים מילה באמצע Ink
או מחליפים אותה במילה אחרת. הגרסה הקודמת נמצאת כנראה באמצע משפט, אבל הקווים של הגרסה הקודמת נמצאים בסוף רצף הקווים.
במקרה כזה, מומלץ לשלוח בנפרד את המילה הכתובה החדשה ל-API ולמזג את התוצאה עם ההגנות הקודמות באמצעות לוגיקה משלכם.
התמודדות עם צורות לא ברורות
יש מקרים שבהם המשמעות של הצורה שסופקה למזהה היא רב משמעית. לדוגמה, ניתן לראות מלבן עם קצוות מעוגלים מאוד בתור מלבן או שלוש נקודות.
לא ניתן לטפל במקרים הלא ברורים האלה באמצעות ציוני הכרה, אם הם זמינים. רק המסווגים מעוצבים נותנים ציונים. אם המודל בטוח מאוד, הציון של התוצאה העליונה יהיה גבוה בהרבה מהציון השני. אם לא בטוחים, התוצאות של שתי התוצאות המובילות יהיו קרובות. כמו כן, חשוב לזכור שהמסווגים של הצורות מפרשים את כל הInk
כצורה אחת. לדוגמה, אם השדה Ink
מכיל מלבן ושלוש נקודות יחד, המזהה עשוי להחזיר אחד את השני (או משהו אחר לחלוטין) מכיוון שמועמד יחיד לזיהוי אינו יכול לייצג שתי צורות.