בעזרת זיהוי הדיו הדיגיטלי של ML Kit, אפשר לזהות טקסט שכתבתם בכתב יד על משטח דיגיטלי במאות שפות, וגם לסווג סקיצות.
רוצה לנסות?
- מומלץ לשחק עם האפליקציה לדוגמה כדי .
לפני שמתחילים
- בקובץ
build.gradle
ברמת הפרויקט, חשוב לכלול את מאגר Maven של Google גם בקטעbuildscript
וגם בקטעallprojects
. - מוסיפים את יחסי התלות של ספריות ML Kit ל-Android לקובץ Gradle ברמת האפליקציה של המודול, שבדרך כלל הוא
app/build.gradle
:
dependencies {
// ...
implementation 'com.google.mlkit:digital-ink-recognition:18.1.0'
}
עכשיו אפשר להתחיל לזהות טקסט באובייקטים מסוג Ink
.
יצירה של אובייקט Ink
הדרך העיקרית לבנות אובייקט Ink
היא לשרטט אותו על מסך מגע. ב-Android, אפשר להשתמש ב-Canvas למטרה הזו. שלך
מטפלים באירועי מגע
צריך לקרוא ל-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));
טיפים לשיפור הדיוק של זיהוי הטקסט
רמת הדיוק של זיהוי הטקסט משתנה בהתאם לשפה. רמת הדיוק תלויה גם בסגנון הכתיבה. התכונה 'זיהוי דיו דיגיטלי' מאומנת לטפל בסגנונות כתיבה רבים, אבל התוצאות עשויות להשתנות בהתאם למשתמש.
ריכזנו כאן כמה דרכים לשיפור הדיוק של זיהוי טקסט. חשוב לזכור שהשיטות האלה לא חלות על הסיווג של ציורים של אמוג'י, AutoDraw וצורות.
אזור כתיבה
באפליקציות רבות יש אזור כתיבה מוגדר היטב לקלט של משתמשים. המשמעות של סמל היא נקבע באופן חלקי על פי גודלו ביחס לגודל של אזור הכתיבה שמכיל אותו. לדוגמה, ההבדל בין האות O באותיות קטנות או גדולות או C, ופסיק לעומת קו נטוי.
ציון הרוחב והגובה של אזור הכתיבה יכול לשפר את הדיוק. אבל, לפעמים המזהה מניח שאזור הכתיבה מכיל רק שורת טקסט אחת. אם ההכנסה הפיזית גדול מספיק כדי לאפשר למשתמש לכתוב שתי שורות או יותר, ייתכן תוצאות על ידי העברת אזור כתיבה עם גובה שהוא האומדן הטוב ביותר לגובה של שורת טקסט אחת. אובייקט WritingArea שאתם מעבירים למזהה לא חייב להתאים בדיוק לאזור הכתיבה הפיזי במסך. שינוי גובה אזור הכתיבה באופן הזה פועל טוב יותר בשפות מסוימות מאשר בשפות אחרות.
כשמציינים את אזור הכתיבה, מציינים את הרוחב והגובה שלו באותן יחידות של הקו של הקואורדינטות. אין דרישה ליחידות בארגומנטים של הקואורדינטות x,y – ה-API מבצע נורמליזציה של כל היחידות, כך שהדבר היחיד שחשוב הוא המיקום והגודל היחסי של הקווים. אתם יכולים להעביר קואורדינטות בכל קנה מידה שמתאים למערכת שלכם.
לפני ההקשר
ההקשר המקדים הוא הטקסט שמופיע מיד לפני הקווים ב-Ink
שאתם מנסים לזהות. כדי לעזור למזהה, תוכלו לספר לו על ההקשר שלפני ההקשר.
לדוגמה, האותיות 'n' ו-'u' בכתב יד מעורבב נוטות להתבלבל זו בזו. אם למשתמש יש כבר הזנת את המילה החלקית "arg", ייתכן שהם ימשיכו עם קווים שניתן לזהות אותם בתור ument או 'nment'. ציון ה'ארגומנט' לפני ההקשר פותרים את חוסר הבהירות, כי המילה "argument" יש סבירות גבוהה יותר מ'ארגומנט'.
ההקשר המקדים יכול גם לעזור למזהה לזהות את הפסקות המילים ואת הרווחים בין המילים. אפשר מקלידים תו רווח אבל אי אפשר לשרטט תו, אז איך המזהה יכול לקבוע מתי מילה אחת מסתיימת והסרטון הבא מתחיל? אם המשתמש כבר כתב "hello" והמשיך עם המילה הכתובה "world", ללא הקשר מראש, המזהה מחזיר את המחרוזת "world". אבל אם מציינים לפני ההקשר "hello", המודל יחזיר את המחרוזת " בעולם, עם חלל מוביל, כי "שלום עולם" יותר הגיוני מ"מילת מפתח".
צריך לספק מחרוזת ארוכה ככל האפשר לפני ההקשר, עד 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
מכיל מלבן ואליפסה לצד כל אחד מהם
אחר, המזהה עשוי להחזיר אחד או את השני (או משהו שונה לחלוטין)
התוצאה, מכיוון שמועמד להכרה יחיד לא יכול לייצג שתי צורות.