Com o reconhecimento de tinta digital do Kit de ML, é possível reconhecer texto escrito à mão em uma superfície digital em centenas de idiomas, bem como classificar esboços.
Faça um teste
- Teste o app de exemplo para um exemplo de uso dessa API.
Antes de começar
Inclua as seguintes bibliotecas do Kit de ML no seu Podfile:
pod 'GoogleMLKit/DigitalInkRecognition', '3.2.0'
Depois de instalar ou atualizar os pods do seu projeto, abra o projeto Xcode usando a
.xcworkspace
dele. O kit de ML é compatível com a versão do Xcode 13.2.1 ou superior.
Agora está tudo pronto para você reconhecer texto em objetos Ink
.
Criar um objeto Ink
A principal maneira de criar um objeto Ink
é desenhá-lo em uma tela sensível ao toque. No iOS,
é possível usar uma UIImageView junto com
evento de toque
gerenciadores
que desenham traços na tela e também os armazenam pontos para construir
objeto Ink
. Esse padrão geral é demonstrado no código a seguir
snippet. Consulte o guia de início rápido
app para um
exemplo mais completo, que separa o gerenciamento de eventos de toque, o desenho da tela,
e gerenciamento de dados de AVC.
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]; }
O snippet de código inclui uma função de exemplo para desenhar o traço
UIImageView,
que deve ser adaptado conforme necessário. Recomendamos usar
limites redondos ao desenhar os segmentos de linha, de modo que os segmentos de comprimento zero sejam
desenhada como um ponto (pense no ponto em uma letra minúscula com um i). O doRecognition()
é chamada depois que cada traço é escrito e será definida abaixo.
Receba uma instância de DigitalInkRecognizer
Para realizar o reconhecimento, precisamos transmitir o objeto Ink
para um
DigitalInkRecognizer
. Para conseguir a instância DigitalInkRecognizer
,
primeiro precisamos fazer o download do modelo de reconhecimento para o idioma desejado e
carregar o modelo na RAM. Isso pode ser feito com o seguinte código:
snippet, que, para simplificar, é colocado no método viewDidLoad()
e usa um
nome do idioma fixado no código. Consulte o guia de início rápido
app para um
exemplo de como mostrar a lista de idiomas disponíveis para o usuário e fazer o download
no idioma selecionado.
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]; }
Os aplicativos de início rápido incluem código adicional que mostra como lidar com várias downloads ao mesmo tempo, e como determinar qual download foi bem-sucedido lidando as notificações de conclusão.
Reconhecer um objeto Ink
Em seguida, chegamos à função doRecognition()
, que, para simplificar, é chamada
de touchesEnded()
. Em outros aplicativos, pode ser útil invocar
reconhecimento hipotético somente após um tempo limite ou quando o usuário pressionou um botão para acionar
e reconhecimento de objetos.
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]; }]; }
Como gerenciar downloads de modelos
Já vimos como fazer o download de um modelo de reconhecimento. O código a seguir ilustram como verificar se um modelo já foi baixado, ou um modelo pode ser excluído quando não for mais necessário recuperar o espaço de armazenamento.
Verificar se um modelo já foi baixado
Swift
let model : DigitalInkRecognitionModel = ... let modelManager = ModelManager.modelManager() modelManager.isModelDownloaded(model)
Objective-C
MLKDigitalInkRecognitionModel *model = ...; MLKModelManager *modelManager = [MLKModelManager modelManager]; [modelManager isModelDownloaded:model];
Excluir um modelo baixado
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."); }]; }
Dicas para melhorar a precisão do reconhecimento de texto
A precisão do reconhecimento de texto pode variar de acordo com o idioma. A precisão também depende sobre o estilo de escrita. Embora o reconhecimento de tinta digital seja treinado para lidar com muitos tipos de estilos de escrita, os resultados podem variar de usuário para usuário.
Aqui estão algumas maneiras de melhorar a precisão de um reconhecedor de texto. Observe que essas técnicas não se aplicam aos classificadores de desenho para emojis, desenho automático e formas.
Área de escrita
Muitos aplicativos têm uma área de gravação bem definida para entradas do usuário. O significado de um símbolo é parcialmente determinada por seu tamanho em relação ao tamanho da área de escrita que a contém. Por exemplo, a diferença entre uma letra "o" minúscula ou uma letra maiúscula. ou "c", e uma vírgula versus barra para a direita.
Dizer ao reconhecedor a largura e a altura da área de escrita pode melhorar a precisão. No entanto, o reconhecedor presume que a área de escrita contém apenas uma linha de texto. Se o ponto físico área de escrita for grande o suficiente para permitir que o usuário escreva duas ou mais linhas, talvez seja melhor resultados passando um WritingArea com uma altura que seja a melhor estimativa da altura de um uma única linha de texto. O objeto WritingArea que você passa para o reconhecedor não precisa corresponder exatamente com a área de escrita física na tela. Como alterar a altura da área de escrita dessa forma funciona melhor em alguns idiomas do que em outros.
Ao especificar a área de escrita, especifique a largura e a altura nas mesmas unidades que o traço. coordenadas. Os argumentos de coordenadas x,y não têm requisito de unidade. A API normaliza todos então o que importa é o tamanho relativo e a posição dos traços. Você pode e passar coordenadas em qualquer escala que faça sentido para o seu sistema.
Pré-contexto
Pré-contexto é o texto que precede imediatamente os traços no Ink
que você
estão tentando reconhecer. Você pode ajudar o reconhecedor contando sobre o pré-contexto.
Por exemplo, as letras cursivas "n" e "u" muitas vezes são confundidos uns com os outros. Se o usuário tiver já inseriram a palavra parcial "arg", eles podem continuar com traços que podem ser reconhecidos como “umentar” ou "nment". Como especificar o "argumento" de pré-contexto resolve a ambiguidade, já que a palavra "argumento" é mais provável que "argnamento".
O pré-contexto também pode ajudar o reconhecedor a identificar quebras de palavras, os espaços entre palavras. Você pode digite um caractere de espaço, mas não desenhe um, então como um reconhecedor pode determinar quando uma palavra termina e o próximo começa? Se o usuário já tiver escrito "hello" e continua com a palavra escrita "world", sem pré-contexto, o reconhecedor retorna a string "world". No entanto, se você especificar pré-contexto "hello", o modelo retornará a string " mundo", com um espaço na liderança, já que "olá" mundo" faz mais sentido do que "helloword".
Você deve fornecer a maior string de pré-contexto possível, com até 20 caracteres, incluindo espaços Se a string for maior, o reconhecedor usará apenas os últimos 20 caracteres.
O exemplo de código abaixo mostra como definir uma área de escrita e usar uma
Objeto RecognitionContext
para especificar o pré-contexto.
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); }];
Ordenação dos traços
A precisão do reconhecimento depende da ordem dos traços. Os reconhecedores esperam que os traços ocorrer na ordem em que as pessoas escreveriam naturalmente; por exemplo, da esquerda para a direita, no caso do inglês. Qualquer caso que se desvie desse padrão, como escrever uma frase começando com a última palavra, fornece resultados menos precisos.
Outro exemplo é quando uma palavra no meio de uma Ink
é removida e substituída por
outra palavra. A revisão provavelmente está no meio de uma frase, mas os traços da revisão
estão no final da sequência de traços.
Nesse caso, recomendamos enviar a nova palavra escrita separadamente para a API e mesclar o
com os reconhecimentos anteriores usando sua própria lógica.
Como lidar com formas ambíguas
Há casos em que o significado da forma fornecida ao reconhecedor é ambíguo. Para exemplo, um retângulo com bordas muito arredondadas pode ser visto como um retângulo ou uma elipse.
Esses casos pouco claros podem ser tratados com o uso de pontuações de reconhecimento quando disponíveis. Somente
classificadores de formas fornecem pontuações. Se o modelo estiver muito confiante, a pontuação do primeiro resultado será
muito melhor do que o segundo melhor. Se houver incerteza, as pontuações dos dois primeiros resultados serão
estar perto. Além disso, lembre-se de que os classificadores de formas interpretam toda a Ink
como uma
forma única. Por exemplo, se Ink
contiver um retângulo e uma elipse ao lado de cada
o reconhecedor pode retornar um ou outro (ou algo completamente diferente) como uma
resultado, já que um único candidato de reconhecimento não pode representar duas formas.