التعرّف على الحبر الرقمي باستخدام أدوات تعلّم الآلة على نظام التشغيل Android

باستخدام ميزة التعرّف على الحبر الرقمي من ML Kit، يمكنك التعرّف على النص المكتوب بخط اليد على سطح رقمي بمئات اللغات، بالإضافة إلى تصنيف الرسومات.

جرّبه الآن

  • يمكنك تجربة نموذج التطبيق من أجل يمكنك الاطّلاع على مثال حول استخدام واجهة برمجة التطبيقات هذه.

قبل البدء

  1. في ملف build.gradle على مستوى المشروع، تأكَّد من تضمين مستودع Maven التابع لشركة Google في كلٍّ من القسمَين "buildscript" و"allprojects".
  2. أضِف الاعتماديات الخاصة بمكتبات 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" أو "اسم". تحديد "الوسيطة" التي تسبق السياق وتحل الغموض، نظرًا لأن كلمة "وسيطة" أكثر احتمالاً من "التأثر".

يمكن أن يساعد التعرّف على السياق المسبق أيضًا أداة التعرّف على فواصل الكلمات، أي المسافات بين الكلمات. يمكنك كتابة مسافة، ولكن لا يمكنك رسم حرف، فكيف يمكن لأداة التعرّف تحديد وقت انتهاء الكلمة وتبدأ المرحلة التالية؟ إذا كتب المستخدم "مرحبًا" بالفعل ويستمر بالكلمة المكتوبة "world"، بدون سياق ما قبل السياق، تعرض أداة التعرّف على السلسلة "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 يحتوي على مستطيل وقطع ناقص بجانب كل منهما. الآخر، فإن أداة التعرف قد تعرض أحدهما أو الآخر (أو شيئًا مختلفًا تمامًا) نظرًا لأن مرشحًا واحدًا للاعتراف لا يمكن أن يمثل شكلين.