Reconhecer a escrita à mão dos usuários

A API Handwriting Recognition permite reconhecer o texto de uma 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 caneta ou o 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, 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 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 reconhecedor

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.queryHandwritingRecognizerSupport(), você pode verificar se a plataforma de destino oferece suporte aos recursos de reconhecimento de escrita à mão que você pretende usar. 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 { 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 resolvida com um objeto de resultado. Se o navegador oferecer suporte ao recurso especificado pelo desenvolvedor, o valor será definido como true. Caso contrário, será definido como false. Você pode usar essas informações para ativar ou desativar determinados recursos no seu aplicativo ou ajustar a consulta e enviar uma nova.

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 de 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 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', '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, 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 "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 à mão. 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 mudanças 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 digite 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.