A API Handwriting Recognition permite reconhecer texto de entrada manuscrita 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á incluem essas APIs há muito tempo, e com essa nova capacidade, seus web apps podem finalmente usar essa funcionalidade. A conversão acontece diretamente no dispositivo do usuário, funciona mesmo no modo off-line, tudo 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 manuscrita é 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 oferecer um nível mais alto de precisão devido a sinais adicionais, como a sequência temporal e a pressão dos traços individuais de tinta.
Casos de uso sugeridos para a API Handwriting Recognition
Exemplos de uso:
- Aplicativos de anotações em que os usuários querem capturar notas escritas à mão e traduzi-las para texto.
- Formulários em que os usuários podem usar uma stylus ou entrada com os dedos 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 Handwriting Recognition está disponível a partir do Chromium 99.
Como usar a API Handwriting Recognition
Detecção de recursos
Para detectar o suporte do navegador, verifique a existência do método createHandwritingRecognizer()
no objeto do navegador:
if ('createHandwritingRecognizer' in navigator) {
// 🎉 The Handwriting Recognition API is supported!
}
Principais conceitos
A API Handwriting Recognition converte entradas manuscritas em texto, independente do método de entrada (mouse, toque, stylus). A API tem quatro entidades principais:
- Um ponto representa onde o ponteiro estava em um determinado momento.
- 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.
- Um desenho consiste em um ou mais traços. O reconhecimento real ocorre nesse nível.
- 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.
Como criar um identificador
Para reconhecer texto de entrada manuscrita, é necessário obter 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, você pode especificar uma lista de idiomas por ordem de preferência:
const recognizer = await navigator.createHandwritingRecognizer({
languages: ['en'],
});
O método retorna uma promessa que é resolvida com uma instância de um HandwritingRecognizer
quando o
navegador pode atender à sua solicitação. Caso contrário, ela vai rejeitar a promessa com um erro, e o reconhecimento de escrita à mão não estará disponível. Por isso, talvez seja melhor consultar primeiro o suporte do reconhecedor para recursos de reconhecimento específicos.
Consultar suporte do reconhecedor
Ao chamar navigator.queryHandwritingRecognizer()
, é possível verificar se a plataforma de destino
é compatível com os recursos de reconhecimento de manuscrito que você pretende usar. Esse método usa o mesmo objeto de restrição que o método navigator.createHandwritingRecognizer()
, contendo a lista de idiomas solicitados. O método retorna uma promessa que é resolvida com um objeto de resultado se um reconhecedor compatível for encontrado. Caso contrário, a promessa será resolvida como null
.
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 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 dele 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 para enviar uma nova consulta para um conjunto diferente de idiomas.
Começar um desenho
No aplicativo, ofereça uma área de entrada em que o usuário faça as entradas manuscritas. Por motivos de desempenho, recomendamos implementar isso com a ajuda de um objeto de tela. A implementação exata dessa 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 várias 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 stylus (
inputType
) - O texto anterior (
textContext
) - O número de previsões alternativas menos prováveis que devem 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 os Eventos de ponteiro, que fornecem uma interface abstrata para consumir entradas 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 proprietário, introduzi uma verificação de consistência para garantir
que apenas 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 isso, crie uma nova
instância de HandwritingStroke
. Além disso, armazene 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 depois, 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 desde o 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 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 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 há 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, adicione o traço ao desenho chamando o método
addStroke()
dele. O exemplo a seguir também redefine o activeStroke
. Assim, o manipulador pointermove
não adiciona 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 à dica alternatives
. Você
pode usar essa matriz para apresentar ao usuário uma escolha de possíveis correspondências e pedir que ele selecione uma
opção. Outra opção é 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 vou discutir 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 reconhecido
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.
Reconhecimento completo
Depois que o reconhecimento for concluído, libere os 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 progressivamente aprimorado capaz de reconhecimento de
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 for compatível ou se a plataforma não oferecer suporte aos recursos solicitados, o botão de edição
será ocultado. Mas o controle de edição básica ainda pode ser usado como um <textarea>
.
O componente da Web oferece propriedades e atributos para definir o comportamento de reconhecimento de fora, incluindo languages
e recognitiontype
. Você pode 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
.
Teste o componente usando esta demonstração no GitHub. Confira também o código-fonte. Para usar o controle no seu 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 Controlling Access to Powerful Web Platform Features (em inglês), 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 só está disponível para sites entregues via HTTPS e só pode ser chamado do contexto de navegação de nível superior.
Transparência
Não há indicação se o reconhecimento de escrita à mão está ativo. Para evitar o fingerprinting, o navegador implementa contramedidas, como mostrar uma solicitação de permissão ao usuário quando detecta possíveis abusos.
Permanê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 forma alguma.
Feedback
A equipe do Chromium quer saber sobre suas experiências com a API Handwriting Recognition.
Fale 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 implementar sua ideia? Tem uma dúvida ou um 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 já 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 Componentes.
Mostrar suporte para a API
Você planeja 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 usar isso na thread do WICG Discourse. Envie um tweet para
@ChromiumDev usando a hashtag
#HandwritingRecognition
e conte para nós onde e como você está usando.
Links úteis
- Explicação
- Rascunho de especificação
- Repositório do GitHub
- ChromeStatus
- Bug do Chromium
- Revisão da TAG
- Intenção de prototipar
- Thread do WebKit-Dev (em inglês)
- Posição da Mozilla sobre padrões
Agradecimentos
Este documento foi revisado por Joe Medley, Honglin Yu e Jiewei Qian.