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