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