La reconnaissance d'encre numérique de ML Kit vous permet de reconnaître du texte manuscrit une surface numérique dans des centaines de langues, et de classer des croquis.
Essayer
- Testez l'application exemple pour : consultez 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
.xcworkspace
. ML Kit est compatible avec la version Xcode 13.2.1 ou version ultérieure.
Vous êtes maintenant prêt à commencer à reconnaître du texte dans les objets Ink
.
Créer un objet Ink
La principale façon de créer un objet Ink
consiste à le dessiner sur un écran tactile. Sur iOS,
vous pouvez utiliser une classe UIImageView avec
événement tactile
gestionnaires
qui dessinent les traits à l'écran et les stockent points à établir
l'objet Ink
. Ce schéma général est illustré dans le code suivant :
extrait. Consultez le guide de démarrage rapide
l'application mobile
un exemple plus complet, qui sépare la gestion
des événements tactiles, le dessin d'écran
et la gestion des données d'accident vasculaire cérébral.
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 pour dessiner le trait dans
la classe UIImageView,
que vous devez adapter selon les besoins de votre application. Nous vous recommandons d'utiliser
des arrondis lors du dessin des segments de ligne, de sorte que les segments de longueur nulle soient
dessiné par un point (pensez au point sur une lettre i minuscule). doRecognition()
est appelée après l'écriture de chaque trait et sera définie ci-dessous.
Obtenir une instance de DigitalInkRecognizer
Pour effectuer la reconnaissance, nous devons transmettre l'objet Ink
à
DigitalInkRecognizer
instance. Pour obtenir l'instance DigitalInkRecognizer
, procédez comme suit :
nous devons d'abord télécharger le modèle de reconnaissance pour la langue souhaitée, puis
charger le modèle dans la RAM. Pour ce faire, utilisez le code suivant :
un extrait de code, qui, pour plus de simplicité, est placé dans la méthode viewDidLoad()
et utilise un
nom de langue codé en dur. Consultez le guide de démarrage rapide
l'application mobile pour une
exemple montrant comment afficher la liste des langues disponibles pour l'utilisateur et comment 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 de téléchargements en même temps, et comment déterminer quel téléchargement a réussi en traitant les notifications de confirmation.
Reconnaître un objet Ink
Nous arrivons ensuite à la fonction doRecognition()
, appelée pour plus de simplicité.
de touchesEnded()
. Dans d'autres applications, on peut appeler
la reconnaissance uniquement après un délai d'inactivité, ou lorsque l'utilisateur a appuyé sur un bouton pour déclencher
de la reconnaissance vocale.
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. Le code suivant Les extraits montrent comment vérifier si un modèle a déjà été téléchargé. 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 d'une langue à l'autre. La précision dépend aussi sur le style d'écriture. La reconnaissance d'encre numérique est entraînée à gérer de nombreux 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 dessins pour les emoji, AutoDraw et les formes.
Zone d'écriture
De nombreuses applications disposent d'un espace d'écriture bien défini pour les entrées utilisateur. La signification d'un symbole est partiellement déterminée par sa taille par rapport à celle de la zone d’écriture qui la contient. Par exemple, la différence entre une lettre minuscule ou un "o" ou « c », et une virgule par rapport à .
Vous pouvez indiquer au programme de reconnaissance la largeur et la hauteur de la zone d'écriture pour 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 le serveur physique une zone d'écriture suffisamment grande pour permettre à l'utilisateur d'écrire deux lignes ou plus, vous pouvez en spécifiant une zone d'écriture dont la hauteur est votre meilleure estimation de la hauteur sur une seule ligne de texte. Il n'est pas nécessaire que l'objet "WriteArea" que vous transmettez à l'outil de reconnaissance exactement avec 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, spécifiez sa largeur et sa hauteur dans les mêmes unités que le trait. coordonnées. Les arguments des coordonnées x,y n'ont pas d'unité - l'API normalise toutes unités, donc la seule chose qui compte est la taille et la position relatives des traits. Vous êtes libre de transmettre les coordonnées à n'importe quelle échelle pour votre système.
Pré-contexte
Le précontexte est le texte qui précède immédiatement les traits dans l'Ink
que vous
que vous essayez de reconnaître. Vous pouvez l'aider en lui parlant du contexte préalable.
Par exemple, les lettres cursives "n" et "u" sont souvent confondus. Si l'utilisateur a a déjà saisi le mot partiel "arg", il peut continuer avec des traits qui peuvent être reconnus comme "ument" ou "nment". Spécifier l'argument de pré-contexte "arg" résout l'ambiguïté, puisque le mot "argument" est plus probable que "argnment".
Le pré-contexte peut également aider l'outil de reconnaissance à identifier les coupures, les espaces entre les mots. Vous pouvez taper un caractère d'espacement mais vous ne pouvez pas en dessiner un, alors comment un outil de reconnaissance peut-il déterminer quand un mot se termine et que la suivante commence ? Si l'utilisateur a déjà écrit "hello" et continue avec l'écriture écrite "world", sans pré-contexte, le programme de reconnaissance renvoie la chaîne "world". Toutefois, si vous spécifiez le paramètre pré-contexte "hello", le modèle renvoie la chaîne " "hello", avec un espace au début, du monde" est plus logique que "helloword".
Vous devez fournir la chaîne pré-contexte la plus longue possible, jusqu'à 20 caractères, y compris les espaces. Si la chaîne est plus longue, le programme 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
Un objet RecognitionContext
pour spécifier le pré-contexte.
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 dépend de l'ordre des traits. Les programmes 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. N'importe quel cas qui s'écarte de ce modèle, comme écrire une phrase en anglais commençant par le dernier mot, donne des résultats moins précis.
Autre exemple : un mot situé au milieu d'un élément Ink
est supprimé et remplacé par
un autre mot. La révision est probablement au milieu d'une phrase, mais ses traits
se trouvent à la fin de la séquence de trait.
Dans ce cas, nous vous recommandons d'envoyer le mot nouvellement écrit séparément à l'API et de fusionner les
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ë. Pour Par exemple, un rectangle aux bords très arrondis peut être considéré comme un rectangle ou une ellipse.
Ces cas peu clairs peuvent être gérés à l'aide des scores de reconnaissance lorsqu'ils sont disponibles. Uniquement
les classificateurs de formes fournissent des scores. Si le modèle est très confiant, le score du meilleur résultat sera
bien mieux que le deuxième meilleur. En cas d'incertitude, les scores des deux premiers résultats
d'être proche. Gardez également à l'esprit que les classificateurs de formes interprètent l'intégralité de la Ink
comme
une seule forme. Par exemple, si Ink
contient un rectangle et une ellipse à côté de chaque
le programme de reconnaissance peut renvoyer l'un ou l'autre (ou quelque chose de complètement différent)
résultat, car un seul candidat à la reconnaissance ne peut pas représenter deux formes.