باستخدام ميزة "التعرّف على الكتابة بالحبر الرقمي" في حزمة تعلّم الآلة، يمكنك التعرّف على النص المكتوب بخط اليد على سطح رقمي بمئات اللغات، بالإضافة إلى تصنيف الرسومات.
للتجربة:
- يمكنك تجربة نموذج التطبيق للاطّلاع على مثال على استخدام واجهة برمجة التطبيقات هذه.
قبل البدء
- في ملف
build.gradleعلى مستوى المشروع، تأكَّد من تضمين مستودع Maven من Google في القسمَينbuildscriptوallprojects. - أضِف تبعيات مكتبات "حزمة تعلّم الآلة" على Android إلى ملف Gradle على مستوى التطبيق في الوحدة، والذي يكون عادةً
app/build.gradle:
dependencies {
// ...
implementation 'com.google.mlkit:digital-ink-recognition:19.0.0'
}
أنت الآن جاهز لبدء التعرّف على النص في عناصر Ink.
إنشاء عنصر Ink
الطريقة الرئيسية لإنشاء عنصر Ink هي رسمه على شاشة لمس. على نظام التشغيل Android، يمكنك استخدام Canvas لهذا الغرض. يجب أن تستدعي معالِجات أحداث اللمس الطريقة 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()
جافا
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())
جافا
// 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") }
جافا
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") }
جافا
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)
جافا
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));
نصائح لتحسين دقة التعرّف على النص
قد تختلف دقة التعرّف على النص باختلاف اللغات. تعتمد الدقة أيضًا على أسلوب الكتابة. على الرغم من أنّ واجهة Digital Ink Recognition مدرَّبة على التعامل مع العديد من أنواع أساليب الكتابة، يمكن أن تختلف النتائج من مستخدم لآخر.
في ما يلي بعض الطرق لتحسين دقة أداة التعرّف على النص. يُرجى العِلم أنّ هذه الأساليب لا تنطبق على مصنّفات الرسم الخاصة بالإيموجي والرسم التلقائي والأشكال.
مساحة الكتابة
تحتوي العديد من التطبيقات على منطقة كتابة محدّدة جيدًا لإدخال المستخدم. يتم تحديد معنى الرمز جزئيًا من خلال حجمه بالنسبة إلى حجم مساحة الكتابة التي تحتوي عليه. على سبيل المثال، الفرق بين الحرف الصغير أو الكبير "o" أو "c"، والفاصلة مقابل الشرطة المائلة للأمام.
يمكن أن يؤدي إخبار أداة التعرّف بعرض منطقة الكتابة وارتفاعها إلى تحسين الدقة. ومع ذلك، يفترض أداة التعرف أن منطقة الكتابة تحتوي فقط على سطر واحد من النص. إذا كانت مساحة الكتابة الفعلية كبيرة بما يكفي للسماح للمستخدم بكتابة سطرين أو أكثر، يمكنك الحصول على نتائج أفضل من خلال تمرير WritingArea بارتفاع يمثّل أفضل تقدير لارتفاع سطر واحد من النص. لا يشترط أن يتطابق عنصر WritingArea الذي يتم تمريره إلى أداة التعرّف تمامًا مع منطقة الكتابة الفعلية على الشاشة. يُعدّ تغيير ارتفاع WritingArea بهذه الطريقة أفضل في بعض اللغات مقارنةً بغيرها.
عند تحديد منطقة الكتابة، حدِّد عرضها وارتفاعها بالوحدات نفسها المستخدَمة في إحداثيات الخطوط. لا تتطلّب وسيطات الإحداثيات x وy وحدة معيّنة، لأنّ واجهة برمجة التطبيقات توحّد جميع الوحدات، وبالتالي فإنّ الشيء الوحيد المهم هو الحجم والموضع النسبيَّين للخطوط. يمكنك تمرير الإحداثيات بأي مقياس يناسب نظامك.
السياق السابق
السياق السابق هو النص الذي يسبق مباشرةً الخطوط في Ink التي
تحاول التعرّف عليها. يمكنك مساعدة أداة التعرّف من خلال إخبارها بالسياق المسبق.
على سبيل المثال، غالبًا ما يتم الخلط بين الحرفَين "n" و "u" المكتوبَين بخط متصل. إذا كان المستخدم قد أدخل الكلمة الجزئية "arg"، يمكنه المتابعة بضربات يمكن التعرّف عليها على أنّها "ument" أو "nment". يؤدي تحديد السياق المسبق "arg" إلى حلّ الالتباس، لأنّ كلمة "argument" أكثر احتمالاً من "argnment".
يمكن أن يساعد السياق المسبق أيضًا أداة التعرّف على الكلام في تحديد فواصل الكلمات، أي المسافات بين الكلمات. يمكنك كتابة مسافة ولكن لا يمكنك رسمها، فكيف يمكن لبرنامج التعرّف تحديد نهاية كلمة وبداية كلمة أخرى؟ إذا كتب المستخدم كلمة "مرحبًا" وتابع كتابة كلمة "بالجميع"، سيعرض أداة التعرّف السلسلة "بالجميع" بدون سياق مسبق. ومع ذلك، إذا حدّدت السياق المسبق "مرحبًا"، سيعرض النموذج السلسلة "بالجميع"، مع مسافة بادئة، لأنّ "مرحبًا بالجميع" أكثر منطقية من "مرحبًابالجميع".
يجب تقديم أطول سلسلة سياق مسبق ممكنة، بما يصل إلى 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)
جافا
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 يحتوي على مستطيل وشكل بيضاوي بجانب بعضهما البعض، فقد يعرض أداة التعرّف أحدهما أو الآخر (أو شيئًا مختلفًا تمامًا) نتيجة لذلك، نظرًا لأن مرشح التعرف الفردي لا يمكنه تمثيل شكلين.