Reconhecer a escrita à mão dos usuários

A API de reconhecimento de escrita manual permite reconhecer texto de entrada escrita à mão enquanto ele acontece.

O que é a API Handwriting Recognition?

A API de reconhecimento de escrita manual permite converter a escrita à mão (tinta) dos usuários em texto. Alguns sistemas operacionais já incluem essas APIs há muito tempo e, com esse novo recurso, seus apps da Web podem finalmente usar essa funcionalidade. A conversão ocorre diretamente no dispositivo do usuário e funciona até mesmo no modo off-line, sem a necessidade de adicionar bibliotecas ou serviços de terceiros.

Essa API implementa o reconhecimento chamado "on-line" ou quase em tempo real. Isso significa que a entrada escrita à mão é reconhecida enquanto o usuário a desenha, capturando e analisando os traços únicos. Ao contrário de procedimentos "off-line", como reconhecimento óptico de caracteres (OCR), em que apenas o produto final é conhecido, os algoritmos on-line podem fornecer um nível mais alto de precisão devido a outros sinais, como a sequência temporal e a pressão de traços de tinta individuais.

Casos de uso sugeridos para a API Handwriting Recognition

Exemplos de usos incluem:

  • Aplicativos de anotações em que os usuários querem capturar notas escritas à mão e fazer com que elas sejam traduzidas em texto.
  • Aplicativos de formulários em que os usuários podem inserir entrada com caneta ou dedo devido a restrições de tempo.
  • Jogos que exigem o preenchimento de letras ou números, como palavras cruzadas, travesseiro ou sudoku.

Status atual

A API Handwriting Recognition está disponível no Chromium 99.

Como usar a API Handwriting Recognition

Detecção de recursos

Detecte o suporte do navegador verificando a existência do método createHandwritingRecognizer() no objeto Navigator:

if ('createHandwritingRecognizer' in navigator) {
  // 🎉 The Handwriting Recognition API is supported!
}

Principais conceitos

A API de reconhecimento de escrita manual converte entradas manuscritas em texto, independentemente do método de entrada (mouse, toque, caneta). A API tem quatro entidades principais:

  1. Um ponto representa onde o ponteiro estava em um determinado momento.
  2. Um traço consiste em um ou mais pontos. A gravação de um traço começa quando o usuário coloca o ponteiro para baixo (ou seja, clica no botão principal do mouse ou toca na tela com a caneta ou o dedo) e termina quando ele o levanta.
  3. Um desenho consiste em um ou mais traços. O reconhecimento real ocorre nesse nível.
  4. O Reconhecer está configurado com o idioma de entrada esperado. Ele é usado para criar uma instância de um desenho com a configuração do reconhecedor aplicada.

Esses conceitos são implementados como interfaces e dicionários específicos, que abordaremos em breve.

As entidades principais da API Handwriting Recognition: um ou mais pontos compõem um traço, um ou mais traços compõem um desenho que o reconhecedor cria. O reconhecimento real ocorre no nível de desenho.

Como criar um reconhecedor

Para reconhecer texto de entrada manuscrita, você precisa conseguir uma instância de um HandwritingRecognizer chamando navigator.createHandwritingRecognizer() e transmitindo restrições a ela. As restrições determinam o modelo de reconhecimento de escrita manual que precisa ser usado. Atualmente, é possível especificar uma lista de idiomas em ordem de preferência:

const recognizer = await navigator.createHandwritingRecognizer({
  languages: ['en'],
});

O método retorna uma promessa que resolve com uma instância de HandwritingRecognizer quando o navegador pode atender à sua solicitação. Caso contrário, ele rejeitará a promessa com um erro, e o reconhecimento de escrita manual não ficará disponível. Por esse motivo, consulte primeiro o suporte do reconhecedor para recursos de reconhecimento específicos.

Como consultar o suporte do reconhecedor

Ao chamar navigator.queryHandwritingRecognizerSupport(), é possível verificar se a plataforma de destino é compatível com os recursos de reconhecimento de escrita manual que você pretende usar. No exemplo a seguir, o desenvolvedor:

  • quer detectar textos em inglês
  • receber previsões alternativas e menos prováveis quando disponíveis
  • acessar o resultado da segmentação, ou seja, os caracteres reconhecidos, incluindo os pontos e traços que os compõem.
const { languages, alternatives, segmentationResults } =
  await navigator.queryHandwritingRecognizerSupport({
    languages: ['en'],
    alternatives: true,
    segmentationResult: true,
  });

console.log(languages); // true or false
console.log(alternatives); // true or false
console.log(segmentationResult); // true or false

O método retorna uma promessa que é resolvida com um objeto de resultado. Se o navegador for compatível com o recurso especificado pelo desenvolvedor, o valor dele será definido como true. Caso contrário, ele é definido como false. É possível usar essas informações para ativar ou desativar determinados recursos no app ou para ajustar sua consulta e enviar uma nova.

Iniciar um desenho

No seu aplicativo, ofereça uma área de entrada em que o usuário faz entradas manuscritas. Para melhorar o desempenho, recomendamos fazer essa implementação com a ajuda de um objeto de tela. A implementação exata desta parte está fora do escopo deste artigo, mas você pode consultar a demonstração para ver como fazer isso.

Para iniciar um novo desenho, chame o método startDrawing() no reconhecedor. Esse método usa um objeto que contém dicas diferentes para ajustar o algoritmo de reconhecimento. Todas as dicas são opcionais:

  • O tipo de texto inserido: texto, endereços de e-mail, números ou um caractere individual (recognitionType)
  • O tipo de dispositivo de entrada: mouse, toque ou caneta (inputType)
  • O texto anterior (textContext)
  • O número de previsões alternativas menos prováveis que precisam ser retornadas (alternatives)
  • Uma lista de caracteres identificáveis pelo usuário ("grafemas") que o usuário provavelmente vai inserir (graphemeSet)

A API Handwriting Recognition funciona bem com eventos de ponteiro, que fornecem uma interface abstrata para consumir entrada de qualquer dispositivo apontador. Os argumentos do evento de ponteiro contêm o tipo de ponteiro que está sendo usado. Isso significa que é possível usar eventos de ponteiro para determinar o tipo de entrada automaticamente. No exemplo a seguir, o desenho para reconhecimento de escrita manual é criado automaticamente na primeira ocorrência de um evento pointerdown na área de escrita à mão. Como pointerType pode estar vazio ou definido como um valor reservado, introduzi uma verificação de consistência para garantir que apenas os valores com suporte sejam definidos para o tipo de entrada do desenho.

let drawing;
let activeStroke;

canvas.addEventListener('pointerdown', (event) => {
  if (!drawing) {
    drawing = recognizer.startDrawing({
      recognitionType: 'text', // email, number, per-character
      inputType: ['mouse', 'touch', 'pen'].find((type) => type === event.pointerType),
      textContext: 'Hello, ',
      alternatives: 2,
      graphemeSet: ['f', 'i', 'z', 'b', 'u'], // for a fizz buzz entry form
    });
  }
  startStroke(event);
});

Adicionar um traço

O evento pointerdown também é o lugar certo para iniciar um novo traço. Para fazer isso, crie uma nova instância de HandwritingStroke. Além disso, é preciso armazenar o horário atual como ponto de referência para os pontos subsequentes adicionados a ele:

function startStroke(event) {
  activeStroke = {
    stroke: new HandwritingStroke(),
    startTime: Date.now(),
  };
  addPoint(event);
}

Adicionar um ponto

Depois de criar o traço, adicione o primeiro ponto diretamente a ele. Como você adicionará mais pontos posteriormente, faz sentido implementar a lógica de criação de pontos em um método separado. No exemplo abaixo, o método addPoint() calcula o tempo decorrido a partir do carimbo de data/hora de referência. As informações temporais são opcionais, mas podem melhorar a qualidade do reconhecimento. Em seguida, ele lê as coordenadas X e Y do evento do ponteiro e adiciona o ponto ao traço atual.

function addPoint(event) {
  const timeElapsed = Date.now() - activeStroke.startTime;
  activeStroke.stroke.addPoint({
    x: event.offsetX,
    y: event.offsetY,
    t: timeElapsed,
  });
}

O manipulador de eventos pointermove é chamado quando o ponteiro é movido pela tela. Esses pontos também precisam ser adicionados ao traço. O evento também poderá ser acionado se o ponteiro não estiver no estado "para baixo", por exemplo, ao mover o cursor pela tela sem pressionar o botão do mouse. O manipulador de eventos do exemplo a seguir verifica se existe um traço ativo e adiciona o novo ponto a ele.

canvas.addEventListener('pointermove', (event) => {
  if (activeStroke) {
    addPoint(event);
  }
});

Reconhecer texto

Quando o usuário levantar o ponteiro novamente, você poderá adicionar o traço ao desenho chamando o método addStroke(). O exemplo a seguir também redefine o activeStroke. Portanto, o gerenciador pointermove não adicionará pontos ao traço concluído.

Em seguida, é hora de reconhecer a entrada do usuário chamando o método getPrediction() no desenho. O reconhecimento geralmente leva menos do que algumas centenas de milissegundos. Assim, você pode executar previsões repetidamente, se necessário. O exemplo a seguir executa uma nova previsão após cada traço concluído.

canvas.addEventListener('pointerup', async (event) => {
  drawing.addStroke(activeStroke.stroke);
  activeStroke = null;

  const [mostLikelyPrediction, ...lessLikelyAlternatives] = await drawing.getPrediction();
  if (mostLikelyPrediction) {
    console.log(mostLikelyPrediction.text);
  }
  lessLikelyAlternatives?.forEach((alternative) => console.log(alternative.text));
});

Esse método retorna uma promessa que é resolvida com uma matriz de previsões ordenadas pela probabilidade. O número de elementos depende do valor transmitido para a dica alternatives. Você pode usar essa matriz para apresentar ao usuário uma escolha de possíveis correspondências e para que ele selecione uma opção. Outra opção é usar a previsão mais provável, que é o que fiz no exemplo.

O objeto de previsão contém o texto reconhecido e um resultado de segmentação opcional, que será discutido na próxima seção.

Insights detalhados com resultados de segmentação

Se for compatível com a plataforma de destino, o objeto de previsão também poderá conter um resultado de segmentação. Essa é uma matriz que contém todos os segmentos de escrita à mão reconhecidos, uma combinação do caractere identificável pelo usuário (grapheme) com a posição no texto reconhecido (beginIndex, endIndex) e os traços e pontos que o criaram.

if (mostLikelyPrediction.segmentationResult) {
  mostLikelyPrediction.segmentationResult.forEach(
    ({ grapheme, beginIndex, endIndex, drawingSegments }) => {
      console.log(grapheme, beginIndex, endIndex);
      drawingSegments.forEach(({ strokeIndex, beginPointIndex, endPointIndex }) => {
        console.log(strokeIndex, beginPointIndex, endPointIndex);
      });
    },
  );
}

Você pode usar essas informações para rastrear novamente os grafemas reconhecidos na tela.

Há caixas desenhadas ao redor de cada grafema reconhecido.

Reconhecimento completo

Após a conclusão do reconhecimento, é possível liberar recursos chamando o método clear() no HandwritingDrawing e o método finish() no HandwritingRecognizer:

drawing.clear();
recognizer.finish();

Demonstração

O componente da Web <handwriting-textarea> implementa um controle de edição aprimorado progressivamente que pode reconhecer a escrita à mão. Para ativar o modo de desenho, clique no botão no canto inferior direito do controle de edição. Quando você concluir o desenho, o componente da Web vai iniciar automaticamente o reconhecimento e adicionar o texto reconhecido de volta ao controle de edição. Se a API de reconhecimento de escrita manual não tiver suporte ou a plataforma não oferecer suporte aos recursos solicitados, o botão de edição ficará oculto. No entanto, o controle básico de edição ainda pode ser usado como um <textarea>.

O componente da Web oferece propriedades e atributos para definir o comportamento de reconhecimento externo, incluindo languages e recognitiontype. É possível definir o conteúdo do controle usando o atributo value:

<handwriting-textarea languages="en" recognitiontype="text" value="Hello"></handwriting-textarea>

Para ser informado sobre qualquer mudança no valor, detecte o evento input.

Você pode testar o componente usando esta demonstração no Glitch. Além disso, confira o código-fonte. Para usar o controle no seu aplicativo, solicite-o no npm.

Segurança e permissões

A equipe do Chromium projetou e implementou a API de reconhecimento de escrita manual usando os princípios fundamentais definidos em Como controlar o acesso a recursos avançados da plataforma da Web, incluindo controle do usuário, transparência e ergonomia.

Controle do usuário

A API de reconhecimento de escrita manual não pode ser desativada pelo usuário. Ela só está disponível para sites enviados via HTTPS e só pode ser chamada no contexto de navegação de nível superior.

Transparência

Não há indicação de que o reconhecimento de escrita manual está ativo. Para evitar o uso de técnicas de impressão digital, o navegador implementa medidas corretivas, como mostrar um aviso de permissão ao usuário quando detectar um possível abuso.

Persistência da permissão

No momento, a API Handwriting Recognition não mostra nenhum prompt de permissão. Portanto, a permissão não precisa ser mantida de nenhuma forma.

Feedback

A equipe do Chromium quer saber mais sobre suas experiências com a API de reconhecimento de escrita manual.

Fale sobre o design da API

Há algo na API que não funciona como você esperava? Ou faltam métodos ou propriedades para implementar sua ideia? Tem alguma dúvida ou comentário sobre o modelo de segurança? Registre um problema de especificação no repositório do GitHub correspondente ou adicione suas ideias a um problema atual.

Informar um problema com a implementação

Você encontrou um bug na implementação do Chromium? Ou a implementação é diferente da especificação? Registre um bug em new.crbug.com. Inclua o máximo de detalhes possível, instruções simples para reprodução e insira Blink>Handwriting na caixa Componentes. O Glitch funciona muito bem para compartilhar repetições rápidas e fáceis.

Mostre seu suporte à API

Você planeja usar a API Handwriting Recognition? Seu suporte público ajuda a equipe do Chromium a priorizar recursos e mostra a outros fornecedores de navegadores como é fundamental oferecer suporte a eles.

Compartilhe como você planeja usá-lo na conversa do discurso do WiCG. Envie um tweet para @ChromiumDev usando a hashtag #HandwritingRecognition e informe onde e como ela está sendo usada.

Agradecimentos

Este artigo foi revisado por Joe Medley, Honglin Yu e Jiewei Qian. Imagem principal de Samir Bouaked no Unsplash (links em inglês).