Handwriting Recognition API를 사용하면 필기 입력이 이루어지는 대로 텍스트를 인식할 수 있습니다.
Handwriting Recognition API란 무엇인가요?
Handwriting Recognition API를 사용하면 사용자의 필기 (잉크)를 텍스트로 변환할 수 있습니다. 일부 운영체제에는 오래 전부터 이러한 API가 포함되어 있었으며, 이 새로운 기능을 통해 웹 앱에서도 마침내 이 기능을 사용할 수 있게 되었습니다. 변환은 사용자 기기에서 직접 발생하고 오프라인 모드에서도 작동하며 서드 파티 라이브러리나 서비스를 추가하지 않고도 작동합니다.
이 API는 소위 '온라인' 또는 거의 실시간 인식을 구현합니다. 즉, 사용자가 그리면서 단일 획을 캡처하고 분석하여 필기 입력이 인식됩니다. 최종 제품만 알려진 광학 문자 인식(OCR)과 같은 '오프라인' 절차와 달리 온라인 알고리즘은 개별 잉크 획의 시간적 순서 및 압력과 같은 추가 신호로 인해 더 높은 수준의 정확성을 제공할 수 있습니다.
Handwriting Recognition API의 추천 사용 사례
사용 예는 다음과 같습니다.
- 사용자가 필기 메모를 캡처하여 텍스트로 번역하려는 메모 작성 애플리케이션
- 사용자가 시간 제약으로 인해 펜이나 손가락 입력을 사용할 수 있는 양식 애플리케이션
- 크로스워드, 숫자 퀴즈, 스도쿠 등 문자나 숫자를 입력해야 하는 게임
현재 상태
Handwriting Recognition API는 (Chromium 99)부터 사용할 수 있습니다.
Handwriting Recognition API 사용 방법
기능 감지
탐색기 객체에서 createHandwritingRecognizer()
메서드의 존재를 확인하여 브라우저 지원을 감지합니다.
if ('createHandwritingRecognizer' in navigator) {
// 🎉 The Handwriting Recognition API is supported!
}
핵심 개념
Handwriting Recognition API는 입력 방법(마우스, 터치, 펜)과 관계없이 필기 입력을 텍스트로 변환합니다. 이 API에는 네 가지 기본 항목이 있습니다.
- 점은 특정 시점에 포인터가 있던 위치를 나타냅니다.
- 획은 하나 이상의 점으로 구성됩니다. 획 녹화는 사용자가 포인터를 아래로 내리면(즉, 기본 마우스 버튼을 클릭하거나 펜이나 손가락으로 화면을 터치하면) 시작되고 포인터를 다시 위로 올리면 종료됩니다.
- 그리기는 하나 이상의 획으로 구성됩니다. 실제 인식은 이 수준에서 이루어집니다.
- recognizer가 예상 입력 언어로 구성됩니다. 인식기 구성이 적용된 그리기 인스턴스를 만드는 데 사용됩니다.
이러한 개념은 특정 인터페이스 및 사전으로 구현되며, 이는 곧 다루겠습니다.
인식기 만들기
필기 입력에서 텍스트를 인식하려면 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
)
Handwriting Recognition 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>
로 계속 사용할 수 있습니다.
웹 구성요소는 languages
및 recognitiontype
를 비롯하여 외부에서 인식 동작을 정의하는 속성과 속성을 제공합니다. value
속성을 통해 컨트롤의 콘텐츠를 설정할 수 있습니다.
<handwriting-textarea languages="en" recognitiontype="text" value="Hello"></handwriting-textarea>
값의 변경사항을 알리려면 input
이벤트를 수신 대기하면 됩니다.
Glitch의 이 데모를 사용하여 구성요소를 사용해 볼 수 있습니다. 소스 코드도 살펴보세요. 애플리케이션에서 컨트롤을 사용하려면 npm에서 가져옵니다.
보안 및 권한
Chromium팀은 사용자 제어, 투명성, 인체 공학을 비롯하여 강력한 웹 플랫폼 기능에 대한 액세스 제어에 정의된 핵심 원칙을 사용하여 필기 인식 API를 설계하고 구현했습니다.
사용자 제어
사용자는 Handwriting Recognition API를 사용 중지할 수 없습니다. HTTPS를 통해 제공되는 웹사이트에서만 사용할 수 있으며 최상위 탐색 컨텍스트에서만 호출할 수 있습니다.
투명성
필기 인식이 활성화되어 있는지 여부를 나타내는 표시가 없습니다. 브라우저는 핑거프린팅을 방지하기 위해 악용 가능성을 감지하면 사용자에게 권한 메시지를 표시하는 등의 대책을 구현합니다.
권한 유지
Handwriting Recognition API는 현재 권한 메시지를 표시하지 않습니다. 따라서 어떤 방식으로든 권한을 유지할 필요가 없습니다.
의견
Chromium팀은 Handwriting Recognition API 사용 경험에 관한 의견을 듣고자 합니다.
API 설계 설명
API에 예상대로 작동하지 않는 부분이 있나요? 아니면 아이디어를 구현하는 데 필요한 메서드나 속성이 누락되어 있나요? 보안 모델에 관해 질문이나 의견이 있으신가요? 해당 GitHub 저장소에서 사양 문제를 제출하거나 기존 문제에 의견을 추가합니다.
구현 문제 신고
Chromium 구현에 버그가 있나요? 아니면 구현이 사양과 다른가요?
new.crbug.com에서 버그를 신고합니다. 최대한 자세한 내용과 재현을 위한 간단한 안내를 포함하고 구성요소 상자에 Blink>Handwriting
를 입력합니다.
Glitch는 빠르고 간편한 재현을 공유하는 데 적합합니다.
API 지원 표시
Handwriting Recognition API를 사용하려면 공개적으로 지원하면 Chromium팀이 기능의 우선순위를 정하고 다른 브라우저 공급업체에 기능을 지원하는 것이 얼마나 중요한지 알릴 수 있습니다.
WICG 담화 대화목록에서 사용 계획을 공유합니다. #HandwritingRecognition
해시태그를 사용하여 @ChromiumDev에 트윗을 보내고 사용 빈도와 사용 방법을 알려주세요.
유용한 링크
감사의 말씀
이 문서는 조 미들리, 홍린 유, 지웨이 치앙이 검토했습니다.