Распознавайте своих пользователей' почерк

API распознавания рукописного ввода позволяет распознавать рукописный текст по мере его ввода.

Что такое API распознавания рукописного ввода?

API распознавания рукописного ввода позволяет преобразовывать рукописный текст (чернила) ваших пользователей в печатный. Некоторые операционные системы уже давно поддерживают подобные API, и благодаря этой новой возможности ваши веб-приложения наконец-то смогут использовать эту функциональность. Преобразование происходит непосредственно на устройстве пользователя, работает даже в офлайн-режиме, без установки каких-либо сторонних библиотек или сервисов.

Этот API реализует так называемое «онлайновое» или почти онлайновое распознавание. Это означает, что рукописный ввод распознаётся непосредственно во время рисования пользователем, путём захвата и анализа отдельных штрихов. В отличие от «офлайновых» процедур, таких как оптическое распознавание символов (OCR), где известен только конечный результат, онлайновые алгоритмы могут обеспечить более высокий уровень точности благодаря дополнительным сигналам, таким как временная последовательность и нажим отдельных штрихов чернил.

Предлагаемые варианты использования API распознавания рукописного ввода

Примеры использования включают в себя:

  • Приложения для создания заметок, в которых пользователи хотят сохранять рукописные заметки и переводить их в текст.
  • Создает приложения, в которых пользователи могут использовать стилус или ввод пальцем из-за ограничений по времени.
  • Игры, требующие заполнения букв или цифр, например, кроссворды, виселица или судоку.

Текущий статус

API распознавания рукописного ввода доступен в (Chromium 99).

Как использовать API распознавания рукописного ввода

Обнаружение особенностей

Определите поддержку браузера, проверив наличие метода createHandwritingRecognizer() в объекте навигатора:

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

Основные концепции

API распознавания рукописного ввода преобразует рукописный ввод в текст независимо от способа ввода (мышь, сенсорный экран, стилус). API включает четыре основных компонента:

  1. Точка обозначает место, где находился указатель в определенный момент времени.
  2. Росчерк состоит из одной или нескольких точек. Запись росчерка начинается, когда пользователь опускает указатель (т. е. нажимает основную кнопку мыши или касается экрана стилусом или пальцем), и заканчивается, когда он снова поднимает указатель.
  3. Рисунок состоит из одного или нескольких штрихов. Именно на этом уровне происходит фактическое распознавание.
  4. Распознаватель настроен на ожидаемый язык ввода. Он используется для создания экземпляра чертежа с примененной конфигурацией распознавателя.

Эти концепции реализованы в виде специальных интерфейсов и словарей, о которых я расскажу далее.

Основные сущности API распознавания рукописного ввода: одна или несколько точек составляют штрих, один или несколько штрихов составляют рисунок, создаваемый распознавателем. Само распознавание происходит на уровне рисунка.

Создание распознавателя

Для распознавания рукописного текста необходимо получить экземпляр HandwritingRecognizer , вызвав navigator.createHandwritingRecognizer() и передав ему ограничения. Ограничения определяют модель распознавания рукописного ввода, которую следует использовать. В настоящее время можно указать список языков в порядке предпочтения:

const recognizer = await navigator.createHandwritingRecognizer({
  languages: ['en'],
});

Метод возвращает обещание, разрешающееся экземпляром HandwritingRecognizer , когда браузер может выполнить ваш запрос. В противном случае он отклонит обещание с ошибкой, и распознавание рукописного ввода будет недоступно. Поэтому вам может потребоваться сначала запросить поддержку распознавателем определённых функций распознавания.

Запрос поддержки распознавателя

Вызвав navigator.queryHandwritingRecognizer() , вы можете проверить, поддерживает ли целевая платформа функции распознавания рукописного ввода, которые вы планируете использовать. Этот метод принимает тот же объект ограничений, что и метод navigator.createHandwritingRecognizer() , содержащий список запрошенных языков. Метод возвращает обещание, разрешающееся с объектом результата, если найден совместимый распознаватель. В противном случае обещание разрешается в null . В следующем примере разработчик:

  • хочет обнаружить тексты на английском языке
  • получить альтернативные, менее вероятные прогнозы, когда они доступны
  • получить доступ к результату сегментации, т.е. распознанным символам, включая точки и штрихи, из которых они состоят
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

Если браузер поддерживает нужную разработчику функцию, в объекте результата будет установлено значение true . В противном случае — false . Вы можете использовать эту информацию для включения или отключения определённых функций в вашем приложении или для отправки нового запроса для другого набора языков.

Начать рисунок

В вашем приложении необходимо предусмотреть область ввода, где пользователь может вносить рукописные данные. Из соображений производительности рекомендуется реализовать это с помощью объекта Canvas . Точная реализация этой части выходит за рамки данной статьи, но вы можете ознакомиться с демонстрационной версией , чтобы увидеть, как это можно сделать.

Чтобы начать новый рисунок, вызовите метод startDrawing() распознавателя. Этот метод принимает объект, содержащий различные подсказки для точной настройки алгоритма распознавания. Все подсказки необязательны:

  • Тип вводимого текста: текст, адреса электронной почты, цифры или отдельный символ ( recognitionType )
  • Тип устройства ввода: мышь, сенсорный экран или стилус ( inputType )
  • Предшествующий текст ( textContext )
  • Количество менее вероятных альтернативных прогнозов, которые должны быть возвращены ( alternatives )
  • Список идентифицируемых пользователем символов («графем»), которые пользователь, скорее всего, введет ( graphemeSet )

API распознавания рукописного ввода хорошо работает с событиями указателя , которые предоставляют абстрактный интерфейс для обработки ввода с любого указывающего устройства. Аргументы событий указателя содержат тип используемого указателя. Это означает, что вы можете использовать события указателя для автоматического определения типа ввода. В следующем примере рисунок для распознавания рукописного ввода автоматически создаётся при первом возникновении события pointerdown в области рукописного ввода. Поскольку pointerType может быть пустым или иметь собственное значение, я ввёл проверку согласованности, чтобы убедиться, что для типа ввода рисунка заданы только поддерживаемые значения.

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);
});

Добавить штрих

Событие pointerdown также является подходящим местом для начала нового штриха. Для этого создайте новый экземпляр HandwritingStroke . Кроме того, следует сохранить текущее время в качестве точки отсчёта для последующих добавляемых к нему точек:

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

Добавить точку

После создания штриха необходимо непосредственно добавить к нему первую точку. Поскольку в дальнейшем вы будете добавлять другие точки, имеет смысл реализовать логику создания точки в отдельном методе. В следующем примере метод addPoint() вычисляет прошедшее время на основе контрольной временной метки. Временная информация необязательна, но может улучшить качество распознавания. Затем он считывает координаты X и Y из события указателя и добавляет точку к текущему штриху.

function addPoint(event) {
  const timeElapsed = Date.now() - activeStroke.startTime;
  activeStroke.stroke.addPoint({
    x: event.offsetX,
    y: event.offsetY,
    t: timeElapsed,
  });
}

Обработчик событий pointermove вызывается при перемещении указателя по экрану. Эти точки также необходимо добавить к штриху. Событие также может быть вызвано, если указатель не находится в состоянии «нажат», например, при перемещении курсора по экрану без нажатия кнопки мыши. Обработчик событий из следующего примера проверяет наличие активного штриха и добавляет к нему новую точку.

canvas.addEventListener('pointermove', (event) => {
  if (activeStroke) {
    addPoint(event);
  }
});

Распознать текст

Когда пользователь снова поднимет указатель, вы можете добавить штрих к рисунку, вызвав метод addStroke() . Следующий пример также сбрасывает activeStroke , поэтому обработчик pointermove не будет добавлять точки к завершённому штриху.

Далее нужно распознать ввод пользователя, вызвав метод getPrediction() для рисунка. Распознавание обычно занимает менее нескольких сотен миллисекунд, поэтому при необходимости можно многократно запускать прогнозы. В следующем примере новый прогноз выполняется после каждого завершённого штриха.

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));
});

Этот метод возвращает обещание, которое разрешается массивом прогнозов, упорядоченных по степени вероятности. Количество элементов зависит от значения, переданного в подсказку об alternatives . Вы можете использовать этот массив, чтобы предоставить пользователю выбор возможных совпадений и предложить ему выбрать один из вариантов. В качестве альтернативы можно просто выбрать наиболее вероятный прогноз, как я и делаю в примере.

Объект прогнозирования содержит распознанный текст и необязательный результат сегментации, который я рассмотрю в следующем разделе.

Подробная информация с результатами сегментации

Если целевая платформа поддерживает эту функцию, объект прогнозирования также может содержать результат сегментации. Это массив, содержащий все распознанные сегменты рукописного текста, комбинацию распознанного пользователем символа ( grapheme ) и его положения в распознанном тексте ( beginIndex , endIndex ), а также штрихов и точек, которые его создали.

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);
      });
    },
  );
}

Эту информацию можно использовать для повторного отслеживания распознанных графем на холсте.

Вокруг каждой распознанной графемы рисуются рамки.

Полное признание

После завершения распознавания вы можете освободить ресурсы, вызвав метод clear() для HandwritingDrawing и метод finish() для HandwritingRecognizer :

drawing.clear();
recognizer.finish();

Демо

Веб-компонент <handwriting-textarea> реализует усовершенствованный элемент управления для редактирования, способный распознавать рукописный ввод. Нажатие кнопки в правом нижнем углу элемента управления активирует режим рисования. После завершения рисования веб-компонент автоматически запустит распознавание и добавит распознанный текст обратно в элемент управления. Если API распознавания рукописного ввода вообще не поддерживается или платформа не поддерживает запрошенные функции, кнопка редактирования будет скрыта. Однако базовый элемент управления для редактирования по-прежнему можно использовать как <textarea> .

Веб-компонент предлагает свойства и атрибуты для определения поведения распознавания извне, включая languages и recognitiontype . Содержимое элемента управления можно задать с помощью атрибута value :

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

Чтобы быть в курсе любых изменений значения, вы можете прослушать событие input .

Вы можете опробовать компонент, используя эту демо-версию на GitHub . Также обязательно ознакомьтесь с исходным кодом . Чтобы использовать элемент управления в своём приложении, получите его из npm .

Безопасность и разрешения

Команда Chromium разработала и реализовала API распознавания рукописного ввода, используя основные принципы, определенные в документе «Управление доступом к мощным функциям веб-платформы» , включая пользовательский контроль, прозрачность и эргономичность.

Пользовательский контроль

API распознавания рукописного ввода не может быть отключен пользователем. Он доступен только для веб-сайтов, работающих по протоколу HTTPS, и может быть вызван только из контекста браузера верхнего уровня.

Прозрачность

Индикация активности распознавания рукописного ввода отсутствует. Чтобы предотвратить создание цифровых отпечатков, браузер реализует контрмеры, например, отображает пользователю запрос на разрешение при обнаружении возможного злоупотребления.

Сохранение разрешения

API распознавания рукописного ввода в настоящее время не выводит запросы на разрешение. Таким образом, разрешение не требуется каким-либо образом сохранять.

Обратная связь

Команда Chromium хочет узнать о вашем опыте работы с API распознавания рукописного ввода.

Расскажите нам о дизайне API

Есть ли что-то в API, что работает не так, как вы ожидали? Или отсутствуют методы или свойства, необходимые для реализации вашей идеи? Есть вопросы или комментарии по модели безопасности? Отправьте запрос на спецификацию в соответствующий репозиторий GitHub или добавьте свои замечания к существующему запросу.

Сообщить о проблеме с реализацией

Вы обнаружили ошибку в реализации Chromium? Или реализация отличается от спецификации? Сообщите об ошибке на сайте new.crbug.com . Опишите проблему как можно подробнее, добавьте простые инструкции по её воспроизведению и введите Blink>Handwriting в поле «Компоненты» .

Показать поддержку API

Планируете ли вы использовать API распознавания рукописного ввода? Ваша публичная поддержка помогает команде Chromium расставлять приоритеты в отношении функций и показывает другим разработчикам браузеров, насколько важна их поддержка.

Расскажите, как вы планируете использовать эту функцию, в теме обсуждения WICG . Отправьте твит @ChromiumDev с хэштегом #HandwritingRecognition и расскажите, где и как вы её используете.

Благодарности

Этот документ был рассмотрен Джо Медлей , Хунлинем Юем и Цзевэем Цянем.