MLKitHandwriting 拓展:手写识别(写字变文字)

« 返回首页

logo MLKitHandwriting 拓展

把用户在画布上写的字识别成文字。基于 Google ML Kit 数字墨水识别(Digital Ink Recognition), 支持 300 多种语言 / 20 多种文字系统,另外还能识别表情涂鸦和基本几何图形。 识别过程完全在设备本地完成,不上传笔迹。

为什么选它:

  • 只要一个「开始识别」块。语言包没下载会自动先下载再识别 —— 不用自己串「下载模型 → 等下载完成事件 → 初始化 → 识别」这一长串。
  • 笔迹自动采集。在设计器里给 书写画布 属性选一块画布就行,用户照常在画布上画线,笔迹同时被记录,画布本身的绘制功能完全不受影响。不用自己在「拖动」事件里一个点一个点地喂坐标。
  • 自动带上书写区域。画布尺寸会自动作为”书写区域”信息告诉识别器,这对准确率有明显帮助。
  • 支持候选字。候选数量 设成 5,就能拿到多个候选做候选字列表(像输入法那样)。

重要限制:语言包需要联网从 Google 服务器下载(一种语言几 MB,下载后永久离线可用)。 国内网络访问 Google 服务器可能失败,此时触发 ModelDownloadFailed 事件。 请在应用里对用户交代清楚,或引导用户在能访问 Google 服务的网络下完成首次下载。 识别引擎本身(约 13MB,4 个 CPU 架构)已打包在 .aix 里,不需要下载。

  • .aix 拓展下载:

cn.fun123.MLKit.Handwriting.aix


快速上手

一、写字识别(最小可用)

设计器摆放:

  1. 一个画布(Canvas),宽度「充满」、高度 300 像素,画笔粗细 设 5 左右(笔画太细会影响识别)。
  2. 一个 MLKitHandwriting,把 书写画布(DrawingCanvas) 属性选成刚才那个画布。
  3. 两个按钮(识别、清空)、一个标签显示结果。
when 按钮_识别.Click() {
  Handwriting1.Recognize()
}
when 按钮_清除.Click() {
  Handwriting1.Clear()
}
when Handwriting1.GotText(text, candidatesJson) {
  标签1.Text = text
}
when Handwriting1.RecognizeFailed(errorMessage) {
  标签1.Text = errorMessage
}

就这样 —— 语言默认是 zh-Hani(中文,简繁都认),第一次点「识别」会自动下载中文语言包。

二、写完自动识别,识别完自动清空(连续手写输入)

用「计时器」(Clock)做一个”停笔 800 毫秒就识别”的效果,体验接近手写输入法。

global penIdle = 0

when 画布1.Dragged(startX, startY, prevX, prevY, currentX, currentY, draggedAnySprite) {
  penIdle = 0
}
when 计时器1.Timer() {
  penIdle = penIdle + 1
  if penIdle == 4 {
    if Handwriting1.StrokeCount > 0 {
      Handwriting1.Recognize()
    }
  }
}
when Handwriting1.GotText(text, candidatesJson) {
  文本输入框_结果.Text = join(文本输入框_结果.Text, text)
  Handwriting1.Clear()
  penIdle = 0
}

计时器间隔设 200 毫秒,penIdle == 4 就是停笔约 800 毫秒。

三、做候选字列表(像输入法)

把 候选数量 设成 5,从 candidatesJson 里取候选。

when Screen1.Initialize() {
  Handwriting1.MaxResultCount = 5
}
when Handwriting1.GotText(text, candidatesJson) {
  标签1.Text = text
  标签_原始结果.Text = candidatesJson
}

candidatesJson 的形状是(用「JSON 文本解码」块解析):

[{"text":"你","score":0.93},{"text":"尔","score":0.41},{"text":"仦","score":0.12}]

四、利用上文提升准确率

已经输入了一些内容时,把它设进 上文内容,识别器会据此判断。 例如上文是「今天天」,接着写的字更可能被认成「气」而不是形近字。

when Handwriting1.GotText(text, candidatesJson) {
  文本输入框_结果.Text = join(文本输入框_结果.Text, text)
  Handwriting1.PreContext = 文本输入框_结果.Text
  Handwriting1.Clear()
}

五、应用启动时提前下载语言包

让用户第一次识别不用等,并且能明确看到网络问题。

when Screen1.Initialize() {
  Handwriting1.CheckModel()
}
when Handwriting1.GotModelState(isDownloaded) {
  if isDownloaded {
    标签1.Text = "语言包已就绪"
  } else {
    标签1.Text = "正在下载语言包,请稍候…"
    Handwriting1.DownloadModel()
  }
}
when Handwriting1.ModelDownloaded() {
  标签1.Text = "语言包下载完成,可以开始书写了"
}
when Handwriting1.ModelDownloadFailed(errorMessage) {
  标签1.Text = errorMessage
}

六、识别几何图形 / 表情涂鸦

把 语言标签 换掉即可,其余代码不用改:

when 按钮_形状.Click() {
  Handwriting1.LanguageTag = "shapes"
  Handwriting1.Clear()
  标签1.Text = "请画一个圆形、三角形或方形"
}
when 按钮_表情.Click() {
  Handwriting1.LanguageTag = "emoji"
  Handwriting1.Clear()
  标签1.Text = "请画一个笑脸、爱心之类"
}

换语言后需要下载对应的语言包(shapes、emoji 的包都很小)。


属性

DrawingCanvas
用户书写用的画布组件。在设计器里选好即可。 选定之后用户在画布上画线,笔迹会被自动记录,画布自己的绘制功能不受影响(还是照常画出线条)。 建议画布 画笔粗细 设 4~6,太细的笔画会影响识别率。
LanguageTag
识别语言。可以填标准语言标签,也可以填简称。常用值:
值 含义
zh-Hani 中文(默认,简体繁体都能认)
zh-Hani-TW / zh-Hani-HK 繁体(台湾 / 香港)
en-US 英文
ja 日文
ko 韩文
emoji 表情涂鸦
shapes 基本几何图形
autodraw 涂鸦联想(画得像什么就认成什么)

完整语言列表见 ML Kit 数字墨水支持语言。 改了语言需要重新下载对应语言包。

MaxResultCount
返回几个候选结果,默认 1(最像的那个)。 设成 5 之类的值可以在 GotText 事件的 candidatesJson 里拿到多个候选,用来做候选字列表。
PreContext
上文内容。填上用户已经输入的前一段文字,识别器会据此提升准确率。只在块里设置(不是设计器属性)。
StrokeCount
只读。当前已经采集到几笔笔迹。可以用它判断用户有没有写东西(> 0 才值得识别)。
Busy
只读。是否正在识别或下载模型。

事件

GotText(text,candidatesJson)
识别成功。
  • text — 最像的那个结果,直接用这个就行。
  • candidatesJson — 全部候选结果的 JSON 数组(受 候选数量 属性控制), 形如 [{"text":"你","score":0.93}]。score 是置信度,部分语言不提供。
RecognizeFailed(errorMessage)
识别失败。也包括这些”用法不对”的情况:没设 书写画布、还没写就点识别、上一次还没处理完。 错误文本可以直接显示给用户。
ModelDownloaded()
语言包下载完成。自动下载和手动 DownloadModel 都会触发。
ModelDownloadFailed(errorMessage)
语言包下载失败。最常见的原因是网络无法访问 Google 服务器。
GotModelState(isDownloaded)
CheckModel 的结果。
GotDeleteResult(deleted,errorMessage)
DeleteModel 的结果。

方法

Recognize()
识别当前笔迹。语言包没下载会自动先下载再识别(下载成功会先触发 ModelDownloaded)。 结果通过 GotText 返回。
Clear()
清空已采集的笔迹,同时清空画布上的画面,方便重新写。
DownloadModel()
手动下载当前语言的语言包。 一般不需要单独调用(Recognize 会自动下载),但可以用它在应用启动时提前下载好。
CheckModel()
查询当前语言的语言包是否已下载。结果通过 GotModelState 返回。
DeleteModel()
删除当前语言的语言包,释放存储空间。结果通过 GotDeleteResult 返回。

运行环境:AI 伴侣联机需要 Android 11 及以上

本拓展的识别模型放在依赖库自带的 assets 里,AI 伴侣联机调试要把它挂进 AssetManager,用的是 Android 11(API 30)才有的 ResourcesLoader —— 这是系统提供的 唯一公开办法,更低版本没有等价 API。所以:

运行方式 Android 6 ~ 10 Android 11 及以上
打包 APK ✅ 正常 ✅ 正常
AI 伴侣联机 ❌ 模型加载不了 ✅ 正常

打包 APK 之所以不受限制,是因为模型在编译时就被并进了 APK 的 assets,装到手机上 就是 App 自己的资源,与系统版本无关。伴侣的 APK 是提前编好装在手机上的,运行时没法 往自己已安装的包里加文件,只能靠系统的运行时挂载 API。

低版本设备上想验证功能,直接打包 APK 测即可;要用伴侣边改边调,请换 Android 11+ 的设备。

常见问题

点「识别」提示”请先给书写画布属性选一个画布组件”。 设计器里选中 MLKitHandwriting,在属性面板把 书写画布 选成你的画布。

点「识别」一直触发 ModelDownloadFailed。 语言包连不上 Google 服务器。这是网络问题,不是拓展的问题。 可以引导用户切换到能访问 Google 服务的网络完成一次下载,之后就永久离线可用了。

识别率不高。 按影响大小依次排查:

  1. 画笔太细 —— 把画布 画笔粗细 调到 4~6。
  2. 写得太小 —— 让用户占满画布书写;识别器是按整块书写区域理解的。
  3. 一次写太多字 —— 一次识别一到几个字最准,写满一行再识别会明显下降。
  4. 没用上文 —— 连续输入时把已输入内容设进 上文内容。
  5. 语言不对 —— 写英文却用 zh-Hani,或反过来。

画布上的线还在,但识别说”还没有笔迹”。 Clear 是同时清笔迹和画面的;如果你用画布自己的 清除画布 块,只清了画面、笔迹还在(反过来也一样会错位)。 统一用本拓展的 Clear 方法。

能识别整页手写笔记吗? 不适合。数字墨水识别是为”边写边认”设计的(几个字到一行),整页笔记应该用 OCR 类拓展对图片做识别。

笔迹会上传吗? 不会。识别在设备本地完成,只有语言包下载需要联网。

文档反馈