باستخدام ميزة التعرّف على الحبر الرقمي في 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، يمكنك استخدام لوحة رسم لهذا الغرض. من المفترض أن تستدعي معالِجات
حدث اللمس الطريقة addNewTouchEvent()
التي تعرِض مقتطف الرمز التالي لتخزين النقاط التي يرسمها المستخدم في عنصر Ink
.
يتم توضيح هذا النمط العام في مقتطف الرمز التالي. راجِع نموذج البدء السريع لاستخدام حزمة تعلّم الآلة للاطّلاع على مثال أكثر اكتمالاً.
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));
يفترض رمز النموذج أعلاه أن نموذج التعرف قد تم تنزيله مسبقًا، كما هو موضح في القسم التالي.
إدارة عمليات تنزيل النماذج
على الرغم من أن واجهة برمجة التطبيقات للتعرف على الحبر الرقمي تتيح مئات اللغات، فإن كل لغة تتطلب تنزيل بعض البيانات قبل أي عملية التعرف عليها. وتتطلّب كل لغة حوالي 20 ميغابايت من مساحة التخزين. تتم معالجة هذا الإجراء من خلال
الكائن 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" والفاصلة مقابل الشرطة المائلة للأمام.
يمكن أن يؤدي تحديد أداة التعرُّف بعرض مساحة الكتابة وارتفاعها إلى تحسين الدقة. ومع ذلك، تفترض أداة التعرف أن منطقة الكتابة تحتوي فقط على سطر واحد من النص. إذا كانت منطقة الكتابة الفعلية كبيرة بما يكفي للسماح للمستخدم بكتابة سطرين أو أكثر، يمكنك الحصول على نتائج أفضل من خلال الانتقال في WriteArea (الارتفاع) ليكون أفضل تقدير لارتفاع سطر واحد من النص. ليس من الضروري أن يتطابق كائن WriteArea الذي تنقله إلى أداة التعرُّف بشكل تام مع منطقة الكتابة الفعلية على الشاشة. جدير بالذكر أنّ تغيير ارتفاع writeArea بهذه الطريقة يعمل بشكل أفضل في بعض اللغات أكثر من غيرها.
عند تحديد منطقة الكتابة، حدد عرضها وارتفاعها في نفس الوحدات مثل إحداثيات الخط. لا توجد أي متطلبات للوسيطات الإحداثية x وy - حيث تعمل واجهة برمجة التطبيقات على تسوية جميع الوحدات، لذا فإن الشيء الوحيد المهم هو الحجم وموضع الحدود النسبي. لك مطلق الحرية في تمرير الإحداثيات بأي مقياس يناسب نظامك.
ما قبل السياق
ما قبل السياق هو النص الذي يسبق مباشرةً الضغطات في Ink
الذي
تحاول التعرّف عليه. يمكنك مساعدة أداة التعرّف من خلال إخبارها بالسياق السابق.
على سبيل المثال، غالبًا ما يتم الخلط بين الحرفين المتصلين "n" و "u" مع بعضهما البعض. إذا أدخل المستخدم بالفعل الكلمة الجزئية "arg"، فقد يستمر بالضغط على المفاتيح التي يمكن التعرف عليها كـ "ument" أو "nment". يؤدي تحديد "وسيطة" ما قبل السياق إلى حل الغموض، لأنّ استخدام كلمة "وسيطة" أكثر احتمالاً من كلمة "وسيطة".
يمكن أن يساعد التعرّف على السياق المسبق أيضًا أداة التعرّف على فواصل الكلمات، أي المسافات بين الكلمات. يمكنك كتابة مسافة ولكن لا يمكنك رسمه، فكيف يمكن لأداة التعرف تحديد وقت انتهاء الكلمة وبدء الكلمة التالية؟ إذا كتب المستخدم كلمة "hello" بالفعل واستمر في كتابة الكلمة "world"، دون وجود سياق، فإن أداة التعرف تعرض السلسلة "world" بدون سياق. ومع ذلك، إذا حددت "hello" لما قبل السياق، فسيعرض النموذج السلسلة " world"، مع مسافة بادئة، لأن "helloword" أكثر منطقية من "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
واستبدالها بكلمة أخرى. ربما تكون المراجعة في منتصف الجملة، لكن ضغطات المراجعة
تكون في نهاية تسلسل الخط.
في هذه الحالة، نوصي بإرسال الكلمة المكتوبة حديثًا بشكل منفصل إلى واجهة برمجة التطبيقات ودمج النتيجة مع عمليات التعرّف السابقة باستخدام منطقك الخاص.
التعامل مع الأشكال الغامضة
هناك حالات يكون فيها معنى الشكل المقدم إلى أداة التعرف غامضًا. على سبيل المثال، يمكن اعتبار مستطيل ذو حواف دائرية جدًا إما مستطيلاً أو قطعًا ناقصًا.
يمكن التعامل مع هذه الحالات غير الواضحة باستخدام درجات التقدير عندما تكون متاحة. مصنِّفات الأشكال فقط هي التي توفر الدرجات. إذا كان النموذج واثقًا جدًا من ذلك، ستكون نتيجة أعلى نتيجة
أفضل بكثير من ثاني أفضل نتيجة. إذا كان هناك عدم يقين، فستكون نتائج أعلى نتيجتين
متقاربة. ضع في اعتبارك أيضًا أن أدوات تصنيف الأشكال تفسّر Ink
بالكامل على أنه شكل واحد. على سبيل المثال، إذا كان Ink
يحتوي على مستطيل وقطع ناقص بجانب بعضهما،
قد تعرض أداة التعرّف أحدهما أو الآخر (أو شيئًا مختلفًا تمامًا) كنتيجة،
لأن المرشح الفردي لا يمكن أن يمثل شكلين.