تشخیص جوهر دیجیتال با کیت ML در اندروید

با تشخیص جوهر دیجیتال کیت ML، می‌توانید متن دست‌نویس روی سطح دیجیتال را به صدها زبان تشخیص دهید، و همچنین طرح‌ها را طبقه‌بندی کنید.

آن را امتحان کنید

قبل از اینکه شروع کنی

  1. در فایل build.gradle در سطح پروژه خود، مطمئن شوید که مخزن Maven Google را در هر دو بخش buildscript و allprojects خود قرار دهید.
  2. وابستگی های کتابخانه های اندروید ML Kit را به فایل Gradle سطح برنامه ماژول خود اضافه کنید، که معمولا app/build.gradle است:
dependencies {
  // ...
  implementation 'com.google.mlkit:digital-ink-recognition:18.1.0'
}

اکنون برای شروع به تشخیص متن در اشیاء Ink آماده هستید.

یک شی Ink بسازید

راه اصلی برای ساخت یک شی Ink این است که آن را روی صفحه لمسی بکشید. در اندروید می توانید از Canvas برای این منظور استفاده کنید. کنترل‌کننده‌های رویداد لمسی شما باید متد addNewTouchEvent() را که قطعه کد زیر نشان داده شده است فراخوانی کنند تا نقاط در strokes‌هایی که کاربر در شیء Ink می‌کشد ذخیره کنند.

این الگوی کلی در قطعه کد زیر نشان داده شده است. برای مثال کاملتر به نمونه راه اندازی سریع ML Kit مراجعه کنید.

کاتلین

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 نمونه برداری کرد.

کاتلین

// 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 را پردازش کنید

کاتلین

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));

کد نمونه بالا فرض می کند که مدل شناسایی قبلاً دانلود شده است، همانطور که در بخش بعدی توضیح داده شد.

مدیریت دانلودهای مدل

در حالی که API تشخیص جوهر دیجیتال از صدها زبان پشتیبانی می کند، هر زبانی نیاز دارد تا قبل از هر گونه شناسایی، مقداری داده دانلود شود. حدود 20 مگابایت فضای ذخیره سازی برای هر زبان مورد نیاز است. این توسط شی RemoteModelManager مدیریت می شود.

دانلود مدل جدید

کاتلین

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));

بررسی کنید که آیا یک مدل قبلا دانلود شده است یا خیر

کاتلین

var model: DigitalInkRecognitionModel =  ...
remoteModelManager.isModelDownloaded(model)

جاوا

DigitalInkRecognitionModel model = ...;
remoteModelManager.isModelDownloaded(model);

یک مدل دانلود شده را حذف کنید

با حذف یک مدل از حافظه دستگاه، فضا آزاد می شود.

کاتلین

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 نیازی به واحد ندارند - API همه واحدها را عادی می‌کند، بنابراین تنها چیزی که مهم است اندازه و موقعیت نسبی ضربه‌ها است. شما آزاد هستید که مختصات را در هر مقیاسی که برای سیستم شما منطقی است پاس کنید.

پیش زمینه

پیش زمینه متنی است که بلافاصله قبل از ضربه های موجود در Ink است که می خواهید تشخیص دهید. می توانید با گفتن پیش زمینه به تشخیص دهنده کمک کنید.

به عنوان مثال، حروف شکسته "n" و "u" اغلب با یکدیگر اشتباه گرفته می شوند. اگر کاربر قبلاً کلمه جزئی "arg" را وارد کرده باشد، ممکن است با سکته هایی که می توانند به عنوان "ument" یا "nment" تشخیص داده شوند، ادامه دهند. مشخص کردن پیش زمینه "arg" ابهام را برطرف می کند، زیرا احتمال کلمه "argument" بیشتر از "argnment" است.

پیش زمینه همچنین می‌تواند به تشخیص‌دهنده کمک کند تا شکستن کلمه، فاصله بین کلمات را شناسایی کند. شما می توانید یک کاراکتر فاصله تایپ کنید اما نمی توانید یکی را ترسیم کنید، بنابراین چگونه یک شناساگر می تواند تعیین کند که یک کلمه چه زمانی تمام می شود و کلمه بعدی شروع می شود؟ اگر کاربر قبلاً "hello" نوشته باشد و با کلمه نوشته شده "world" ادامه دهد، بدون پیش زمینه، شناساگر رشته "world" را برمی گرداند. با این حال، اگر پیش زمینه "hello" را مشخص کنید، مدل رشته "جهان" را با یک فاصله پیشرو برمی گرداند، زیرا "Hello world" بیشتر از "Helloword" معنی دارد.

شما باید طولانی ترین رشته پیش زمینه ممکن را، تا 20 کاراکتر، از جمله فاصله، ارائه دهید. اگر رشته طولانی تر باشد، شناساگر فقط از 20 کاراکتر آخر استفاده می کند.

نمونه کد زیر نحوه تعریف ناحیه نوشتن و استفاده از یک شی RecognitionContext برای تعیین پیش زمینه را نشان می دهد.

کاتلین

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 حذف می شود و با کلمه دیگری جایگزین می شود. بازبینی احتمالاً در وسط جمله است، اما سکته‌های مربوط به تجدیدنظر در انتهای دنباله سکته مغزی قرار دارند. در این مورد توصیه می کنیم کلمه جدید نوشته شده را به طور جداگانه به API ارسال کنید و با استفاده از منطق خود نتیجه را با شناسایی های قبلی ادغام کنید.

برخورد با اشکال مبهم

مواردی وجود دارد که معنای شکل ارائه شده به تشخیص دهنده مبهم است. برای مثال، یک مستطیل با لبه های بسیار گرد می تواند به صورت مستطیل یا بیضی دیده شود.

این موارد نامشخص را می توان با استفاده از نمرات تشخیص زمانی که در دسترس هستند، رسیدگی کرد. فقط طبقه‌بندی‌کننده‌های شکل امتیاز ارائه می‌کنند. اگر مدل بسیار مطمئن باشد، امتیاز نتیجه برتر بسیار بهتر از دومین بهترین خواهد بود. در صورت عدم قطعیت، امتیازات دو نتیجه برتر نزدیک به هم خواهد بود. همچنین، به خاطر داشته باشید که طبقه‌بندی‌کننده‌های شکل، کل Ink به صورت یک شکل تفسیر می‌کنند. به عنوان مثال، اگر Ink حاوی یک مستطیل و یک بیضی در کنار یکدیگر باشد، شناسایی کننده ممکن است یکی یا دیگری (یا چیزی کاملاً متفاوت) را در نتیجه برگرداند، زیرا یک نامزد تشخیص واحد نمی تواند دو شکل را نشان دهد.