La API de Handwriting Recognition te permite reconocer texto a partir de la entrada manuscrita a medida que se produce.
¿Qué es la API de Handwriting Recognition?
La API de Handwriting Recognition te permite convertir la escritura a mano (tinta) de tus usuarios en texto. Algunos sistemas operativos incluyen estas APIs desde hace mucho tiempo, y con esta nueva capacidad, tus apps web finalmente pueden usar esta funcionalidad. La conversión se realiza directamente en el dispositivo del usuario, funciona incluso en modo sin conexión y no requiere agregar bibliotecas ni servicios de terceros.
Esta API implementa el llamado reconocimiento "en línea" o casi en tiempo real. Esto significa que la entrada manuscrita se reconoce mientras el usuario la dibuja, ya que se capturan y analizan los trazos individuales. A diferencia de los procedimientos "sin conexión", como el reconocimiento óptico de caracteres (OCR), en los que solo se conoce el producto final, los algoritmos en línea pueden proporcionar un mayor nivel de precisión debido a indicadores adicionales, como la secuencia temporal y la presión de los trazos de tinta individuales.
Casos de uso sugeridos para la API de Handwriting Recognition
Estos son algunos ejemplos de uso:
- Aplicaciones para tomar notas en las que los usuarios quieren capturar notas escritas a mano y traducirlas a texto.
- Aplicaciones de formularios en las que los usuarios pueden usar la entrada con lápiz o con el dedo debido a limitaciones de tiempo
- Juegos que requieren completar letras o números, como crucigramas, ahorcado o sudoku
Estado actual
La API de Handwriting Recognition está disponible a partir de Chromium 99.
Cómo usar la API de Handwriting Recognition
Detección de características
Para detectar la compatibilidad del navegador, verifica la existencia del método createHandwritingRecognizer()
en el objeto navigator:
if ('createHandwritingRecognizer' in navigator) {
// 🎉 The Handwriting Recognition API is supported!
}
Conceptos básicos
La API de Handwriting Recognition convierte la entrada manuscrita en texto, independientemente del método de entrada (mouse, táctil, lápiz). La API tiene cuatro entidades principales:
- Un punto representa dónde estaba el puntero en un momento determinado.
- Un trazo consta de uno o más puntos. La grabación de un trazo comienza cuando el usuario baja el puntero (es decir, hace clic con el botón principal del mouse o toca la pantalla con el lápiz óptico o el dedo) y termina cuando vuelve a levantar el puntero.
- Un dibujo consta de uno o más trazos. El reconocimiento real se lleva a cabo en este nivel.
- El reconocedor se configura con el idioma de entrada esperado. Se usa para crear una instancia de un dibujo con la configuración del reconocedor aplicada.
Estos conceptos se implementan como interfaces y diccionarios específicos, que abordaré en breve.
Cómo crear un reconocedor
Para reconocer texto a partir de la entrada manuscrita, debes obtener una instancia de HandwritingRecognizer
llamando a navigator.createHandwritingRecognizer()
y pasando restricciones. Las restricciones determinan el modelo de reconocimiento de escritura a mano que se debe usar. Actualmente, puedes especificar una lista de idiomas en orden de preferencia:
const recognizer = await navigator.createHandwritingRecognizer({
languages: ['en'],
});
El método devuelve una promesa que se resuelve con una instancia de HandwritingRecognizer
cuando el navegador puede satisfacer tu solicitud. De lo contrario, rechazará la promesa con un error y no estará disponible el reconocimiento de escritura a mano. Por este motivo, es posible que primero desees consultar la compatibilidad del reconocedor con funciones de reconocimiento particulares.
Cómo consultar la compatibilidad del reconocedor
Si llamas a navigator.queryHandwritingRecognizer()
, puedes verificar si la plataforma de destino admite las funciones de reconocimiento de escritura a mano que deseas usar. Este método toma el mismo objeto de restricción que el método navigator.createHandwritingRecognizer()
, que contiene la lista de idiomas solicitados. El método devuelve una promesa que se resuelve con un objeto de resultado si se encuentra un reconocedor compatible. De lo contrario, la promesa se resuelve en null
.
En el siguiente ejemplo, el desarrollador hace lo siguiente:
- quiere detectar textos en inglés
- Obtener predicciones alternativas y menos probables cuando estén disponibles
- obtener acceso al resultado de la segmentación, es decir, los caracteres reconocidos, incluidos los puntos y los trazos que los componen
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
Si el navegador admite la función que necesita el desarrollador, su valor se establecerá en true
en el objeto de resultado. De lo contrario, se establecerá como false
.
Puedes usar esta información para habilitar o inhabilitar ciertas funciones dentro de tu aplicación, o bien para enviar una nueva consulta para un conjunto diferente de idiomas.
Cómo iniciar un dibujo
En tu aplicación, debes ofrecer un área de entrada donde el usuario realice sus entradas escritas a mano. Por motivos de rendimiento, se recomienda implementar esto con la ayuda de un objeto de lienzo. La implementación exacta de esta parte queda fuera del alcance de este artículo, pero puedes consultar la demostración para ver cómo se puede hacer.
Para comenzar un nuevo dibujo, llama al método startDrawing()
en el reconocedor. Este método toma un objeto que contiene diferentes sugerencias para ajustar el algoritmo de reconocimiento. Todas las sugerencias son opcionales:
- El tipo de texto que se ingresa: texto, direcciones de correo electrónico, números o un carácter individual
(
recognitionType
) - Tipo de dispositivo de entrada: mouse, entrada táctil o con lápiz (
inputType
) - El texto anterior (
textContext
) - Cantidad de predicciones alternativas menos probables que se deben devolver (
alternatives
) - Lista de caracteres identificables por el usuario (“grafemas”) que es más probable que el usuario ingrese (
graphemeSet
)
La API de Handwriting Recognition funciona bien con los eventos de puntero, que proporcionan una interfaz abstracta para consumir la entrada de cualquier dispositivo de puntero. Los argumentos del evento de puntero contienen el tipo de puntero que se usa. Esto significa que puedes usar eventos de puntero para determinar el tipo de entrada automáticamente. En el siguiente ejemplo, el dibujo para el reconocimiento de escritura a mano se crea automáticamente en la primera ocurrencia de un evento pointerdown
en el área de escritura a mano. Como pointerType
puede estar vacío o establecido en un valor propietario, introduje una verificación de coherencia para asegurarme de que solo se establezcan valores admitidos para el tipo de entrada del dibujo.
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);
});
Cómo agregar un trazo
El evento pointerdown
también es el lugar adecuado para comenzar un nuevo trazo. Para ello, crea una instancia nueva de HandwritingStroke
. Además, debes almacenar la hora actual como punto de referencia para los puntos posteriores que se agreguen:
function startStroke(event) {
activeStroke = {
stroke: new HandwritingStroke(),
startTime: Date.now(),
};
addPoint(event);
}
Añada un punto
Después de crear el trazo, debes agregarle el primer punto directamente. Como agregarás más puntos más adelante, tiene sentido implementar la lógica de creación de puntos en un método independiente. En el siguiente ejemplo, el método addPoint()
calcula el tiempo transcurrido desde la marca de tiempo de referencia.
La información temporal es opcional, pero puede mejorar la calidad del reconocimiento. Luego, lee las coordenadas X e Y del evento del puntero y agrega el punto al trazo actual.
function addPoint(event) {
const timeElapsed = Date.now() - activeStroke.startTime;
activeStroke.stroke.addPoint({
x: event.offsetX,
y: event.offsetY,
t: timeElapsed,
});
}
Se llama al controlador de eventos pointermove
cuando se mueve el puntero por la pantalla. Esos puntos también deben agregarse al trazo. El evento también se puede generar si el puntero no está en un estado "hacia abajo", por ejemplo, cuando se mueve el cursor por la pantalla sin presionar el botón del mouse. El controlador de eventos del siguiente ejemplo verifica si existe un trazo activo y le agrega el punto nuevo.
canvas.addEventListener('pointermove', (event) => {
if (activeStroke) {
addPoint(event);
}
});
Reconoce texto
Cuando el usuario vuelve a levantar el puntero, puedes agregar el trazo a tu dibujo llamando a su método addStroke()
. En el siguiente ejemplo, también se restablece activeStroke
, por lo que el controlador pointermove
no agregará puntos al trazo completado.
A continuación, es momento de reconocer la entrada del usuario llamando al método getPrediction()
en el dibujo. El reconocimiento suele tardar menos de unos cientos de milisegundos, por lo que puedes ejecutar predicciones varias veces si es necesario. En el siguiente ejemplo, se ejecuta una nueva predicción después de cada trazo completado.
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));
});
Este método devuelve una promesa que se resuelve con un array de predicciones ordenadas según su probabilidad. La cantidad de elementos depende del valor que pasaste a la sugerencia alternatives
. Puedes usar este array para presentarle al usuario una opción de posibles coincidencias y permitirle seleccionar una opción. Como alternativa, puedes simplemente elegir la predicción más probable, que es lo que hago en el ejemplo.
El objeto de predicción contiene el texto reconocido y un resultado de segmentación opcional, que analizaré en la siguiente sección.
Estadísticas detalladas con resultados de segmentación
Si la plataforma de destino lo admite, el objeto de predicción también puede contener un resultado de segmentación.
Es un array que contiene todos los segmentos de escritura a mano reconocidos, una combinación del carácter reconocible por el usuario reconocido (grapheme
) junto con su posición en el texto reconocido (beginIndex
, endIndex
) y los trazos y puntos que lo crearon.
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);
});
},
);
}
Puedes usar esta información para volver a rastrear los grafemas reconocidos en el lienzo.
Reconocimiento completo
Una vez que se complete el reconocimiento, puedes liberar recursos llamando al método clear()
en HandwritingDrawing
y al método finish()
en HandwritingRecognizer
:
drawing.clear();
recognizer.finish();
Demostración
El componente web <handwriting-textarea>
implementa un control de edición mejorado progresivamente que puede reconocer la escritura a mano. Si haces clic en el botón que se encuentra en la esquina inferior derecha del control de edición, activarás el modo de dibujo. Cuando completes el dibujo, el componente web iniciará automáticamente el reconocimiento y agregará el texto reconocido al control de edición. Si la API de Handwriting Recognition no es compatible en absoluto o la plataforma no admite las funciones solicitadas, se ocultará el botón de edición. Sin embargo, el control de edición básico sigue siendo utilizable como un <textarea>
.
El componente web ofrece propiedades y atributos para definir el comportamiento del reconocimiento desde el exterior, incluidos languages
y recognitiontype
. Puedes establecer el contenido del control a través del atributo value
:
<handwriting-textarea languages="en" recognitiontype="text" value="Hello"></handwriting-textarea>
Para estar al tanto de cualquier cambio en el valor, puedes escuchar el evento input
.
Puedes probar el componente con esta demostración en GitHub. También asegúrate de consultar el código fuente. Para usar el control en tu aplicación, obténlo de npm.
Seguridad y permisos
El equipo de Chromium diseñó e implementó la API de Handwriting Recognition con los principios fundamentales definidos en Controlling Access to Powerful Web Platform Features, incluidos el control del usuario, la transparencia y la ergonomía.
Control de usuarios
El usuario no puede desactivar la API de Handwriting Recognition. Solo está disponible para sitios web que se entregan a través de HTTPS y solo se puede llamar desde el contexto de navegación de nivel superior.
Transparencia
No hay ninguna indicación de si el reconocimiento de escritura a mano está activo. Para evitar el fingerprinting, el navegador implementa contramedidas, como mostrarle al usuario una solicitud de permiso cuando detecta un posible abuso.
Persistencia de permisos
Actualmente, la API de Handwriting Recognition no muestra ningún mensaje de permisos. Por lo tanto, no es necesario conservar el permiso de ninguna manera.
Comentarios
El equipo de Chromium quiere conocer tu experiencia con la API de Handwriting Recognition.
Cuéntanos sobre el diseño de la API
¿Hay algo sobre la API que no funciona como esperabas? ¿O faltan métodos o propiedades que necesitas para implementar tu idea? ¿Tienes alguna pregunta o comentario sobre el modelo de seguridad? Informa un problema de especificación en el repositorio de GitHub correspondiente o agrega tu opinión a un problema existente.
Informa un problema con la implementación
¿Encontraste un error en la implementación de Chromium? ¿O la implementación es diferente de la especificación?
Presenta un error en new.crbug.com. Asegúrate de incluir tantos detalles como sea posible, instrucciones sencillas para reproducir el error y, luego, ingresa Blink>Handwriting
en el cuadro Components.
Cómo mostrar compatibilidad con la API
¿Planeas usar la API de Handwriting Recognition? Tu apoyo público ayuda al equipo de Chromium a priorizar funciones y muestra a otros proveedores de navegadores lo importante que es admitirlas.
Comparte cómo planeas usarlo en el hilo de Discourse del WICG. Envía un tweet a @ChromiumDev con el hashtag #HandwritingRecognition
y cuéntanos dónde y cómo lo usas.
Vínculos útiles
- Explicación
- Borrador de especificaciones
- Repositorio de GitHub
- ChromeStatus
- Error de Chromium
- Revisión del TAG
- Intención de crear un prototipo
- Hilo de WebKit-Dev
- Posición de los estándares de Mozilla
Agradecimientos
Joe Medley, Honglin Yu y Jiewei Qian revisaron este documento.