Android-এ ML Kit-এর মাধ্যমে ডিজিটাল ইঙ্ক শনাক্ত করা

ML Kit-এর ডিজিটাল ইঙ্ক শনাক্তকরণ ফিচারের সাহায্যে, আপনি শত শত ভাষায় ডিজিটাল সারফেসে হাতে লেখা টেক্সট শনাক্ত করতে পারবেন, পাশাপাশি স্কেচও শ্রেণীবদ্ধ করতে পারবেন।

এটি ব্যবহার করে দেখুন

শুরু করার আগে

  1. আপনার প্রোজেক্ট-লেভেল build.gradle ফাইলে, আপনার buildscript এবং allprojects উভয় বিভাগেই Google-এর Maven রিপোজিটরি অন্তর্ভুক্ত করতে ভুলবেন না।
  2. আপনার মডিউলের অ্যাপ-লেভেল Gradle ফাইলে ML Kit Android লাইব্রেরির জন্য ডিপেন্ডেন্সি যোগ করুন, যা সাধারণত 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 ট্যাগ থেকে কীভাবে এই ধরনের রেcognizer ইনস্ট্যানশিয়েট করতে হয় তা নিচে দেওয়া কোড থেকে জানতে পারবেন।

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

উপরের স্যাম্পেল কোড থেকে ধরে নেওয়া যায় যে শনাক্তকরণ মডেলটি ইতিমধ্যেই ডাউনলোড করা হয়েছে, যেমনটি পরবর্তী বিভাগে বর্ণনা করা হয়েছে।

মডেল ডাউনলোড ম্যানেজ করা

ডিজিটাল ইনক শনাক্তকরণ API শত শত ভাষায় কাজ করলেও, প্রতিটি ভাষার ক্ষেত্রে শনাক্তকরণের আগে কিছু ডেটা ডাউনলোড করতে হয়। প্রতিটি ভাষার জন্য প্রায় ২০ এমবি স্টোরেজ প্রয়োজন। এটি 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" এবং কমা বনাম ফরওয়ার্ড স্ল্যাশের মধ্যে পার্থক্য।

লেখার জায়গার প্রস্থ ও উচ্চতা শনাক্তকারীকে জানালে নির্ভুলতা উন্নত হতে পারে। তবে, রেcognizer ধরে নেয় যে লেখার জায়গায় শুধু একটি লাইন টেক্সট আছে। যদি ফিজিক্যাল রাইটিং এরিয়াটি এতটাই বড় হয় যে ব্যবহারকারী দুটি বা তার বেশি লাইন লিখতে পারেন, তাহলে আপনি এমন একটি WritingArea পাস করে আরও ভাল ফলাফল পেতে পারেন, যার উচ্চতা হল টেক্সটের একটি লাইনের উচ্চতার আপনার সেরা অনুমান। রিকগনাইজারকে আপনি যে WritingArea অবজেক্ট পাস করেন, সেটি স্ক্রিনে থাকা ফিজিক্যাল লেখার জায়গার সাথে হুবহু মিলতে হবে না। এইভাবে WritingArea-এর উচ্চতা পরিবর্তন করলে কিছু ভাষায় অন্যদের তুলনায় ভালো কাজ করে।

আপনি লেখার জায়গা নির্দিষ্ট করার সময়, স্ট্রোক কোঅর্ডিনেটের মতো একই ইউনিটে এর প্রস্থ ও উচ্চতা নির্দিষ্ট করুন। x,y কোঅর্ডিনেট আর্গুমেন্টের জন্য কোনও ইউনিটের প্রয়োজন হয় না - API সব ইউনিটকে নর্মালাইজ করে, তাই স্ট্রোকের আপেক্ষিক সাইজ ও পজিশনই শুধু গুরুত্বপূর্ণ। আপনার সিস্টেমের জন্য উপযুক্ত স্কেলে আপনি স্থানাঙ্ক পাস করতে পারেন।

প্রি-কনটেক্সট

প্রি-কনটেক্সট হল সেই টেক্সট যা Ink-এর মধ্যে স্ট্রোকের ঠিক আগে থাকে এবং আপনি সেটি শনাক্ত করার চেষ্টা করছেন। আপনি রিকগনাইজারকে প্রি-কনটেক্সট সম্পর্কে জানিয়ে সাহায্য করতে পারেন।

যেমন, "n" ও "u" অক্ষর দু'টি প্রায়শই একে অপরের সাথে গুলিয়ে ফেলা হয়। ব্যবহারকারী যদি আগে থেকেই "arg" আংশিক শব্দটি লিখে থাকেন, তাহলে তিনি এমন স্ট্রোক দিয়ে লেখা চালিয়ে যেতে পারেন যা "ument" বা "nment" হিসেবে শনাক্ত করা যেতে পারে। "arg" প্রি-কনটেক্সট উল্লেখ করলে অস্পষ্টতা দূর হয়, কারণ "argnment"-এর চেয়ে "argument" শব্দটি হওয়ার সম্ভাবনা বেশি।

এছাড়াও, প্রি-কনটেক্সট, শব্দ বিরতি, শব্দের মধ্যে স্পেস শনাক্ত করতেও সাহায্য করতে পারে। আপনি স্পেস ক্যারেক্টার টাইপ করতে পারেন, কিন্তু সেটি আঁকতে পারবেন না, তাহলে কীভাবে কোনও শব্দ কখন শেষ হচ্ছে এবং পরেরটি কখন শুরু হচ্ছে তা কোনও শনাক্তকারী নির্ধারণ করবে? ব্যবহারকারী যদি আগে থেকেই "হ্যালো" লিখে থাকেন এবং লিখিত শব্দ "ওয়ার্ল্ড" দিয়ে চালিয়ে যান, তাহলে পূর্ববর্তী প্রসঙ্গ ছাড়াই শনাক্তকারী "ওয়ার্ল্ড" স্ট্রিং রিটার্ন করে। তবে, আপনি যদি প্রি-কন্টেক্সট "হ্যালো" নির্দিষ্ট করেন, তাহলে মডেলটি " ওয়ার্ল্ড" স্ট্রিং রিটার্ন করবে, যার আগে একটি স্পেস থাকবে, কারণ "হ্যালো ওয়ার্ল্ড" কথাটি "হ্যালোওয়ার্ল্ড"-এর চেয়ে বেশি অর্থপূর্ণ।

আপনাকে সবচেয়ে বড় সম্ভাব্য প্রি-কনটেক্সট স্ট্রিং দিতে হবে, ২০টি অক্ষর পর্যন্ত, যার মধ্যে স্পেস অন্তর্ভুক্ত। স্ট্রিংটি আরও বড় হলে, শনাক্তকারী শুধুমাত্র শেষ ২০টি অক্ষর ব্যবহার করে।

নিচের কোড স্যাম্পেল থেকে কীভাবে লেখার জায়গা নির্ধারণ করতে হয় এবং পূর্ববর্তী প্রসঙ্গ নির্দিষ্ট করতে 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-এর মাঝখান থেকে একটি শব্দ সরিয়ে অন্য একটি শব্দ দিয়ে প্রতিস্থাপন করা। সংশোধনটি সম্ভবত কোনও বাক্যের মাঝে করা হয়েছে, কিন্তু সংশোধনের স্ট্রোকগুলি স্ট্রোক সিকোয়েন্সের শেষে রয়েছে। এই ক্ষেত্রে, আমরা সাজেস্ট করি যে নতুন করে লেখা শব্দটি API-তে আলাদাভাবে পাঠান এবং আপনার নিজস্ব যুক্তি ব্যবহার করে আগের স্বীকৃতি সহ ফলাফল মার্জ করুন।

অস্পষ্ট আকৃতি নিয়ে কাজ করা

এমন কিছু পরিস্থিতি আছে যেখানে রিকগনাইজারকে প্রদান করা আকারের অর্থ অস্পষ্ট। যেমন, খুব গোলাকার কোণযুক্ত একটি আয়তক্ষেত্রকে আয়তক্ষেত্র বা উপবৃত্ত হিসেবে দেখা যেতে পারে।

উপলভ্য হলে, শনাক্তকরণ স্কোর ব্যবহার করে এই ধরনের অস্পষ্ট কেসগুলি ম্যানেজ করা যেতে পারে। শুধুমাত্র শেপ ক্লাসিফায়ার স্কোর প্রদান করে। মডেলটি খুব আত্মবিশ্বাসী হলে, সেরা ফলাফলের স্কোর দ্বিতীয় সেরা ফলাফলের চেয়ে অনেক ভালো হবে। অনিশ্চয়তা থাকলে, সেরা দুটি ফলাফলের স্কোর কাছাকাছি হবে। এছাড়াও, মনে রাখবেন যে শেপ ক্লাসিফায়ার সম্পূর্ণ Ink-কে একটি আকার হিসেবে ব্যাখ্যা করে। যেমন, Ink-এ যদি একটি আয়তক্ষেত্র ও তার ঠিক পাশে একটি উপবৃত্ত থাকে, তাহলে শনাক্তকারী ফলাফল হিসেবে একটি বা অন্যটি (অথবা সম্পূর্ণ আলাদা কিছু) রিটার্ন করতে পারে, কারণ একটি শনাক্তকরণ ক্যান্ডিডেট দুটি শেপকে প্রতিনিধিত্ব করতে পারে না।