এমএল কিট-এর ডিজিটাল কালি শনাক্তকরণের সাহায্যে, আপনি শত শত ভাষায় ডিজিটাল পৃষ্ঠে হাতে লেখা পাঠ্য শনাক্ত করতে এবং স্কেচ শ্রেণীবদ্ধ করতে পারবেন।
চেষ্টা করে দেখুন
- এই API-টির একটি উদাহরণমূলক ব্যবহার দেখতে নমুনা অ্যাপটি ব্যবহার করে দেখুন।
শুরু করার আগে
- আপনার প্রোজেক্ট-স্তরের
build.gradleফাইলে,buildscriptএবংallprojectsউভয় সেকশনেই গুগলের মেভেন রিপোজিটরি অন্তর্ভুক্ত করা নিশ্চিত করুন। - আপনার মডিউলের অ্যাপ-লেভেল গ্রেডল ফাইলে (যা সাধারণত
app/build.gradleহয়) এমএল কিট অ্যান্ড্রয়েড লাইব্রেরির ডিপেন্ডেন্সিগুলো যোগ করুন:
dependencies {
// ...
implementation 'com.google.mlkit:digital-ink-recognition:19.0.0'
}
আপনি এখন Ink অবজেক্টগুলিতে টেক্সট শনাক্ত করা শুরু করতে প্রস্তুত।
একটি Ink অবজেক্ট তৈরি করুন
একটি Ink অবজেক্ট তৈরি করার প্রধান উপায় হলো টাচ স্ক্রিনে এটি আঁকা। অ্যান্ড্রয়েডে, আপনি এই কাজের জন্য একটি ক্যানভাস ব্যবহার করতে পারেন। ব্যবহারকারীর আঁকা স্ট্রোকের পয়েন্টগুলো Ink অবজেক্টে সংরক্ষণ করার জন্য, আপনার টাচ ইভেন্ট হ্যান্ডলারগুলোতে নিচের কোড স্নিপেটে দেখানো addNewTouchEvent() মেথডটি কল করা উচিত।
এই সাধারণ প্যাটার্নটি নিম্নলিখিত কোড স্নিপেটে দেখানো হয়েছে। আরও সম্পূর্ণ উদাহরণের জন্য এমএল কিট কুইকস্টার্ট স্যাম্পলটি দেখুন।
কোটলিন
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));
উপরের নমুনা কোডটি ধরে নেয় যে, পরবর্তী বিভাগে বর্ণিত পদ্ধতি অনুযায়ী রিকগনিশন মডেলটি ইতিমধ্যেই ডাউনলোড করা হয়েছে।
মডেল ডাউনলোড পরিচালনা করা
যদিও ডিজিটাল ইঙ্ক রিকগনিশন এপিআই শত শত ভাষা সমর্থন করে, যেকোনো শনাক্তকরণের আগে প্রতিটি ভাষার জন্য কিছু ডেটা ডাউনলোড করার প্রয়োজন হয়। প্রতি ভাষার জন্য প্রায় ২০ মেগাবাইট স্টোরেজ প্রয়োজন। এই কাজটি 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 স্থানাঙ্ক আর্গুমেন্টগুলোর কোনো এককের বাধ্যবাধকতা নেই — এপিআই সমস্ত একককে স্বাভাবিক করে, তাই একমাত্র গুরুত্বপূর্ণ বিষয় হলো স্ট্রোকগুলোর আপেক্ষিক আকার এবং অবস্থান। আপনার সিস্টেমের জন্য সুবিধাজনক যেকোনো স্কেলে আপনি স্থানাঙ্ক প্রদান করতে পারেন।
পূর্ব-প্রসঙ্গ
প্রি-কন্টেক্সট হলো সেই টেক্সট যা আপনি শনাক্ত করার চেষ্টা করছেন এমন Ink -এর স্ট্রোকগুলোর ঠিক আগে থাকে। আপনি প্রি-কন্টেক্সট সম্পর্কে জানিয়ে শনাক্তকারীকে সাহায্য করতে পারেন।
উদাহরণস্বরূপ, কারসিভ অক্ষর 'n' এবং 'u' প্রায়শই একটিকে অন্যটি বলে ভুল করা হয়। যদি ব্যবহারকারী ইতিমধ্যেই 'arg' শব্দটি আংশিকভাবে লিখে থাকেন, তবে তিনি এমনভাবে লিখতে পারেন যা 'ument' বা 'nment' হিসাবে চেনা যায়। এক্ষেত্রে, পূর্ব-প্রসঙ্গ 'arg' উল্লেখ করলে এই অস্পষ্টতা দূর হয়, কারণ সেক্ষেত্রে 'argnment'-এর চেয়ে 'argument' শব্দটি আসার সম্ভাবনা বেশি থাকে।
প্রি-কনটেক্সট রিকগনাইজারকে শব্দের মাঝের ফাঁকা স্থান বা ওয়ার্ড ব্রেক শনাক্ত করতেও সাহায্য করতে পারে। আপনি একটি স্পেস ক্যারেক্টার টাইপ করতে পারেন কিন্তু আঁকতে পারেন না, তাহলে একটি রিকগনাইজার কীভাবে নির্ধারণ করবে যে একটি শব্দ কখন শেষ হয় এবং পরেরটি শুরু হয়? যদি ব্যবহারকারী ইতিমধ্যেই "hello" লিখে থাকেন এবং এরপর "world" শব্দটি লেখেন, তাহলে প্রি-কনটেক্সট ছাড়া রিকগনাইজার "world" স্ট্রিংটি রিটার্ন করবে। কিন্তু, যদি আপনি প্রি-কনটেক্সট হিসেবে "hello" নির্দিষ্ট করে দেন, তাহলে মডেলটি শুরুতে একটি স্পেসসহ "world" স্ট্রিংটি রিটার্ন করবে, কারণ "helloword"-এর চেয়ে "hello world" বেশি অর্থবহ।
আপনাকে স্পেস সহ সর্বোচ্চ ২০ অক্ষরের সম্ভাব্য দীর্ঘতম প্রি-কন্টেক্সট স্ট্রিংটি প্রদান করতে হবে। স্ট্রিংটি এর চেয়ে দীর্ঘ হলে, রিকগনাইজারটি শুধুমাত্র শেষের ২০টি অক্ষর ব্যবহার করে।
নিচের কোড নমুনাটিতে দেখানো হয়েছে কীভাবে একটি লেখার ক্ষেত্র নির্ধারণ করতে হয় এবং প্রাক-প্রসঙ্গ নির্দিষ্ট করার জন্য একটি 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 মাঝখান থেকে একটি শব্দ মুছে ফেলে তার জায়গায় অন্য একটি শব্দ বসানো হয়। সংশোধনটি সম্ভবত একটি বাক্যের মাঝখানে করা হয়েছে, কিন্তু সংশোধনের জন্য স্ট্রোকগুলো স্ট্রোক সিকোয়েন্সের শেষে রয়েছে। এই ক্ষেত্রে আমরা পরামর্শ দিই যে, নতুন লেখা শব্দটি আলাদাভাবে এপিআই-তে পাঠান এবং আপনার নিজস্ব লজিক ব্যবহার করে পূর্ববর্তী শনাক্তকরণগুলোর সাথে ফলাফলটি মার্জ করুন।
অস্পষ্ট আকার নিয়ে কাজ করা
এমন কিছু ক্ষেত্র আছে যেখানে শনাক্তকারীকে দেওয়া আকৃতির অর্থ অস্পষ্ট থাকে। উদাহরণস্বরূপ, খুব গোলাকার প্রান্তযুক্ত একটি আয়তক্ষেত্রকে আয়তক্ষেত্র বা উপবৃত্ত উভয় হিসাবেই দেখা যেতে পারে।
এই অস্পষ্ট পরিস্থিতিগুলো রিকগনিশন স্কোর ব্যবহার করে সমাধান করা যায়, যদি তা পাওয়া যায়। শুধুমাত্র শেপ ক্লাসিফায়ারগুলোই স্কোর প্রদান করে। যদি মডেলটি খুব আত্মবিশ্বাসী হয়, তবে সেরা ফলাফলের স্কোর দ্বিতীয় সেরার চেয়ে অনেক ভালো হবে। যদি অনিশ্চয়তা থাকে, তবে সেরা দুটি ফলাফলের স্কোর কাছাকাছি হবে। এছাড়াও, মনে রাখতে হবে যে শেপ ক্লাসিফায়ারগুলো পুরো Ink একটি একক আকৃতি হিসেবে ব্যাখ্যা করে। উদাহরণস্বরূপ, যদি Ink পাশাপাশি একটি আয়তক্ষেত্র এবং একটি উপবৃত্ত থাকে, তবে রিকগনাইজারটি ফলাফল হিসেবে দুটির মধ্যে যেকোনো একটি (বা সম্পূর্ণ ভিন্ন কিছু) ফেরত দিতে পারে, কারণ একটি একক রিকগনিশন ক্যান্ডিডেট দুটি আকৃতিকে উপস্থাপন করতে পারে না।