我们通过 wasm-pack 编译出带 wasm 与模型权重的 npm 包后,就可以应用在前端项目上了。
这里主要讲一些关键的细节。
由于 模型加载和模型推理是比较耗时的操作,因此我们需要把这两个操作放到 web worker 进程里去做。
同时,我们还需要一个单例,让模型只在第一次加载。
import init, { Model } from "ochw-wasm"
// 实现一个 HandingWrite 类的单例
export class HandingWrite {
private static instance: Model | undefined;
private constructor() { }
public static async getInstance(): Promise<Model> {
if (!this.instance) {
await init(wasmUrl)
self.postMessage({ status: `loading model` });
this.instance = Model.new()
return this.instance;
} else {
return this.instance;
}
}
}
然后再监听来自主线程的事件,接收图片数据,并进行模型推理:
// 监听来自主线程的消息事件
self.addEventListener("message", async (event: MessageEvent) => {
try {
// 获取 HandingWrite 类的单例实例,确保模型已加载
const model = await HandingWrite.getInstance();
// 从消息事件中提取 uint8Array 数据,用于模型预测
const { uint8Array } = event.data;
// 使用模型对输入的 uint8Array 数据进行预测
const res = model.predict(uint8Array);
// 将预测结果解析为 JSON 格式,并通过 postMessage 返回给主线程
self.postMessage({
status: "complete", // 标记任务完成
output: JSON.parse(res), // 返回预测结果
});
} catch (e) {
// 如果发生错误,捕获异常并通过 postMessage 返回错误信息
self.postMessage({ error: `worker error: ${e}` });
}
});
同时在 web 页面上,初始化这个 worker
const worker = new Worker(new URL("./worker.js", import.meta.url), {
type: "module",
});
用户每写一次,都在画布上获取图片,发送到工作进程进行推理:
const stage = e.target.getStage();
if (stage) {
const blob: Blob = (await stage.toBlob()) as Blob;
const arrayBuffer = await blob.arrayBuffer();
const uint8Array = new Uint8Array(arrayBuffer);
// console.log(uint8Array)
worker.postMessage({
uint8Array,
width: stage.width(),
height: stage.height(),
});
// debug:下载图片
// const dataURL = stage.toDataURL({
// pixelRatio: 1, // double resolution
// });
// create link to download
// const link = document.createElement("a");
// link.download = "stage.png";
// link.href = dataURL;
// document.body.appendChild(link);
// link.click();
// document.body.removeChild(link);
}
我们还要获取 web worker 的结果:
const [candidateWords, setCandidateWords] = useState<CandidateWord[]>([]);
useEffect(() => {
worker.onmessage = (e) => {
// console.log(e.data);
if (e.data && e.data.status =='complete') {
setCandidateWords(e.data.output);
}
};
}, []);
问题
目前此模型还有一些问题。
比如,一些简单的笔划的字反而无法识别, 说明数据集的处理有一些问题。还需要做一些消融实验去改进识别效果。
如果你对《从零开始构建手写输入法》 代码感兴趣,可以访问 : https://github.com/ximeiorg/ochw , 麻烦给个星。
如果你想访问效果,请访问: https://ochw.ximei.me
如果你在 PC 访问发现识别此变差,那么有可能是因为,画布因为 PC 变形,长宽比不一样,导致输入模型的尺寸差异问题而精度下降。