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-এ, আপনি এই উদ্দেশ্যে Canvas ব্যবহার করতে পারেন। আপনার টাচ ইভেন্ট হ্যান্ডলারের 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));

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

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

ডিজিটাল ইঙ্ক রিকগনিশন 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" এবং কমা বনাম ফরওয়ার্ড স্ল্যাশের মধ্যে পার্থক্য।

লেখার জায়গার প্রস্থ ও উচ্চতা শনাক্তকারীকে জানালে নির্ভুলতা উন্নত হতে পারে। তবে, রিকগনাইজার ধরে নেয় যে লেখার জায়গায় শুধুমাত্র একটি লাইনের টেক্সট আছে। ব্যবহারকারী যাতে দুই বা তার বেশি লাইন লিখতে পারেন তার জন্য লেখার জায়গাটি যথেষ্ট বড় হলে, আপনি WritingArea-তে একটি লাইনের উচ্চতা অনুমান করে সেই উচ্চতা পাস করলে আরও ভালো ফলাফল পেতে পারেন। The WritingArea object you pass to the recognizer does not have to correspond exactly with the physical writing area on the screen. এইভাবে WritingArea-এর উচ্চতা পরিবর্তন করলে কিছু ভাষায় অন্যদের চেয়ে ভালো কাজ করে।

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

প্রাক-প্রসঙ্গ

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

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

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

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

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