Reconoce la escritura a mano de tus usuarios

La API de reconocimiento de escritura a mano te permite reconocer el texto de las entradas escritas a mano a medida que sucede.

¿Qué es la API de reconocimiento de escritura a mano?

La API de reconocimiento de escritura a mano te permite convertir la escritura a mano (tinta) de los usuarios en texto. Algunos sistemas operativos llevan mucho tiempo incluyendo esas APIs y, con esta nueva capacidad, tus aplicaciones web finalmente pueden usarla. La conversión se realiza directamente en el dispositivo del usuario y funciona incluso en modo sin conexión, sin necesidad de agregar bibliotecas o servicios de terceros.

Esta API implementa el conocido reconocimiento "en línea" o casi en tiempo real. Esto significa que la entrada de escritura a mano se reconoce mientras el usuario la dibuja mediante la captura y el análisis de 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 señales adicionales, como la secuencia temporal y la presión de los trazos individuales de la tinta.

Casos de uso sugeridos para la API de reconocimiento de escritura a mano

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
  • Crea aplicaciones en las que los usuarios pueden utilizar la entrada con un lápiz o un dedo debido a las limitaciones de tiempo.
  • Juegos que requieren completar letras o números, como crucigramas, ahorcados o sudoku.

Estado actual

La API de reconocimiento de escritura a mano está disponible en Chromium 99.

Cómo usar la API de reconocimiento de escritura a mano

Detección de atributos

Para detectar la compatibilidad con el navegador, verifica la existencia del método createHandwritingRecognizer() en el objeto de navegador:

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

Conceptos básicos

La API de reconocimiento de escritura a mano convierte entradas escritas a mano en texto, sin importar el método de entrada (mouse, táctil, lápiz). La API tiene cuatro entidades principales:

  1. Un punto representa la ubicación del puntero en un momento determinado.
  2. 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 en el botón principal del mouse o toca la pantalla con el bolígrafo o el dedo) y finaliza cuando vuelve a levantar el puntero.
  3. Un dibujo consta de uno o más trazos. El reconocimiento real se realiza en este nivel.
  4. El recognizer está configurado 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, de los que hablaremos más adelante.

Entidades centrales de la API de reconocimiento de escritura a mano: uno o más puntos componen un trazo, uno o más trazos componen un dibujo, que crea el reconocedor. El reconocimiento real se realiza en el nivel del dibujo.

Crea un reconocedor

Para reconocer texto de entradas escritas a mano, debes obtener una instancia de un HandwritingRecognizer llamando a navigator.createHandwritingRecognizer() y pasándole 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 muestra una promesa que se resuelve con una instancia de HandwritingRecognizer cuando el navegador puede cumplir con tu solicitud. De lo contrario, rechazará la promesa con un error, y el reconocimiento de escritura a mano no estará disponible. Por este motivo, te recomendamos que primero consultes la compatibilidad del reconocedor con características de reconocimiento específicas.

Consulta la compatibilidad con el reconocedor

Si llamas a navigator.queryHandwritingRecognizerSupport(), puedes verificar si la plataforma de destino es compatible con las funciones de reconocimiento de escritura a mano que deseas usar. 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 trazos que los componen
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

El método muestra una promesa que se resuelve con un objeto de resultado. Si el navegador admite la función que especificó el desarrollador, su valor se establecerá como true. 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 ajustar tu consulta y enviar una nueva.

Comienza un dibujo

Dentro de tu aplicación, debes ofrecer un área de entrada en la que el usuario realice sus entradas escritas a mano. Por motivos de rendimiento, se recomienda implementar esta práctica con la ayuda de un objeto de lienzo. La implementación exacta de esta parte está 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)
  • El tipo de dispositivo de entrada: entrada táctil, de mouse o con forma de lápiz (inputType)
  • El texto anterior (textContext)
  • La cantidad de predicciones alternativas menos probables que se deben mostrar (alternatives)
  • Una lista de caracteres que identifican el usuario (“grafemas”) con mayor probabilidad de ingresar (graphemeSet)

La API de reconocimiento de escritura a mano funciona bien con los eventos de puntero, que proporcionan una interfaz abstracta para consumir entradas desde cualquier dispositivo apuntador. Los argumentos de eventos del 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 la primera vez que se produce un evento pointerdown en el área de escritura a mano. Como pointerType puede estar vacío o establecerse en un valor de propiedad, introduje una verificación de coherencia para garantizar que solo se establezcan los 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', 'pen'].find((type) => type === event.pointerType),
      textContext: 'Hello, ',
      alternatives: 2,
      graphemeSet: ['f', 'i', 'z', 'b', 'u'], // for a fizz buzz entry form
    });
  }
  startStroke(event);
});

Agregar un trazo

El evento pointerdown también es el lugar adecuado para comenzar un nuevo trazo. Para hacerlo, crea una instancia nueva de HandwritingStroke. Además, debes almacenar la hora actual como punto de referencia para los puntos posteriores que se le agregaron:

function startStroke(event) {
  activeStroke = {
    stroke: new HandwritingStroke(),
    startTime: Date.now(),
  };
  addPoint(event);
}

Agregar un punto

Después de crear el trazo, debes agregarle directamente el primer punto. Como agregarás más puntos más adelante, tiene sentido implementar la lógica de creación de puntos en un método separado. En el siguiente ejemplo, el método addPoint() calcula el tiempo transcurrido a partir de 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 el puntero se mueve por la pantalla. Esos puntos también se deben agregar al trazo. El evento también se puede generar si el puntero no está en estado "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 vuelva a levantar el puntero, podrás agregar el trazo a tu dibujo llamando a su método addStroke(). En el siguiente ejemplo, también se restablece el activeStroke, por lo que el controlador pointermove no agregará puntos al trazo completado.

A continuación, es el momento de reconocer la entrada del usuario mediante una llamada al método getPrediction() en el dibujo. El reconocimiento suele tardar menos de unos cientos de milisegundos, por lo que puedes ejecutar predicciones de forma repetida si es necesario. En el siguiente ejemplo, se ejecuta una predicción nueva 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 muestra una promesa que se resuelve con un arreglo de predicciones ordenadas según su probabilidad. La cantidad de elementos depende del valor que hayas pasado a la sugerencia alternatives. Puedes usar este array para presentar al usuario una variedad de coincidencias posibles y hacer que seleccione una opción. Como alternativa, puedes simplemente usar 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 analizaremos 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. Este es un array que contiene todos los segmentos de escritura a mano reconocidos, una combinación del carácter reconocido por el usuario (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);
      });
    },
  );
}

Podrías usar esta información para rastrear los grafemas reconocidos en el lienzo nuevamente.

Se dibujan cajas alrededor de cada grafema reconocido

Reconocimiento completo

Una vez que se haya completado 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 de forma progresiva capaz de reconocer la escritura a mano. Si haces clic en el botón de la esquina inferior derecha del control de edición, se activará el modo de dibujo. Cuando completes el dibujo, el componente web iniciará automáticamente el reconocimiento y agregará el texto reconocido de nuevo al control de edición. Si la API de reconocimiento de escritura a mano no es compatible o la plataforma no admite las funciones solicitadas, el botón de edición estará oculto. Sin embargo, el control de edición básico aún se puede usar como <textarea>.

El componente web ofrece propiedades y atributos para definir el comportamiento de reconocimiento desde el exterior, incluidos languages y recognitiontype. Puedes configurar el contenido del control mediante el atributo value:

<handwriting-textarea languages="en" recognitiontype="text" value="Hello"></handwriting-textarea>

Para recibir notificaciones sobre cualquier cambio en el valor, puedes escuchar el evento input.

Puedes probar el componente con esta demostración en Glitch. Además, asegúrate de consultar el código fuente. Para usar el control en tu aplicación, obtenlo de npm.

Seguridad y permisos

El equipo de Chromium diseñó e implementó la API de reconocimiento de escritura a mano mediante los principios fundamentales definidos en el artículo Cómo controlar el acceso a funciones potentes de la plataforma web, incluidos el control del usuario, la transparencia y la ergonomía.

Control de usuarios

El usuario no puede desactivar la API de reconocimiento de escritura a mano. Solo está disponible para sitios web entregados a través de HTTPS y solo se puede llamar desde el contexto de navegación de nivel superior.

Transparencia

No se indica si el reconocimiento de escritura a mano está activo. Para evitar la creación de huellas digitales, el navegador implementa medidas, como mostrar un mensaje de permiso al usuario cuando detecta un posible abuso.

Persistencia de permisos

Actualmente, la API de reconocimiento de escritura a mano no muestra mensajes de permiso. 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 reconocimiento de escritura a mano.

Cuéntanos sobre el diseño de API

¿Hay algo acerca de 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 error de especificaciones en el repositorio de GitHub correspondiente o agrega tus ideas sobre un problema existente.

Informar un problema con la implementación

¿Encontraste un error en la implementación de Chromium? ¿La implementación es diferente de la especificación? Informa un error en new.crbug.com. Asegúrate de incluir todos los detalles que puedas, además de instrucciones simples para la reproducción, y, luego, ingresa Blink>Handwriting en el cuadro Componentes. Glitch funciona muy bien para compartir repros rápidos y fáciles.

Demuestra compatibilidad con la API

¿Planeas usar la API de reconocimiento de escritura a mano? Tu asistencia pública ayuda al equipo de Chromium a priorizar las funciones y les muestra a otros proveedores de navegadores lo fundamental que es admitirlas.

Comparte cómo planeas usarlo en la conversación del curso de WICG. Envía un tuit a @ChromiumDev con el hashtag #HandwritingRecognition y cuéntanos dónde y cómo lo estás usando.

Agradecimientos

Joe Medley, Honglin Yu y Jiewei Qian revisaron este artículo. Hero image de Samir Bouaked en Unsplash.