La reconnaissance d'encre numérique de ML Kit vous permet de reconnaître du texte manuscrit sur une surface numérique dans des centaines de langues et de classer des croquis.
Essayer
- Testez l'application exemple pour voir un exemple d'utilisation de cette API.
Avant de commencer
Incluez les bibliothèques ML Kit suivantes dans votre Podfile:
pod 'GoogleMLKit/DigitalInkRecognition', '3.2.0'
Après avoir installé ou mis à jour les pods de votre projet, ouvrez votre projet Xcode à l'aide de son fichier
.xcworkspace
. ML Kit est compatible avec Xcode version 13.2.1 ou ultérieure.
Vous êtes maintenant prêt à reconnaître du texte dans les objets Ink
.
Créer un objet Ink
La principale méthode pour créer un objet Ink
consiste à le dessiner sur un écran tactile. Sur iOS, vous pouvez utiliser un élément UIImageView avec des gestionnaires d'événements tactiles qui tracent les traits à l'écran et stockent également les points des traits pour créer l'objet Ink
. Ce modèle général est illustré dans l'extrait de code suivant. Consultez l'application de démarrage rapide pour obtenir un exemple plus complet, qui sépare la gestion des événements tactiles, le dessin d'écran et la gestion des données de trait.
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]; }
Notez que l'extrait de code inclut un exemple de fonction permettant de dessiner le trait dans l'élément UIImageView, qui doit être adapté si nécessaire à votre application. Nous vous recommandons d'utiliser des majuscules lorsque vous tracez les segments de ligne afin que les segments de longueur nulle soient dessinés comme un point (pensez au point sur la lettre i minuscule). La fonction doRecognition()
est appelée après l'écriture de chaque trait. Elle est définie ci-dessous.
Obtenir une instance de DigitalInkRecognizer
Pour effectuer la reconnaissance, nous devons transmettre l'objet Ink
à une instance DigitalInkRecognizer
. Pour obtenir l'instance DigitalInkRecognizer
, nous devons d'abord télécharger le modèle de reconnaissance pour la langue souhaitée, puis le charger dans la mémoire RAM. Pour ce faire, vous pouvez utiliser l'extrait de code suivant, qui par souci de simplicité est placé dans la méthode viewDidLoad()
et utilise un nom de langage codé en dur. Consultez l'application de démarrage rapide pour découvrir comment présenter la liste des langues disponibles à l'utilisateur et télécharger la langue sélectionnée.
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]; }
Les applications de démarrage rapide incluent du code supplémentaire qui montre comment gérer plusieurs téléchargements en même temps et comment identifier le téléchargement qui a réussi grâce à la gestion des notifications d'achèvement.
Reconnaître un objet Ink
Passons maintenant à la fonction doRecognition()
, qui est appelée par touchesEnded()
pour plus de simplicité. Dans d'autres applications, il peut être utile d'appeler la reconnaissance uniquement après un délai d'inactivité ou lorsque l'utilisateur appuie sur un bouton pour déclencher la reconnaissance.
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]; }]; }
Gérer les téléchargements de modèles
Nous avons déjà vu comment télécharger un modèle de reconnaissance. Les extraits de code suivants montrent comment vérifier si un modèle a déjà été téléchargé ou comment supprimer un modèle lorsqu'il n'est plus nécessaire pour récupérer l'espace de stockage.
Vérifier si un modèle a déjà été téléchargé
Swift
let model : DigitalInkRecognitionModel = ... let modelManager = ModelManager.modelManager() modelManager.isModelDownloaded(model)
Objective-C
MLKDigitalInkRecognitionModel *model = ...; MLKModelManager *modelManager = [MLKModelManager modelManager]; [modelManager isModelDownloaded:model];
Supprimer un modèle téléchargé
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."); }]; }
Conseils pour améliorer la précision de la reconnaissance de texte
La précision de la reconnaissance de texte peut varier selon la langue. La justesse dépend également du style d'écriture. Bien que la reconnaissance d'encre numérique soit entraînée à gérer de nombreux types de styles d'écriture, les résultats peuvent varier d'un utilisateur à l'autre.
Voici quelques conseils pour améliorer la précision d'un outil de reconnaissance de texte. Notez que ces techniques ne s'appliquent pas aux classificateurs de dessin pour les emoji, les dessins automatiques et les formes.
Zone d'écriture
De nombreuses applications disposent d'une zone d'écriture bien définie pour les entrées utilisateur. La signification d'un symbole est partiellement déterminée par sa taille par rapport à la zone d'écriture qui le contient. Par exemple, la différence entre une lettre "o" ou "c" minuscule ou majuscule, et une virgule par rapport à une barre oblique.
Indiquer au programme de reconnaissance la largeur et la hauteur de la zone d'écriture peut améliorer la précision. Toutefois, l'outil de reconnaissance suppose que la zone d'écriture ne contient qu'une seule ligne de texte. Si la zone d'écriture physique est suffisamment grande pour permettre à l'utilisateur d'écrire deux lignes ou plus, vous pouvez obtenir de meilleurs résultats en indiquant une zone d'écriture dont la hauteur est votre meilleure estimation de la hauteur d'une seule ligne de texte. L'objet WriteArea que vous transmettez à l'outil de reconnaissance ne doit pas nécessairement correspondre exactement à la zone d'écriture physique à l'écran. Modifier la hauteur de WriteArea de cette manière fonctionne mieux dans certaines langues que dans d'autres.
Lorsque vous spécifiez la zone d'écriture, indiquez sa largeur et sa hauteur dans les mêmes unités que les coordonnées du trait. Aucune unité n'est requise pour les arguments de coordonnées x,y. L'API normalise toutes les unités. La seule chose qui compte, c'est la taille et la position relatives des traits. Vous êtes libre de transmettre des coordonnées à l'échelle qui convient à votre système.
Pré-contexte
Le pré-contexte correspond au texte qui précède immédiatement les traits dans le Ink
que vous essayez de reconnaître. Vous pouvez aider l'outil de reconnaissance en lui parlant du contexte préalable.
Par exemple, les lettres cursives "n" et "u" sont souvent confondues avec l'autre. Si l'utilisateur a déjà saisi le mot partiel "arg", il peut continuer avec des traits pouvant être reconnus comme "ument" ou "nment". La spécification de l'argument "arg" de pré-contexte résout l'ambiguïté, car le mot "argument" est plus probable que "argument".
Le pré-contexte peut également aider l'outil de reconnaissance à identifier les coupures entre les mots, c'est-à-dire les espaces entre les mots. Vous pouvez saisir un espace, mais vous ne pouvez pas en dessiner un. Comment un outil de reconnaissance peut-il déterminer quand un mot se termine et quand commence le suivant ? Si l'utilisateur a déjà écrit "hello" et continue avec le mot écrit "world", sans contexte préalable, l'outil de reconnaissance renvoie la chaîne "world". Toutefois, si vous spécifiez "hello" en pré-contexte, le modèle renvoie la chaîne "world" avec un espace au début, car "hello world" est plus logique que "helloword".
Vous devez fournir la chaîne de pré-contexte la plus longue possible, jusqu'à 20 caractères, espaces compris. Si la chaîne est plus longue, l'outil de reconnaissance n'utilise que les 20 derniers caractères.
L'exemple de code ci-dessous montre comment définir une zone d'écriture et utiliser un objet RecognitionContext
pour spécifier un contexte préalable.
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); }];
Ordre des traits
La précision de la reconnaissance est sensible à l'ordre des traits. Les outils de reconnaissance s'attendent à ce que les traits se produisent dans l'ordre dans lequel les gens écrivent naturellement ; par exemple de gauche à droite pour l'anglais. Tout cas qui ne respecte pas ce schéma, par exemple l'écriture d'une phrase en anglais en commençant par le dernier mot, produit des résultats moins précis.
Autre exemple : un mot situé au milieu d'un Ink
est supprimé et remplacé par un autre mot. La révision se trouve probablement au milieu d'une phrase, mais les traits de la révision se trouvent à la fin de la séquence de traits.
Dans ce cas, nous vous recommandons d'envoyer le mot récemment écrit séparément à l'API et de fusionner le résultat avec les reconnaissances précédentes en utilisant votre propre logique.
Gérer les formes ambiguës
Dans certains cas, la signification de la forme fournie à l'outil de reconnaissance est ambiguë. Par exemple, un rectangle aux bords très arrondis peut être considéré comme un rectangle ou comme une ellipse.
Ces cas peu clairs peuvent être traités à l'aide de scores de reconnaissance lorsqu'ils sont disponibles. Seuls les classificateurs de formes fournissent des scores. Si le modèle est très confiant, le meilleur score sera bien meilleur que le deuxième meilleur résultat. En cas d'incertitude, les scores des deux premiers résultats seront proches. Gardez également à l'esprit que les classificateurs de formes interprètent l'ensemble de l'élément Ink
comme une forme unique. Par exemple, si Ink
contient un rectangle et une ellipse l'un à côté de l'autre, l'outil de reconnaissance peut renvoyer l'un ou l'autre (ou quelque chose de complètement différent), car un seul candidat de reconnaissance ne peut pas représenter deux formes.