Makine Öğrenimi Kiti'nin dijital mürekkep tanıma özelliğiyle yüzlerce dilde dijital yüzeylerde elle yazılmış metinleri tanıyabilir ve çizimleri sınıflandırabilirsiniz.
Deneyin
- Bu API'nin örnek kullanımını görmek için örnek uygulamayı inceleyin.
Başlamadan önce
Aşağıdaki ML Kit kitaplıklarını Podfile'ınıza ekleyin:
pod 'GoogleMLKit/DigitalInkRecognition', '3.2.0'
Projenizin kapsüllerini yükledikten veya güncelledikten sonra
.xcworkspace
kullanarak Xcode projenizi açın. ML Kit, Xcode 13.2.1 veya sonraki sürümlerde desteklenir.
Artık Ink
nesnedeki metinleri tanımaya başlamak için hazırsınız.
Ink
nesnesi oluşturun
Bir Ink
nesnesi oluşturmanın ana yolu, onu dokunmatik ekranda çizmektir. iOS'ta, Ink
nesnesini oluşturmak için ekrandaki darbeleri çizen ve aynı zamanda fırça noktalarını depolayan dokunma etkinliği işleyicileri ile birlikte bir UIImageView öğesi kullanabilirsiniz. Bu genel kalıp, aşağıdaki kod snippet'inde gösterilmektedir. Dokunma etkinliği işleme, ekran çizimi ve fırça veri yönetimini ayıran daha eksiksiz bir örnek için hızlı başlangıç uygulamasına göz atın.
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]; }
Kod snippet'inin, fırçayı UIImageView öğesine çizmek için örnek bir işlev içerdiğini unutmayın. Bu işlev, uygulamanız için gereken şekilde uyarlanmalıdır. Sıfır uzunluktaki segmentler nokta olarak çizilecek şekilde çizgi segmentlerini çizerken yuvarlak başlık kullanmanızı öneririz (küçük i harfinin üzerindeki noktayı düşünün). doRecognition()
işlevi, her çizgi yazıldıktan sonra çağrılır ve aşağıda tanımlanacaktır.
DigitalInkRecognizer
örneği al
Tanıma işlemi gerçekleştirmek için Ink
nesnesini bir DigitalInkRecognizer
örneğine iletmemiz gerekir. DigitalInkRecognizer
örneğini elde etmek için öncelikle istenen dile ait tanıyıcı modelini indirip modeli RAM'e yüklememiz gerekir. Bu işlem, kolaylık sağlamak için viewDidLoad()
yöntemine yerleştirilmiş ve sabit kodlu bir dil adı kullanan aşağıdaki kod snippet'i kullanılarak gerçekleştirilebilir. Kullanıcıya sunulan dillerin listesini nasıl göstereceğinize ve seçilen dili nasıl indireceğinize dair örnek için hızlı başlangıç uygulamasına göz atın.
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]; }
Hızlı başlangıç uygulamaları, aynı anda birden fazla indirmenin nasıl işleneceğini ve tamamlama bildirimleri işlenerek hangi indirme işleminin başarılı olduğunun nasıl belirleneceğini gösteren ek kod içerir.
Ink
nesnesini tanı
Şimdi, basitlik için touchesEnded()
'dan çağrılan doRecognition()
işlevine geliyoruz. Diğer uygulamalarda, bir kullanıcı tanımayı yalnızca bir zaman aşımından sonra veya kullanıcı, tanımayı tetiklemek için bir düğmeye bastığında çağırmak isteyebilir.
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]; }]; }
Model indirmelerini yönetme
Tanıma modelinin nasıl indirileceğini zaten görmüştük. Aşağıdaki kod snippet'leri, bir modelin önceden indirilip indirilmediğini nasıl kontrol edeceğinizi veya depolama alanını kurtarmak için artık ihtiyaç duyulmayan bir modeli nasıl sileceğinizi gösterir.
Bir modelin önceden indirilip indirilmediğini kontrol etme
Swift
let model : DigitalInkRecognitionModel = ... let modelManager = ModelManager.modelManager() modelManager.isModelDownloaded(model)
Objective-C
MLKDigitalInkRecognitionModel *model = ...; MLKModelManager *modelManager = [MLKModelManager modelManager]; [modelManager isModelDownloaded:model];
İndirilen bir modeli silme
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."); }]; }
Metin tanıma doğruluğunu iyileştirmeye yönelik ipuçları
Metin tanımanın doğruluğu diller arasında farklılık gösterebilir. Doğruluk yazı stiline de bağlıdır. Dijital Mürekkep Tanıma, pek çok yazma stilini işleyecek şekilde eğitilmiş olsa da sonuçlar kullanıcıdan kullanıcıya değişiklik gösterebilir.
Metin tanıyıcının doğruluğunu artırmanın bazı yolları aşağıda verilmiştir. Bu tekniklerin emoji, otomatik çizim ve şekillere yönelik çizim sınıflandırıcıları için geçerli olmadığını unutmayın.
Yazma alanı
Birçok uygulamanın, kullanıcı girişi için iyi tanımlanmış bir yazma alanı vardır. Bir simgenin anlamı, kısmen simgelerin bulunduğu yazı alanının boyutuna göre belirlenir. Örneğin, küçük veya büyük harf "o" veya "c" ile virgül ile düz eğik çizgi arasındaki fark.
Tanıyıcıya, yazma alanının genişliğini ve yüksekliğini söylemek doğruluğu artırabilir. Ancak tanıyıcı, yazma alanının yalnızca tek bir metin satırı içerdiğini varsayar. Fiziksel yazma alanı kullanıcının iki veya daha fazla satır yazmasına izin verecek kadar büyükse tek bir metin satırının yüksekliğine dair en iyi tahmininiz olan bir yükseklikle bir ComposeArea'dan geçerek daha iyi sonuçlar alabilirsiniz. Tanıyıcıya ilettiğiniz WriteArea nesnesinin, ekrandaki fiziksel yazma alanına tam olarak karşılık gelmesi gerekmez. WriteArea yüksekliğini bu şekilde değiştirmek bazı dillerde diğerlerine göre daha iyi sonuç verir.
Yazma alanını belirtirken genişliğini ve yüksekliğini fırça koordinatlarıyla aynı birimlerde belirtin. x,y koordinat bağımsız değişkenlerinin birim gereksinimi yoktur. API tüm birimleri normalleştirdiğinden, tek önemli nokta çizgilerin göreli boyutu ve konumudur. Sisteminiz için uygun olan ölçekte koordinatlar verebilirsiniz.
Bağlam öncesi
Bağlam ön bilgisi, Ink
içinde tanımaya çalıştığınız darbelerden hemen önce gelen metindir. Tanıyıcıya bağlam öncesi hakkında bilgi vererek yardımcı olabilirsiniz.
Örneğin, el yazısı harfleri "n" ve "u" genellikle birbiriyle karıştırılır. Kullanıcı kısmi olarak "arg" kelimesini girmişse "ument" veya "nment" olarak algılanabilen darbelerle devam edebilir. Bağlam öncesi "bağımsız değişken" kelimesinin belirtilmesi, "bağımsız değişken" kelimesinin "bağımsız değişken" kelimesinden daha olası olduğu için belirsizliği ortadan kaldırır.
Bağlam ön bilgisi, tanıyıcının kelime sonlarını, yani kelimeler arasındaki boşlukları belirlemesine de yardımcı olabilir. Boşluk karakteri yazabilirsiniz ama bir karakter çizemezsiniz. Öyleyse tanıyıcı bir kelimenin bitip bir sonrakinin başladığını nasıl belirleyebilir? Kullanıcı zaten "merhaba" yazdıysa ve "dünya" kelimesiyle devam ediyorsa bağlam öncesi "dünya" dizesini döndürür. Ancak bağlam öncesi "hello" ifadesini belirtirseniz "helloworld", "helloword"den daha anlamlı olduğu için model, başında boşlukla birlikte "world" dizesini döndürür.
Boşluklar dahil olmak üzere en fazla 20 karakterden oluşan mümkün olan en uzun bağlam öncesi dizesini sağlamanız gerekir. Dize daha uzunsa tanıyıcı yalnızca son 20 karakteri kullanır.
Aşağıdaki kod örneğinde, bir yazma alanının nasıl tanımlanacağı ve bağlam öncesi belirtmek için bir RecognitionContext
nesnesinin nasıl kullanılacağı gösterilmektedir.
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); }];
Çizgi sıralama
Tanıma doğruluğu kulaçların sırasına göre hassastır. Tanımlayıcılar, darbelerin insanların doğal yazdıkları sırayla gerçekleşmesini bekler. Örneğin, İngilizce için soldan sağa doğru yazılırlar. Bu kalıptan farklı olan herhangi bir durumda (örneğin, son kelimeyle başlayan İngilizce bir cümle yazmak) daha az doğru sonuçlar verir.
Başka bir örnek de Ink
kelimesinin ortasındaki bir kelimenin kaldırılıp başka bir kelimeyle değiştirilmesidir. Düzeltme muhtemelen bir cümlenin ortasındadır ancak düzeltme darbeleri çizgi dizisinin sonundadır.
Bu durumda, yeni yazılan kelimeyi API'ye ayrı olarak göndermenizi ve kendi mantığınızı kullanarak sonucu önceki tanımalarla birleştirmenizi öneririz.
Belirsiz şekillerle başa çıkma
Tanıyıcıya sağlanan şeklin anlamının belirsiz olduğu durumlar vardır. Örneğin, kenarları çok yuvarlanmış bir dikdörtgen, dikdörtgen veya elips olarak görülebilir.
Net olmayan bu destek kayıtları, mevcut olduğunda tanıma puanları kullanılarak ele alınabilir. Yalnızca şekil sınıflandırıcılar puan verir. Model çok güvenliyse en yüksek sonucun puanı, ikinci en iyiden çok daha iyi olur. Belirsizlik varsa ilk iki sonucun puanları yakın olur. Ayrıca, şekil sınıflandırıcılarının tüm Ink
işaretini tek bir şekil olarak yorumladığını unutmayın. Örneğin, Ink
öğesinde bir dikdörtgen ve birbirine bitişik bir elips varsa tek bir tanıma adayı iki şekli temsil edemediğinden sonuç olarak tanıyıcı ikisinden birini (ya da tamamen farklı bir şeyi) döndürebilir.