사용자 필기 인식

필기 인식 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의 핵심 개체: 인식기에서 만들어지는 한 개 이상의 점이 획을 구성하고, 하나 이상의 획이 그림을 구성합니다. 실제 인식은 그리기 수준에서 이루어집니다.

인식기 만들기

필기 입력에서 텍스트를 인식하려면 navigator.createHandwritingRecognizer()를 호출하고 제약 조건을 전달하여 HandwritingRecognizer의 인스턴스를 가져와야 합니다. 제약 조건은 사용해야 하는 필기 인식 모델을 결정합니다. 현재 선호하는 순서대로 언어 목록을 지정할 수 있습니다.

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

이 메서드는 브라우저가 요청을 처리할 수 있을 때 HandwritingRecognizer의 인스턴스를 포함하는 프로미스 확인을 반환합니다. 그렇지 않으면 오류가 있는 프로미스가 거부되고 필기 인식을 사용할 수 없습니다. 이러한 이유로 먼저 특정 인식 기능에 대한 인식기의 지원을 쿼리하는 것이 좋습니다.

인식기 지원 쿼리

navigator.queryHandwritingRecognizerSupport()를 호출하여 사용하려는 필기 인식 기능을 타겟 플랫폼에서 지원하는지 확인할 수 있습니다. 다음 예에서 개발자는

  • 영어로 된 텍스트를 감지하려고 함
  • 대체 값 가져오기, 가능한 경우 예측 가능성 낮음
  • 세그멘테이션 결과, 즉 이를 구성하는 점과 획을 포함하여 인식된 문자에 액세스할 수 있습니다.
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

이 메서드는 결과 객체로 결정된 프로미스를 반환합니다. 브라우저가 개발자가 지정한 기능을 지원하면 값이 true로 설정됩니다. 그렇지 않으면 false로 설정됩니다. 이 정보를 사용하여 애플리케이션 내의 특정 기능을 사용 설정 또는 중지하거나 쿼리를 조정하고 새 쿼리를 전송할 수 있습니다.

그리기 시작

애플리케이션 내에 사용자가 필기 입력을 하는 입력 영역을 제공해야 합니다. 성능상의 이유로 캔버스 객체를 사용하여 구현하는 것이 좋습니다. 이 부분의 정확한 구현은 이 도움말의 범위를 벗어나지만 데모를 참고하여 방법을 확인할 수 있습니다.

새 그리기를 시작하려면 인식기에서 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', 'pen'].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);
      });
    },
  );
}

이 정보를 사용하여 캔버스에서 인식된 그래프를 다시 추적할 수 있습니다.

인식된 각 그래픽 이미지 주위에 상자가 그려져 있음

인식 완료

인식이 완료된 후 HandwritingDrawing에서 clear() 메서드를 호출하고 HandwritingRecognizer에서 finish() 메서드를 호출하여 리소스를 해제할 수 있습니다.

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

데모

웹 구성요소 <handwriting-textarea>는 필기 인식을 할 수 있는 편집 컨트롤을 점진적으로 개선합니다. 수정 컨트롤의 오른쪽 하단에 있는 버튼을 클릭하면 그리기 모드가 활성화됩니다. 그리기를 완료하면 웹 구성요소가 자동으로 인식을 시작하고 인식된 텍스트를 다시 편집 컨트롤에 추가합니다. 필기 인식 API가 전혀 지원되지 않거나 플랫폼에서 요청된 기능을 지원하지 않으면 수정 버튼이 숨겨집니다. 그러나 기본 수정 컨트롤은 <textarea>으로 계속 사용할 수 있습니다.

웹 구성요소는 languagesrecognitiontype를 비롯하여 외부에서 인식 동작을 정의하는 속성과 속성을 제공합니다. value 속성을 통해 컨트롤의 콘텐츠를 설정할 수 있습니다.

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

값의 변경사항에 관한 알림을 받으려면 input 이벤트를 수신 대기하면 됩니다.

Glitch에서 이 데모를 사용하여 구성요소를 사용해 볼 수 있습니다. 소스 코드도 확인해야 합니다. 애플리케이션에서 컨트롤을 사용하려면 npm에서 컨트롤을 가져오세요.

보안 및 권한

Chromium팀은 사용자 제어, 투명성, 인체공학 등 강력한 웹 플랫폼 기능에 대한 액세스 제어에 정의된 핵심 원칙을 사용하여 Handwriting Recognition API를 설계하고 구현했습니다.

사용자 제어

필기 인식 API는 사용자가 사용 중지할 수 없습니다. HTTPS를 통해 제공되는 웹사이트에서만 사용할 수 있으며 최상위 탐색 컨텍스트에서만 호출할 수 있습니다.

투명성

필기 인식이 활성화되었는지 여부는 표시되지 않습니다. 디지털 지문 수집을 방지하기 위해 브라우저는 가능한 악용 사례를 감지할 때 사용자에게 권한 메시지를 표시하는 등의 대응 조치를 구현합니다.

권한 지속성

필기 인식 API에서는 현재 권한 메시지를 표시하지 않습니다. 따라서 권한을 어떤 식으로든 유지할 필요가 없습니다.

의견

Chromium팀은 Handwriting Recognition API 사용 경험에 관한 의견을 기다리고 있습니다.

API 디자인에 대해 알려주세요.

API에 대해 예상한 대로 작동하지 않는 부분이 있나요? 아니면 아이디어를 구현하는 데 필요한 메서드나 속성이 누락되어 있나요? 보안 모델에 대한 질문이나 의견이 있으신가요? 해당 GitHub 저장소에서 사양 문제를 제출하거나 기존 문제에 대한 의견을 추가하세요.

구현 관련 문제 신고

Chromium 구현에서 버그를 발견했나요? 아니면 구현이 사양과 다른가요? new.crbug.com에서 버그를 신고합니다. 가능한 한 많은 세부정보와 간단한 재현 안내를 포함하고 Components 상자에 Blink>Handwriting를 입력합니다. Glitch는 쉽고 빠르게 재현을 공유하는 데 효과적입니다.

API 지원 표시

필기 인식 API를 사용할 계획인가요? 공개 지원을 통해 Chromium팀은 기능의 우선순위를 정하고 다른 브라우저 공급업체에 이러한 기능을 지원하는 것이 얼마나 중요한지 보여줄 수 있습니다.

어떻게 사용할 계획인지는 WICG 토론 대화목록에 공유해 주세요. 해시태그 #HandwritingRecognition를 사용하여 @ChromiumDev로 트윗을 보내 어디서, 어떻게 사용하고 있는지 알려주세요.

감사의 말씀

Joe Medley, Honglin Yu, Jiewei Qian님이 검토했습니다. Unsplash사미르 보우크드의 히어로 이미지