iOS पर एमएल किट की मदद से डिजिटल इंक की पहचान करना

ML Kit की डिजिटल इंक पहचानने की सुविधा से, हाथ से लिखे टेक्स्ट को और स्केच को उन भाषाओं में इस्तेमाल किया जा सकता है.

इसे आज़माएं

शुरू करने से पहले

  1. अपनी Podfile में, नीचे दी गई ML Kit लाइब्रेरी शामिल करें:

    pod 'GoogleMLKit/DigitalInkRecognition', '3.2.0'
    
    
  2. अपने प्रोजेक्ट के Pods को इंस्टॉल या अपडेट करने के बाद, अपना Xcode प्रोजेक्ट खोलें इसके .xcworkspace का इस्तेमाल कर रहा है. ML किट 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 इंस्टेंस पाने के लिए, हमें सबसे पहले अपनी पसंद की भाषा के लिए, आइडेंटिफ़ायर मॉडल डाउनलोड करना होगा और मॉडल को रैम में लोड करें. इसके लिए, इस कोड का इस्तेमाल करें स्निपेट, जिसे आसानी के लिए 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", और कॉमा बनाम a फ़ॉरवर्ड स्लैश.

आइडेंटिफ़ायर को, लिखने की जगह की चौड़ाई और ऊंचाई के बारे में बताने से, सटीक नतीजे पाने में मदद मिल सकती है. हालांकि, आइडेंटिफ़ायर यह मानता है कि लिखने की जगह में टेक्स्ट की सिर्फ़ एक लाइन है. अगर फ़िज़िकल लिखने का क्षेत्र इतना बड़ा है कि उपयोगकर्ता दो या उससे ज़्यादा लाइनें लिख सकता है. इससे आपको बेहतर की लंबाई के साथ, उस लंबाई के साथ टेक्स्ट की एक लाइन. पहचानकर्ता को आपके पास किए जाने वाले WriteArea ऑब्जेक्ट का संगत होना आवश्यक नहीं है स्क्रीन पर मौजूद लिखने की जगह पर रखें. राइटिंग एरिया की लंबाई को इस तरह बदलना अन्य भाषाओं के मुकाबले कुछ भाषाओं में बेहतर काम करती है.

लिखने की जगह बताते समय, इसकी चौड़ाई और ऊंचाई वही बताएं जो स्ट्रोक की इकाइयां हैं निर्देशांक. x,y निर्देशांक आर्ग्युमेंट के लिए, इकाई की कोई ज़रूरत नहीं है - एपीआई सभी को नॉर्मलाइज़ करता है इकाइयां, इसलिए केवल एक ही चीज़ महत्वपूर्ण है, वह है स्ट्रोक का सापेक्ष आकार और स्थिति. आपके पास ये काम करने के विकल्प हैं निर्देशांकों को किसी भी स्केल पर पास करना आपके सिस्टम के लिए फ़ायदेमंद होता है.

प्री-कॉन्टेक्स्ट

प्री-कॉन्टेक्स्ट, वह टेक्स्ट है जो Ink में स्ट्रोक से ठीक पहले होता है को पहचानने की कोशिश कर रहे हैं. पहचान करने वाले को पहचान से पहले की प्रक्रिया के बारे में बताकर उसकी मदद की जा सकती है.

उदाहरण के लिए, कर्सिव अक्षर "n" और "u" अक्सर लोग एक-दूसरे को समझ लेते हैं. अगर उपयोगकर्ता के पास पहले से आंशिक शब्द "आर्ग" दर्ज किया है, तो वे स्ट्रोक के साथ जारी रख सकते हैं जिन्हें समझा जा सकता है "ument" या "nment" शामिल करें. प्री-कॉन्टेक्स्ट "आर्ग" तय करना समस्या को हल करता है, क्योंकि "तर्क" "तर्क" के बजाय सबसे ज़्यादा इस्तेमाल होता है.

प्री-कॉन्टेक्स्ट, पहचान करने वाले व्यक्ति को शब्दों के बीच के स्पेस यानी ब्रेक ब्रेक को पहचानने में भी मदद कर सकता है. आप स्पेस वर्ण टाइप करें, लेकिन आप उसे नहीं बना सकते, इसलिए पहचानकर्ता यह कैसे तय कर सकता है कि कोई शब्द कब खत्म होगा और अगला वाला शुरू हो जाता है? अगर उपयोगकर्ता ने पहले से ही "नमस्ते" लिखा है और पहले से लिखे शब्द के साथ आगे बढ़ते हैं "world", प्री-कॉन्टेक्स्ट के बिना, आइडेंटिफ़ायर "world" स्ट्रिंग दिखाता है. हालांकि, अगर आप "हैलो" से पहले, मॉडल स्ट्रिंग " "हैलो" के साथ एक प्रमुख स्थान के साथ, दुनिया" "हैलोवर्ड" से ज़्यादा समझ में आता है.

आपको कॉन्टेक्स्ट के पहले की सबसे लंबी स्ट्रिंग देनी चाहिए. इसमें ज़्यादा से ज़्यादा 20 वर्ण होने चाहिए. इनमें ये भी शामिल हैं स्पेसेज़. अगर स्ट्रिंग लंबी है, तो आइडेंटिफ़ायर सिर्फ़ आखिरी 20 वर्णों का इस्तेमाल करता है.

नीचे दिया गया कोड सैंपल, लिखने की जगह तय करने और प्री-कॉन्टेक्स्ट के बारे में बताने के लिए 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 में एक रेक्टैंगल और हर के बगल में एक इलिप्स है अन्य, पहचानकर्ता किसी एक या दूसरे (या कुछ बिलकुल अलग) को से लिया गया है, क्योंकि मान्यता प्राप्त करने वाला एक उम्मीदवार दो आकारों का प्रतिनिधित्व नहीं कर सकता है.