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