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

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

امتحانش کن.

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

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

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

ساخت یک شیء Ink

راه اصلی برای ساخت یک شیء Ink ، ترسیم آن روی صفحه لمسی است. در اندروید، می‌توانید از Canvas برای این منظور استفاده کنید. کنترل‌کننده‌های رویداد لمسی شما باید متد addNewTouchEvent() را که در قطعه کد زیر نشان داده شده است، فراخوانی کنند تا نقاط موجود در خطوطی که کاربر روی شیء 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" را مشخص کنید، مدل رشته "world" را با یک فاصله در ابتدا برمی‌گرداند، زیرا "hello world" از "helloword" معنی بیشتری دارد.

شما باید طولانی‌ترین رشته‌ی پیش‌زمینه‌ی ممکن، حداکثر تا ۲۰ کاراکتر، شامل فاصله‌ها، را ارائه دهید. اگر رشته طولانی‌تر باشد، تشخیص‌دهنده فقط از ۲۰ کاراکتر آخر استفاده می‌کند.

نمونه کد زیر نحوه تعریف یک ناحیه نوشتاری و استفاده از شیء RecognitionContext برای مشخص کردن پیش‌زمینه (pre-context) را نشان می‌دهد.

کاتلین

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 شامل یک مستطیل و یک بیضی در کنار یکدیگر باشد، تشخیص‌دهنده ممکن است یکی از آنها (یا چیزی کاملاً متفاوت) را به عنوان نتیجه برگرداند، زیرا یک نامزد تشخیص واحد نمی‌تواند دو شکل را نشان دهد.