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, forca ou sudoku.
Status atual
A API de reconhecimento de escrita 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 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:
- Um ponto representa onde o ponteiro estava em um momento específico.
- 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.
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, talvez seja melhor consultar o
suporte do reconhecedor para recursos de reconhecimento específicos.
Consultar o suporte do reconhecedor
Chamando 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 for compatível com o recurso especificado pelo desenvolvedor, o valor dele 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 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 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: 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 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 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, 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 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 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 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 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 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 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.
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 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 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. 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 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.
Fale sobre o design da API
Alguma coisa na API não funciona como você esperava? 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 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 digite Blink>Handwriting
na caixa Components.
O Glitch é ótimo para compartilhar reprosagens rápidas e fáceis.
Mostrar suporte à 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á-la na conversa sobre a WCG (em inglês). Envie um tweet para
@ChromiumDev usando a hashtag
#HandwritingRecognition
e nos informe onde e como você está usando.
Links úteis
- Explicação
- Especificação do rascunho
- Repositório do GitHub
- ChromeStatus (link em inglês)
- Bug do Chromium
- Análise da TAG
- Intent to Prototype (em inglês)
- Conversa do WebKit-Dev
- Posição dos padrões do Mozilla
Agradecimentos
Este documento foi revisado por Joe Medley, Honglin Yu e Jiewei Qian.