ML Kit में डिजिटल इंक को पहचानने की सुविधा उपलब्ध है. इसकी मदद से, डिजिटल प्लैटफ़ॉर्म पर हाथ से लिखे गए टेक्स्ट को सैकड़ों भाषाओं में पहचाना जा सकता है. साथ ही, स्केच को कैटगरी में बांटा जा सकता है.
इसे आज़माएं
- इस एपीआई के इस्तेमाल का उदाहरण देखने के लिए, सैंपल ऐप्लिकेशन का इस्तेमाल करें.
शुरू करने से पहले
अपने Podfile में, ML Kit की ये लाइब्रेरी शामिल करें:
pod 'GoogleMLKit/DigitalInkRecognition', '8.0.0'अपने प्रोजेक्ट के पॉड इंस्टॉल या अपडेट करने के बाद, Xcode प्रोजेक्ट को
.xcworkspaceका इस्तेमाल करके खोलें. 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 इंस्टेंस पाने के लिए, हमें सबसे पहले अपनी पसंद की भाषा के लिए, पहचानने वाला मॉडल डाउनलोड करना होगा. इसके बाद, मॉडल को रैम में लोड करना होगा. इसके लिए, यहां दिया गया कोड स्निपेट इस्तेमाल किया जा सकता है. इसे आसानी से समझने के लिए, 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" के बीच का अंतर और कॉमा बनाम फ़ॉरवर्ड स्लैश.
लिखने की जगह की चौड़ाई और ऊंचाई की जानकारी देने से, पहचान करने वाले टूल को बेहतर तरीके से काम करने में मदद मिलती है. हालांकि, पहचान करने वाला टूल यह मानता है कि लिखने की जगह में सिर्फ़ एक लाइन का टेक्स्ट मौजूद है. अगर लिखने की जगह इतनी बड़ी है कि उपयोगकर्ता दो या उससे ज़्यादा लाइनें लिख सकता है, तो आपको WritingArea को पास करके बेहतर नतीजे मिल सकते हैं. इसकी ऊंचाई, टेक्स्ट की एक लाइन की ऊंचाई का सबसे अच्छा अनुमान होना चाहिए. आपने पहचान करने वाले टूल को जो WritingArea ऑब्जेक्ट पास किया है वह स्क्रीन पर मौजूद फ़िज़िकल राइटिंग एरिया से पूरी तरह मेल नहीं खाना चाहिए. इस तरह से WritingArea की ऊंचाई बदलने की सुविधा, कुछ भाषाओं में अन्य भाषाओं के मुकाबले बेहतर तरीके से काम करती है.
लिखने की जगह तय करते समय, उसकी चौड़ाई और ऊंचाई को स्ट्रोक के कोऑर्डिनेट वाली इकाइयों में ही तय करें. x,y कोऑर्डिनेट आर्ग्युमेंट के लिए किसी यूनिट की ज़रूरत नहीं होती - एपीआई सभी यूनिट को सामान्य करता है, इसलिए सिर्फ़ स्ट्रोक के साइज़ और पोज़िशन से फ़र्क़ पड़ता है. आपके पास अपने सिस्टम के हिसाब से, किसी भी स्केल में कोऑर्डिनेट पास करने का विकल्प होता है.
प्री-कॉन्टेक्स्ट
प्री-कॉन्टेक्स्ट, Ink में मौजूद स्ट्रोक से ठीक पहले का टेक्स्ट होता है. इसकी मदद से, स्ट्रोक की पहचान की जाती है. प्री-कॉन्टेक्स्ट के बारे में बताकर, पहचान करने वाले सिस्टम की मदद की जा सकती है.
उदाहरण के लिए, अक्सर "n" और "u" को एक जैसा मान लिया जाता है. अगर उपयोगकर्ता ने "arg" शब्द का कुछ हिस्सा पहले ही डाल दिया है, तो वह ऐसे स्ट्रोक का इस्तेमाल कर सकता है जिन्हें "ument" या "nment" के तौर पर पहचाना जा सकता है. प्री-कॉन्टेक्स्ट "arg" तय करने से, अस्पष्टता दूर हो जाती है. ऐसा इसलिए, क्योंकि "argnment" के मुकाबले "argument" शब्द के होने की संभावना ज़्यादा है.
प्री-कॉन्टेक्स्ट से, शब्दों के बीच में छोड़ी गई खाली जगह और शब्दों को अलग करने में भी मदद मिलती है. आपके पास स्पेस का निशान टाइप करने का विकल्प होता है, लेकिन इसे बनाया नहीं जा सकता. ऐसे में, यह पहचानने वाला सॉफ़्टवेयर कैसे तय करेगा कि कोई शब्द कब खत्म होता है और अगला शब्द कब शुरू होता है? अगर उपयोगकर्ता ने पहले ही "hello" लिख दिया है और वह "world" लिखता है, तो रिकॉग्नाइज़र, पहले के कॉन्टेक्स्ट के बिना "world" स्ट्रिंग दिखाता है. हालांकि, अगर आपने प्री-कॉन्टेक्स्ट "hello" तय किया है, तो मॉडल " world" स्ट्रिंग दिखाएगा. इसमें शुरुआत में स्पेस होगा, क्योंकि "helloword" के मुकाबले "hello 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 के बीच में मौजूद किसी शब्द को हटाकर, उसकी जगह कोई दूसरा शब्द जोड़ दिया जाता है. बदलाव शायद वाक्य के बीच में किया गया है, लेकिन बदलाव के लिए स्ट्रोक, स्ट्रोक के क्रम के आखिर में हैं.
ऐसे में, हमारा सुझाव है कि नए शब्द को एपीआई को अलग से भेजें. साथ ही, अपने लॉजिक का इस्तेमाल करके, नतीजे को पहले से पहचाने गए शब्दों के साथ मर्ज करें.
अस्पष्ट आकृतियों से निपटना
कुछ मामलों में, पहचान करने वाले सिस्टम को दी गई शेप का मतलब साफ़ तौर पर समझ नहीं आता. उदाहरण के लिए, बहुत ज़्यादा गोल किनारों वाले आयत को आयत या दीर्घवृत्त के तौर पर देखा जा सकता है.
जब पहचान के स्कोर उपलब्ध हों, तब इन मामलों को हैंडल किया जा सकता है. सिर्फ़ शेप क्लासिफ़ायर स्कोर देते हैं. अगर मॉडल को जवाब के सही होने का पूरा भरोसा है, तो सबसे ऊपर दिखने वाले जवाब का स्कोर, दूसरे सबसे अच्छे जवाब के स्कोर से बहुत ज़्यादा होगा. अगर कोई नतीजा पक्का नहीं है, तो सबसे ऊपर दिखने वाले दो नतीजों के स्कोर एक-दूसरे के आस-पास होंगे. यह भी ध्यान रखें कि शेप क्लासिफ़ायर, पूरे Ink को एक ही शेप के तौर पर पहचानते हैं. उदाहरण के लिए, अगर Ink में एक रेक्टैंगल और एक एलिप्स एक-दूसरे के बगल में मौजूद हैं, तो पहचान करने वाला टूल नतीजे के तौर पर इनमें से कोई एक या कोई बिलकुल अलग चीज़ दिखा सकता है. ऐसा इसलिए, क्योंकि पहचान के लिए एक ही उम्मीदवार दो शेप को नहीं दिखा सकता.