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