Con il riconoscimento dell'inchiostro digitale di ML Kit, puoi riconoscere il testo scritto a mano su una superficie digitale in centinaia di lingue, nonché classificare gli schizzi.
Prova
- Prova l'app di esempio per vedere un esempio di utilizzo di questa API.
Prima di iniziare
Includi le seguenti librerie ML Kit nel tuo Podfile:
pod 'GoogleMLKit/DigitalInkRecognition', '8.0.0'
Dopo aver installato o aggiornato i pod del progetto, apri il progetto Xcode utilizzando il file
.xcworkspace
. ML Kit è supportato in Xcode versione 13.2.1 o successive.
Ora puoi iniziare a riconoscere il testo negli oggetti Ink
.
Crea un oggetto Ink
Il modo principale per creare un oggetto Ink
è disegnarlo su un touchscreen. Su iOS,
puoi utilizzare un UIImageView insieme ai
gestori di eventi
tocco
che disegnano i tratti sullo schermo e memorizzano anche i punti dei tratti per creare
l'oggetto Ink
. Questo pattern generale è illustrato nel seguente snippet di codice. Per un esempio più completo, consulta l'app
di avvio rapido, che separa la gestione degli eventi tocco, il disegno dello schermo
e la gestione dei dati dei tratti.
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]; }
Tieni presente che lo snippet di codice include una funzione di esempio per disegnare il tratto in
UIImageView,
che deve essere adattata in base alle esigenze della tua applicazione. Ti consigliamo di utilizzare
roundcaps quando disegni i segmenti di linea in modo che i segmenti di lunghezza zero vengano
disegnati come un punto (pensa al punto sulla lettera minuscola i). La funzione doRecognition()
viene chiamata dopo la scrittura di ogni tratto e verrà definita di seguito.
Ottieni un'istanza di DigitalInkRecognizer
Per eseguire il riconoscimento, dobbiamo passare l'oggetto Ink
a un'istanza di DigitalInkRecognizer
. Per ottenere l'istanza DigitalInkRecognizer
,
dobbiamo prima scaricare il modello di riconoscimento per la lingua desiderata e
caricarlo nella RAM. Per farlo, puoi utilizzare il seguente snippet di codice, che per semplicità è inserito nel metodo viewDidLoad()
e utilizza un nome di lingua hardcoded. Consulta l'app
quickstart per un
esempio di come mostrare all'utente l'elenco delle lingue disponibili e scaricare
la lingua selezionata.
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]; }
Le app di avvio rapido includono codice aggiuntivo che mostra come gestire più download contemporaneamente e come determinare quale download è andato a buon fine gestendo le notifiche di completamento.
Riconoscere un oggetto Ink
Poi arriviamo alla funzione doRecognition()
, che per semplicità viene chiamata
da touchesEnded()
. In altre applicazioni si potrebbe voler richiamare
il riconoscimento solo dopo un timeout o quando l'utente ha premuto un pulsante per attivare
il riconoscimento.
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]; }]; }
Gestione dei download dei modelli
Abbiamo già visto come scaricare un modello di riconoscimento. I seguenti snippet di codice mostrano come verificare se un modello è già stato scaricato o come eliminarlo quando non è più necessario per recuperare spazio di archiviazione.
Controllare se un modello è già stato scaricato
Swift
let model : DigitalInkRecognitionModel = ... let modelManager = ModelManager.modelManager() modelManager.isModelDownloaded(model)
Objective-C
MLKDigitalInkRecognitionModel *model = ...; MLKModelManager *modelManager = [MLKModelManager modelManager]; [modelManager isModelDownloaded:model];
Eliminare un modello scaricato
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."); }]; }
Suggerimenti per migliorare l'accuratezza del riconoscimento del testo
L'accuratezza del riconoscimento del testo può variare a seconda delle lingue. L'accuratezza dipende anche dallo stile di scrittura. Sebbene il riconoscimento dell'inchiostro digitale sia addestrato a gestire molti tipi di stili di scrittura, i risultati possono variare da utente a utente.
Ecco alcuni modi per migliorare l'accuratezza di un sistema di riconoscimento del testo. Tieni presente che queste tecniche non si applicano ai classificatori di disegni per emoji, AutoDraw e forme.
Area di scrittura
Molte applicazioni hanno un'area di scrittura ben definita per l'input dell'utente. Il significato di un simbolo è parzialmente determinato dalle sue dimensioni rispetto a quelle dell'area di scrittura che lo contiene. Ad esempio, la differenza tra una lettera "o" o "c" maiuscola o minuscola e una virgola rispetto a una barra.
Comunicare al sistema di riconoscimento la larghezza e l'altezza dell'area di scrittura può migliorare la precisione. Tuttavia, il sistema di riconoscimento presuppone che l'area di scrittura contenga una sola riga di testo. Se l'area di scrittura fisica è abbastanza grande da consentire all'utente di scrivere due o più righe, potresti ottenere risultati migliori passando un WritingArea con un'altezza che corrisponde alla tua migliore stima dell'altezza di una singola riga di testo. L'oggetto WritingArea che passi al riconoscitore non deve corrispondere esattamente all'area di scrittura fisica sullo schermo. Modificare l'altezza di WritingArea in questo modo funziona meglio in alcune lingue rispetto ad altre.
Quando specifichi l'area di scrittura, indica la larghezza e l'altezza nelle stesse unità delle coordinate del tratto. Gli argomenti delle coordinate x,y non hanno requisiti di unità: l'API normalizza tutte le unità, quindi l'unica cosa che conta sono le dimensioni e la posizione relative dei tratti. Puoi passare le coordinate nella scala più adatta al tuo sistema.
Pre-context
Il pre-contesto è il testo che precede immediatamente i tratti nel Ink
che
stai cercando di riconoscere. Puoi aiutare il sistema di riconoscimento fornendogli il pre-contesto.
Ad esempio, le lettere "n" e "u" in corsivo vengono spesso confuse tra loro. Se l'utente ha già inserito la parola parziale "arg", potrebbe continuare con tratti che possono essere riconosciuti come "umento" o "namento". La specifica del pre-contesto "arg" risolve l'ambiguità, poiché la parola "argument" è più probabile di "argnment".
Il pre-contesto può anche aiutare il sistema di riconoscimento a identificare le interruzioni di parola, ovvero gli spazi tra le parole. Puoi digitare uno spazio, ma non puoi disegnarlo. Come fa un sistema di riconoscimento a determinare quando termina una parola e inizia la successiva? Se l'utente ha già scritto "hello" e continua con la parola scritta "world", senza pre-contesto il sistema di riconoscimento restituisce la stringa "world". Tuttavia, se specifichi il pre-contesto "hello", il modello restituirà la stringa " world", con uno spazio iniziale, poiché "hello world" ha più senso di "helloword".
Devi fornire la stringa di pre-contesto più lunga possibile, fino a 20 caratteri, spazi inclusi. Se la stringa è più lunga, il sistema di riconoscimento utilizza solo gli ultimi 20 caratteri.
L'esempio di codice riportato di seguito mostra come definire un'area di scrittura e utilizzare un oggetto
RecognitionContext
per specificare il pre-contesto.
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); }];
Ordine dei tratti
L'accuratezza del riconoscimento è sensibile all'ordine dei tratti. I sistemi di riconoscimento si aspettano che i tratti vengano eseguiti nell'ordine in cui le persone scrivono naturalmente, ad esempio da sinistra a destra per l'inglese. Qualsiasi caso che si discosta da questo schema, ad esempio scrivere una frase in inglese che inizia con l'ultima parola, produce risultati meno accurati.
Un altro esempio è quando una parola al centro di un Ink
viene rimossa e sostituita con
un'altra parola. La revisione si trova probabilmente a metà di una frase, ma i tratti della revisione
si trovano alla fine della sequenza di tratti.
In questo caso, ti consigliamo di inviare la parola appena scritta separatamente all'API e di unire il risultato con i riconoscimenti precedenti utilizzando la tua logica.
Gestire le forme ambigue
Esistono casi in cui il significato della forma fornita al sistema di riconoscimento è ambiguo. Ad esempio, un rettangolo con bordi molto arrotondati potrebbe essere visto come un rettangolo o un'ellisse.
Questi casi poco chiari possono essere gestiti utilizzando i punteggi di riconoscimento quando sono disponibili. Solo i classificatori di forme forniscono punteggi. Se il modello è molto sicuro, il punteggio del primo risultato sarà
molto migliore del secondo. In caso di incertezza, i punteggi dei primi due risultati saranno
simili. Inoltre, tieni presente che i classificatori di forme interpretano l'intero Ink
come una
singola forma. Ad esempio, se Ink
contiene un rettangolo e un'ellisse uno accanto all'altro, il sistema di riconoscimento potrebbe restituire l'uno o l'altro (o qualcosa di completamente diverso) come risultato, poiché un singolo candidato al riconoscimento non può rappresentare due forme.