Reconhecer a escrita à mão dos usuários

A API Handwriting Recognition permite reconhecer texto de entrada escrita à mão em tempo real.

O que é a API Handwriting Recognition?

A API Handwriting Recognition permite converter a escrita à mão (tinta) dos usuários em texto. Alguns sistemas operacionais já incluíam essas APIs há muito tempo. 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 mesmo no modo off-line, sem a necessidade de adicionar bibliotecas ou serviços de terceiros.

Essa API implementa o chamado reconhecimento "on-line" ou quase em tempo real. Isso significa que a entrada manuscrita é reconhecida enquanto o usuário a desenha, capturando e analisando os traços individuais. Em contraste com procedimentos "off-line", como o reconhecimento óptico de caracteres (OCR, na sigla em inglês), em que apenas o produto final é conhecido, os algoritmos on-line podem fornecer um nível mais alto de precisão devido a sinais adicionais, 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 uso incluem:

  • Aplicativos de anotações em que os usuários querem capturar notas manuscritas e convertê-las em texto.
  • Formulários em que os usuários podem usar a stylus ou a entrada por dedo devido a restrições de tempo.
  • Jogos que exigem o preenchimento de letras ou números, como palavras cruzadas, jogo da forca 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 de navegação:

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

Principais conceitos

A API Handwriting Recognition converte a entrada manuscrita em texto, independentemente do método de entrada (mouse, toque, stylus). 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 stylus ou o dedo) e termina quando ele levanta o ponteiro novamente.
  3. Um desenho consiste em um ou mais traços. O reconhecimento real ocorre nesse nível.
  4. O reconhecedor é 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 vou abordar em breve.

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

Como criar um identificador

Para reconhecer texto de uma entrada manuscrita, é necessário chamar navigator.createHandwritingRecognizer() e transmitir restrições a uma instância de HandwritingRecognizer. As restrições determinam o modelo de reconhecimento de escrita manual que deve ser usado. No momento, é 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 resolvida com uma instância de um HandwritingRecognizer quando o navegador pode atender à solicitação. Caso contrário, a promessa será rejeitada com um erro, e o reconhecimento de escrita à mão não estará disponível. Por esse motivo, é recomendável consultar o suporte do reconhecedor para recursos de reconhecimento específicos.

Consultar o suporte do reconhecedor

Ao chamar navigator.queryHandwritingRecognizer(), você pode verificar se a plataforma de destino oferece suporte aos recursos de reconhecimento de escrita à mão que você pretende usar. Esse método usa o mesmo objeto de restrição do método navigator.createHandwritingRecognizer(), contendo a lista de idiomas solicitados. O método retorna uma promessa resolvida com um objeto de resultado se um reconhecedor compatível for encontrado. Caso contrário, a promessa é resolvida como null. No exemplo abaixo, o desenvolvedor:

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

console.log(result?.textAlternatives); // true if alternatives are supported
console.log(result?.textSegmentation); // true if segmentation is supported

Se o navegador oferecer suporte ao recurso necessário para o desenvolvedor, o valor será definido como true no objeto de resultado. Caso contrário, será definido como false. Você pode usar essas informações para ativar ou desativar determinados recursos no seu aplicativo ou enviar uma nova consulta para um conjunto diferente de idiomas.

Começar a desenhar

No app, ofereça uma área de entrada em que o usuário faça anotações escritas à mão. Por motivos de desempenho, é recomendável implementar isso 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 saber como fazer isso.

Para iniciar um novo desenho, chame o método startDrawing() no reconhecedor. Esse método usa um objeto que contém diferentes dicas 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 entrada por stylus (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 entradas de qualquer dispositivo de ponteiro. Os argumentos do evento do ponteiro contêm o tipo de ponteiro que está sendo usado. Isso significa que você pode usar eventos de ponteiro para determinar o tipo de entrada automaticamente. No exemplo abaixo, o desenho para reconhecimento de escrita manual é criado automaticamente na primeira ocorrência de um evento pointerdown na área de escrita manual. Como o pointerType pode estar vazio ou definido como um valor reservado, introduzi uma verificação de consistência para garantir que apenas os valores compatíveis 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', 'stylus'].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, você precisa armazenar o horário atual como um 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ê vai adicionar mais pontos mais tarde, 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 gerenciador de eventos pointermove é chamado quando o ponteiro é movido pela tela. Esses pontos também precisam ser adicionados ao traço. O evento também pode ser gerado se o ponteiro não estiver em um 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 um traço ativo existe e adiciona o novo ponto a ele.

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

Reconhecer texto

Quando o usuário levanta o ponteiro novamente, você pode adicionar o traço ao desenho chamando o método addStroke(). O exemplo a seguir também redefine a activeStroke, para que o gerenciador pointermove não adicione 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 de algumas centenas de milissegundos, então 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 por probabilidade. O número de elementos depende do valor transmitido para a sugestão alternatives. Você pode usar essa matriz para apresentar ao usuário uma escolha de possíveis correspondências e fazer com que ele selecione uma opção. Como alternativa, você pode usar a previsão mais provável, que é o que eu faço 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 a plataforma de destino oferecer suporte, o objeto de previsão também poderá conter um resultado de segmentação. É 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 dele 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 os grafemas reconhecidos na tela novamente.

Caixas são desenhadas em torno de cada grafema reconhecido

Reconhecimento completo

Depois que o reconhecimento for concluído, você poderá 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 gradualmente aprimorado, capaz de reconhecer a escrita manual. Ao clicar no botão no canto inferior direito do controle de edição, você ativa o modo de desenho. Quando você terminar 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 Handwriting Recognition não tiver suporte ou a plataforma não oferecer suporte aos recursos solicitados, o botão de edição será ocultado. Mas o controle básico de edição continua utilizável como <textarea>.

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

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

Para receber notificações sobre qualquer mudança no valor, detecte o evento input.

Teste o componente usando esta demonstração no Glitch. Confira também o código-fonte. Para usar o controle no aplicativo, faça o download dele no npm.

Segurança e permissões

A equipe do Chromium projetou e implementou a API Handwriting Recognition usando os princípios básicos definidos em Como controlar o acesso a recursos poderosos da plataforma da Web, incluindo controle do usuário, transparência e ergonomia.

Controle do usuário

A API Handwriting Recognition não pode ser desativada pelo usuário. Ele está disponível apenas para sites entregues por HTTPS e só pode ser chamado do 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 fingerprinting, o navegador implementa contramedidas, como mostrar uma solicitação de permissão ao usuário quando detecta possível abuso.

Persistência de permissões

No momento, a API Handwriting Recognition não mostra solicitações de permissão. Portanto, a permissão não precisa ser mantida de nenhuma forma.

Feedback

A equipe do Chromium quer saber sobre sua experiência com a API Handwriting Recognition.

Conte sobre o design da API

Há algo na API que não funciona como esperado? Ou há métodos ou propriedades ausentes que você precisa para implementar sua ideia? Tem dúvidas ou comentários sobre o modelo de segurança? Envie um problema de especificação no repositório do GitHub correspondente ou adicione sua opinião a um problema existente.

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 Components. O Glitch é ótimo para compartilhar reprosagens rápidas e fáceis.

Mostrar suporte para a API

Você pretende usar a API Handwriting Recognition? Seu apoio público ajuda a equipe do Chromium a priorizar recursos e mostra a outros fornecedores de navegadores a importância de oferecer suporte a eles.

Compartilhe como você planeja usá-lo na discussão do Discourse do WICG. Envie um tweet para @ChromiumDev usando a hashtag #HandwritingRecognition e nos informe onde e como você está usando.

Agradecimentos

Este documento foi revisado por Joe Medley, Honglin Yu e Jiewei Qian.