Reconhecer a escrita à mão dos usuários

A API de reconhecimento de escrita permite que você reconheça um texto de uma entrada escrita à mão enquanto ele acontece.

O que é a API de reconhecimento de escrita manual?

A API de reconhecimento de escrita permite que você converta a escrita à mão (tinta) de seus usuários em texto. Alguns sistemas operacionais já incluem essas APIs há algum 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 mesmo no modo off-line, sem 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 escrita à mão é reconhecida enquanto o usuário a desenha capturando e analisando os traços únicos. Ao contrário dos procedimentos "off-line", como o reconhecimento óptico de caracteres (OCR), em que apenas o produto final é conhecido, os algoritmos on-line podem fornecer um nível maior 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

Alguns exemplos de usos incluem:

  • Apps de anotações em que os usuários querem capturar notas escritas à mão e traduzi-las em texto.
  • Aplicativos de formulários em que os usuários podem digitar com caneta ou dedo devido a restrições de tempo.
  • Jogos que exigem o preenchimento de letras ou números, como palavras cruzadas, forca ou sudoku.

Status atual

A API de reconhecimento de escrita está disponível no Chromium 99.

Como usar a API de reconhecimento de escrita manual

Detecção de recursos

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

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

Conceitos básicos

A API de reconhecimento de escrita converte entradas escritas à mão 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 momento específico.
  2. Um traço consiste em um ou mais pontos. O registro de um traço começa quando o usuário posiciona o ponteiro (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 está configurado com o idioma de entrada esperado. Ele é usado para criar uma instância de um desenho com a configuração de reconhecedor aplicada.

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

As principais entidades da API de reconhecimento de escrita manual: um ou mais pontos compõe um traço, um ou mais traços compõe 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, você precisa conseguir uma instância de um HandwritingRecognizer chamando navigator.createHandwritingRecognizer() e transmitindo restrições a ele. 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 que resolve com uma instância de HandwritingRecognizer quando o navegador pode atender à solicitação. Caso contrário, a promessa será rejeitada com um erro, e o reconhecimento de escrita manual não estará disponível. Por esse motivo, pode ser necessário consultar primeiro o suporte do reconhecedor para recursos de reconhecimento específicos.

Consulta ao suporte do reconhecedor

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

  • quer detectar textos em inglês
  • receba previsões alternativas, menos prováveis, quando disponíveis
  • acesso ao 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, será definido como false. É possível usar essas informações para ativar ou desativar determinados recursos no aplicativo ou para ajustar sua consulta e enviar uma nova.

Iniciar um desenho

No aplicativo, ofereça uma área de entrada em que o usuário faça entradas manuscritas. 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 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: entrada por 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 ele provavelmente vai inserir (graphemeSet)

A API de reconhecimento de escrita funciona bem com eventos de ponteiro, que fornecem uma interface abstrata para consumir uma entrada de qualquer dispositivo apontador. Os argumentos do evento de 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 a seguir, 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 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 isso, crie uma nova instância de HandwritingStroke. Além disso, armazene a hora atual como um ponto de referência para os próximos pontos 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 diretamente o primeiro ponto a ele. Como você vai adicionar mais pontos posteriormente, faz sentido implementar a lógica de criação de pontos em um método separado. No exemplo a seguir, o método addPoint() calcula o tempo decorrido a partir do carimbo de data/hora de referência. A informação temporal é opcional, mas pode melhorar a qualidade do reconhecimento. Em seguida, ele lê as coordenadas X e Y do evento de 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 pode ser gerado 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 levanta o ponteiro novamente, você pode adicionar o traço ao seu desenho chamando o método addStroke(). O exemplo a seguir também redefine o activeStroke, de modo que 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, para que você possa executar as 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. Use essa matriz para apresentar ao usuário várias opções de correspondências e peça para ele selecionar uma opção. Como alternativa, você pode simplesmente usar a previsão mais provável, que é o que 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 compatível com a plataforma de destino, o objeto de previsão também pode 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 identificado pelo usuário reconhecido (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.

As caixas são desenhadas em torno 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 capaz de reconhecer a escrita à mão. Clique no botão no canto inferior direito do controle de edição para ativar o modo de desenho. Quando você concluir o desenho, o componente da Web iniciará o reconhecimento automaticamente e adicionará o texto reconhecido de volta ao controle de edição. Se a API de reconhecimento de escrita não for compatível ou a plataforma não oferecer suporte aos recursos solicitados, o botão de edição ficará oculto. Mas 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 de origem, 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 saber sobre alterações no valor, detecte o evento input.

Você pode testar o componente usando esta demonstração no Glitch (link em inglês). Além disso, consulte o código-fonte. Para usar o controle no seu aplicativo, consiga-o no npm.

Segurança e permissões

A equipe do Chromium projetou e implementou a API de reconhecimento manual usando os princípios básicos definidos em Como controlar o acesso a recursos avançados da plataforma da Web, incluindo controle de 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 está disponível apenas para sites entregues via HTTPS e só pode ser chamado a partir 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 uso de técnicas de impressão digital, o navegador implementa medidas, como exibir uma solicitação de permissão ao usuário quando detecta um possível abuso.

Persistência da permissão

No momento, a API de reconhecimento de escrita manual não mostra solicitações de permissão. Assim, a permissão não precisa ser mantida.

Feedback

A equipe do Chromium quer saber mais sobre suas experiências com a API Handwriting Recognition.

Fale sobre o design da API

Alguma coisa na API não funciona como você esperava? Ou faltam métodos ou propriedades que você precisa para implementar sua ideia? Tem uma pergunta ou comentário sobre o modelo de segurança? Registre um problema de especificação no repositório do GitHub (link em inglês) correspondente ou adicione sua opinião a um problema.

Informar um problema com a implementação

Você encontrou um bug na implementação do Chromium? Ou a implementação é diferente das especificações? 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 é ótimo para compartilhar repetições rápidas e fáceis.

Mostrar suporte à API

Você planeja usar a API de reconhecimento de escrita manual? Seu apoio público ajuda a equipe do Chromium a priorizar recursos e mostrar a outros fornecedores de navegadores como é fundamental oferecer suporte a eles.

Compartilhe como você planeja usá-la na conversa sobre a WCG (em inglês). Envie um tweet para @ChromiumDev usando a hashtag #HandwritingRecognition e informe onde e como você está usando essa hashtag.

Agradecimentos

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