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