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

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

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

শুরু করার আগে

  1. আপনার Podfile-এ নিম্নলিখিত ML Kit লাইব্রেরি যোগ করুন:

    pod 'GoogleMLKit/DigitalInkRecognition', '8.0.0'
    
    
  2. আপনার প্রোজেক্টের Pod ইনস্টল বা আপডেট করার পরে, এর .xcworkspace ব্যবহার করে আপনার Xcode প্রোজেক্ট খুলুন। ML Kit, Xcode-এর 13.2.1 বা এর পরের যেকোনও ভার্সনে কাজ করে।

আপনি এখন Ink অবজেক্টে টেক্সট শনাক্ত করা শুরু করার জন্য প্রস্তুত।

Ink অবজেক্ট তৈরি করুন

Ink অবজেক্ট তৈরি করার প্রধান উপায় হল টাচ স্ক্রিনে সেটি আঁকা। iOS-এ, আপনি UIImageView-এর সাথে টাচ ইভেন্ট হ্যান্ডলার ব্যবহার করতে পারেন, যা স্ক্রিনে স্ট্রোক আঁকে এবং Ink অবজেক্ট তৈরি করতে স্ট্রোকের পয়েন্টও স্টোর করে। এই সাধারণ প্যাটার্নটি নিম্নলিখিত কোড স্নিপেটে দেখানো হয়েছে। আরও সম্পূর্ণ উদাহরণের জন্য দ্রুত শুরু অ্যাপ দেখুন, যা টাচ ইভেন্ট হ্যান্ডেলিং, স্ক্রিন ড্রয়িং এবং স্ট্রোক ডেটা ম্যানেজমেন্টকে আলাদা করে।

Swift

@IBOutlet weak var mainImageView: UIImageView!
var kMillisecondsPerTimeInterval = 1000.0
var lastPoint = CGPoint.zero
private var strokes: [Stroke] = []
private var points: [StrokePoint] = []

func drawLine(from fromPoint: CGPoint, to toPoint: CGPoint) {
  UIGraphicsBeginImageContext(view.frame.size)
  guard let context = UIGraphicsGetCurrentContext() else {
    return
  }
  mainImageView.image?.draw(in: view.bounds)
  context.move(to: fromPoint)
  context.addLine(to: toPoint)
  context.setLineCap(.round)
  context.setBlendMode(.normal)
  context.setLineWidth(10.0)
  context.setStrokeColor(UIColor.white.cgColor)
  context.strokePath()
  mainImageView.image = UIGraphicsGetImageFromCurrentImageContext()
  mainImageView.alpha = 1.0
  UIGraphicsEndImageContext()
}

override func touchesBegan(_ touches: Set, with event: UIEvent?) {
  guard let touch = touches.first else {
    return
  }
  lastPoint = touch.location(in: mainImageView)
  let t = touch.timestamp
  points = [StrokePoint.init(x: Float(lastPoint.x),
                             y: Float(lastPoint.y),
                             t: Int(t * kMillisecondsPerTimeInterval))]
  drawLine(from:lastPoint, to:lastPoint)
}

override func touchesMoved(_ touches: Set, with event: UIEvent?) {
  guard let touch = touches.first else {
    return
  }
  let currentPoint = touch.location(in: mainImageView)
  let t = touch.timestamp
  points.append(StrokePoint.init(x: Float(currentPoint.x),
                                 y: Float(currentPoint.y),
                                 t: Int(t * kMillisecondsPerTimeInterval)))
  drawLine(from: lastPoint, to: currentPoint)
  lastPoint = currentPoint
}

override func touchesEnded(_ touches: Set, with event: UIEvent?) {
  guard let touch = touches.first else {
    return
  }
  let currentPoint = touch.location(in: mainImageView)
  let t = touch.timestamp
  points.append(StrokePoint.init(x: Float(currentPoint.x),
                                 y: Float(currentPoint.y),
                                 t: Int(t * kMillisecondsPerTimeInterval)))
  drawLine(from: lastPoint, to: currentPoint)
  lastPoint = currentPoint
  strokes.append(Stroke.init(points: points))
  self.points = []
  doRecognition()
}

Objective-C

// Interface
@property (weak, nonatomic) IBOutlet UIImageView *mainImageView;
@property(nonatomic) CGPoint lastPoint;
@property(nonatomic) NSMutableArray *strokes;
@property(nonatomic) NSMutableArray *points;

// Implementations
static const double kMillisecondsPerTimeInterval = 1000.0;

- (void)drawLineFrom:(CGPoint)fromPoint to:(CGPoint)toPoint {
  UIGraphicsBeginImageContext(self.mainImageView.frame.size);
  [self.mainImageView.image drawInRect:CGRectMake(0, 0, self.mainImageView.frame.size.width,
                                                  self.mainImageView.frame.size.height)];
  CGContextMoveToPoint(UIGraphicsGetCurrentContext(), fromPoint.x, fromPoint.y);
  CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), toPoint.x, toPoint.y);
  CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound);
  CGContextSetLineWidth(UIGraphicsGetCurrentContext(), 10.0);
  CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), 1, 1, 1, 1);
  CGContextSetBlendMode(UIGraphicsGetCurrentContext(), kCGBlendModeNormal);
  CGContextStrokePath(UIGraphicsGetCurrentContext());
  CGContextFlush(UIGraphicsGetCurrentContext());
  self.mainImageView.image = UIGraphicsGetImageFromCurrentImageContext();
  UIGraphicsEndImageContext();
}

- (void)touchesBegan:(NSSet *)touches withEvent:(nullable UIEvent *)event {
  UITouch *touch = [touches anyObject];
  self.lastPoint = [touch locationInView:self.mainImageView];
  NSTimeInterval time = [touch timestamp];
  self.points = [NSMutableArray array];
  [self.points addObject:[[MLKStrokePoint alloc] initWithX:self.lastPoint.x
                                                         y:self.lastPoint.y
                                                         t:time * kMillisecondsPerTimeInterval]];
  [self drawLineFrom:self.lastPoint to:self.lastPoint];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(nullable UIEvent *)event {
  UITouch *touch = [touches anyObject];
  CGPoint currentPoint = [touch locationInView:self.mainImageView];
  NSTimeInterval time = [touch timestamp];
  [self.points addObject:[[MLKStrokePoint alloc] initWithX:currentPoint.x
                                                         y:currentPoint.y
                                                         t:time * kMillisecondsPerTimeInterval]];
  [self drawLineFrom:self.lastPoint to:currentPoint];
  self.lastPoint = currentPoint;
}

- (void)touchesEnded:(NSSet *)touches withEvent:(nullable UIEvent *)event {
  UITouch *touch = [touches anyObject];
  CGPoint currentPoint = [touch locationInView:self.mainImageView];
  NSTimeInterval time = [touch timestamp];
  [self.points addObject:[[MLKStrokePoint alloc] initWithX:currentPoint.x
                                                         y:currentPoint.y
                                                         t:time * kMillisecondsPerTimeInterval]];
  [self drawLineFrom:self.lastPoint to:currentPoint];
  self.lastPoint = currentPoint;
  if (self.strokes == nil) {
    self.strokes = [NSMutableArray array];
  }
  [self.strokes addObject:[[MLKStroke alloc] initWithPoints:self.points]];
  self.points = nil;
  [self doRecognition];
}

মনে রাখবেন যে কোড স্নিপেটে UIImageView-এ স্ট্রোক আঁকার জন্য একটি নমুনা ফাংশন অন্তর্ভুক্ত রয়েছে, যা আপনার অ্যাপ্লিকেশনের জন্য প্রয়োজন অনুযায়ী মানিয়ে নিতে হবে। লাইন সেগমেন্ট আঁকার সময় আমরা রাউন্ডক্যাপ ব্যবহার করার সাজেশন দিই, যাতে শূন্য দৈর্ঘ্যের সেগমেন্টকে ডট হিসেবে আঁকা যায় (ছোট হাতের i-এর উপর ডটের কথা ভাবুন)। প্রতিটি স্ট্রোক লেখা হয়ে গেলে doRecognition() ফাংশন কল করা হয় এবং নিচে এটি সম্পর্কে বলা হয়েছে।

DigitalInkRecognizer-এর ইনস্ট্যান্স পান

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

Swift

override func viewDidLoad() {
  super.viewDidLoad()
  let languageTag = "en-US"
  let identifier = DigitalInkRecognitionModelIdentifier(forLanguageTag: languageTag)
  if identifier == nil {
    // no model was found or the language tag couldn't be parsed, handle error.
  }
  let model = DigitalInkRecognitionModel.init(modelIdentifier: identifier!)
  let modelManager = ModelManager.modelManager()
  let conditions = ModelDownloadConditions.init(allowsCellularAccess: true,
                                         allowsBackgroundDownloading: true)
  modelManager.download(model, conditions: conditions)
  // Get a recognizer for the language
  let options: DigitalInkRecognizerOptions = DigitalInkRecognizerOptions.init(model: model)
  recognizer = DigitalInkRecognizer.digitalInkRecognizer(options: options)
}

Objective-C

- (void)viewDidLoad {
  [super viewDidLoad];
  NSString *languagetag = @"en-US";
  MLKDigitalInkRecognitionModelIdentifier *identifier =
      [MLKDigitalInkRecognitionModelIdentifier modelIdentifierForLanguageTag:languagetag];
  if (identifier == nil) {
    // no model was found or the language tag couldn't be parsed, handle error.
  }
  MLKDigitalInkRecognitionModel *model = [[MLKDigitalInkRecognitionModel alloc]
                                          initWithModelIdentifier:identifier];
  MLKModelManager *modelManager = [MLKModelManager modelManager];
  [modelManager downloadModel:model conditions:[[MLKModelDownloadConditions alloc]
                                                initWithAllowsCellularAccess:YES
                                                allowsBackgroundDownloading:YES]];
  MLKDigitalInkRecognizerOptions *options =
      [[MLKDigitalInkRecognizerOptions alloc] initWithModel:model];
  self.recognizer = [MLKDigitalInkRecognizer digitalInkRecognizerWithOptions:options];
}

কুইকস্টার্ট অ্যাপে অতিরিক্ত কোড থাকে যা দেখায় যে কীভাবে একই সময়ে একাধিক ডাউনলোড ম্যানেজ করতে হয় এবং সম্পূর্ণ হওয়ার বিজ্ঞপ্তি ম্যানেজ করে কীভাবে নির্ধারণ করতে হয় যে কোন ডাউনলোড সম্পূর্ণ হয়েছে।

Ink অবজেক্ট শনাক্ত করা

এরপর আমরা doRecognition() ফাংশনটি দেখব, যা সহজে বোঝার জন্য touchesEnded() থেকে কল করা হয়। অন্যান্য অ্যাপ্লিকেশনে, কেউ হয়তো টাইমআউটের পরে বা ব্যবহারকারী শনাক্তকরণ ট্রিগার করার জন্য বোতাম প্রেস করার পরে শনাক্তকরণ চালু করতে চাইতে পারেন।

Swift

func doRecognition() {
  let ink = Ink.init(strokes: strokes)
  recognizer.recognize(
    ink: ink,
    completion: {
      [unowned self]
      (result: DigitalInkRecognitionResult?, error: Error?) in
      var alertTitle = ""
      var alertText = ""
      if let result = result, let candidate = result.candidates.first {
        alertTitle = "I recognized this:"
        alertText = candidate.text
      } else {
        alertTitle = "I hit an error:"
        alertText = error!.localizedDescription
      }
      let alert = UIAlertController(title: alertTitle,
                                  message: alertText,
                           preferredStyle: UIAlertController.Style.alert)
      alert.addAction(UIAlertAction(title: "OK",
                                    style: UIAlertAction.Style.default,
                                  handler: nil))
      self.present(alert, animated: true, completion: nil)
    }
  )
}

Objective-C

- (void)doRecognition {
  MLKInk *ink = [[MLKInk alloc] initWithStrokes:self.strokes];
  __weak typeof(self) weakSelf = self;
  [self.recognizer
      recognizeInk:ink
        completion:^(MLKDigitalInkRecognitionResult *_Nullable result,
                     NSError *_Nullable error) {
    typeof(weakSelf) strongSelf = weakSelf;
    if (strongSelf == nil) {
      return;
    }
    NSString *alertTitle = nil;
    NSString *alertText = nil;
    if (result.candidates.count > 0) {
      alertTitle = @"I recognized this:";
      alertText = result.candidates[0].text;
    } else {
      alertTitle = @"I hit an error:";
      alertText = [error localizedDescription];
    }
    UIAlertController *alert =
        [UIAlertController alertControllerWithTitle:alertTitle
                                            message:alertText
                                     preferredStyle:UIAlertControllerStyleAlert];
    [alert addAction:[UIAlertAction actionWithTitle:@"OK"
                                              style:UIAlertActionStyleDefault
                                            handler:nil]];
    [strongSelf presentViewController:alert animated:YES completion:nil];
  }];
}

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

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

কোনও মডেল আগে থেকেই ডাউনলোড করা আছে কিনা তা চেক করা

Swift

let model : DigitalInkRecognitionModel = ...
let modelManager = ModelManager.modelManager()
modelManager.isModelDownloaded(model)

Objective-C

MLKDigitalInkRecognitionModel *model = ...;
MLKModelManager *modelManager = [MLKModelManager modelManager];
[modelManager isModelDownloaded:model];

ডাউনলোড করা মডেল মুছে দেওয়া

Swift

let model : DigitalInkRecognitionModel = ...
let modelManager = ModelManager.modelManager()

if modelManager.isModelDownloaded(model) {
  modelManager.deleteDownloadedModel(
    model!,
    completion: {
      error in
      if error != nil {
        // Handle error
        return
      }
      NSLog(@"Model deleted.");
    })
}

Objective-C

MLKDigitalInkRecognitionModel *model = ...;
MLKModelManager *modelManager = [MLKModelManager modelManager];

if ([self.modelManager isModelDownloaded:model]) {
  [self.modelManager deleteDownloadedModel:model
                                completion:^(NSError *_Nullable error) {
                                  if (error) {
                                    // Handle error.
                                    return;
                                  }
                                  NSLog(@"Model deleted.");
                                }];
}

টেক্সট শনাক্তকরণের নির্ভুলতা উন্নত করার ব্যাপারে পরামর্শ

বিভিন্ন ভাষায় টেক্সট শনাক্তকরণের নির্ভুলতা আলাদা আলাদা হতে পারে। এছাড়াও, লেখার স্টাইলের উপর নির্ভর করে নির্ভুলতা নির্ধারণ করা হয়। ডিজিটাল ইনক স্বীকৃতিকে বিভিন্ন ধরনের লেখার স্টাইল হ্যান্ডেল করার জন্য প্রশিক্ষণ দেওয়া হলেও, ফলাফল ব্যবহারকারী ভেদে আলাদা হতে পারে।

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

লেখার জায়গা

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

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

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

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

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

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

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

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

নিচের কোড স্যাম্পেল থেকে কীভাবে লেখার জায়গা নির্ধারণ করতে হয় এবং পূর্ববর্তী প্রসঙ্গ নির্দিষ্ট করতে RecognitionContext অবজেক্ট ব্যবহার করতে হয় তা জানতে পারবেন।

Swift

let ink: Ink = ...;
let recognizer: DigitalInkRecognizer =  ...;
let preContext: String = ...;
let writingArea = WritingArea.init(width: ..., height: ...);

let context: DigitalInkRecognitionContext.init(
    preContext: preContext,
    writingArea: writingArea);

recognizer.recognizeHandwriting(
  from: ink,
  context: context,
  completion: {
    (result: DigitalInkRecognitionResult?, error: Error?) in
    if let result = result, let candidate = result.candidates.first {
      NSLog("Recognized \(candidate.text)")
    } else {
      NSLog("Recognition error \(error)")
    }
  })

Objective-C

MLKInk *ink = ...;
MLKDigitalInkRecognizer *recognizer = ...;
NSString *preContext = ...;
MLKWritingArea *writingArea = [MLKWritingArea initWithWidth:...
                                              height:...];

MLKDigitalInkRecognitionContext *context = [MLKDigitalInkRecognitionContext
       initWithPreContext:preContext
       writingArea:writingArea];

[recognizer recognizeHandwritingFromInk:ink
            context:context
            completion:^(MLKDigitalInkRecognitionResult
                         *_Nullable result, NSError *_Nullable error) {
                               NSLog(@"Recognition result %@",
                                     result.candidates[0].text);
                         }];

স্ট্রোকের ক্রম

স্ট্রোকের ক্রমের উপর স্বীকৃতি সংক্রান্ত নির্ভুলতা নির্ভর করে। স্বীকৃতিকারীরা আশা করেন যে স্ট্রোকগুলি এমন ক্রমে ঘটবে যা লোকেরা স্বাভাবিকভাবে লিখবে; যেমন, ইংরেজির জন্য বাঁদিক থেকে ডানদিকে। এই প্যাটার্ন অনুসরণ না করে অন্য কোনওভাবে লিখলে, যেমন শেষ শব্দ দিয়ে শুরু করে কোনও ইংরেজি বাক্য লিখলে, ফলাফল কম নির্ভুল হয়।

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

অস্পষ্ট শেপ নিয়ে কাজ করা

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

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