借助手写识别 API,您可以在手写输入中实时识别文字。
什么是手写识别 API?
借助手写识别 API,您可以将用户的手写内容(手写)转换为文本。 一些操作系统早就包含了此类 API,而借助这项新功能,您的 Web 应用终于可以使用此功能了。转换直接在用户设备上进行,即使在离线模式下也可以进行,所有这一切均无需添加任何第三方库或服务。
此 API 实现所谓的“在线”或近乎实时的识别。这意味着,当用户通过捕获和分析单次笔画绘制时,系统会识别手写输入。与光学字符识别 (OCR) 等仅知道最终成品的“离线”过程相反,在线算法由于其他信号(例如单个墨水笔画的时间顺序和压力)而可以提供更高的准确度。
手写识别 API 的推荐用例
应用示例包括:
- 记事应用,用户希望捕获手写记事并将其翻译为文本。
- 由于时间限制,用户可以使用触控笔或手指输入的表单应用。
- 要求填入字母或数字的游戏,例如填字游戏、字谜或数独游戏。
当前状态
手写识别 API 从 (Chromium 99) 开始。
如何使用手写识别 API
功能检测
通过检查导航器对象上是否存在 createHandwritingRecognizer()
方法,检测浏览器支持情况:
if ('createHandwritingRecognizer' in navigator) {
// 🎉 The Handwriting Recognition API is supported!
}
核心概念
无论使用哪种输入方法(鼠标、触摸、笔),手写识别 API 都会将手写输入转换为文本。该 API 有四个主要实体:
- 点表示指针在特定时间的位置。
- 一个描边,包含一个或多个点。当用户放下指针(即点击鼠标主按钮,或者用钢笔/手指轻触屏幕)时,开始记录笔画,并在用户再次抬起指针时结束笔触记录。
- 一个绘图由一个或多个笔画组成。实际识别发生在此级别。
- 为识别器配置了预期的输入语言。它用于创建应用了识别器配置的绘图实例。
这些概念以特定的接口和字典的形式实现,我稍后将介绍这些内容。
创建识别器
如需识别手写输入中的文本,您需要通过调用 navigator.createHandwritingRecognizer()
并向其传递约束条件来获取 HandwritingRecognizer
的实例。限制条件决定了应使用的手写识别模型。目前,您可以按偏好顺序指定语言列表:
const recognizer = await navigator.createHandwritingRecognizer({
languages: ['en'],
});
当浏览器可以满足您的请求时,该方法会返回使用 HandwritingRecognizer
实例进行解析的 promise。否则,它将拒绝 promise 并报错,且将不提供手写识别功能。因此,您可能需要先查询识别器对特定识别特征的支持。
查询识别器支持
通过调用 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
该方法会返回使用结果对象解析的 promise。如果浏览器支持开发者指定的功能,则其值将设置为 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));
});
此方法会返回一个 promise,并通过一组按概率排序的预测结果进行解析。元素数量取决于您传递给 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>
使用。
Web 组件提供了用于从外部定义识别行为的属性,包括 languages
和 recognitiontype
。您可以通过 value
属性设置控件的内容:
<handwriting-textarea languages="en" recognitiontype="text" value="Hello"></handwriting-textarea>
如需了解该值的任何更改,您可以监听 input
事件。
您可以使用 Glitch 上的演示来试用该组件。此外,请务必查看源代码。如需在应用中使用该控件,请从 npm 获取。
安全与权限
Chromium 团队根据控制对强大的 Web 平台功能的访问权限(包括用户控制、透明度和人体工程学)中定义的核心原则设计和实现了手写识别 API。
用户控制
用户无法关闭手写识别 API。它仅适用于通过 HTTPS 传送的网站,并且只能从顶级浏览上下文中调用。
透明度
系统不会显示手写识别功能是否已启用的指示。为防止数字“指纹”收集,浏览器采取了一些应对措施,例如在检测到可能存在滥用行为时向用户显示权限提示。
权限保留
手写识别 API 目前不显示任何权限提示。因此,权限不需要以任何方式持久保留。
反馈
Chromium 团队希望了解您使用手写识别 API 的体验。
向我们介绍 API 设计
API 是否有什么无法按预期运行?或者,您是否缺少实现自己的想法所需的方法或属性?如果您对安全模型有疑问或意见,在相应的 GitHub 代码库中提交规范问题,或添加您对现有问题的看法。
报告实现存在的问题
您是否发现了 Chromium 实现方面的错误?或者,实现是否与规范不同?
在 new.crbug.com 上提交 bug。请务必提供尽可能多的详情以及有关重现的简单说明,并在组件框中输入 Blink>Handwriting
。Glitch 非常适用于分享轻松快速的重现问题。
表示对 API 的支持
您打算使用手写识别 API 吗?您的公开支持可帮助 Chromium 团队确定功能的优先级,并向其他浏览器供应商展示支持这些功能的重要性。
请在 WICG Discourse 会话中说明您打算如何使用它。使用 # 标签 #HandwritingRecognition
向 @ChromiumDev 发送一条 Twitter 微博,告诉我们您使用它的地点和方式。
实用链接
致谢
本文由 Joe Medley、Honglin Yu 和 Jiewei Qian 审核。主打图片,作者是 Samir Bouaked,在 Unsplash 网站上。