Con il riconoscimento a inchiostro digitale di ML Kit, puoi riconoscere il testo scritto a mano su un superficie digitale in centinaia di lingue, oltre a classificare gli schizzi.
Prova
- Prova l'app di esempio per per vedere un esempio di utilizzo di questa API.
Prima di iniziare
Includi le seguenti librerie di ML Kit nel podfile:
pod 'GoogleMLKit/DigitalInkRecognition', '3.2.0'
Dopo aver installato o aggiornato i pod del progetto, apri il progetto Xcode utilizzando
.xcworkspace
. ML Kit è supportato nella versione Xcode 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 componente UIImageView
evento touch
Gestori
che disegnano i tratti sullo schermo e memorizzano anche i tratti punti da creare
l'oggetto Ink
. Questo pattern generale è dimostrato nel codice che segue
snippet di codice. Consulta la guida rapida
per un'app
esempio più completo, che separa la gestione degli eventi touch, il disegno
e la gestione dei dati relativi all'ictus.
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 in cui inserire il tratto.
UIImageView,
che devono essere adattati in base alle esigenze della tua applicazione. È consigliabile utilizzare
quando si disegnano segmenti di linea, in modo che quelli di lunghezza pari a zero
disegnato come un punto (pensa al punto su una lettera minuscola i). doRecognition()
viene richiamata dopo la scrittura di ogni tratto e viene definita di seguito.
Ottieni un'istanza di DigitalInkRecognizer
Per eseguire il riconoscimento dobbiamo passare l'oggetto Ink
a un
DigitalInkRecognizer
istanza. Per ottenere l'istanza DigitalInkRecognizer
,
dobbiamo prima scaricare il modello di riconoscimento per il linguaggio desiderato
per caricare il modello nella RAM. A questo scopo, puoi utilizzare il seguente codice
che, per semplicità, viene inserito nel metodo viewDidLoad()
e utilizza un
il nome della lingua hardcoded. Consulta la guida rapida
per un'app
esempio di come mostrare all'utente l'elenco delle lingue disponibili e scaricare
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 della guida rapida includono codice aggiuntivo che mostra come gestire download contemporaneamente e come stabilire quali download sono riusciti gestendo le notifiche di completamento.
Riconosci un oggetto Ink
Ora vediamo la funzione doRecognition()
, che per semplicità è chiamata
da touchesEnded()
. In altre applicazioni si potrebbe voler richiamare
il riconoscimento solo dopo un timeout o quando l'utente ha premuto un pulsante
di riconoscimento dei volti delle celebrità
basata su rigidi criteri di controllo.
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. Il seguente codice Gli snippet spiegano come verificare se un modello è già stato scaricato. per eliminare un modello 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 la precisione del riconoscimento del testo
La precisione del riconoscimento del testo può variare a seconda della lingua. La precisione dipende anche sullo stile di scrittura. La funzione di riconoscimento inchiostro digitale è addestrata per gestire molti tipi di stili di scrittura, i risultati possono variare da utente a utente.
Di seguito sono riportati alcuni modi per migliorare la precisione di un riconoscimento del testo. Tieni presente che queste tecniche non si applica ai classificatori dei disegni per emoji, autoDraw e forme.
Area di scrittura
Molte applicazioni dispongono di un'area di scrittura ben definita per l'input dell'utente. Il significato di un simbolo è determinato parzialmente dalle sue dimensioni rispetto all'area di scrittura che lo contiene. Ad esempio, la differenza tra la lettera "o" e quella minuscola o "c" e una virgola rispetto a barra obliqua.
Indicare al riconoscimento la larghezza e l'altezza dell'area di scrittura può migliorare la precisione. Tuttavia, il riconoscimento presuppone che l'area di scrittura contenga solo una singola riga di testo. Se l'ordine fisico di scrittura sia abbastanza grande da consentire all'utente di scrivere due o più righe, la visualizzazione potrebbe risultati passando in un'area di scrittura con un'altezza che rappresenta la stima migliore dell'altezza di riga di testo singola. L'oggetto WriteArea che passi al riconoscimento non deve corrispondere esattamente con l'area di scrittura fisica sullo schermo. Modifica l'altezza dell'area di scrittura in questo modo funziona meglio in alcune lingue rispetto ad altre.
Quando specifichi l'area di scrittura, specifica la larghezza e l'altezza utilizzando le stesse unità del tratto coordinate. Gli argomenti delle coordinate x,y non hanno requisiti di unità: l'API normalizza tutti Unità di misura, perciò l'unica cosa che conta è la dimensione e la posizione relative dei tratti. Sei libero di passare le coordinate nella scala più adatta al tuo sistema.
Pre-contesto
Il pre-contesto è il testo che precede immediatamente i tratti nel Ink
che
gli utenti che tentano di riconoscere. Puoi aiutare il riconoscimento parlando del pre-contesto.
Ad esempio, le lettere corsie "n" e "u" spesso scambiati l'uno per l'altro. Se l'utente ha l'utente ha già inserito la parola parziale "arg", potrebbero continuare con tratti riconoscibili come "ument" o "nment". Specificare l'"arg" del pre-contesto risolve l'ambiguità, poiché la parola "argomento" è più probabile di "argnment".
Il pre-contesto può anche aiutare il riconoscimento a identificare le interruzioni di parola, ovvero gli spazi tra le parole. Puoi digita uno spazio ma non riesci a disegnarne uno, 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 riconoscimento restituisce la stringa "world". Tuttavia, se specifichi pre-contesto "hello", il modello restituisce la stringa " mondiale", con uno spazio iniziale, poiché "hello mondo" ha più senso di "helloword".
Devi fornire la stringa di pre-contesto più lunga possibile, fino a 20 caratteri, tra cui: spazi di archiviazione. Se la stringa è più lunga, il riconoscimento utilizza solo gli ultimi 20 caratteri.
L'esempio di codice seguente mostra come definire un'area di scrittura e utilizzare un
RecognitionContext
oggetto 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); }];
Ordinazione tratto
La precisione del riconoscimento dipende dall'ordine dei tratti. I responsabili del riconoscimento si aspettano che avvengono nell'ordine in cui le persone scrivono in modo naturale; 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, fornisce risultati meno precisi.
Un altro esempio è quando una parola al centro di un Ink
viene rimossa e sostituita
un'altra parola. La revisione probabilmente si trova nel bel mezzo di una frase, ma i tratti relativi alla revisione
si trovano alla fine della sequenza del tratto.
In questo caso consigliamo di inviare la parola appena scritta separatamente all'API e di unire la
il risultato con i riconoscimenti precedenti
utilizzando la tua logica.
Gestire forme ambigue
Esistono casi in cui il significato della forma fornita al riconoscimento è ambiguo. Per Ad esempio, un rettangolo con bordi molto arrotondati può essere visto come un rettangolo o un'ellisse.
Questi casi non chiari possono essere gestiti utilizzando i punteggi di riconoscimento, se disponibili. Solo
classificatori di forme forniscono punteggi. Se il modello è molto sicuro, il punteggio del miglior risultato sarà
molto meglio del secondo migliore. In caso di incertezza, i punteggi dei primi due risultati
essere vicini. Inoltre, tieni presente che i classificatori di forma interpretano l'intero Ink
come un
singola forma. Ad esempio, se Ink
contiene un rettangolo e un'ellisse accanto a ogni
il riconoscimento potrebbe restituire l'uno o l'altro (o qualcosa di completamente diverso) come
poiché un singolo candidato al riconoscimento non può rappresentare due forme.