Digitale Tinte mit ML Kit für iOS erkennen

Mit der digitalen Tintenerkennung von ML Kit können Sie handschriftlichen Text auf digitalen Oberflächen in Hunderten von Sprachen verfügbar machen und Skizzen klassifizieren.

Jetzt ausprobieren

  • Probieren Sie die Beispiel-App aus, um sehen Sie sich ein Anwendungsbeispiel für diese API an.

Hinweis

  1. Fügen Sie die folgenden ML Kit-Bibliotheken in Ihre Podfile-Datei ein:

    pod 'GoogleMLKit/DigitalInkRecognition', '3.2.0'
    
    
  2. Öffnen Sie nach der Installation oder Aktualisierung der Pods Ihres Projekts das Xcode-Projekt. mit .xcworkspace. ML Kit wird in der Xcode-Version unterstützt 13.2.1 oder höher.

Sie können jetzt mit der Texterkennung in Ink-Objekten beginnen.

Ink-Objekt erstellen

Die Hauptmethode zum Erstellen eines Ink-Objekts besteht darin, es auf einen Touchscreen zu zeichnen. Auf iOS-Geräten können Sie UIImageView zusammen mit Touch-Event Handler die die Striche auf den Bildschirm zeichnen und Punkte zum Erstellen Ink. Dieses allgemeine Muster wird im folgenden Code veranschaulicht: Snippet. Kurzanleitung ansehen App für eine in dem die Handhabung von Touch-Ereignissen, Bildschirmzeichnungen und Schlaganfalldatenmanagement.

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];
}

Beachten Sie, dass das Code-Snippet eine Beispielfunktion enthält, um den Strich in UIImageView die nach Bedarf an Ihre Anwendung angepasst werden sollten. Wir empfehlen die Verwendung von beim Zeichnen der Liniensegmente, sodass Segmente der Länge Null als Punkt gezeichnet (z. B. den Punkt auf einem Kleinbuchstaben i). Das doRecognition() -Funktion wird aufgerufen, nachdem jeder Strich geschrieben wurde, und wird unten definiert.

Instanz von DigitalInkRecognizer abrufen

Für eine Erkennung muss das Ink-Objekt an ein DigitalInkRecognizer-Instanz. So rufen Sie die Instanz DigitalInkRecognizer ab: müssen wir zuerst das Erkennungsmodell für die gewünschte Sprache herunterladen. das Modell in den RAM zu laden. Dies kann mithilfe des folgenden Codes erreicht werden: Snippet, das der Einfachheit halber in die Methode viewDidLoad() eingefügt wird und ein hartcodierter Sprachname. Kurzanleitung ansehen App für eine Beispiel für die Anzeige der Liste verfügbarer Sprachen für den Nutzer und den Download in der ausgewählten Sprache.

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];
}

Die Schnellstart-Anwendungen enthalten zusätzlichen Code, der zeigt, wie mehrere Downloads gleichzeitig heruntergeladen werden können und wie Sie feststellen, welcher Download erfolgreich war, Fertigstellungsbenachrichtigungen.

Ink-Objekt erkennen

Als Nächstes kommt die Funktion doRecognition(), die der Einfachheit halber von touchesEnded(). In anderen Anwendungen kann man zum Beispiel Erkennung erst nach Ablauf einer Zeitüberschreitung oder wenn der Nutzer eine Schaltfläche zum Auslösen einer Anerkennung.

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];
  }];
}

Modelldownloads verwalten

Wir haben bereits gesehen, wie man ein Erkennungsmodell herunterlädt. Der folgende Code Snippets veranschaulichen, wie geprüft wird, ob ein Modell bereits heruntergeladen wurde, oder um ein Modell zu löschen, wenn es zum Freigeben von Speicherplatz nicht mehr benötigt wird.

Prüfen, ob ein Modell bereits heruntergeladen wurde

Swift

let model : DigitalInkRecognitionModel = ...
let modelManager = ModelManager.modelManager()
modelManager.isModelDownloaded(model)

Objective-C

MLKDigitalInkRecognitionModel *model = ...;
MLKModelManager *modelManager = [MLKModelManager modelManager];
[modelManager isModelDownloaded:model];

Heruntergeladenes Modell löschen

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.");
                                }];
}

Tipps zur Verbesserung der Genauigkeit der Texterkennung

Die Genauigkeit der Texterkennung kann je nach Sprache variieren. Die Genauigkeit hängt auch von zum Schreibstil. Die digitale Tintenerkennung wurde für viele verschiedene Schreibstile trainiert, können die Ergebnisse von Nutzer zu Nutzer variieren.

Hier finden Sie einige Möglichkeiten, die Genauigkeit einer Texterkennung zu verbessern. Beachten Sie, dass diese Methoden gelten nicht für die Zeichenklassifikatoren für Emojis, AutoDraw und Formen.

Schreibbereich

Viele Anwendungen haben einen klar definierten Schreibbereich für Nutzereingaben. Die Bedeutung eines Symbols teilweise durch seine Größe im Verhältnis zur Größe des Schreibbereichs bestimmt, in dem sie enthalten ist. Zum Beispiel die Differenz zwischen einem Groß- und Kleinschreibung („O“) oder "c" und ein Komma im Vergleich zu Schrägstrich.

Wenn Sie der Erkennung die Breite und Höhe des Schreibbereichs mitteilen, kann dies die Genauigkeit verbessern. Sie können jedoch wird angenommen, dass der Schreibbereich nur eine einzige Textzeile enthält. Wenn die physische der Schreibbereich groß genug ist, damit Nutzende zwei oder mehr Zeilen schreiben können, Ergebnisse, indem Sie ein WritingArea mit einer Höhe übergeben, die Ihrer besten Schätzung der Höhe eines eine Textzeile schreiben. Das WritingArea-Objekt, das Sie an die Erkennung übergeben, muss nicht mit dem Schreibbereich auf dem Bildschirm. Die Schreibbereichshöhe auf diese Weise ändern funktioniert in einigen Sprachen besser als in anderen.

Wenn Sie den Schreibbereich angeben, geben Sie dessen Breite und Höhe in denselben Einheiten wie der Strich an. Koordinaten. Für die x- und y-Koordinatenargumente gibt es keine Einheitenanforderungen. Die API normalisiert alle -Einheiten, daher ist nur die relative Größe und Position der Striche wichtig. Sie können Folgendes tun: in jedem für Ihr System sinnvollen Maßstab übergeben.

Vor dem Kontext

Der Kontext vor dem Kontext ist der Text, der den Strichen in Ink unmittelbar vorausgeht, den Sie zu erkennen. Sie können dem Erkennungsmodul helfen, indem Sie es über den Vorkontext informieren.

Zum Beispiel werden die Schreibschriften „n“ und „u“ werden oft verwechselt. Wenn der Nutzer bereits das Teilwort „arg“ eingegeben hat, werden sie möglicherweise mit Strichen fortgesetzt, die als „Ument“ oder „Nment“. „arg“ im Vorkontext angeben die Ambiguität gelöst, da das Wort „Argument“ wahrscheinlicher als „argnment“.

Vor dem Kontext kann die Erkennung auch dabei helfen, Wortumbrüche, also die Leerzeichen zwischen Wörtern, zu erkennen. Sie können Leerzeichen eingeben, aber nicht zeichnen. Wie kann eine Erkennung feststellen, wann ein Wort endet? und die nächste beginnt? Wenn der Nutzer bereits „hello“ geschrieben hat und fährt mit dem Wort „world“. Ohne Vorkontext gibt die Erkennung den String „world“ zurück. Wenn Sie jedoch die pre-context "hello" enthält, gibt das Modell den String " Welt“ mit einem vorangestellten Leerzeichen, da „Hallo“ Welt“ sinnvoller ist als "Hallowort".

Geben Sie den längstmöglichen Pre-Kontext-String an, der bis zu 20 Zeichen umfasst, einschließlich Leerzeichen. Wenn der String länger ist, verwendet die Erkennung nur die letzten 20 Zeichen.

Das folgende Codebeispiel zeigt, wie Sie einen Schreibbereich definieren und einen RecognitionContext-Objekt, um einen Vorkontext anzugeben.

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);
                         }];

Strichreihenfolge

Die Genauigkeit der Erkennung hängt von der Reihenfolge der Striche ab. Die Erkennungsfunktionen erwarten, dass in der Reihenfolge, in der die Nutzer schreiben würden; z. B. von links nach rechts für Englisch. Beliebiger Fall zum Beispiel einen englischen Satz, der mit dem letzten Wort beginnt, führt zu weniger genauen Ergebnissen.

Ein weiteres Beispiel: Ein Wort mitten in einem Ink wird entfernt und durch oder ein anderes Wort. Die Überarbeitung steht wahrscheinlich mitten in einem Satz, aber die Überarbeitung am Ende der Strichfolge stehen. In diesem Fall empfehlen wir, das neu geschriebene Wort separat an die API zu senden und die Ergebnis mit den vorherigen Erkennungen unter Verwendung Ihrer eigenen Logik erhalten.

Umgang mit mehrdeutigen Formen

Es gibt Fälle, in denen die Bedeutung der dem Erkennungsmodul übergebenen Form mehrdeutig ist. Für Ein Rechteck mit stark abgerundeten Ecken kann entweder als Rechteck oder als Ellipse betrachtet werden.

Diese unklaren Fälle können mithilfe von Erkennungswerten bearbeitet werden, sofern sie verfügbar sind. Nur Formklassifikatoren liefern Bewertungen. Wenn das Modell sehr sicher ist, ist das Top-Ergebnis viel besser als die zweitbeste. Wenn es Unsicherheiten gibt, werden die Punkte für die beiden besten Ergebnisse nah sein. Beachten Sie außerdem, dass die Formklassifikatoren das gesamte Ink als in einer einzigen Form dargestellt werden. Wenn Ink beispielsweise ein Rechteck und eine Ellipse daneben enthält, kann das Erkennungsmodul das eine oder das andere (oder etwas völlig anderes) als da ein Erkennungskandidat nicht zwei Formen darstellen kann.