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

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

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

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

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

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

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

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

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

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 .

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Поделитесь тем, как вы планируете использовать его, в ветке WICG Discourse . Отправьте твит @ChromiumDev с хэштегом #HandwritingRecognition и расскажите нам, где и как вы его используете.

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

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