iOS'te ML Kit ile dijital mürekkebi tanıma

ML Kit'in dijital mürekkep tanıma özelliğiyle tek bir cihazda el yazısıyla yazılmış metinleri yüzlerce dilde dijital yüzeyler oluşturabilir ve çizimleri sınıflandırabilir.

Deneyin

Başlamadan önce

  1. Aşağıdaki ML Kit kitaplıklarını Podfile'ınıza ekleyin:

    pod 'GoogleMLKit/DigitalInkRecognition', '3.2.0'
    
    
  2. Projenizin kapsüllerini yükledikten veya güncelledikten sonra Xcode projenizi açın (.xcworkspace) kullanılıyor. Makine Öğrenimi Kiti, Xcode sürümünde desteklenir 13.2.1 veya daha sonraki sürümler.

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'te birlikte bir UIImageView öğesini kullanabilirsiniz dokunma etkinliği işleyiciler Çizgileri çizen ve aynı zamanda çizgilerin kurulacak puan Ink nesnesini ifade eder. Bu genel kalıp aşağıdaki kodda gösterilmiştir snippet'i Hızlı başlangıç kılavuzuna göz atın uygulamasını ile dokunma etkinliği işlemeyi, ekran çizimini, ve kulaç veri yönetimi.

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, çizgiyi içine çizebileceğiniz örnek bir işlev içerdiğini unutmayın. UIImageView, ve uygulamanız için gerektiği şekilde uyarlanmalıdır. Önerilerimiz çizgi segmentlerini sıfır uzunluktaki segmentler çizecek şekilde yuvarlak resim bir nokta olarak çizilir (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ımayı gerçekleştirmek için Ink nesnesini bir DigitalInkRecognizer örneği. DigitalInkRecognizer örneğini almak için önce istenen dil için tanıyıcı modeli indirmemiz ve modeli RAM'e yükler. Bu işlem, aşağıdaki kod kullanılarak yapılabilir. snippet'i kullanır. Bu snippet, basitlik için viewDidLoad() yöntemine yerleştirilir ve dilinin adını yazın. Hızlı başlangıç kılavuzuna göz atın uygulamasını kullanılabilir dillerin listesini kullanıcıya gösterme ve indirme örneği seçilen dili seçin.

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ı, birden fazla işlemin nasıl yapılacağını gösteren ek kod içerir. aynı anda nasıl indirildiğini görebilir ve hangi indirme işleminin tamamlama bildirimlerini devre dışı bırakabilirsiniz.

Ink nesnesini tanı

Şimdi doRecognition() işlevine geliyor. Bu işlev, basitlik için touchesEnded() başlangıç fiyatıyla. Diğer uygulamalarda bir zaman aşımına uğradıktan sonra ya da kullanıcı, Google Ad Manager'ı tetiklemek için teşekkür ederiz.

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 veya simgesini tıklayın.

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 oranı şuna da bağlıdır: ele alacağız. Dijital Mürekkep Tanıma özelliği, pek çok yazma stilini işleyecek şekilde eğitilir. sonuç kullanıcıya göre değişebilir.

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ında geçerli değildir.

Yazma alanı

Birçok uygulamanın, kullanıcı girişi için iyi tanımlanmış bir yazma alanı vardır. Bir simgenin anlamı boyutu, onu içeren yazı alanının boyutuna göre kısmen belirlenir. Örneğin, "o" harfi ile küçük veya büyük harf veya "c", virgül ve a eğik çizgi.

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ında yalnızca tek bir satırlık metin olduğunu varsayar. Fiziksel yazma alanı kullanıcının iki veya daha fazla satır yazmasına izin verecek kadar büyük olursa yüksekliğini en iyi tahmin ettiğiniz yükseklikle bir WriteArea'dan geçerek sonuçlar elde edebilirsiniz. tek satırlık metin kullanarak ekleyebilirsiniz. Tanıyıcıya ilettiğiniz WriteArea nesnesinin karşılık gelmesine gerek yok ekrandaki fiziksel yazı alanıyla aynı olur. WriteArea yüksekliğini bu şekilde değiştirme bazı dillerde daha iyi çalışır.

Yazma alanını belirlerken genişliğini ve yüksekliğini fırçayla aynı birimlerde belirtin koordinatlar. x, y koordinat bağımsız değişkenlerinin birim gereksinimleri yoktur; API, olduğundan, önemli olan tek şey çizgilerin göreceli boyutu ve konumudur. İsterseniz sisteminiz için uygun olan ölçekte koordinat verebilir.

Bağlam öncesi

Bağlam ön bilgisi, eklediğiniz Ink içindeki fırçalardan hemen önce gelen metindir birlikte çalışır. Tanıyıcıya bağlam öncesi hakkında bilgi vererek yardımcı olabilirsiniz.

Örneğin, "n" şeklinde el yazısı harfleri ve "u" genellikle birbiriyle karıştırılır. Kullanıcı "arg" kısmi kelimesini girmişlerse "ument" veya "nment" olur. Bağlam öncesi "arg" değerini belirtme muğlaklığı giderir çünkü "bağımsız değişken" "bağımsız değişken"den daha olasıdır.

Bağlam ön bilgisi, tanıyıcının kelime sonlarını, yani kelimeler arasındaki boşlukları belirlemesine de yardımcı olabilir. Şunları yapabilirsiniz: boşluk karakteri giriyor ama çizemiyorsun, o halde tanıyıcı bir kelimenin ne zaman sona erdiğini nasıl belirleyebilir bir sonraki başlıyor mu? Kullanıcı zaten "merhaba" yazdıysa ve şöyle devam eder: "world" değerini, bağlam öncesi olmadan tanıyıcı "world" dizesini döndürür. Ancak, bağlam öncesi "merhaba" ise model, " "merhaba"dan itibaren baştaki boşlukla dünya" "merhabakelime"den daha anlamlıdır.

Mümkün olan en uzun bağlam öncesi dizesini (en fazla 20 karakter dahil) sağlamanız gerekir. alanlar'a dokunun. 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 RecognitionContext nesnesini ifade eder.

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ıyıcılar darbelerin kullanıcıların doğal olarak yazdıkları sırayla olmasıdır; örneğin İngilizce için soldan sağa. Tüm durumlar bu kalıptan ayrılırsa örneğin son kelimeyle başlayan İngilizce bir cümle yazın. doğruluk oranı daha düşük sonuçlar verir.

Başka bir örnek de Ink kelimesinin ortasındaki bir kelime kaldırılıp ekleyebilirsiniz. Revizyon muhtemelen bir cümlenin ortasındadır, ancak düzeltmedeki fırça darbeleri çizgi dizisinin sonundadır. Bu durumda, yeni yazılan kelimeyi API'ye ayrı olarak göndermenizi ve ve kendi mantığınızı kullanarak önceki tanımalardan geleceksiniz.

Belirsiz şekillerle başa çıkma

Tanıyıcıya sağlanan şeklin anlamının belirsiz olduğu durumlar vardır. Örneğin, Örneğin, kenarları çok yuvarlanmış bir dikdörtgen, dikdörtgen ya da 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 puanlar sağlar. Model çok güveniyorsa en iyi sonucun puanı ikinci en iyisinden çok daha iyi. Belirsizlik varsa ilk iki sonuca ait puanlar yakın olun. Ayrıca, şekil sınıflandırıcılarının tüm Ink öğesini bir tek şekil. Örneğin, Ink her birinin yanında bir dikdörtgen ve bir elips içeriyorsa, tanıyıcı, bu uyarının diğerini (ya da tamamen farklı bir şeyi) Çünkü tek bir tanıma adayı iki şekli temsil edemez.